/** @file | |
Library functions which relates with booting. | |
Copyright (c) 2011 - 2015, Intel Corporation. All rights reserved.<BR> | |
(C) Copyright 2015 Hewlett Packard Enterprise Development LP<BR> | |
This program and the accompanying materials | |
are licensed and made available under the terms and conditions of the BSD License | |
which accompanies this distribution. The full text of the license may be found at | |
http://opensource.org/licenses/bsd-license.php | |
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
**/ | |
#include "InternalBm.h" | |
#define VENDOR_IDENTIFICATION_OFFSET 3 | |
#define VENDOR_IDENTIFICATION_LENGTH 8 | |
#define PRODUCT_IDENTIFICATION_OFFSET 11 | |
#define PRODUCT_IDENTIFICATION_LENGTH 16 | |
CONST UINT16 mBmUsbLangId = 0x0409; // English | |
CHAR16 mBmUefiPrefix[] = L"UEFI "; | |
EFI_BOOT_MANAGER_REFRESH_LEGACY_BOOT_OPTION mBmRefreshLegacyBootOption = NULL; | |
EFI_BOOT_MANAGER_LEGACY_BOOT mBmLegacyBoot = NULL; | |
LIST_ENTRY mPlatformBootDescriptionHandlers = INITIALIZE_LIST_HEAD_VARIABLE (mPlatformBootDescriptionHandlers); | |
/// | |
/// This GUID is used for an EFI Variable that stores the front device pathes | |
/// for a partial device path that starts with the HD node. | |
/// | |
EFI_GUID mBmHardDriveBootVariableGuid = { 0xfab7e9e1, 0x39dd, 0x4f2b, { 0x84, 0x08, 0xe2, 0x0e, 0x90, 0x6c, 0xb6, 0xde } }; | |
EFI_GUID mBmAutoCreateBootOptionGuid = { 0x8108ac4e, 0x9f11, 0x4d59, { 0x85, 0x0e, 0xe2, 0x1a, 0x52, 0x2c, 0x59, 0xb2 } }; | |
/** | |
The function registers the legacy boot support capabilities. | |
@param RefreshLegacyBootOption The function pointer to create all the legacy boot options. | |
@param LegacyBoot The function pointer to boot the legacy boot option. | |
**/ | |
VOID | |
EFIAPI | |
EfiBootManagerRegisterLegacyBootSupport ( | |
EFI_BOOT_MANAGER_REFRESH_LEGACY_BOOT_OPTION RefreshLegacyBootOption, | |
EFI_BOOT_MANAGER_LEGACY_BOOT LegacyBoot | |
) | |
{ | |
mBmRefreshLegacyBootOption = RefreshLegacyBootOption; | |
mBmLegacyBoot = LegacyBoot; | |
} | |
/** | |
For a bootable Device path, return its boot type. | |
@param DevicePath The bootable device Path to check | |
@retval AcpiFloppyBoot If given device path contains ACPI_DEVICE_PATH type device path node | |
which HID is floppy device. | |
@retval MessageAtapiBoot If given device path contains MESSAGING_DEVICE_PATH type device path node | |
and its last device path node's subtype is MSG_ATAPI_DP. | |
@retval MessageSataBoot If given device path contains MESSAGING_DEVICE_PATH type device path node | |
and its last device path node's subtype is MSG_SATA_DP. | |
@retval MessageScsiBoot If given device path contains MESSAGING_DEVICE_PATH type device path node | |
and its last device path node's subtype is MSG_SCSI_DP. | |
@retval MessageUsbBoot If given device path contains MESSAGING_DEVICE_PATH type device path node | |
and its last device path node's subtype is MSG_USB_DP. | |
@retval MessageNetworkBoot If given device path contains MESSAGING_DEVICE_PATH type device path node | |
and its last device path node's subtype is MSG_MAC_ADDR_DP, MSG_VLAN_DP, | |
MSG_IPv4_DP or MSG_IPv6_DP. | |
@retval MessageHttpBoot If given device path contains MESSAGING_DEVICE_PATH type device path node | |
and its last device path node's subtype is MSG_URI_DP. | |
@retval UnsupportedBoot If tiven device path doesn't match the above condition, it's not supported. | |
**/ | |
BM_BOOT_TYPE | |
BmDevicePathType ( | |
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath | |
) | |
{ | |
EFI_DEVICE_PATH_PROTOCOL *Node; | |
EFI_DEVICE_PATH_PROTOCOL *NextNode; | |
ASSERT (DevicePath != NULL); | |
for (Node = DevicePath; !IsDevicePathEndType (Node); Node = NextDevicePathNode (Node)) { | |
switch (DevicePathType (Node)) { | |
case ACPI_DEVICE_PATH: | |
if (EISA_ID_TO_NUM (((ACPI_HID_DEVICE_PATH *) Node)->HID) == 0x0604) { | |
return BmAcpiFloppyBoot; | |
} | |
break; | |
case HARDWARE_DEVICE_PATH: | |
if (DevicePathSubType (Node) == HW_CONTROLLER_DP) { | |
return BmHardwareDeviceBoot; | |
} | |
break; | |
case MESSAGING_DEVICE_PATH: | |
// | |
// Skip LUN device node | |
// | |
NextNode = Node; | |
do { | |
NextNode = NextDevicePathNode (NextNode); | |
} while ( | |
(DevicePathType (NextNode) == MESSAGING_DEVICE_PATH) && | |
(DevicePathSubType(NextNode) == MSG_DEVICE_LOGICAL_UNIT_DP) | |
); | |
// | |
// If the device path not only point to driver device, it is not a messaging device path, | |
// | |
if (!IsDevicePathEndType (NextNode)) { | |
continue; | |
} | |
switch (DevicePathSubType (Node)) { | |
case MSG_ATAPI_DP: | |
return BmMessageAtapiBoot; | |
break; | |
case MSG_SATA_DP: | |
return BmMessageSataBoot; | |
break; | |
case MSG_USB_DP: | |
return BmMessageUsbBoot; | |
break; | |
case MSG_SCSI_DP: | |
return BmMessageScsiBoot; | |
break; | |
case MSG_MAC_ADDR_DP: | |
case MSG_VLAN_DP: | |
case MSG_IPv4_DP: | |
case MSG_IPv6_DP: | |
return BmMessageNetworkBoot; | |
break; | |
case MSG_URI_DP: | |
return BmMessageHttpBoot; | |
break; | |
} | |
} | |
} | |
return BmMiscBoot; | |
} | |
/** | |
Find the boot option in the NV storage and return the option number. | |
@param OptionToFind Boot option to be checked. | |
@return The option number of the found boot option. | |
**/ | |
UINTN | |
BmFindBootOptionInVariable ( | |
IN EFI_BOOT_MANAGER_LOAD_OPTION *OptionToFind | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_BOOT_MANAGER_LOAD_OPTION BootOption; | |
UINTN OptionNumber; | |
CHAR16 OptionName[BM_OPTION_NAME_LEN]; | |
EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions; | |
UINTN BootOptionCount; | |
UINTN Index; | |
OptionNumber = LoadOptionNumberUnassigned; | |
// | |
// Try to match the variable exactly if the option number is assigned | |
// | |
if (OptionToFind->OptionNumber != LoadOptionNumberUnassigned) { | |
UnicodeSPrint ( | |
OptionName, sizeof (OptionName), L"%s%04x", | |
mBmLoadOptionName[OptionToFind->OptionType], OptionToFind->OptionNumber | |
); | |
Status = EfiBootManagerVariableToLoadOption (OptionName, &BootOption); | |
if (!EFI_ERROR (Status)) { | |
ASSERT (OptionToFind->OptionNumber == BootOption.OptionNumber); | |
if ((OptionToFind->Attributes == BootOption.Attributes) && | |
(StrCmp (OptionToFind->Description, BootOption.Description) == 0) && | |
(CompareMem (OptionToFind->FilePath, BootOption.FilePath, GetDevicePathSize (OptionToFind->FilePath)) == 0) && | |
(OptionToFind->OptionalDataSize == BootOption.OptionalDataSize) && | |
(CompareMem (OptionToFind->OptionalData, BootOption.OptionalData, OptionToFind->OptionalDataSize) == 0) | |
) { | |
OptionNumber = OptionToFind->OptionNumber; | |
} | |
EfiBootManagerFreeLoadOption (&BootOption); | |
} | |
} | |
// | |
// The option number assigned is either incorrect or unassigned. | |
// | |
if (OptionNumber == LoadOptionNumberUnassigned) { | |
BootOptions = EfiBootManagerGetLoadOptions (&BootOptionCount, LoadOptionTypeBoot); | |
Index = EfiBootManagerFindLoadOption (OptionToFind, BootOptions, BootOptionCount); | |
if (Index != -1) { | |
OptionNumber = BootOptions[Index].OptionNumber; | |
} | |
EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount); | |
} | |
return OptionNumber; | |
} | |
/** | |
Get the file buffer using a Memory Mapped Device Path. | |
FV address may change across reboot. This routine promises the FV file device path is right. | |
@param DevicePath The Memory Mapped Device Path to get the file buffer. | |
@param FullPath Receive the updated FV Device Path pointint to the file. | |
@param FileSize Receive the file buffer size. | |
@return The file buffer. | |
**/ | |
VOID * | |
BmGetFileBufferByMemmapFv ( | |
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, | |
OUT EFI_DEVICE_PATH_PROTOCOL **FullPath, | |
OUT UINTN *FileSize | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN Index; | |
EFI_DEVICE_PATH_PROTOCOL *FvFileNode; | |
EFI_HANDLE FvHandle; | |
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; | |
UINT32 AuthenticationStatus; | |
UINTN FvHandleCount; | |
EFI_HANDLE *FvHandles; | |
EFI_DEVICE_PATH_PROTOCOL *NewDevicePath; | |
VOID *FileBuffer; | |
FvFileNode = DevicePath; | |
Status = gBS->LocateDevicePath (&gEfiFirmwareVolume2ProtocolGuid, &FvFileNode, &FvHandle); | |
if (!EFI_ERROR (Status)) { | |
FileBuffer = GetFileBufferByFilePath (TRUE, DevicePath, FileSize, &AuthenticationStatus); | |
if (FileBuffer != NULL) { | |
*FullPath = DuplicateDevicePath (DevicePath); | |
} | |
return FileBuffer; | |
} | |
FvFileNode = NextDevicePathNode (DevicePath); | |
// | |
// Firstly find the FV file in current FV | |
// | |
gBS->HandleProtocol ( | |
gImageHandle, | |
&gEfiLoadedImageProtocolGuid, | |
(VOID **) &LoadedImage | |
); | |
NewDevicePath = AppendDevicePathNode (DevicePathFromHandle (LoadedImage->DeviceHandle), FvFileNode); | |
FileBuffer = BmGetFileBufferByMemmapFv (NewDevicePath, FullPath, FileSize); | |
FreePool (NewDevicePath); | |
if (FileBuffer != NULL) { | |
return FileBuffer; | |
} | |
// | |
// Secondly find the FV file in all other FVs | |
// | |
gBS->LocateHandleBuffer ( | |
ByProtocol, | |
&gEfiFirmwareVolume2ProtocolGuid, | |
NULL, | |
&FvHandleCount, | |
&FvHandles | |
); | |
for (Index = 0; (Index < FvHandleCount) && (FileBuffer == NULL); Index++) { | |
if (FvHandles[Index] == LoadedImage->DeviceHandle) { | |
// | |
// Skip current FV | |
// | |
continue; | |
} | |
NewDevicePath = AppendDevicePathNode (DevicePathFromHandle (FvHandles[Index]), FvFileNode); | |
FileBuffer = BmGetFileBufferByMemmapFv (NewDevicePath, FullPath, FileSize); | |
FreePool (NewDevicePath); | |
} | |
if (FvHandles != NULL) { | |
FreePool (FvHandles); | |
} | |
return FileBuffer; | |
} | |
/** | |
Check if it's a Memory Mapped FV Device Path. | |
The function doesn't garentee the device path points to existing FV file. | |
@param DevicePath Input device path. | |
@retval TRUE The device path is a Memory Mapped FV Device Path. | |
@retval FALSE The device path is NOT a Memory Mapped FV Device Path. | |
**/ | |
BOOLEAN | |
BmIsMemmapFvFilePath ( | |
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath | |
) | |
{ | |
EFI_DEVICE_PATH_PROTOCOL *FileNode; | |
if ((DevicePathType (DevicePath) == HARDWARE_DEVICE_PATH) && (DevicePathSubType (DevicePath) == HW_MEMMAP_DP)) { | |
FileNode = NextDevicePathNode (DevicePath); | |
if ((DevicePathType (FileNode) == MEDIA_DEVICE_PATH) && (DevicePathSubType (FileNode) == MEDIA_PIWG_FW_FILE_DP)) { | |
return IsDevicePathEnd (NextDevicePathNode (FileNode)); | |
} | |
} | |
return FALSE; | |
} | |
/** | |
Check whether a USB device match the specified USB Class device path. This | |
function follows "Load Option Processing" behavior in UEFI specification. | |
@param UsbIo USB I/O protocol associated with the USB device. | |
@param UsbClass The USB Class device path to match. | |
@retval TRUE The USB device match the USB Class device path. | |
@retval FALSE The USB device does not match the USB Class device path. | |
**/ | |
BOOLEAN | |
BmMatchUsbClass ( | |
IN EFI_USB_IO_PROTOCOL *UsbIo, | |
IN USB_CLASS_DEVICE_PATH *UsbClass | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_USB_DEVICE_DESCRIPTOR DevDesc; | |
EFI_USB_INTERFACE_DESCRIPTOR IfDesc; | |
UINT8 DeviceClass; | |
UINT8 DeviceSubClass; | |
UINT8 DeviceProtocol; | |
if ((DevicePathType (UsbClass) != MESSAGING_DEVICE_PATH) || | |
(DevicePathSubType (UsbClass) != MSG_USB_CLASS_DP)){ | |
return FALSE; | |
} | |
// | |
// Check Vendor Id and Product Id. | |
// | |
Status = UsbIo->UsbGetDeviceDescriptor (UsbIo, &DevDesc); | |
if (EFI_ERROR (Status)) { | |
return FALSE; | |
} | |
if ((UsbClass->VendorId != 0xffff) && | |
(UsbClass->VendorId != DevDesc.IdVendor)) { | |
return FALSE; | |
} | |
if ((UsbClass->ProductId != 0xffff) && | |
(UsbClass->ProductId != DevDesc.IdProduct)) { | |
return FALSE; | |
} | |
DeviceClass = DevDesc.DeviceClass; | |
DeviceSubClass = DevDesc.DeviceSubClass; | |
DeviceProtocol = DevDesc.DeviceProtocol; | |
if (DeviceClass == 0) { | |
// | |
// If Class in Device Descriptor is set to 0, use the Class, SubClass and | |
// Protocol in Interface Descriptor instead. | |
// | |
Status = UsbIo->UsbGetInterfaceDescriptor (UsbIo, &IfDesc); | |
if (EFI_ERROR (Status)) { | |
return FALSE; | |
} | |
DeviceClass = IfDesc.InterfaceClass; | |
DeviceSubClass = IfDesc.InterfaceSubClass; | |
DeviceProtocol = IfDesc.InterfaceProtocol; | |
} | |
// | |
// Check Class, SubClass and Protocol. | |
// | |
if ((UsbClass->DeviceClass != 0xff) && | |
(UsbClass->DeviceClass != DeviceClass)) { | |
return FALSE; | |
} | |
if ((UsbClass->DeviceSubClass != 0xff) && | |
(UsbClass->DeviceSubClass != DeviceSubClass)) { | |
return FALSE; | |
} | |
if ((UsbClass->DeviceProtocol != 0xff) && | |
(UsbClass->DeviceProtocol != DeviceProtocol)) { | |
return FALSE; | |
} | |
return TRUE; | |
} | |
/** | |
Eliminate the extra spaces in the Str to one space. | |
@param Str Input string info. | |
**/ | |
VOID | |
BmEliminateExtraSpaces ( | |
IN CHAR16 *Str | |
) | |
{ | |
UINTN Index; | |
UINTN ActualIndex; | |
for (Index = 0, ActualIndex = 0; Str[Index] != L'\0'; Index++) { | |
if ((Str[Index] != L' ') || ((ActualIndex > 0) && (Str[ActualIndex - 1] != L' '))) { | |
Str[ActualIndex++] = Str[Index]; | |
} | |
} | |
Str[ActualIndex] = L'\0'; | |
} | |
/** | |
Try to get the controller's ATA/ATAPI description. | |
@param Handle Controller handle. | |
@return The description string. | |
**/ | |
CHAR16 * | |
BmGetDescriptionFromDiskInfo ( | |
IN EFI_HANDLE Handle | |
) | |
{ | |
UINTN Index; | |
EFI_STATUS Status; | |
EFI_DISK_INFO_PROTOCOL *DiskInfo; | |
UINT32 BufferSize; | |
EFI_ATAPI_IDENTIFY_DATA IdentifyData; | |
EFI_SCSI_INQUIRY_DATA InquiryData; | |
CHAR16 *Description; | |
UINTN Length; | |
CONST UINTN ModelNameLength = 40; | |
CONST UINTN SerialNumberLength = 20; | |
CHAR8 *StrPtr; | |
UINT8 Temp; | |
Description = NULL; | |
Status = gBS->HandleProtocol ( | |
Handle, | |
&gEfiDiskInfoProtocolGuid, | |
(VOID **) &DiskInfo | |
); | |
if (EFI_ERROR (Status)) { | |
return NULL; | |
} | |
if (CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoAhciInterfaceGuid) || | |
CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoIdeInterfaceGuid)) { | |
BufferSize = sizeof (EFI_ATAPI_IDENTIFY_DATA); | |
Status = DiskInfo->Identify ( | |
DiskInfo, | |
&IdentifyData, | |
&BufferSize | |
); | |
if (!EFI_ERROR (Status)) { | |
Description = AllocateZeroPool ((ModelNameLength + SerialNumberLength + 2) * sizeof (CHAR16)); | |
ASSERT (Description != NULL); | |
for (Index = 0; Index + 1 < ModelNameLength; Index += 2) { | |
Description[Index] = (CHAR16) IdentifyData.ModelName[Index + 1]; | |
Description[Index + 1] = (CHAR16) IdentifyData.ModelName[Index]; | |
} | |
Length = Index; | |
Description[Length++] = L' '; | |
for (Index = 0; Index + 1 < SerialNumberLength; Index += 2) { | |
Description[Length + Index] = (CHAR16) IdentifyData.SerialNo[Index + 1]; | |
Description[Length + Index + 1] = (CHAR16) IdentifyData.SerialNo[Index]; | |
} | |
Length += Index; | |
Description[Length++] = L'\0'; | |
ASSERT (Length == ModelNameLength + SerialNumberLength + 2); | |
BmEliminateExtraSpaces (Description); | |
} | |
} else if (CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoScsiInterfaceGuid)) { | |
BufferSize = sizeof (EFI_SCSI_INQUIRY_DATA); | |
Status = DiskInfo->Inquiry ( | |
DiskInfo, | |
&InquiryData, | |
&BufferSize | |
); | |
if (!EFI_ERROR (Status)) { | |
Description = AllocateZeroPool ((VENDOR_IDENTIFICATION_LENGTH + PRODUCT_IDENTIFICATION_LENGTH + 2) * sizeof (CHAR16)); | |
ASSERT (Description != NULL); | |
// | |
// Per SCSI spec, EFI_SCSI_INQUIRY_DATA.Reserved_5_95[3 - 10] save the Verdor identification | |
// EFI_SCSI_INQUIRY_DATA.Reserved_5_95[11 - 26] save the product identification, | |
// Here combine the vendor identification and product identification to the description. | |
// | |
StrPtr = (CHAR8 *) (&InquiryData.Reserved_5_95[VENDOR_IDENTIFICATION_OFFSET]); | |
Temp = StrPtr[VENDOR_IDENTIFICATION_LENGTH]; | |
StrPtr[VENDOR_IDENTIFICATION_LENGTH] = '\0'; | |
AsciiStrToUnicodeStr (StrPtr, Description); | |
StrPtr[VENDOR_IDENTIFICATION_LENGTH] = Temp; | |
// | |
// Add one space at the middle of vendor information and product information. | |
// | |
Description[VENDOR_IDENTIFICATION_LENGTH] = L' '; | |
StrPtr = (CHAR8 *) (&InquiryData.Reserved_5_95[PRODUCT_IDENTIFICATION_OFFSET]); | |
StrPtr[PRODUCT_IDENTIFICATION_LENGTH] = '\0'; | |
AsciiStrToUnicodeStr (StrPtr, Description + VENDOR_IDENTIFICATION_LENGTH + 1); | |
BmEliminateExtraSpaces (Description); | |
} | |
} | |
return Description; | |
} | |
/** | |
Try to get the controller's USB description. | |
@param Handle Controller handle. | |
@return The description string. | |
**/ | |
CHAR16 * | |
BmGetUsbDescription ( | |
IN EFI_HANDLE Handle | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_USB_IO_PROTOCOL *UsbIo; | |
CHAR16 NullChar; | |
CHAR16 *Manufacturer; | |
CHAR16 *Product; | |
CHAR16 *SerialNumber; | |
CHAR16 *Description; | |
EFI_USB_DEVICE_DESCRIPTOR DevDesc; | |
UINTN DescMaxSize; | |
Status = gBS->HandleProtocol ( | |
Handle, | |
&gEfiUsbIoProtocolGuid, | |
(VOID **) &UsbIo | |
); | |
if (EFI_ERROR (Status)) { | |
return NULL; | |
} | |
NullChar = L'\0'; | |
Status = UsbIo->UsbGetDeviceDescriptor (UsbIo, &DevDesc); | |
if (EFI_ERROR (Status)) { | |
return NULL; | |
} | |
Status = UsbIo->UsbGetStringDescriptor ( | |
UsbIo, | |
mBmUsbLangId, | |
DevDesc.StrManufacturer, | |
&Manufacturer | |
); | |
if (EFI_ERROR (Status)) { | |
Manufacturer = &NullChar; | |
} | |
Status = UsbIo->UsbGetStringDescriptor ( | |
UsbIo, | |
mBmUsbLangId, | |
DevDesc.StrProduct, | |
&Product | |
); | |
if (EFI_ERROR (Status)) { | |
Product = &NullChar; | |
} | |
Status = UsbIo->UsbGetStringDescriptor ( | |
UsbIo, | |
mBmUsbLangId, | |
DevDesc.StrSerialNumber, | |
&SerialNumber | |
); | |
if (EFI_ERROR (Status)) { | |
SerialNumber = &NullChar; | |
} | |
if ((Manufacturer == &NullChar) && | |
(Product == &NullChar) && | |
(SerialNumber == &NullChar) | |
) { | |
return NULL; | |
} | |
DescMaxSize = StrSize (Manufacturer) + StrSize (Product) + StrSize (SerialNumber); | |
Description = AllocateZeroPool (DescMaxSize); | |
ASSERT (Description != NULL); | |
StrCatS (Description, DescMaxSize/sizeof(CHAR16), Manufacturer); | |
StrCatS (Description, DescMaxSize/sizeof(CHAR16), L" "); | |
StrCatS (Description, DescMaxSize/sizeof(CHAR16), Product); | |
StrCatS (Description, DescMaxSize/sizeof(CHAR16), L" "); | |
StrCatS (Description, DescMaxSize/sizeof(CHAR16), SerialNumber); | |
if (Manufacturer != &NullChar) { | |
FreePool (Manufacturer); | |
} | |
if (Product != &NullChar) { | |
FreePool (Product); | |
} | |
if (SerialNumber != &NullChar) { | |
FreePool (SerialNumber); | |
} | |
BmEliminateExtraSpaces (Description); | |
return Description; | |
} | |
/** | |
Return the boot description for the controller based on the type. | |
@param Handle Controller handle. | |
@return The description string. | |
**/ | |
CHAR16 * | |
BmGetMiscDescription ( | |
IN EFI_HANDLE Handle | |
) | |
{ | |
EFI_STATUS Status; | |
CHAR16 *Description; | |
EFI_BLOCK_IO_PROTOCOL *BlockIo; | |
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs; | |
switch (BmDevicePathType (DevicePathFromHandle (Handle))) { | |
case BmAcpiFloppyBoot: | |
Description = L"Floppy"; | |
break; | |
case BmMessageAtapiBoot: | |
case BmMessageSataBoot: | |
Status = gBS->HandleProtocol (Handle, &gEfiBlockIoProtocolGuid, (VOID **) &BlockIo); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Assume a removable SATA device should be the DVD/CD device | |
// | |
Description = BlockIo->Media->RemovableMedia ? L"DVD/CDROM" : L"Hard Drive"; | |
break; | |
case BmMessageUsbBoot: | |
Description = L"USB Device"; | |
break; | |
case BmMessageScsiBoot: | |
Description = L"SCSI Device"; | |
break; | |
case BmHardwareDeviceBoot: | |
Status = gBS->HandleProtocol (Handle, &gEfiBlockIoProtocolGuid, (VOID **) &BlockIo); | |
if (!EFI_ERROR (Status)) { | |
Description = BlockIo->Media->RemovableMedia ? L"Removable Disk" : L"Hard Drive"; | |
} else { | |
Description = L"Misc Device"; | |
} | |
break; | |
case BmMessageNetworkBoot: | |
Description = L"Network"; | |
break; | |
case BmMessageHttpBoot: | |
Description = L"Http"; | |
break; | |
default: | |
Status = gBS->HandleProtocol (Handle, &gEfiSimpleFileSystemProtocolGuid, (VOID **) &Fs); | |
if (!EFI_ERROR (Status)) { | |
Description = L"Non-Block Boot Device"; | |
} else { | |
Description = L"Misc Device"; | |
} | |
break; | |
} | |
return AllocateCopyPool (StrSize (Description), Description); | |
} | |
/** | |
Register the platform provided boot description handler. | |
@param Handler The platform provided boot description handler | |
@retval EFI_SUCCESS The handler was registered successfully. | |
@retval EFI_ALREADY_STARTED The handler was already registered. | |
@retval EFI_OUT_OF_RESOURCES There is not enough resource to perform the registration. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
EfiBootManagerRegisterBootDescriptionHandler ( | |
IN EFI_BOOT_MANAGER_BOOT_DESCRIPTION_HANDLER Handler | |
) | |
{ | |
LIST_ENTRY *Link; | |
BM_BOOT_DESCRIPTION_ENTRY *Entry; | |
for ( Link = GetFirstNode (&mPlatformBootDescriptionHandlers) | |
; !IsNull (&mPlatformBootDescriptionHandlers, Link) | |
; Link = GetNextNode (&mPlatformBootDescriptionHandlers, Link) | |
) { | |
Entry = CR (Link, BM_BOOT_DESCRIPTION_ENTRY, Link, BM_BOOT_DESCRIPTION_ENTRY_SIGNATURE); | |
if (Entry->Handler == Handler) { | |
return EFI_ALREADY_STARTED; | |
} | |
} | |
Entry = AllocatePool (sizeof (BM_BOOT_DESCRIPTION_ENTRY)); | |
if (Entry == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Entry->Signature = BM_BOOT_DESCRIPTION_ENTRY_SIGNATURE; | |
Entry->Handler = Handler; | |
InsertTailList (&mPlatformBootDescriptionHandlers, &Entry->Link); | |
return EFI_SUCCESS; | |
} | |
BM_GET_BOOT_DESCRIPTION mBmBootDescriptionHandlers[] = { | |
BmGetUsbDescription, | |
BmGetDescriptionFromDiskInfo, | |
BmGetMiscDescription | |
}; | |
/** | |
Return the boot description for the controller. | |
@param Handle Controller handle. | |
@return The description string. | |
**/ | |
CHAR16 * | |
BmGetBootDescription ( | |
IN EFI_HANDLE Handle | |
) | |
{ | |
LIST_ENTRY *Link; | |
BM_BOOT_DESCRIPTION_ENTRY *Entry; | |
CHAR16 *Description; | |
CHAR16 *DefaultDescription; | |
CHAR16 *Temp; | |
UINTN Index; | |
// | |
// Firstly get the default boot description | |
// | |
DefaultDescription = NULL; | |
for (Index = 0; Index < sizeof (mBmBootDescriptionHandlers) / sizeof (mBmBootDescriptionHandlers[0]); Index++) { | |
DefaultDescription = mBmBootDescriptionHandlers[Index] (Handle); | |
if (DefaultDescription != NULL) { | |
// | |
// Avoid description confusion between UEFI & Legacy boot option by adding "UEFI " prefix | |
// ONLY for core provided boot description handler. | |
// | |
Temp = AllocatePool (StrSize (DefaultDescription) + sizeof (mBmUefiPrefix)); | |
ASSERT (Temp != NULL); | |
StrCpyS ( Temp, | |
(StrSize (DefaultDescription) + sizeof (mBmUefiPrefix))/sizeof(CHAR16), | |
mBmUefiPrefix | |
); | |
StrCatS ( Temp, | |
(StrSize (DefaultDescription) + sizeof (mBmUefiPrefix))/sizeof(CHAR16), | |
DefaultDescription | |
); | |
FreePool (DefaultDescription); | |
DefaultDescription = Temp; | |
break; | |
} | |
} | |
ASSERT (DefaultDescription != NULL); | |
// | |
// Secondly query platform for the better boot description | |
// | |
for ( Link = GetFirstNode (&mPlatformBootDescriptionHandlers) | |
; !IsNull (&mPlatformBootDescriptionHandlers, Link) | |
; Link = GetNextNode (&mPlatformBootDescriptionHandlers, Link) | |
) { | |
Entry = CR (Link, BM_BOOT_DESCRIPTION_ENTRY, Link, BM_BOOT_DESCRIPTION_ENTRY_SIGNATURE); | |
Description = Entry->Handler (Handle, DefaultDescription); | |
if (Description != NULL) { | |
FreePool (DefaultDescription); | |
return Description; | |
} | |
} | |
return DefaultDescription; | |
} | |
/** | |
Check whether a USB device match the specified USB WWID device path. This | |
function follows "Load Option Processing" behavior in UEFI specification. | |
@param UsbIo USB I/O protocol associated with the USB device. | |
@param UsbWwid The USB WWID device path to match. | |
@retval TRUE The USB device match the USB WWID device path. | |
@retval FALSE The USB device does not match the USB WWID device path. | |
**/ | |
BOOLEAN | |
BmMatchUsbWwid ( | |
IN EFI_USB_IO_PROTOCOL *UsbIo, | |
IN USB_WWID_DEVICE_PATH *UsbWwid | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_USB_DEVICE_DESCRIPTOR DevDesc; | |
EFI_USB_INTERFACE_DESCRIPTOR IfDesc; | |
UINT16 *LangIdTable; | |
UINT16 TableSize; | |
UINT16 Index; | |
CHAR16 *CompareStr; | |
UINTN CompareLen; | |
CHAR16 *SerialNumberStr; | |
UINTN Length; | |
if ((DevicePathType (UsbWwid) != MESSAGING_DEVICE_PATH) || | |
(DevicePathSubType (UsbWwid) != MSG_USB_WWID_DP)) { | |
return FALSE; | |
} | |
// | |
// Check Vendor Id and Product Id. | |
// | |
Status = UsbIo->UsbGetDeviceDescriptor (UsbIo, &DevDesc); | |
if (EFI_ERROR (Status)) { | |
return FALSE; | |
} | |
if ((DevDesc.IdVendor != UsbWwid->VendorId) || | |
(DevDesc.IdProduct != UsbWwid->ProductId)) { | |
return FALSE; | |
} | |
// | |
// Check Interface Number. | |
// | |
Status = UsbIo->UsbGetInterfaceDescriptor (UsbIo, &IfDesc); | |
if (EFI_ERROR (Status)) { | |
return FALSE; | |
} | |
if (IfDesc.InterfaceNumber != UsbWwid->InterfaceNumber) { | |
return FALSE; | |
} | |
// | |
// Check Serial Number. | |
// | |
if (DevDesc.StrSerialNumber == 0) { | |
return FALSE; | |
} | |
// | |
// Get all supported languages. | |
// | |
TableSize = 0; | |
LangIdTable = NULL; | |
Status = UsbIo->UsbGetSupportedLanguages (UsbIo, &LangIdTable, &TableSize); | |
if (EFI_ERROR (Status) || (TableSize == 0) || (LangIdTable == NULL)) { | |
return FALSE; | |
} | |
// | |
// Serial number in USB WWID device path is the last 64-or-less UTF-16 characters. | |
// | |
CompareStr = (CHAR16 *) (UINTN) (UsbWwid + 1); | |
CompareLen = (DevicePathNodeLength (UsbWwid) - sizeof (USB_WWID_DEVICE_PATH)) / sizeof (CHAR16); | |
if (CompareStr[CompareLen - 1] == L'\0') { | |
CompareLen--; | |
} | |
// | |
// Compare serial number in each supported language. | |
// | |
for (Index = 0; Index < TableSize / sizeof (UINT16); Index++) { | |
SerialNumberStr = NULL; | |
Status = UsbIo->UsbGetStringDescriptor ( | |
UsbIo, | |
LangIdTable[Index], | |
DevDesc.StrSerialNumber, | |
&SerialNumberStr | |
); | |
if (EFI_ERROR (Status) || (SerialNumberStr == NULL)) { | |
continue; | |
} | |
Length = StrLen (SerialNumberStr); | |
if ((Length >= CompareLen) && | |
(CompareMem (SerialNumberStr + Length - CompareLen, CompareStr, CompareLen * sizeof (CHAR16)) == 0)) { | |
FreePool (SerialNumberStr); | |
return TRUE; | |
} | |
FreePool (SerialNumberStr); | |
} | |
return FALSE; | |
} | |
/** | |
Find a USB device which match the specified short-form device path start with | |
USB Class or USB WWID device path. If ParentDevicePath is NULL, this function | |
will search in all USB devices of the platform. If ParentDevicePath is not NULL, | |
this function will only search in its child devices. | |
@param DevicePath The device path that contains USB Class or USB WWID device path. | |
@param ParentDevicePathSize The length of the device path before the USB Class or | |
USB WWID device path. | |
@param UsbIoHandleCount A pointer to the count of the returned USB IO handles. | |
@retval NULL The matched USB IO handles cannot be found. | |
@retval other The matched USB IO handles. | |
**/ | |
EFI_HANDLE * | |
BmFindUsbDevice ( | |
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, | |
IN UINTN ParentDevicePathSize, | |
OUT UINTN *UsbIoHandleCount | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_HANDLE *UsbIoHandles; | |
EFI_DEVICE_PATH_PROTOCOL *UsbIoDevicePath; | |
EFI_USB_IO_PROTOCOL *UsbIo; | |
UINTN Index; | |
BOOLEAN Matched; | |
ASSERT (UsbIoHandleCount != NULL); | |
// | |
// Get all UsbIo Handles. | |
// | |
Status = gBS->LocateHandleBuffer ( | |
ByProtocol, | |
&gEfiUsbIoProtocolGuid, | |
NULL, | |
UsbIoHandleCount, | |
&UsbIoHandles | |
); | |
if (EFI_ERROR (Status)) { | |
*UsbIoHandleCount = 0; | |
UsbIoHandles = NULL; | |
} | |
for (Index = 0; Index < *UsbIoHandleCount; ) { | |
// | |
// Get the Usb IO interface. | |
// | |
Status = gBS->HandleProtocol( | |
UsbIoHandles[Index], | |
&gEfiUsbIoProtocolGuid, | |
(VOID **) &UsbIo | |
); | |
UsbIoDevicePath = DevicePathFromHandle (UsbIoHandles[Index]); | |
Matched = FALSE; | |
if (!EFI_ERROR (Status) && (UsbIoDevicePath != NULL)) { | |
// | |
// Compare starting part of UsbIoHandle's device path with ParentDevicePath. | |
// | |
if (CompareMem (UsbIoDevicePath, DevicePath, ParentDevicePathSize) == 0) { | |
if (BmMatchUsbClass (UsbIo, (USB_CLASS_DEVICE_PATH *) ((UINTN) DevicePath + ParentDevicePathSize)) || | |
BmMatchUsbWwid (UsbIo, (USB_WWID_DEVICE_PATH *) ((UINTN) DevicePath + ParentDevicePathSize))) { | |
Matched = TRUE; | |
} | |
} | |
} | |
if (!Matched) { | |
(*UsbIoHandleCount) --; | |
CopyMem (&UsbIoHandles[Index], &UsbIoHandles[Index + 1], (*UsbIoHandleCount - Index) * sizeof (EFI_HANDLE)); | |
} else { | |
Index++; | |
} | |
} | |
return UsbIoHandles; | |
} | |
/** | |
Expand USB Class or USB WWID device path node to be full device path of a USB | |
device in platform. | |
This function support following 4 cases: | |
1) Boot Option device path starts with a USB Class or USB WWID device path, | |
and there is no Media FilePath device path in the end. | |
In this case, it will follow Removable Media Boot Behavior. | |
2) Boot Option device path starts with a USB Class or USB WWID device path, | |
and ended with Media FilePath device path. | |
3) Boot Option device path starts with a full device path to a USB Host Controller, | |
contains a USB Class or USB WWID device path node, while not ended with Media | |
FilePath device path. In this case, it will follow Removable Media Boot Behavior. | |
4) Boot Option device path starts with a full device path to a USB Host Controller, | |
contains a USB Class or USB WWID device path node, and ended with Media | |
FilePath device path. | |
@param FilePath The device path pointing to a load option. | |
It could be a short-form device path. | |
@param FullPath Return the full device path of the load option after | |
short-form device path expanding. | |
Caller is responsible to free it. | |
@param FileSize Return the load option size. | |
@param ShortformNode Pointer to the USB short-form device path node in the FilePath buffer. | |
@return The load option buffer. Caller is responsible to free the memory. | |
**/ | |
VOID * | |
BmExpandUsbDevicePath ( | |
IN EFI_DEVICE_PATH_PROTOCOL *FilePath, | |
OUT EFI_DEVICE_PATH_PROTOCOL **FullPath, | |
OUT UINTN *FileSize, | |
IN EFI_DEVICE_PATH_PROTOCOL *ShortformNode | |
) | |
{ | |
UINTN ParentDevicePathSize; | |
EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath; | |
EFI_DEVICE_PATH_PROTOCOL *FullDevicePath; | |
EFI_HANDLE *Handles; | |
UINTN HandleCount; | |
UINTN Index; | |
VOID *FileBuffer; | |
ParentDevicePathSize = (UINTN) ShortformNode - (UINTN) FilePath; | |
RemainingDevicePath = NextDevicePathNode (ShortformNode); | |
FileBuffer = NULL; | |
Handles = BmFindUsbDevice (FilePath, ParentDevicePathSize, &HandleCount); | |
for (Index = 0; (Index < HandleCount) && (FileBuffer == NULL); Index++) { | |
FullDevicePath = AppendDevicePath (DevicePathFromHandle (Handles[Index]), RemainingDevicePath); | |
FileBuffer = BmGetLoadOptionBuffer (FullDevicePath, FullPath, FileSize); | |
FreePool (FullDevicePath); | |
} | |
if (Handles != NULL) { | |
FreePool (Handles); | |
} | |
return FileBuffer; | |
} | |
/** | |
Save the partition DevicePath to the CachedDevicePath as the first instance. | |
@param CachedDevicePath The device path cache. | |
@param DevicePath The partition device path to be cached. | |
**/ | |
VOID | |
BmCachePartitionDevicePath ( | |
IN OUT EFI_DEVICE_PATH_PROTOCOL **CachedDevicePath, | |
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath | |
) | |
{ | |
EFI_DEVICE_PATH_PROTOCOL *TempDevicePath; | |
UINTN Count; | |
if (BmMatchDevicePaths (*CachedDevicePath, DevicePath)) { | |
TempDevicePath = *CachedDevicePath; | |
*CachedDevicePath = BmDelPartMatchInstance (*CachedDevicePath, DevicePath); | |
FreePool (TempDevicePath); | |
} | |
if (*CachedDevicePath == NULL) { | |
*CachedDevicePath = DuplicateDevicePath (DevicePath); | |
return; | |
} | |
TempDevicePath = *CachedDevicePath; | |
*CachedDevicePath = AppendDevicePathInstance (DevicePath, *CachedDevicePath); | |
if (TempDevicePath != NULL) { | |
FreePool (TempDevicePath); | |
} | |
// | |
// Here limit the device path instance number to 12, which is max number for a system support 3 IDE controller | |
// If the user try to boot many OS in different HDs or partitions, in theory, the 'HDDP' variable maybe become larger and larger. | |
// | |
Count = 0; | |
TempDevicePath = *CachedDevicePath; | |
while (!IsDevicePathEnd (TempDevicePath)) { | |
TempDevicePath = NextDevicePathNode (TempDevicePath); | |
// | |
// Parse one instance | |
// | |
while (!IsDevicePathEndType (TempDevicePath)) { | |
TempDevicePath = NextDevicePathNode (TempDevicePath); | |
} | |
Count++; | |
// | |
// If the CachedDevicePath variable contain too much instance, only remain 12 instances. | |
// | |
if (Count == 12) { | |
SetDevicePathEndNode (TempDevicePath); | |
break; | |
} | |
} | |
} | |
/** | |
Expand a device path that starts with a hard drive media device path node to be a | |
full device path that includes the full hardware path to the device. We need | |
to do this so it can be booted. As an optimization the front match (the part point | |
to the partition node. E.g. ACPI() /PCI()/ATA()/Partition() ) is saved in a variable | |
so a connect all is not required on every boot. All successful history device path | |
which point to partition node (the front part) will be saved. | |
@param FilePath The device path pointing to a load option. | |
It could be a short-form device path. | |
@param FullPath Return the full device path of the load option after | |
short-form device path expanding. | |
Caller is responsible to free it. | |
@param FileSize Return the load option size. | |
@return The load option buffer. Caller is responsible to free the memory. | |
**/ | |
VOID * | |
BmExpandPartitionDevicePath ( | |
IN EFI_DEVICE_PATH_PROTOCOL *FilePath, | |
OUT EFI_DEVICE_PATH_PROTOCOL **FullPath, | |
OUT UINTN *FileSize | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN BlockIoHandleCount; | |
EFI_HANDLE *BlockIoBuffer; | |
VOID *FileBuffer; | |
EFI_DEVICE_PATH_PROTOCOL *BlockIoDevicePath; | |
UINTN Index; | |
EFI_DEVICE_PATH_PROTOCOL *CachedDevicePath; | |
EFI_DEVICE_PATH_PROTOCOL *TempNewDevicePath; | |
EFI_DEVICE_PATH_PROTOCOL *TempDevicePath; | |
UINTN CachedDevicePathSize; | |
BOOLEAN NeedAdjust; | |
EFI_DEVICE_PATH_PROTOCOL *Instance; | |
UINTN Size; | |
FileBuffer = NULL; | |
// | |
// Check if there is prestore 'HDDP' variable. | |
// If exist, search the front path which point to partition node in the variable instants. | |
// If fail to find or 'HDDP' not exist, reconnect all and search in all system | |
// | |
GetVariable2 (L"HDDP", &mBmHardDriveBootVariableGuid, (VOID **) &CachedDevicePath, &CachedDevicePathSize); | |
// | |
// Delete the invalid 'HDDP' variable. | |
// | |
if ((CachedDevicePath != NULL) && !IsDevicePathValid (CachedDevicePath, CachedDevicePathSize)) { | |
FreePool (CachedDevicePath); | |
CachedDevicePath = NULL; | |
Status = gRT->SetVariable ( | |
L"HDDP", | |
&mBmHardDriveBootVariableGuid, | |
0, | |
0, | |
NULL | |
); | |
ASSERT_EFI_ERROR (Status); | |
} | |
if (CachedDevicePath != NULL) { | |
TempNewDevicePath = CachedDevicePath; | |
NeedAdjust = FALSE; | |
do { | |
// | |
// Check every instance of the variable | |
// First, check whether the instance contain the partition node, which is needed for distinguishing multi | |
// partial partition boot option. Second, check whether the instance could be connected. | |
// | |
Instance = GetNextDevicePathInstance (&TempNewDevicePath, &Size); | |
if (BmMatchPartitionDevicePathNode (Instance, (HARDDRIVE_DEVICE_PATH *) FilePath)) { | |
// | |
// Connect the device path instance, the device path point to hard drive media device path node | |
// e.g. ACPI() /PCI()/ATA()/Partition() | |
// | |
Status = EfiBootManagerConnectDevicePath (Instance, NULL); | |
if (!EFI_ERROR (Status)) { | |
TempDevicePath = AppendDevicePath (Instance, NextDevicePathNode (FilePath)); | |
FileBuffer = BmGetLoadOptionBuffer (TempDevicePath, FullPath, FileSize); | |
FreePool (TempDevicePath); | |
if (FileBuffer != NULL) { | |
// | |
// Adjust the 'HDDP' instances sequence if the matched one is not first one. | |
// | |
if (NeedAdjust) { | |
BmCachePartitionDevicePath (&CachedDevicePath, Instance); | |
// | |
// Save the matching Device Path so we don't need to do a connect all next time | |
// Failing to save only impacts performance next time expanding the short-form device path | |
// | |
Status = gRT->SetVariable ( | |
L"HDDP", | |
&mBmHardDriveBootVariableGuid, | |
EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE, | |
GetDevicePathSize (CachedDevicePath), | |
CachedDevicePath | |
); | |
} | |
FreePool (Instance); | |
FreePool (CachedDevicePath); | |
return FileBuffer; | |
} | |
} | |
} | |
// | |
// Come here means the first instance is not matched | |
// | |
NeedAdjust = TRUE; | |
FreePool(Instance); | |
} while (TempNewDevicePath != NULL); | |
} | |
// | |
// If we get here we fail to find or 'HDDP' not exist, and now we need | |
// to search all devices in the system for a matched partition | |
// | |
EfiBootManagerConnectAll (); | |
Status = gBS->LocateHandleBuffer (ByProtocol, &gEfiBlockIoProtocolGuid, NULL, &BlockIoHandleCount, &BlockIoBuffer); | |
if (EFI_ERROR (Status)) { | |
BlockIoHandleCount = 0; | |
BlockIoBuffer = NULL; | |
} | |
// | |
// Loop through all the device handles that support the BLOCK_IO Protocol | |
// | |
for (Index = 0; Index < BlockIoHandleCount; Index++) { | |
BlockIoDevicePath = DevicePathFromHandle (BlockIoBuffer[Index]); | |
if (BlockIoDevicePath == NULL) { | |
continue; | |
} | |
if (BmMatchPartitionDevicePathNode (BlockIoDevicePath, (HARDDRIVE_DEVICE_PATH *) FilePath)) { | |
// | |
// Find the matched partition device path | |
// | |
TempDevicePath = AppendDevicePath (BlockIoDevicePath, NextDevicePathNode (FilePath)); | |
FileBuffer = BmGetLoadOptionBuffer (TempDevicePath, FullPath, FileSize); | |
FreePool (TempDevicePath); | |
if (FileBuffer != NULL) { | |
BmCachePartitionDevicePath (&CachedDevicePath, BlockIoDevicePath); | |
// | |
// Save the matching Device Path so we don't need to do a connect all next time | |
// Failing to save only impacts performance next time expanding the short-form device path | |
// | |
Status = gRT->SetVariable ( | |
L"HDDP", | |
&mBmHardDriveBootVariableGuid, | |
EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE, | |
GetDevicePathSize (CachedDevicePath), | |
CachedDevicePath | |
); | |
break; | |
} | |
} | |
} | |
if (CachedDevicePath != NULL) { | |
FreePool (CachedDevicePath); | |
} | |
if (BlockIoBuffer != NULL) { | |
FreePool (BlockIoBuffer); | |
} | |
return FileBuffer; | |
} | |
/** | |
Expand the media device path which points to a BlockIo or SimpleFileSystem instance | |
by appending EFI_REMOVABLE_MEDIA_FILE_NAME. | |
@param DevicePath The media device path pointing to a BlockIo or SimpleFileSystem instance. | |
@param FullPath Return the full device path pointing to the load option. | |
@param FileSize Return the size of the load option. | |
@return The load option buffer. | |
**/ | |
VOID * | |
BmExpandMediaDevicePath ( | |
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, | |
OUT EFI_DEVICE_PATH_PROTOCOL **FullPath, | |
OUT UINTN *FileSize | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_HANDLE Handle; | |
EFI_BLOCK_IO_PROTOCOL *BlockIo; | |
VOID *Buffer; | |
EFI_DEVICE_PATH_PROTOCOL *TempDevicePath; | |
UINTN Size; | |
UINTN TempSize; | |
EFI_HANDLE *SimpleFileSystemHandles; | |
UINTN NumberSimpleFileSystemHandles; | |
UINTN Index; | |
VOID *FileBuffer; | |
UINT32 AuthenticationStatus; | |
// | |
// Check whether the device is connected | |
// | |
TempDevicePath = DevicePath; | |
Status = gBS->LocateDevicePath (&gEfiSimpleFileSystemProtocolGuid, &TempDevicePath, &Handle); | |
if (!EFI_ERROR (Status)) { | |
ASSERT (IsDevicePathEnd (TempDevicePath)); | |
TempDevicePath = FileDevicePath (Handle, EFI_REMOVABLE_MEDIA_FILE_NAME); | |
FileBuffer = GetFileBufferByFilePath (TRUE, TempDevicePath, FileSize, &AuthenticationStatus); | |
if (FileBuffer == NULL) { | |
FreePool (TempDevicePath); | |
TempDevicePath = NULL; | |
} | |
*FullPath = TempDevicePath; | |
return FileBuffer; | |
} | |
// | |
// For device boot option only pointing to the removable device handle, | |
// should make sure all its children handles (its child partion or media handles) are created and connected. | |
// | |
gBS->ConnectController (Handle, NULL, NULL, TRUE); | |
// | |
// Issue a dummy read to the device to check for media change. | |
// When the removable media is changed, any Block IO read/write will | |
// cause the BlockIo protocol be reinstalled and EFI_MEDIA_CHANGED is | |
// returned. After the Block IO protocol is reinstalled, subsequent | |
// Block IO read/write will success. | |
// | |
Status = gBS->LocateDevicePath (&gEfiBlockIoProtocolGuid, &TempDevicePath, &Handle); | |
ASSERT_EFI_ERROR (Status); | |
Status = gBS->HandleProtocol (Handle, &gEfiBlockIoProtocolGuid, (VOID **) &BlockIo); | |
ASSERT_EFI_ERROR (Status); | |
Buffer = AllocatePool (BlockIo->Media->BlockSize); | |
if (Buffer != NULL) { | |
BlockIo->ReadBlocks ( | |
BlockIo, | |
BlockIo->Media->MediaId, | |
0, | |
BlockIo->Media->BlockSize, | |
Buffer | |
); | |
FreePool (Buffer); | |
} | |
// | |
// Detect the the default boot file from removable Media | |
// | |
FileBuffer = NULL; | |
*FullPath = NULL; | |
Size = GetDevicePathSize (DevicePath) - END_DEVICE_PATH_LENGTH; | |
gBS->LocateHandleBuffer ( | |
ByProtocol, | |
&gEfiSimpleFileSystemProtocolGuid, | |
NULL, | |
&NumberSimpleFileSystemHandles, | |
&SimpleFileSystemHandles | |
); | |
for (Index = 0; Index < NumberSimpleFileSystemHandles; Index++) { | |
// | |
// Get the device path size of SimpleFileSystem handle | |
// | |
TempDevicePath = DevicePathFromHandle (SimpleFileSystemHandles[Index]); | |
TempSize = GetDevicePathSize (TempDevicePath) - END_DEVICE_PATH_LENGTH; | |
// | |
// Check whether the device path of boot option is part of the SimpleFileSystem handle's device path | |
// | |
if ((Size <= TempSize) && (CompareMem (TempDevicePath, DevicePath, Size) == 0)) { | |
TempDevicePath = FileDevicePath (SimpleFileSystemHandles[Index], EFI_REMOVABLE_MEDIA_FILE_NAME); | |
FileBuffer = GetFileBufferByFilePath (TRUE, TempDevicePath, FileSize, &AuthenticationStatus); | |
if (FileBuffer != NULL) { | |
*FullPath = TempDevicePath; | |
break; | |
} | |
FreePool (TempDevicePath); | |
} | |
} | |
if (SimpleFileSystemHandles != NULL) { | |
FreePool (SimpleFileSystemHandles); | |
} | |
return FileBuffer; | |
} | |
/** | |
Get the load option by its device path. | |
@param FilePath The device path pointing to a load option. | |
It could be a short-form device path. | |
@param FullPath Return the full device path of the load option after | |
short-form device path expanding. | |
Caller is responsible to free it. | |
@param FileSize Return the load option size. | |
@return The load option buffer. Caller is responsible to free the memory. | |
**/ | |
VOID * | |
BmGetLoadOptionBuffer ( | |
IN EFI_DEVICE_PATH_PROTOCOL *FilePath, | |
OUT EFI_DEVICE_PATH_PROTOCOL **FullPath, | |
OUT UINTN *FileSize | |
) | |
{ | |
EFI_HANDLE Handle; | |
VOID *FileBuffer; | |
UINT32 AuthenticationStatus; | |
EFI_DEVICE_PATH_PROTOCOL *Node; | |
EFI_STATUS Status; | |
ASSERT ((FilePath != NULL) && (FullPath != NULL) && (FileSize != NULL)); | |
EfiBootManagerConnectDevicePath (FilePath, NULL); | |
*FullPath = NULL; | |
*FileSize = 0; | |
FileBuffer = NULL; | |
// | |
// Boot from media device by adding a default file name \EFI\BOOT\BOOT{machine type short-name}.EFI | |
// | |
Node = FilePath; | |
Status = gBS->LocateDevicePath (&gEfiSimpleFileSystemProtocolGuid, &Node, &Handle); | |
if (EFI_ERROR (Status)) { | |
Status = gBS->LocateDevicePath (&gEfiBlockIoProtocolGuid, &Node, &Handle); | |
} | |
if (!EFI_ERROR (Status) && IsDevicePathEnd (Node)) { | |
return BmExpandMediaDevicePath (FilePath, FullPath, FileSize); | |
} | |
// | |
// Expand the short-form device path to full device path | |
// | |
if ((DevicePathType (FilePath) == MEDIA_DEVICE_PATH) && | |
(DevicePathSubType (FilePath) == MEDIA_HARDDRIVE_DP)) { | |
// | |
// Expand the Harddrive device path | |
// | |
return BmExpandPartitionDevicePath (FilePath, FullPath, FileSize); | |
} else { | |
for (Node = FilePath; !IsDevicePathEnd (Node); Node = NextDevicePathNode (Node)) { | |
if ((DevicePathType (Node) == MESSAGING_DEVICE_PATH) && | |
((DevicePathSubType (Node) == MSG_USB_CLASS_DP) || (DevicePathSubType (Node) == MSG_USB_WWID_DP))) { | |
break; | |
} | |
} | |
if (!IsDevicePathEnd (Node)) { | |
// | |
// Expand the USB WWID/Class device path | |
// | |
FileBuffer = BmExpandUsbDevicePath (FilePath, FullPath, FileSize, Node); | |
if ((FileBuffer == NULL) && (FilePath == Node)) { | |
// | |
// Boot Option device path starts with USB Class or USB WWID device path. | |
// For Boot Option device path which doesn't begin with the USB Class or | |
// USB WWID device path, it's not needed to connect again here. | |
// | |
BmConnectUsbShortFormDevicePath (FilePath); | |
FileBuffer = BmExpandUsbDevicePath (FilePath, FullPath, FileSize, Node); | |
} | |
return FileBuffer; | |
} | |
} | |
// | |
// Fix up the boot option path if it points to a FV in memory map style of device path | |
// | |
if (BmIsMemmapFvFilePath (FilePath)) { | |
return BmGetFileBufferByMemmapFv (FilePath, FullPath, FileSize); | |
} | |
// | |
// Directly reads the load option when it doesn't reside in simple file system instance (LoadFile/LoadFile2), | |
// or it directly points to a file in simple file system instance. | |
// | |
Node = FilePath; | |
Status = gBS->LocateDevicePath (&gEfiLoadFileProtocolGuid, &Node, &Handle); | |
FileBuffer = GetFileBufferByFilePath (TRUE, FilePath, FileSize, &AuthenticationStatus); | |
if (FileBuffer != NULL) { | |
if (EFI_ERROR (Status)) { | |
*FullPath = DuplicateDevicePath (FilePath); | |
} else { | |
// | |
// LoadFile () may cause the device path of the Handle be updated. | |
// | |
*FullPath = AppendDevicePath (DevicePathFromHandle (Handle), Node); | |
} | |
} | |
return FileBuffer; | |
} | |
/** | |
Attempt to boot the EFI boot option. This routine sets L"BootCurent" and | |
also signals the EFI ready to boot event. If the device path for the option | |
starts with a BBS device path a legacy boot is attempted via the registered | |
gLegacyBoot function. Short form device paths are also supported via this | |
rountine. A device path starting with MEDIA_HARDDRIVE_DP, MSG_USB_WWID_DP, | |
MSG_USB_CLASS_DP gets expaned out to find the first device that matches. | |
If the BootOption Device Path fails the removable media boot algorithm | |
is attempted (\EFI\BOOTIA32.EFI, \EFI\BOOTX64.EFI,... only one file type | |
is tried per processor type) | |
@param BootOption Boot Option to try and boot. | |
On return, BootOption->Status contains the boot status. | |
EFI_SUCCESS BootOption was booted | |
EFI_UNSUPPORTED A BBS device path was found with no valid callback | |
registered via EfiBootManagerInitialize(). | |
EFI_NOT_FOUND The BootOption was not found on the system | |
!EFI_SUCCESS BootOption failed with this error status | |
**/ | |
VOID | |
EFIAPI | |
EfiBootManagerBoot ( | |
IN EFI_BOOT_MANAGER_LOAD_OPTION *BootOption | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_HANDLE ImageHandle; | |
EFI_LOADED_IMAGE_PROTOCOL *ImageInfo; | |
UINT16 Uint16; | |
UINTN OptionNumber; | |
UINTN OriginalOptionNumber; | |
EFI_DEVICE_PATH_PROTOCOL *FilePath; | |
EFI_DEVICE_PATH_PROTOCOL *Node; | |
EFI_HANDLE FvHandle; | |
VOID *FileBuffer; | |
UINTN FileSize; | |
EFI_BOOT_LOGO_PROTOCOL *BootLogo; | |
EFI_EVENT LegacyBootEvent; | |
if (BootOption == NULL) { | |
return; | |
} | |
if (BootOption->FilePath == NULL || BootOption->OptionType != LoadOptionTypeBoot) { | |
BootOption->Status = EFI_INVALID_PARAMETER; | |
return; | |
} | |
// | |
// 1. Create Boot#### for a temporary boot if there is no match Boot#### (i.e. a boot by selected a EFI Shell using "Boot From File") | |
// | |
OptionNumber = BmFindBootOptionInVariable (BootOption); | |
if (OptionNumber == LoadOptionNumberUnassigned) { | |
Status = BmGetFreeOptionNumber (LoadOptionTypeBoot, &Uint16); | |
if (!EFI_ERROR (Status)) { | |
// | |
// Save the BootOption->OptionNumber to restore later | |
// | |
OptionNumber = Uint16; | |
OriginalOptionNumber = BootOption->OptionNumber; | |
BootOption->OptionNumber = OptionNumber; | |
Status = EfiBootManagerLoadOptionToVariable (BootOption); | |
BootOption->OptionNumber = OriginalOptionNumber; | |
} | |
if (EFI_ERROR (Status)) { | |
DEBUG ((EFI_D_ERROR, "[Bds] Failed to create Boot#### for a temporary boot - %r!\n", Status)); | |
BootOption->Status = Status; | |
return ; | |
} | |
} | |
// | |
// 2. Set BootCurrent | |
// | |
Uint16 = (UINT16) OptionNumber; | |
BmSetVariableAndReportStatusCodeOnError ( | |
L"BootCurrent", | |
&gEfiGlobalVariableGuid, | |
EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, | |
sizeof (UINT16), | |
&Uint16 | |
); | |
// | |
// 3. Signal the EVT_SIGNAL_READY_TO_BOOT event when we are about to load and execute | |
// the boot option. | |
// | |
Node = BootOption->FilePath; | |
Status = gBS->LocateDevicePath (&gEfiFirmwareVolume2ProtocolGuid, &Node, &FvHandle); | |
if (!EFI_ERROR (Status) && CompareGuid ( | |
EfiGetNameGuidFromFwVolDevicePathNode ((CONST MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *) Node), | |
PcdGetPtr (PcdBootManagerMenuFile) | |
)) { | |
DEBUG ((EFI_D_INFO, "[Bds] Booting Boot Manager Menu.\n")); | |
BmStopHotkeyService (NULL, NULL); | |
} else { | |
EfiSignalEventReadyToBoot(); | |
// | |
// Report Status Code to indicate ReadyToBoot was signalled | |
// | |
REPORT_STATUS_CODE (EFI_PROGRESS_CODE, (EFI_SOFTWARE_DXE_BS_DRIVER | EFI_SW_DXE_BS_PC_READY_TO_BOOT_EVENT)); | |
// | |
// 4. Repair system through DriverHealth protocol | |
// | |
BmRepairAllControllers (); | |
} | |
PERF_START_EX (gImageHandle, "BdsAttempt", NULL, 0, (UINT32) OptionNumber); | |
// | |
// 5. Load EFI boot option to ImageHandle | |
// | |
ImageHandle = NULL; | |
if (DevicePathType (BootOption->FilePath) != BBS_DEVICE_PATH) { | |
Status = EFI_NOT_FOUND; | |
FileBuffer = BmGetLoadOptionBuffer (BootOption->FilePath, &FilePath, &FileSize); | |
DEBUG_CODE ( | |
if (FileBuffer != NULL && CompareMem (BootOption->FilePath, FilePath, GetDevicePathSize (FilePath)) != 0) { | |
DEBUG ((EFI_D_INFO, "[Bds] DevicePath expand: ")); | |
BmPrintDp (BootOption->FilePath); | |
DEBUG ((EFI_D_INFO, " -> ")); | |
BmPrintDp (FilePath); | |
DEBUG ((EFI_D_INFO, "\n")); | |
} | |
); | |
if (BmIsLoadOptionPeHeaderValid (BootOption->OptionType, FileBuffer, FileSize)) { | |
REPORT_STATUS_CODE (EFI_PROGRESS_CODE, PcdGet32 (PcdProgressCodeOsLoaderLoad)); | |
Status = gBS->LoadImage ( | |
TRUE, | |
gImageHandle, | |
FilePath, | |
FileBuffer, | |
FileSize, | |
&ImageHandle | |
); | |
} | |
if (FileBuffer != NULL) { | |
FreePool (FileBuffer); | |
} | |
if (FilePath != NULL) { | |
FreePool (FilePath); | |
} | |
if (EFI_ERROR (Status)) { | |
// | |
// Report Status Code to indicate that the failure to load boot option | |
// | |
REPORT_STATUS_CODE ( | |
EFI_ERROR_CODE | EFI_ERROR_MINOR, | |
(EFI_SOFTWARE_DXE_BS_DRIVER | EFI_SW_DXE_BS_EC_BOOT_OPTION_LOAD_ERROR) | |
); | |
BootOption->Status = Status; | |
return; | |
} | |
} | |
// | |
// 6. Adjust the different type memory page number just before booting | |
// and save the updated info into the variable for next boot to use | |
// | |
if ((BootOption->Attributes & LOAD_OPTION_CATEGORY) == LOAD_OPTION_CATEGORY_BOOT) { | |
if (PcdGetBool (PcdResetOnMemoryTypeInformationChange)) { | |
BmSetMemoryTypeInformationVariable (); | |
} | |
} | |
DEBUG_CODE_BEGIN(); | |
if (BootOption->Description == NULL) { | |
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "[Bds]Booting from unknown device path\n")); | |
} else { | |
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "[Bds]Booting %s\n", BootOption->Description)); | |
} | |
DEBUG_CODE_END(); | |
// | |
// Check to see if we should legacy BOOT. If yes then do the legacy boot | |
// Write boot to OS performance data for Legacy boot | |
// | |
if ((DevicePathType (BootOption->FilePath) == BBS_DEVICE_PATH) && (DevicePathSubType (BootOption->FilePath) == BBS_BBS_DP)) { | |
if (mBmLegacyBoot != NULL) { | |
// | |
// Write boot to OS performance data for legacy boot. | |
// | |
PERF_CODE ( | |
// | |
// Create an event to be signalled when Legacy Boot occurs to write performance data. | |
// | |
Status = EfiCreateEventLegacyBootEx( | |
TPL_NOTIFY, | |
BmWriteBootToOsPerformanceData, | |
NULL, | |
&LegacyBootEvent | |
); | |
ASSERT_EFI_ERROR (Status); | |
); | |
mBmLegacyBoot (BootOption); | |
} else { | |
BootOption->Status = EFI_UNSUPPORTED; | |
} | |
PERF_END_EX (gImageHandle, "BdsAttempt", NULL, 0, (UINT32) OptionNumber); | |
return; | |
} | |
// | |
// Provide the image with its load options | |
// | |
Status = gBS->HandleProtocol (ImageHandle, &gEfiLoadedImageProtocolGuid, (VOID **) &ImageInfo); | |
ASSERT_EFI_ERROR (Status); | |
ImageInfo->LoadOptionsSize = BootOption->OptionalDataSize; | |
ImageInfo->LoadOptions = BootOption->OptionalData; | |
// | |
// Clean to NULL because the image is loaded directly from the firmwares boot manager. | |
// | |
ImageInfo->ParentHandle = NULL; | |
// | |
// Before calling the image, enable the Watchdog Timer for 5 minutes period | |
// | |
gBS->SetWatchdogTimer (5 * 60, 0x0000, 0x00, NULL); | |
// | |
// Write boot to OS performance data for UEFI boot | |
// | |
PERF_CODE ( | |
BmWriteBootToOsPerformanceData (NULL, NULL); | |
); | |
REPORT_STATUS_CODE (EFI_PROGRESS_CODE, PcdGet32 (PcdProgressCodeOsLoaderStart)); | |
Status = gBS->StartImage (ImageHandle, &BootOption->ExitDataSize, &BootOption->ExitData); | |
DEBUG ((DEBUG_INFO | DEBUG_LOAD, "Image Return Status = %r\n", Status)); | |
BootOption->Status = Status; | |
if (EFI_ERROR (Status)) { | |
// | |
// Report Status Code to indicate that boot failure | |
// | |
REPORT_STATUS_CODE ( | |
EFI_ERROR_CODE | EFI_ERROR_MINOR, | |
(EFI_SOFTWARE_DXE_BS_DRIVER | EFI_SW_DXE_BS_EC_BOOT_OPTION_FAILED) | |
); | |
} | |
PERF_END_EX (gImageHandle, "BdsAttempt", NULL, 0, (UINT32) OptionNumber); | |
// | |
// Clear the Watchdog Timer after the image returns | |
// | |
gBS->SetWatchdogTimer (0x0000, 0x0000, 0x0000, NULL); | |
// | |
// Set Logo status invalid after trying one boot option | |
// | |
BootLogo = NULL; | |
Status = gBS->LocateProtocol (&gEfiBootLogoProtocolGuid, NULL, (VOID **) &BootLogo); | |
if (!EFI_ERROR (Status) && (BootLogo != NULL)) { | |
Status = BootLogo->SetBootLogo (BootLogo, NULL, 0, 0, 0, 0); | |
ASSERT_EFI_ERROR (Status); | |
} | |
// | |
// Clear Boot Current | |
// | |
Status = gRT->SetVariable ( | |
L"BootCurrent", | |
&gEfiGlobalVariableGuid, | |
0, | |
0, | |
NULL | |
); | |
// | |
// Deleting variable with current variable implementation shouldn't fail. | |
// When BootXXXX (e.g.: BootManagerMenu) boots BootYYYY, exiting BootYYYY causes BootCurrent deleted, | |
// exiting BootXXXX causes deleting BootCurrent returns EFI_NOT_FOUND. | |
// | |
ASSERT (Status == EFI_SUCCESS || Status == EFI_NOT_FOUND); | |
} | |
/** | |
Check whether there is a instance in BlockIoDevicePath, which contain multi device path | |
instances, has the same partition node with HardDriveDevicePath device path | |
@param BlockIoDevicePath Multi device path instances which need to check | |
@param HardDriveDevicePath A device path which starts with a hard drive media | |
device path. | |
@retval TRUE There is a matched device path instance. | |
@retval FALSE There is no matched device path instance. | |
**/ | |
BOOLEAN | |
BmMatchPartitionDevicePathNode ( | |
IN EFI_DEVICE_PATH_PROTOCOL *BlockIoDevicePath, | |
IN HARDDRIVE_DEVICE_PATH *HardDriveDevicePath | |
) | |
{ | |
HARDDRIVE_DEVICE_PATH *Node; | |
if ((BlockIoDevicePath == NULL) || (HardDriveDevicePath == NULL)) { | |
return FALSE; | |
} | |
// | |
// find the partition device path node | |
// | |
while (!IsDevicePathEnd (BlockIoDevicePath)) { | |
if ((DevicePathType (BlockIoDevicePath) == MEDIA_DEVICE_PATH) && | |
(DevicePathSubType (BlockIoDevicePath) == MEDIA_HARDDRIVE_DP) | |
) { | |
break; | |
} | |
BlockIoDevicePath = NextDevicePathNode (BlockIoDevicePath); | |
} | |
if (IsDevicePathEnd (BlockIoDevicePath)) { | |
return FALSE; | |
} | |
// | |
// See if the harddrive device path in blockio matches the orig Hard Drive Node | |
// | |
Node = (HARDDRIVE_DEVICE_PATH *) BlockIoDevicePath; | |
// | |
// Match Signature and PartitionNumber. | |
// Unused bytes in Signature are initiaized with zeros. | |
// | |
return (BOOLEAN) ( | |
(Node->PartitionNumber == HardDriveDevicePath->PartitionNumber) && | |
(Node->MBRType == HardDriveDevicePath->MBRType) && | |
(Node->SignatureType == HardDriveDevicePath->SignatureType) && | |
(CompareMem (Node->Signature, HardDriveDevicePath->Signature, sizeof (Node->Signature)) == 0) | |
); | |
} | |
/** | |
Enumerate all boot option descriptions and append " 2"/" 3"/... to make | |
unique description. | |
@param BootOptions Array of boot options. | |
@param BootOptionCount Count of boot options. | |
**/ | |
VOID | |
BmMakeBootOptionDescriptionUnique ( | |
EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions, | |
UINTN BootOptionCount | |
) | |
{ | |
UINTN Base; | |
UINTN Index; | |
UINTN DescriptionSize; | |
UINTN MaxSuffixSize; | |
BOOLEAN *Visited; | |
UINTN MatchCount; | |
if (BootOptionCount == 0) { | |
return; | |
} | |
// | |
// Calculate the maximum buffer size for the number suffix. | |
// The initial sizeof (CHAR16) is for the blank space before the number. | |
// | |
MaxSuffixSize = sizeof (CHAR16); | |
for (Index = BootOptionCount; Index != 0; Index = Index / 10) { | |
MaxSuffixSize += sizeof (CHAR16); | |
} | |
Visited = AllocateZeroPool (sizeof (BOOLEAN) * BootOptionCount); | |
ASSERT (Visited != NULL); | |
for (Base = 0; Base < BootOptionCount; Base++) { | |
if (!Visited[Base]) { | |
MatchCount = 1; | |
Visited[Base] = TRUE; | |
DescriptionSize = StrSize (BootOptions[Base].Description); | |
for (Index = Base + 1; Index < BootOptionCount; Index++) { | |
if (!Visited[Index] && StrCmp (BootOptions[Base].Description, BootOptions[Index].Description) == 0) { | |
Visited[Index] = TRUE; | |
MatchCount++; | |
FreePool (BootOptions[Index].Description); | |
BootOptions[Index].Description = AllocatePool (DescriptionSize + MaxSuffixSize); | |
UnicodeSPrint ( | |
BootOptions[Index].Description, DescriptionSize + MaxSuffixSize, | |
L"%s %d", | |
BootOptions[Base].Description, MatchCount | |
); | |
} | |
} | |
} | |
} | |
FreePool (Visited); | |
} | |
/** | |
Emuerate all possible bootable medias in the following order: | |
1. Removable BlockIo - The boot option only points to the removable media | |
device, like USB key, DVD, Floppy etc. | |
2. Fixed BlockIo - The boot option only points to a Fixed blockIo device, | |
like HardDisk. | |
3. Non-BlockIo SimpleFileSystem - The boot option points to a device supporting | |
SimpleFileSystem Protocol, but not supporting BlockIo | |
protocol. | |
4. LoadFile - The boot option points to the media supporting | |
LoadFile protocol. | |
Reference: UEFI Spec chapter 3.3 Boot Option Variables Default Boot Behavior | |
@param BootOptionCount Return the boot option count which has been found. | |
@retval Pointer to the boot option array. | |
**/ | |
EFI_BOOT_MANAGER_LOAD_OPTION * | |
BmEnumerateBootOptions ( | |
UINTN *BootOptionCount | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions; | |
UINTN HandleCount; | |
EFI_HANDLE *Handles; | |
EFI_BLOCK_IO_PROTOCOL *BlkIo; | |
UINTN Removable; | |
UINTN Index; | |
CHAR16 *Description; | |
ASSERT (BootOptionCount != NULL); | |
*BootOptionCount = 0; | |
BootOptions = NULL; | |
// | |
// Parse removable block io followed by fixed block io | |
// | |
gBS->LocateHandleBuffer ( | |
ByProtocol, | |
&gEfiBlockIoProtocolGuid, | |
NULL, | |
&HandleCount, | |
&Handles | |
); | |
for (Removable = 0; Removable < 2; Removable++) { | |
for (Index = 0; Index < HandleCount; Index++) { | |
Status = gBS->HandleProtocol ( | |
Handles[Index], | |
&gEfiBlockIoProtocolGuid, | |
(VOID **) &BlkIo | |
); | |
if (EFI_ERROR (Status)) { | |
continue; | |
} | |
// | |
// Skip the logical partitions | |
// | |
if (BlkIo->Media->LogicalPartition) { | |
continue; | |
} | |
// | |
// Skip the fixed block io then the removable block io | |
// | |
if (BlkIo->Media->RemovableMedia == ((Removable == 0) ? FALSE : TRUE)) { | |
continue; | |
} | |
Description = BmGetBootDescription (Handles[Index]); | |
BootOptions = ReallocatePool ( | |
sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount), | |
sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount + 1), | |
BootOptions | |
); | |
ASSERT (BootOptions != NULL); | |
Status = EfiBootManagerInitializeLoadOption ( | |
&BootOptions[(*BootOptionCount)++], | |
LoadOptionNumberUnassigned, | |
LoadOptionTypeBoot, | |
LOAD_OPTION_ACTIVE, | |
Description, | |
DevicePathFromHandle (Handles[Index]), | |
NULL, | |
0 | |
); | |
ASSERT_EFI_ERROR (Status); | |
FreePool (Description); | |
} | |
} | |
if (HandleCount != 0) { | |
FreePool (Handles); | |
} | |
// | |
// Parse simple file system not based on block io | |
// | |
gBS->LocateHandleBuffer ( | |
ByProtocol, | |
&gEfiSimpleFileSystemProtocolGuid, | |
NULL, | |
&HandleCount, | |
&Handles | |
); | |
for (Index = 0; Index < HandleCount; Index++) { | |
Status = gBS->HandleProtocol ( | |
Handles[Index], | |
&gEfiBlockIoProtocolGuid, | |
(VOID **) &BlkIo | |
); | |
if (!EFI_ERROR (Status)) { | |
// | |
// Skip if the file system handle supports a BlkIo protocol, which we've handled in above | |
// | |
continue; | |
} | |
Description = BmGetBootDescription (Handles[Index]); | |
BootOptions = ReallocatePool ( | |
sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount), | |
sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount + 1), | |
BootOptions | |
); | |
ASSERT (BootOptions != NULL); | |
Status = EfiBootManagerInitializeLoadOption ( | |
&BootOptions[(*BootOptionCount)++], | |
LoadOptionNumberUnassigned, | |
LoadOptionTypeBoot, | |
LOAD_OPTION_ACTIVE, | |
Description, | |
DevicePathFromHandle (Handles[Index]), | |
NULL, | |
0 | |
); | |
ASSERT_EFI_ERROR (Status); | |
FreePool (Description); | |
} | |
if (HandleCount != 0) { | |
FreePool (Handles); | |
} | |
// | |
// Parse load file, assuming UEFI Network boot option | |
// | |
gBS->LocateHandleBuffer ( | |
ByProtocol, | |
&gEfiLoadFileProtocolGuid, | |
NULL, | |
&HandleCount, | |
&Handles | |
); | |
for (Index = 0; Index < HandleCount; Index++) { | |
Description = BmGetBootDescription (Handles[Index]); | |
BootOptions = ReallocatePool ( | |
sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount), | |
sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount + 1), | |
BootOptions | |
); | |
ASSERT (BootOptions != NULL); | |
Status = EfiBootManagerInitializeLoadOption ( | |
&BootOptions[(*BootOptionCount)++], | |
LoadOptionNumberUnassigned, | |
LoadOptionTypeBoot, | |
LOAD_OPTION_ACTIVE, | |
Description, | |
DevicePathFromHandle (Handles[Index]), | |
NULL, | |
0 | |
); | |
ASSERT_EFI_ERROR (Status); | |
FreePool (Description); | |
} | |
if (HandleCount != 0) { | |
FreePool (Handles); | |
} | |
BmMakeBootOptionDescriptionUnique (BootOptions, *BootOptionCount); | |
return BootOptions; | |
} | |
/** | |
The function enumerates all boot options, creates them and registers them in the BootOrder variable. | |
**/ | |
VOID | |
EFIAPI | |
EfiBootManagerRefreshAllBootOption ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_BOOT_MANAGER_LOAD_OPTION *NvBootOptions; | |
UINTN NvBootOptionCount; | |
EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions; | |
UINTN BootOptionCount; | |
UINTN Index; | |
// | |
// Optionally refresh the legacy boot option | |
// | |
if (mBmRefreshLegacyBootOption != NULL) { | |
mBmRefreshLegacyBootOption (); | |
} | |
BootOptions = BmEnumerateBootOptions (&BootOptionCount); | |
NvBootOptions = EfiBootManagerGetLoadOptions (&NvBootOptionCount, LoadOptionTypeBoot); | |
// | |
// Mark the boot option as added by BDS by setting OptionalData to a special GUID | |
// | |
for (Index = 0; Index < BootOptionCount; Index++) { | |
BootOptions[Index].OptionalData = AllocateCopyPool (sizeof (EFI_GUID), &mBmAutoCreateBootOptionGuid); | |
BootOptions[Index].OptionalDataSize = sizeof (EFI_GUID); | |
} | |
// | |
// Remove invalid EFI boot options from NV | |
// | |
for (Index = 0; Index < NvBootOptionCount; Index++) { | |
if (((DevicePathType (NvBootOptions[Index].FilePath) != BBS_DEVICE_PATH) || | |
(DevicePathSubType (NvBootOptions[Index].FilePath) != BBS_BBS_DP) | |
) && | |
(NvBootOptions[Index].OptionalDataSize == sizeof (EFI_GUID)) && | |
CompareGuid ((EFI_GUID *) NvBootOptions[Index].OptionalData, &mBmAutoCreateBootOptionGuid) | |
) { | |
// | |
// Only check those added by BDS | |
// so that the boot options added by end-user or OS installer won't be deleted | |
// | |
if (EfiBootManagerFindLoadOption (&NvBootOptions[Index], BootOptions, BootOptionCount) == (UINTN) -1) { | |
Status = EfiBootManagerDeleteLoadOptionVariable (NvBootOptions[Index].OptionNumber, LoadOptionTypeBoot); | |
// | |
// Deleting variable with current variable implementation shouldn't fail. | |
// | |
ASSERT_EFI_ERROR (Status); | |
} | |
} | |
} | |
// | |
// Add new EFI boot options to NV | |
// | |
for (Index = 0; Index < BootOptionCount; Index++) { | |
if (EfiBootManagerFindLoadOption (&BootOptions[Index], NvBootOptions, NvBootOptionCount) == (UINTN) -1) { | |
EfiBootManagerAddLoadOptionVariable (&BootOptions[Index], (UINTN) -1); | |
// | |
// Try best to add the boot options so continue upon failure. | |
// | |
} | |
} | |
EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount); | |
EfiBootManagerFreeLoadOptions (NvBootOptions, NvBootOptionCount); | |
} | |
/** | |
This function is called to create the boot option for the Boot Manager Menu. | |
The Boot Manager Menu is shown after successfully booting a boot option. | |
Assume the BootManagerMenuFile is in the same FV as the module links to this library. | |
@param BootOption Return the boot option of the Boot Manager Menu | |
@retval EFI_SUCCESS Successfully register the Boot Manager Menu. | |
@retval Status Return status of gRT->SetVariable (). BootOption still points | |
to the Boot Manager Menu even the Status is not EFI_SUCCESS. | |
**/ | |
EFI_STATUS | |
BmRegisterBootManagerMenu ( | |
OUT EFI_BOOT_MANAGER_LOAD_OPTION *BootOption | |
) | |
{ | |
EFI_STATUS Status; | |
CHAR16 *Description; | |
UINTN DescriptionLength; | |
EFI_DEVICE_PATH_PROTOCOL *DevicePath; | |
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; | |
MEDIA_FW_VOL_FILEPATH_DEVICE_PATH FileNode; | |
Status = GetSectionFromFv ( | |
PcdGetPtr (PcdBootManagerMenuFile), | |
EFI_SECTION_USER_INTERFACE, | |
0, | |
(VOID **) &Description, | |
&DescriptionLength | |
); | |
if (EFI_ERROR (Status)) { | |
Description = NULL; | |
} | |
EfiInitializeFwVolDevicepathNode (&FileNode, PcdGetPtr (PcdBootManagerMenuFile)); | |
Status = gBS->HandleProtocol ( | |
gImageHandle, | |
&gEfiLoadedImageProtocolGuid, | |
(VOID **) &LoadedImage | |
); | |
ASSERT_EFI_ERROR (Status); | |
DevicePath = AppendDevicePathNode ( | |
DevicePathFromHandle (LoadedImage->DeviceHandle), | |
(EFI_DEVICE_PATH_PROTOCOL *) &FileNode | |
); | |
ASSERT (DevicePath != NULL); | |
Status = EfiBootManagerInitializeLoadOption ( | |
BootOption, | |
LoadOptionNumberUnassigned, | |
LoadOptionTypeBoot, | |
LOAD_OPTION_CATEGORY_APP | LOAD_OPTION_ACTIVE | LOAD_OPTION_HIDDEN, | |
(Description != NULL) ? Description : L"Boot Manager Menu", | |
DevicePath, | |
NULL, | |
0 | |
); | |
ASSERT_EFI_ERROR (Status); | |
FreePool (DevicePath); | |
if (Description != NULL) { | |
FreePool (Description); | |
} | |
DEBUG_CODE ( | |
EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions; | |
UINTN BootOptionCount; | |
BootOptions = EfiBootManagerGetLoadOptions (&BootOptionCount, LoadOptionTypeBoot); | |
ASSERT (EfiBootManagerFindLoadOption (BootOption, BootOptions, BootOptionCount) == -1); | |
EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount); | |
); | |
return EfiBootManagerAddLoadOptionVariable (BootOption, 0); | |
} | |
/** | |
Return the boot option corresponding to the Boot Manager Menu. | |
It may automatically create one if the boot option hasn't been created yet. | |
@param BootOption Return the Boot Manager Menu. | |
@retval EFI_SUCCESS The Boot Manager Menu is successfully returned. | |
@retval Status Return status of gRT->SetVariable (). BootOption still points | |
to the Boot Manager Menu even the Status is not EFI_SUCCESS. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
EfiBootManagerGetBootManagerMenu ( | |
EFI_BOOT_MANAGER_LOAD_OPTION *BootOption | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN BootOptionCount; | |
EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions; | |
UINTN Index; | |
EFI_DEVICE_PATH_PROTOCOL *Node; | |
EFI_HANDLE FvHandle; | |
BootOptions = EfiBootManagerGetLoadOptions (&BootOptionCount, LoadOptionTypeBoot); | |
for (Index = 0; Index < BootOptionCount; Index++) { | |
Node = BootOptions[Index].FilePath; | |
Status = gBS->LocateDevicePath (&gEfiFirmwareVolume2ProtocolGuid, &Node, &FvHandle); | |
if (!EFI_ERROR (Status)) { | |
if (CompareGuid ( | |
EfiGetNameGuidFromFwVolDevicePathNode ((CONST MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *) Node), | |
PcdGetPtr (PcdBootManagerMenuFile) | |
) | |
) { | |
Status = EfiBootManagerInitializeLoadOption ( | |
BootOption, | |
BootOptions[Index].OptionNumber, | |
BootOptions[Index].OptionType, | |
BootOptions[Index].Attributes, | |
BootOptions[Index].Description, | |
BootOptions[Index].FilePath, | |
BootOptions[Index].OptionalData, | |
BootOptions[Index].OptionalDataSize | |
); | |
ASSERT_EFI_ERROR (Status); | |
break; | |
} | |
} | |
} | |
EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount); | |
// | |
// Automatically create the Boot#### for Boot Manager Menu when not found. | |
// | |
if (Index == BootOptionCount) { | |
return BmRegisterBootManagerMenu (BootOption); | |
} else { | |
return EFI_SUCCESS; | |
} | |
} | |