/** @file Legacy boot driver. Copyright (c) 2023, Goldfish64. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause **/ #include "LegacyBootInternal.h" #include STATIC EFI_HANDLE mImageHandle; STATIC BOOLEAN mIsAppleInterfaceSupported; STATIC OC_FLEX_ARRAY *mHiddenDevicePaths; // // Fallback PIWG firmware media device path for Apple legacy interface. // MemoryMapped(0xB,0xFFE00000,0xFFF9FFFF)/FvFile(2B0585EB-D8B8-49A9-8B8C-E21B01AEF2B7) // STATIC CONST UINT8 AppleLegacyInterfaceFallbackDevicePathData[] = { 0x01, 0x03, 0x18, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x04, 0x06, 0x14, 0x00, 0xEB, 0x85, 0x05, 0x2B, 0xB8, 0xD8, 0xA9, 0x49, 0x8B, 0x8C, 0xE2, 0x1B, 0x01, 0xAE, 0xF2, 0xB7, 0x7F, 0xFF, 0x04, 0x00 }; STATIC CONST EFI_DEVICE_PATH_PROTOCOL *AppleLegacyInterfaceFallbackDevicePathPath = (EFI_DEVICE_PATH_PROTOCOL *)AppleLegacyInterfaceFallbackDevicePathData; STATIC CHAR8 * GetLegacyEntryName ( OC_LEGACY_OS_TYPE LegacyOsType ) { return AllocateCopyPool (L_STR_SIZE ("Windows (legacy)"), "Windows (legacy)"); } STATIC CHAR8 * GetLegacyEntryFlavour ( OC_LEGACY_OS_TYPE LegacyOsType ) { return AllocateCopyPool (L_STR_SIZE (OC_FLAVOUR_WINDOWS), OC_FLAVOUR_WINDOWS); } STATIC CHAR8 * LoadAppleDiskLabel ( IN OUT OC_PICKER_CONTEXT *PickerContext, IN EFI_HANDLE DiskHandle ) { EFI_STATUS Status; CHAR8 *DiskLabel; EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem; DiskLabel = NULL; if ((PickerContext->PickerAttributes & OC_ATTR_USE_DISK_LABEL_FILE) != 0) { Status = gBS->HandleProtocol ( DiskHandle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&FileSystem ); if (EFI_ERROR (Status)) { return NULL; } DiskLabel = OcReadFile (FileSystem, L".contentDetails", NULL, 0); if (DiskLabel == NULL) { DiskLabel = OcReadFile (FileSystem, L".disk_label.contentDetails", NULL, 0); } if (DiskLabel == NULL) { DEBUG ((DEBUG_INFO, "OLB: %s %s not present\n", L".contentDetails", L".disk_label.contentDetails")); } else { DEBUG ((DEBUG_INFO, "OLB: Found disk label '%a'\n", DiskLabel)); } } return DiskLabel; } STATIC VOID FreePickerEntry ( IN OC_PICKER_ENTRY *Entry ) { ASSERT (Entry != NULL); if (Entry == NULL) { return; } if (Entry->Id != NULL) { FreePool ((CHAR8 *)Entry->Id); } if (Entry->Name != NULL) { FreePool ((CHAR8 *)Entry->Name); } if (Entry->Flavour != NULL) { FreePool ((CHAR8 *)Entry->Flavour); } } STATIC EFI_STATUS UnmanagedBootActionDoLegacyBoot ( IN OUT OC_PICKER_CONTEXT *PickerContext, IN EFI_DEVICE_PATH_PROTOCOL *DevicePath ) { EFI_STATUS Status; EFI_HANDLE DiskHandle; EFI_HANDLE LoadedImageHandle; EFI_DEVICE_PATH_PROTOCOL *LoadedImageDevicePath; EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; BOOLEAN IsExternal; CONST CHAR8 *AppleBootArg; // // Set BootCampHD to desired disk Device Path for non-CD devices. // if (!OcIsDiskCdRom (DevicePath)) { Status = InternalSetBootCampHDPath (DevicePath); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OLB: Failure while setting BootCampHD variable - %r\n", Status)); return Status; } } // // Load and start legacy OS. // // On Macs, use the Apple legacy interface. // On other systems, use the Legacy8259 protocol. // DebugPrintDevicePath (DEBUG_INFO, "OLB: Legacy device path", DevicePath); if (mIsAppleInterfaceSupported) { DiskHandle = OcPartitionGetPartitionHandle (DevicePath); if (DiskHandle == NULL) { return EFI_INVALID_PARAMETER; } Status = InternalLoadAppleLegacyInterface ( mImageHandle, &LoadedImageDevicePath, &LoadedImageHandle ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OLB: Failure while loading Apple legacy interface - %r\n", Status)); return Status; } // // Specify boot device type argument on loaded image. // Status = gBS->HandleProtocol ( LoadedImageHandle, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OLB: Failure while loading Apple legacy interface - %r\n", Status)); gBS->UnloadImage (LoadedImageHandle); return Status; } LoadedImage->LoadOptionsSize = 0; LoadedImage->LoadOptions = NULL; OcGetDevicePolicyType (DiskHandle, &IsExternal); if (OcIsDiskCdRom (DevicePath)) { AppleBootArg = "CD"; } else if (IsExternal) { AppleBootArg = "USB"; } else { AppleBootArg = "HD"; } OcAppendArgumentsToLoadedImage ( LoadedImage, &AppleBootArg, 1, TRUE ); DEBUG ((DEBUG_INFO, "OLB: Apple legacy interface args <%s>\n", LoadedImage->LoadOptions)); Status = gBS->StartImage (LoadedImageHandle, NULL, NULL); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OLB: Failure while starting Apple legacy interface - %r\n", Status)); gBS->UnloadImage (LoadedImageHandle); return Status; } } else { Status = InternalLoadLegacyPbr (DevicePath); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OLB: Failure while starting legacy PBR interface - %r\n", Status)); return Status; } } return EFI_SUCCESS; } STATIC EFI_STATUS UnmanagedBootGetFinalDevicePath ( IN OUT OC_PICKER_CONTEXT *PickerContext, IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath ) { EFI_STATUS Status; EFI_HANDLE LoadedImageHandle; EFI_DEVICE_PATH_PROTOCOL *LoadedImageDevicePath; EFI_DEVICE_PATH_PROTOCOL *AppleLoaderDevicePath; // // Set BootCampHD to desired disk Device Path for non-CD devices. // if (!OcIsDiskCdRom (*DevicePath)) { Status = InternalSetBootCampHDPath (*DevicePath); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OLB: Failure while setting BootCampHD variable - %r\n", Status)); return Status; } } // // Locate Apple legacy interface loader. // // On Macs, use the Apple legacy interface and set BootCampHD. // On other systems, use a default placeholder path. // DebugPrintDevicePath (DEBUG_INFO, "OLB: Legacy device path", *DevicePath); if (mIsAppleInterfaceSupported) { Status = InternalLoadAppleLegacyInterface ( mImageHandle, &LoadedImageDevicePath, &LoadedImageHandle ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OLB: Failure while loading Apple legacy interface - %r\n", Status)); return Status; } gBS->UnloadImage (LoadedImageHandle); AppleLoaderDevicePath = DuplicateDevicePath (LoadedImageDevicePath); } else { AppleLoaderDevicePath = DuplicateDevicePath (AppleLegacyInterfaceFallbackDevicePathPath); } if (AppleLoaderDevicePath == NULL) { return EFI_OUT_OF_RESOURCES; } *DevicePath = AppleLoaderDevicePath; return EFI_SUCCESS; } STATIC EFI_STATUS EFIAPI OcGetLegacyBootEntries ( IN OUT OC_PICKER_CONTEXT *PickerContext, IN CONST EFI_HANDLE Device OPTIONAL, OUT OC_PICKER_ENTRY **Entries, OUT UINTN *NumEntries ) { EFI_STATUS Status; UINTN NoHandles; EFI_HANDLE *Handles; UINTN HandleIndex; UINTN DevicePathIndex; BOOLEAN SkipHiddenDevice; UINT32 ScanPolicy; BOOLEAN IsExternal; CHAR16 *UnicodeDevicePath; CHAR8 *AsciiDevicePath; UINTN AsciiDevicePathSize; CHAR16 **HiddenUnicodeDevicePath; EFI_HANDLE BlockDeviceHandle; EFI_DEVICE_PATH_PROTOCOL *BlockDevicePath; OC_LEGACY_OS_TYPE LegacyOsType; OC_FLEX_ARRAY *FlexPickerEntries; OC_PICKER_ENTRY *PickerEntry; ASSERT (PickerContext != NULL); ASSERT (Entries != NULL); ASSERT (NumEntries != NULL); // // Custom entries only. // if (Device != NULL) { return EFI_NOT_FOUND; } FlexPickerEntries = OcFlexArrayInit (sizeof (OC_PICKER_ENTRY), (OC_FLEX_ARRAY_FREE_ITEM)FreePickerEntry); if (FlexPickerEntries == NULL) { return EFI_OUT_OF_RESOURCES; } // // Get all Block I/O handles. // Status = gBS->LocateHandleBuffer ( ByProtocol, &gEfiBlockIoProtocolGuid, NULL, &NoHandles, &Handles ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OLB: Failed to get Block I/O handles - %r\n", Status)); OcFlexArrayFree (&FlexPickerEntries); return Status; } for (HandleIndex = 0; HandleIndex < NoHandles; HandleIndex++) { BlockDeviceHandle = Handles[HandleIndex]; // // If device type locking is set and this device is not allowed, // skip device. // ScanPolicy = OcGetDevicePolicyType (BlockDeviceHandle, &IsExternal); if (((PickerContext->ScanPolicy & OC_SCAN_DEVICE_LOCK) != 0) && ((ScanPolicy & PickerContext->ScanPolicy) == 0)) { continue; } BlockDevicePath = DevicePathFromHandle (BlockDeviceHandle); if (BlockDevicePath == NULL) { DEBUG ((DEBUG_INFO, "OLB: Could not find Device Path for block device\n")); continue; } // // Device Path will be used as ID and is required for default entry matching. // UnicodeDevicePath = ConvertDevicePathToText (BlockDevicePath, FALSE, FALSE); if (UnicodeDevicePath == NULL) { OcFlexArrayFree (&FlexPickerEntries); return EFI_OUT_OF_RESOURCES; } // // Skip device if on hidden list. // if (mHiddenDevicePaths != NULL) { SkipHiddenDevice = FALSE; for (DevicePathIndex = 0; DevicePathIndex < mHiddenDevicePaths->Count; DevicePathIndex++) { HiddenUnicodeDevicePath = (CHAR16 **)OcFlexArrayItemAt (mHiddenDevicePaths, DevicePathIndex); ASSERT (HiddenUnicodeDevicePath != NULL); if (StrCmp (UnicodeDevicePath, *HiddenUnicodeDevicePath) == 0) { DEBUG ((DEBUG_INFO, "OLB: Skipping hidden device %s\n", *HiddenUnicodeDevicePath)); SkipHiddenDevice = TRUE; break; } } if (SkipHiddenDevice) { FreePool (UnicodeDevicePath); continue; } } // // Detect legacy OS type. // LegacyOsType = InternalGetPartitionLegacyOsType (BlockDeviceHandle, mIsAppleInterfaceSupported); if (LegacyOsType == OcLegacyOsTypeNone) { FreePool (UnicodeDevicePath); continue; } // // Create and add picker entry to entries. // PickerEntry = OcFlexArrayAddItem (FlexPickerEntries); if (PickerEntry == NULL) { FreePool (UnicodeDevicePath); OcFlexArrayFree (&FlexPickerEntries); return EFI_OUT_OF_RESOURCES; } AsciiDevicePathSize = (StrLen (UnicodeDevicePath) + 1) * sizeof (CHAR8); AsciiDevicePath = AllocatePool (AsciiDevicePathSize); if (AsciiDevicePath == NULL) { FreePool (UnicodeDevicePath); OcFlexArrayFree (&FlexPickerEntries); return EFI_OUT_OF_RESOURCES; } Status = UnicodeStrToAsciiStrS (UnicodeDevicePath, AsciiDevicePath, AsciiDevicePathSize); FreePool (UnicodeDevicePath); if (EFI_ERROR (Status)) { FreePool (AsciiDevicePath); OcFlexArrayFree (&FlexPickerEntries); return Status; } PickerEntry->Name = LoadAppleDiskLabel (PickerContext, BlockDeviceHandle); if (PickerEntry->Name == NULL) { PickerEntry->Name = GetLegacyEntryName (LegacyOsType); } PickerEntry->Id = AsciiDevicePath; PickerEntry->Path = NULL; PickerEntry->Arguments = NULL; PickerEntry->Flavour = GetLegacyEntryFlavour (LegacyOsType); PickerEntry->Tool = FALSE; PickerEntry->TextMode = FALSE; PickerEntry->RealPath = FALSE; PickerEntry->External = IsExternal; PickerEntry->UnmanagedBootAction = UnmanagedBootActionDoLegacyBoot; PickerEntry->UnmanagedBootGetFinalDevicePath = UnmanagedBootGetFinalDevicePath; PickerEntry->UnmanagedDevicePath = BlockDevicePath; if ((PickerEntry->Name == NULL) || (PickerEntry->Flavour == NULL)) { OcFlexArrayFree (&FlexPickerEntries); return EFI_OUT_OF_RESOURCES; } } OcFlexArrayFreeContainer (&FlexPickerEntries, (VOID **)Entries, NumEntries); return EFI_SUCCESS; } STATIC VOID EFIAPI OcFreeLegacyBootEntries ( IN OC_PICKER_ENTRY **Entries, IN UINTN NumEntries ) { UINTN Index; ASSERT (Entries != NULL); ASSERT (*Entries != NULL); if ((Entries == NULL) || (*Entries == NULL)) { return; } for (Index = 0; Index < NumEntries; Index++) { FreePickerEntry (&(*Entries)[Index]); } FreePool (*Entries); *Entries = NULL; } STATIC OC_BOOT_ENTRY_PROTOCOL mLegacyBootEntryProtocol = { OC_BOOT_ENTRY_PROTOCOL_REVISION, OcGetLegacyBootEntries, OcFreeLegacyBootEntries, NULL }; EFI_STATUS EFIAPI UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; CHAR16 *DevicePathNames; OC_FLEX_ARRAY *ParsedLoadOptions; Status = gBS->HandleProtocol ( ImageHandle, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage ); if (EFI_ERROR (Status)) { return Status; } mImageHandle = ImageHandle; mHiddenDevicePaths = NULL; Status = OcParseLoadOptions (LoadedImage, &ParsedLoadOptions); if (!EFI_ERROR (Status)) { OcParsedVarsGetUnicodeStr (ParsedLoadOptions, L"--hide-devices", &DevicePathNames); if (DevicePathNames != NULL) { mHiddenDevicePaths = OcStringSplit (DevicePathNames, L';', OcStringFormatUnicode); if (mHiddenDevicePaths == NULL) { return EFI_OUT_OF_RESOURCES; } } } else { if (Status != EFI_NOT_FOUND) { return Status; } } Status = InternalIsLegacyInterfaceSupported (&mIsAppleInterfaceSupported); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "OLB: Legacy boot interface is supported on this system\n")); return Status; } DEBUG ((DEBUG_INFO, "OLB: Apple legacy interface: %d\n", mIsAppleInterfaceSupported)); Status = gBS->InstallMultipleProtocolInterfaces ( &ImageHandle, &gOcBootEntryProtocolGuid, &mLegacyBootEntryProtocol, NULL ); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { return Status; } return EFI_SUCCESS; }