/** @file | |
Rewrite the BootOrder NvVar based on QEMU's "bootorder" fw_cfg file. | |
Copyright (C) 2012 - 2014, Red Hat, Inc. | |
Copyright (c) 2013 - 2016, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Library/QemuFwCfgLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/UefiBootManagerLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/UefiRuntimeServicesTableLib.h> | |
#include <Library/BaseLib.h> | |
#include <Library/PrintLib.h> | |
#include <Library/DevicePathLib.h> | |
#include <Library/QemuBootOrderLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Guid/GlobalVariable.h> | |
#include <Guid/VirtioMmioTransport.h> | |
#include "ExtraRootBusMap.h" | |
/** | |
OpenFirmware to UEFI device path translation output buffer size in CHAR16's. | |
**/ | |
#define TRANSLATION_OUTPUT_SIZE 0x100 | |
/** | |
Output buffer size for OpenFirmware to UEFI device path fragment translation, | |
in CHAR16's, for a sequence of PCI bridges. | |
**/ | |
#define BRIDGE_TRANSLATION_OUTPUT_SIZE 0x40 | |
/** | |
Numbers of nodes in OpenFirmware device paths that are required and examined. | |
**/ | |
#define REQUIRED_PCI_OFW_NODES 2 | |
#define REQUIRED_MMIO_OFW_NODES 1 | |
#define EXAMINED_OFW_NODES 6 | |
/** | |
Simple character classification routines, corresponding to POSIX class names | |
and ASCII encoding. | |
**/ | |
STATIC | |
BOOLEAN | |
IsAlnum ( | |
IN CHAR8 Chr | |
) | |
{ | |
return (('0' <= Chr && Chr <= '9') || | |
('A' <= Chr && Chr <= 'Z') || | |
('a' <= Chr && Chr <= 'z') | |
); | |
} | |
STATIC | |
BOOLEAN | |
IsDriverNamePunct ( | |
IN CHAR8 Chr | |
) | |
{ | |
return (Chr == ',' || Chr == '.' || Chr == '_' || | |
Chr == '+' || Chr == '-' | |
); | |
} | |
STATIC | |
BOOLEAN | |
IsPrintNotDelim ( | |
IN CHAR8 Chr | |
) | |
{ | |
return (32 <= Chr && Chr <= 126 && | |
Chr != '/' && Chr != '@' && Chr != ':'); | |
} | |
/** | |
Utility types and functions. | |
**/ | |
typedef struct { | |
CONST CHAR8 *Ptr; // not necessarily NUL-terminated | |
UINTN Len; // number of non-NUL characters | |
} SUBSTRING; | |
/** | |
Check if Substring and String have identical contents. | |
The function relies on the restriction that a SUBSTRING cannot have embedded | |
NULs either. | |
@param[in] Substring The SUBSTRING input to the comparison. | |
@param[in] String The ASCII string input to the comparison. | |
@return Whether the inputs have identical contents. | |
**/ | |
STATIC | |
BOOLEAN | |
SubstringEq ( | |
IN SUBSTRING Substring, | |
IN CONST CHAR8 *String | |
) | |
{ | |
UINTN Pos; | |
CONST CHAR8 *Chr; | |
Pos = 0; | |
Chr = String; | |
while (Pos < Substring.Len && Substring.Ptr[Pos] == *Chr) { | |
++Pos; | |
++Chr; | |
} | |
return (BOOLEAN)(Pos == Substring.Len && *Chr == '\0'); | |
} | |
/** | |
Parse a comma-separated list of hexadecimal integers into the elements of an | |
UINT64 array. | |
Whitespace, "0x" prefixes, leading or trailing commas, sequences of commas, | |
or an empty string are not allowed; they are rejected. | |
The function relies on ASCII encoding. | |
@param[in] UnitAddress The substring to parse. | |
@param[out] Result The array, allocated by the caller, to receive | |
the parsed values. This parameter may be NULL if | |
NumResults is zero on input. | |
@param[in out] NumResults On input, the number of elements allocated for | |
Result. On output, the number of elements it has | |
taken (or would have taken) to parse the string | |
fully. | |
@retval RETURN_SUCCESS UnitAddress has been fully parsed. | |
NumResults is set to the number of parsed | |
values; the corresponding elements have | |
been set in Result. The rest of Result's | |
elements are unchanged. | |
@retval RETURN_BUFFER_TOO_SMALL UnitAddress has been fully parsed. | |
NumResults is set to the number of parsed | |
values, but elements have been stored only | |
up to the input value of NumResults, which | |
is less than what has been parsed. | |
@retval RETURN_INVALID_PARAMETER Parse error. The contents of Results is | |
indeterminate. NumResults has not been | |
changed. | |
**/ | |
STATIC | |
RETURN_STATUS | |
ParseUnitAddressHexList ( | |
IN SUBSTRING UnitAddress, | |
OUT UINT64 *Result, | |
IN OUT UINTN *NumResults | |
) | |
{ | |
UINTN Entry; // number of entry currently being parsed | |
UINT64 EntryVal; // value being constructed for current entry | |
CHAR8 PrevChr; // UnitAddress character previously checked | |
UINTN Pos; // current position within UnitAddress | |
RETURN_STATUS Status; | |
Entry = 0; | |
EntryVal = 0; | |
PrevChr = ','; | |
for (Pos = 0; Pos < UnitAddress.Len; ++Pos) { | |
CHAR8 Chr; | |
INT8 Val; | |
Chr = UnitAddress.Ptr[Pos]; | |
Val = ('a' <= Chr && Chr <= 'f') ? (Chr - 'a' + 10) : | |
('A' <= Chr && Chr <= 'F') ? (Chr - 'A' + 10) : | |
('0' <= Chr && Chr <= '9') ? (Chr - '0' ) : | |
-1; | |
if (Val >= 0) { | |
if (EntryVal > 0xFFFFFFFFFFFFFFFull) { | |
return RETURN_INVALID_PARAMETER; | |
} | |
EntryVal = LShiftU64 (EntryVal, 4) | Val; | |
} else if (Chr == ',') { | |
if (PrevChr == ',') { | |
return RETURN_INVALID_PARAMETER; | |
} | |
if (Entry < *NumResults) { | |
Result[Entry] = EntryVal; | |
} | |
++Entry; | |
EntryVal = 0; | |
} else { | |
return RETURN_INVALID_PARAMETER; | |
} | |
PrevChr = Chr; | |
} | |
if (PrevChr == ',') { | |
return RETURN_INVALID_PARAMETER; | |
} | |
if (Entry < *NumResults) { | |
Result[Entry] = EntryVal; | |
Status = RETURN_SUCCESS; | |
} else { | |
Status = RETURN_BUFFER_TOO_SMALL; | |
} | |
++Entry; | |
*NumResults = Entry; | |
return Status; | |
} | |
/** | |
A simple array of Boot Option ID's. | |
**/ | |
typedef struct { | |
UINT16 *Data; | |
UINTN Allocated; | |
UINTN Produced; | |
} BOOT_ORDER; | |
/** | |
Array element tracking an enumerated boot option that has the | |
LOAD_OPTION_ACTIVE attribute. | |
**/ | |
typedef struct { | |
CONST EFI_BOOT_MANAGER_LOAD_OPTION *BootOption; // reference only, no | |
// ownership | |
BOOLEAN Appended; // has been added to a | |
// BOOT_ORDER? | |
} ACTIVE_OPTION; | |
/** | |
Append an active boot option to BootOrder, reallocating the latter if needed. | |
@param[in out] BootOrder The structure pointing to the array and holding | |
allocation and usage counters. | |
@param[in] ActiveOption The active boot option whose ID should be | |
appended to the array. | |
@retval RETURN_SUCCESS ID of ActiveOption appended. | |
@retval RETURN_OUT_OF_RESOURCES Memory reallocation failed. | |
**/ | |
STATIC | |
RETURN_STATUS | |
BootOrderAppend ( | |
IN OUT BOOT_ORDER *BootOrder, | |
IN OUT ACTIVE_OPTION *ActiveOption | |
) | |
{ | |
if (BootOrder->Produced == BootOrder->Allocated) { | |
UINTN AllocatedNew; | |
UINT16 *DataNew; | |
ASSERT (BootOrder->Allocated > 0); | |
AllocatedNew = BootOrder->Allocated * 2; | |
DataNew = ReallocatePool ( | |
BootOrder->Allocated * sizeof (*BootOrder->Data), | |
AllocatedNew * sizeof (*DataNew), | |
BootOrder->Data | |
); | |
if (DataNew == NULL) { | |
return RETURN_OUT_OF_RESOURCES; | |
} | |
BootOrder->Allocated = AllocatedNew; | |
BootOrder->Data = DataNew; | |
} | |
BootOrder->Data[BootOrder->Produced++] = | |
(UINT16) ActiveOption->BootOption->OptionNumber; | |
ActiveOption->Appended = TRUE; | |
return RETURN_SUCCESS; | |
} | |
/** | |
Create an array of ACTIVE_OPTION elements for a boot option array. | |
@param[in] BootOptions A boot option array, created with | |
EfiBootManagerRefreshAllBootOption () and | |
EfiBootManagerGetLoadOptions (). | |
@param[in] BootOptionCount The number of elements in BootOptions. | |
@param[out] ActiveOption Pointer to the first element in the new array. | |
The caller is responsible for freeing the array | |
with FreePool() after use. | |
@param[out] Count Number of elements in the new array. | |
@retval RETURN_SUCCESS The ActiveOption array has been created. | |
@retval RETURN_NOT_FOUND No active entry has been found in | |
BootOptions. | |
@retval RETURN_OUT_OF_RESOURCES Memory allocation failed. | |
**/ | |
STATIC | |
RETURN_STATUS | |
CollectActiveOptions ( | |
IN CONST EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions, | |
IN UINTN BootOptionCount, | |
OUT ACTIVE_OPTION **ActiveOption, | |
OUT UINTN *Count | |
) | |
{ | |
UINTN Index; | |
UINTN ScanMode; | |
*ActiveOption = NULL; | |
// | |
// Scan the list twice: | |
// - count active entries, | |
// - store links to active entries. | |
// | |
for (ScanMode = 0; ScanMode < 2; ++ScanMode) { | |
*Count = 0; | |
for (Index = 0; Index < BootOptionCount; Index++) { | |
if ((BootOptions[Index].Attributes & LOAD_OPTION_ACTIVE) != 0) { | |
if (ScanMode == 1) { | |
(*ActiveOption)[*Count].BootOption = &BootOptions[Index]; | |
(*ActiveOption)[*Count].Appended = FALSE; | |
} | |
++*Count; | |
} | |
} | |
if (ScanMode == 0) { | |
if (*Count == 0) { | |
return RETURN_NOT_FOUND; | |
} | |
*ActiveOption = AllocatePool (*Count * sizeof **ActiveOption); | |
if (*ActiveOption == NULL) { | |
return RETURN_OUT_OF_RESOURCES; | |
} | |
} | |
} | |
return RETURN_SUCCESS; | |
} | |
/** | |
OpenFirmware device path node | |
**/ | |
typedef struct { | |
SUBSTRING DriverName; | |
SUBSTRING UnitAddress; | |
SUBSTRING DeviceArguments; | |
} OFW_NODE; | |
/** | |
Parse an OpenFirmware device path node into the caller-allocated OFW_NODE | |
structure, and advance in the input string. | |
The node format is mostly parsed after IEEE 1275-1994, 3.2.1.1 "Node names" | |
(a leading slash is expected and not returned): | |
/driver-name@unit-address[:device-arguments][<LF>] | |
A single trailing <LF> character is consumed but not returned. A trailing | |
<LF> or NUL character terminates the device path. | |
The function relies on ASCII encoding. | |
@param[in out] Ptr Address of the pointer pointing to the start of the | |
node string. After successful parsing *Ptr is set to | |
the byte immediately following the consumed | |
characters. On error it points to the byte that | |
caused the error. The input string is never modified. | |
@param[out] OfwNode The members of this structure point into the input | |
string, designating components of the node. | |
Separators are never included. If "device-arguments" | |
is missing, then DeviceArguments.Ptr is set to NULL. | |
All components that are present have nonzero length. | |
If the call doesn't succeed, the contents of this | |
structure is indeterminate. | |
@param[out] IsFinal In case of successful parsing, this parameter signals | |
whether the node just parsed is the final node in the | |
device path. The call after a final node will attempt | |
to start parsing the next path. If the call doesn't | |
succeed, then this parameter is not changed. | |
@retval RETURN_SUCCESS Parsing successful. | |
@retval RETURN_NOT_FOUND Parsing terminated. *Ptr was (and is) | |
pointing to an empty string. | |
@retval RETURN_INVALID_PARAMETER Parse error. | |
**/ | |
STATIC | |
RETURN_STATUS | |
ParseOfwNode ( | |
IN OUT CONST CHAR8 **Ptr, | |
OUT OFW_NODE *OfwNode, | |
OUT BOOLEAN *IsFinal | |
) | |
{ | |
// | |
// A leading slash is expected. End of string is tolerated. | |
// | |
switch (**Ptr) { | |
case '\0': | |
return RETURN_NOT_FOUND; | |
case '/': | |
++*Ptr; | |
break; | |
default: | |
return RETURN_INVALID_PARAMETER; | |
} | |
// | |
// driver-name | |
// | |
OfwNode->DriverName.Ptr = *Ptr; | |
OfwNode->DriverName.Len = 0; | |
while (OfwNode->DriverName.Len < 32 && | |
(IsAlnum (**Ptr) || IsDriverNamePunct (**Ptr)) | |
) { | |
++*Ptr; | |
++OfwNode->DriverName.Len; | |
} | |
if (OfwNode->DriverName.Len == 0 || OfwNode->DriverName.Len == 32) { | |
return RETURN_INVALID_PARAMETER; | |
} | |
// | |
// unit-address | |
// | |
if (**Ptr != '@') { | |
return RETURN_INVALID_PARAMETER; | |
} | |
++*Ptr; | |
OfwNode->UnitAddress.Ptr = *Ptr; | |
OfwNode->UnitAddress.Len = 0; | |
while (IsPrintNotDelim (**Ptr)) { | |
++*Ptr; | |
++OfwNode->UnitAddress.Len; | |
} | |
if (OfwNode->UnitAddress.Len == 0) { | |
return RETURN_INVALID_PARAMETER; | |
} | |
// | |
// device-arguments, may be omitted | |
// | |
OfwNode->DeviceArguments.Len = 0; | |
if (**Ptr == ':') { | |
++*Ptr; | |
OfwNode->DeviceArguments.Ptr = *Ptr; | |
while (IsPrintNotDelim (**Ptr)) { | |
++*Ptr; | |
++OfwNode->DeviceArguments.Len; | |
} | |
if (OfwNode->DeviceArguments.Len == 0) { | |
return RETURN_INVALID_PARAMETER; | |
} | |
} | |
else { | |
OfwNode->DeviceArguments.Ptr = NULL; | |
} | |
switch (**Ptr) { | |
case '\n': | |
++*Ptr; | |
// | |
// fall through | |
// | |
case '\0': | |
*IsFinal = TRUE; | |
break; | |
case '/': | |
*IsFinal = FALSE; | |
break; | |
default: | |
return RETURN_INVALID_PARAMETER; | |
} | |
DEBUG (( | |
DEBUG_VERBOSE, | |
"%a: DriverName=\"%.*a\" UnitAddress=\"%.*a\" DeviceArguments=\"%.*a\"\n", | |
__FUNCTION__, | |
OfwNode->DriverName.Len, OfwNode->DriverName.Ptr, | |
OfwNode->UnitAddress.Len, OfwNode->UnitAddress.Ptr, | |
OfwNode->DeviceArguments.Len, | |
OfwNode->DeviceArguments.Ptr == NULL ? "" : OfwNode->DeviceArguments.Ptr | |
)); | |
return RETURN_SUCCESS; | |
} | |
/** | |
Translate a PCI-like array of OpenFirmware device nodes to a UEFI device path | |
fragment. | |
@param[in] OfwNode Array of OpenFirmware device nodes to | |
translate, constituting the beginning of an | |
OpenFirmware device path. | |
@param[in] NumNodes Number of elements in OfwNode. | |
@param[in] ExtraPciRoots An EXTRA_ROOT_BUS_MAP object created with | |
CreateExtraRootBusMap(), to be used for | |
translating positions of extra root buses to | |
bus numbers. | |
@param[out] Translated Destination array receiving the UEFI path | |
fragment, allocated by the caller. If the | |
return value differs from RETURN_SUCCESS, its | |
contents is indeterminate. | |
@param[in out] TranslatedSize On input, the number of CHAR16's in | |
Translated. On RETURN_SUCCESS this parameter | |
is assigned the number of non-NUL CHAR16's | |
written to Translated. In case of other return | |
values, TranslatedSize is indeterminate. | |
@retval RETURN_SUCCESS Translation successful. | |
@retval RETURN_BUFFER_TOO_SMALL The translation does not fit into the number | |
of bytes provided. | |
@retval RETURN_UNSUPPORTED The array of OpenFirmware device nodes can't | |
be translated in the current implementation. | |
@retval RETURN_PROTOCOL_ERROR The initial OpenFirmware node refers to an | |
extra PCI root bus (by serial number) that | |
is invalid according to ExtraPciRoots. | |
**/ | |
STATIC | |
RETURN_STATUS | |
TranslatePciOfwNodes ( | |
IN CONST OFW_NODE *OfwNode, | |
IN UINTN NumNodes, | |
IN CONST EXTRA_ROOT_BUS_MAP *ExtraPciRoots, | |
OUT CHAR16 *Translated, | |
IN OUT UINTN *TranslatedSize | |
) | |
{ | |
UINT32 PciRoot; | |
CHAR8 *Comma; | |
UINTN FirstNonBridge; | |
CHAR16 Bridges[BRIDGE_TRANSLATION_OUTPUT_SIZE]; | |
UINTN BridgesLen; | |
UINT64 PciDevFun[2]; | |
UINTN NumEntries; | |
UINTN Written; | |
// | |
// Resolve the PCI root bus number. | |
// | |
// The initial OFW node for the main root bus (ie. bus number 0) is: | |
// | |
// /pci@i0cf8 | |
// | |
// For extra root buses, the initial OFW node is | |
// | |
// /pci@i0cf8,4 | |
// ^ | |
// root bus serial number (not PCI bus number) | |
// | |
if (NumNodes < REQUIRED_PCI_OFW_NODES || | |
!SubstringEq (OfwNode[0].DriverName, "pci") | |
) { | |
return RETURN_UNSUPPORTED; | |
} | |
PciRoot = 0; | |
Comma = ScanMem8 (OfwNode[0].UnitAddress.Ptr, OfwNode[0].UnitAddress.Len, | |
','); | |
if (Comma != NULL) { | |
SUBSTRING PciRootSerialSubString; | |
UINT64 PciRootSerial; | |
// | |
// Parse the root bus serial number from the unit address after the comma. | |
// | |
PciRootSerialSubString.Ptr = Comma + 1; | |
PciRootSerialSubString.Len = OfwNode[0].UnitAddress.Len - | |
(PciRootSerialSubString.Ptr - | |
OfwNode[0].UnitAddress.Ptr); | |
NumEntries = 1; | |
if (RETURN_ERROR (ParseUnitAddressHexList (PciRootSerialSubString, | |
&PciRootSerial, &NumEntries))) { | |
return RETURN_UNSUPPORTED; | |
} | |
// | |
// Map the extra root bus's serial number to its actual bus number. | |
// | |
if (EFI_ERROR (MapRootBusPosToBusNr (ExtraPciRoots, PciRootSerial, | |
&PciRoot))) { | |
return RETURN_PROTOCOL_ERROR; | |
} | |
} | |
// | |
// Translate a sequence of PCI bridges. For each bridge, the OFW node is: | |
// | |
// pci-bridge@1e[,0] | |
// ^ ^ | |
// PCI slot & function on the parent, holding the bridge | |
// | |
// and the UEFI device path node is: | |
// | |
// Pci(0x1E,0x0) | |
// | |
FirstNonBridge = 1; | |
Bridges[0] = L'\0'; | |
BridgesLen = 0; | |
do { | |
UINT64 BridgeDevFun[2]; | |
UINTN BridgesFreeBytes; | |
if (!SubstringEq (OfwNode[FirstNonBridge].DriverName, "pci-bridge")) { | |
break; | |
} | |
BridgeDevFun[1] = 0; | |
NumEntries = sizeof BridgeDevFun / sizeof BridgeDevFun[0]; | |
if (ParseUnitAddressHexList (OfwNode[FirstNonBridge].UnitAddress, | |
BridgeDevFun, &NumEntries) != RETURN_SUCCESS) { | |
return RETURN_UNSUPPORTED; | |
} | |
BridgesFreeBytes = sizeof Bridges - BridgesLen * sizeof Bridges[0]; | |
Written = UnicodeSPrintAsciiFormat (Bridges + BridgesLen, BridgesFreeBytes, | |
"/Pci(0x%Lx,0x%Lx)", BridgeDevFun[0], BridgeDevFun[1]); | |
BridgesLen += Written; | |
// | |
// There's no way to differentiate between "completely used up without | |
// truncation" and "truncated", so treat the former as the latter. | |
// | |
if (BridgesLen + 1 == BRIDGE_TRANSLATION_OUTPUT_SIZE) { | |
return RETURN_UNSUPPORTED; | |
} | |
++FirstNonBridge; | |
} while (FirstNonBridge < NumNodes); | |
if (FirstNonBridge == NumNodes) { | |
return RETURN_UNSUPPORTED; | |
} | |
// | |
// Parse the OFW nodes starting with the first non-bridge node. | |
// | |
PciDevFun[1] = 0; | |
NumEntries = ARRAY_SIZE (PciDevFun); | |
if (ParseUnitAddressHexList ( | |
OfwNode[FirstNonBridge].UnitAddress, | |
PciDevFun, | |
&NumEntries | |
) != RETURN_SUCCESS | |
) { | |
return RETURN_UNSUPPORTED; | |
} | |
if (NumNodes >= FirstNonBridge + 3 && | |
SubstringEq (OfwNode[FirstNonBridge + 0].DriverName, "ide") && | |
SubstringEq (OfwNode[FirstNonBridge + 1].DriverName, "drive") && | |
SubstringEq (OfwNode[FirstNonBridge + 2].DriverName, "disk") | |
) { | |
// | |
// OpenFirmware device path (IDE disk, IDE CD-ROM): | |
// | |
// /pci@i0cf8/ide@1,1/drive@0/disk@0 | |
// ^ ^ ^ ^ ^ | |
// | | | | master or slave | |
// | | | primary or secondary | |
// | PCI slot & function holding IDE controller | |
// PCI root at system bus port, PIO | |
// | |
// UEFI device path: | |
// | |
// PciRoot(0x0)/Pci(0x1,0x1)/Ata(Primary,Master,0x0) | |
// ^ | |
// fixed LUN | |
// | |
UINT64 Secondary; | |
UINT64 Slave; | |
NumEntries = 1; | |
if (ParseUnitAddressHexList ( | |
OfwNode[FirstNonBridge + 1].UnitAddress, | |
&Secondary, | |
&NumEntries | |
) != RETURN_SUCCESS || | |
Secondary > 1 || | |
ParseUnitAddressHexList ( | |
OfwNode[FirstNonBridge + 2].UnitAddress, | |
&Slave, | |
&NumEntries // reuse after previous single-element call | |
) != RETURN_SUCCESS || | |
Slave > 1 | |
) { | |
return RETURN_UNSUPPORTED; | |
} | |
Written = UnicodeSPrintAsciiFormat ( | |
Translated, | |
*TranslatedSize * sizeof (*Translated), // BufferSize in bytes | |
"PciRoot(0x%x)%s/Pci(0x%Lx,0x%Lx)/Ata(%a,%a,0x0)", | |
PciRoot, | |
Bridges, | |
PciDevFun[0], | |
PciDevFun[1], | |
Secondary ? "Secondary" : "Primary", | |
Slave ? "Slave" : "Master" | |
); | |
} else if (NumNodes >= FirstNonBridge + 3 && | |
SubstringEq (OfwNode[FirstNonBridge + 0].DriverName, "pci8086,2922") && | |
SubstringEq (OfwNode[FirstNonBridge + 1].DriverName, "drive") && | |
SubstringEq (OfwNode[FirstNonBridge + 2].DriverName, "disk") | |
) { | |
// | |
// OpenFirmware device path (Q35 SATA disk and CD-ROM): | |
// | |
// /pci@i0cf8/pci8086,2922@1f,2/drive@1/disk@0 | |
// ^ ^ ^ ^ ^ | |
// | | | | device number (fixed 0) | |
// | | | channel (port) number | |
// | PCI slot & function holding SATA HBA | |
// PCI root at system bus port, PIO | |
// | |
// UEFI device path: | |
// | |
// PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x1,0xFFFF,0x0) | |
// ^ ^ ^ | |
// | | LUN (always 0 on Q35) | |
// | port multiplier port number, | |
// | always 0xFFFF on Q35 | |
// channel (port) number | |
// | |
UINT64 Channel; | |
NumEntries = 1; | |
if (RETURN_ERROR (ParseUnitAddressHexList ( | |
OfwNode[FirstNonBridge + 1].UnitAddress, &Channel, | |
&NumEntries))) { | |
return RETURN_UNSUPPORTED; | |
} | |
Written = UnicodeSPrintAsciiFormat ( | |
Translated, | |
*TranslatedSize * sizeof (*Translated), // BufferSize in bytes | |
"PciRoot(0x%x)%s/Pci(0x%Lx,0x%Lx)/Sata(0x%Lx,0xFFFF,0x0)", | |
PciRoot, | |
Bridges, | |
PciDevFun[0], | |
PciDevFun[1], | |
Channel | |
); | |
} else if (NumNodes >= FirstNonBridge + 3 && | |
SubstringEq (OfwNode[FirstNonBridge + 0].DriverName, "isa") && | |
SubstringEq (OfwNode[FirstNonBridge + 1].DriverName, "fdc") && | |
SubstringEq (OfwNode[FirstNonBridge + 2].DriverName, "floppy") | |
) { | |
// | |
// OpenFirmware device path (floppy disk): | |
// | |
// /pci@i0cf8/isa@1/fdc@03f0/floppy@0 | |
// ^ ^ ^ ^ | |
// | | | A: or B: | |
// | | ISA controller io-port (hex) | |
// | PCI slot holding ISA controller | |
// PCI root at system bus port, PIO | |
// | |
// UEFI device path: | |
// | |
// PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x0) | |
// ^ | |
// ACPI UID | |
// | |
UINT64 AcpiUid; | |
NumEntries = 1; | |
if (ParseUnitAddressHexList ( | |
OfwNode[FirstNonBridge + 2].UnitAddress, | |
&AcpiUid, | |
&NumEntries | |
) != RETURN_SUCCESS || | |
AcpiUid > 1 | |
) { | |
return RETURN_UNSUPPORTED; | |
} | |
Written = UnicodeSPrintAsciiFormat ( | |
Translated, | |
*TranslatedSize * sizeof (*Translated), // BufferSize in bytes | |
"PciRoot(0x%x)%s/Pci(0x%Lx,0x%Lx)/Floppy(0x%Lx)", | |
PciRoot, | |
Bridges, | |
PciDevFun[0], | |
PciDevFun[1], | |
AcpiUid | |
); | |
} else if (NumNodes >= FirstNonBridge + 2 && | |
SubstringEq (OfwNode[FirstNonBridge + 0].DriverName, "scsi") && | |
SubstringEq (OfwNode[FirstNonBridge + 1].DriverName, "disk") | |
) { | |
// | |
// OpenFirmware device path (virtio-blk disk): | |
// | |
// /pci@i0cf8/scsi@6[,3]/disk@0,0 | |
// ^ ^ ^ ^ ^ | |
// | | | fixed | |
// | | PCI function corresponding to disk (optional) | |
// | PCI slot holding disk | |
// PCI root at system bus port, PIO | |
// | |
// UEFI device path prefix: | |
// | |
// PciRoot(0x0)/Pci(0x6,0x0) -- if PCI function is 0 or absent | |
// PciRoot(0x0)/Pci(0x6,0x3) -- if PCI function is present and nonzero | |
// | |
Written = UnicodeSPrintAsciiFormat ( | |
Translated, | |
*TranslatedSize * sizeof (*Translated), // BufferSize in bytes | |
"PciRoot(0x%x)%s/Pci(0x%Lx,0x%Lx)", | |
PciRoot, | |
Bridges, | |
PciDevFun[0], | |
PciDevFun[1] | |
); | |
} else if (NumNodes >= FirstNonBridge + 3 && | |
SubstringEq (OfwNode[FirstNonBridge + 0].DriverName, "scsi") && | |
SubstringEq (OfwNode[FirstNonBridge + 1].DriverName, "channel") && | |
SubstringEq (OfwNode[FirstNonBridge + 2].DriverName, "disk") | |
) { | |
// | |
// OpenFirmware device path (virtio-scsi disk): | |
// | |
// /pci@i0cf8/scsi@7[,3]/channel@0/disk@2,3 | |
// ^ ^ ^ ^ ^ | |
// | | | | LUN | |
// | | | target | |
// | | channel (unused, fixed 0) | |
// | PCI slot[, function] holding SCSI controller | |
// PCI root at system bus port, PIO | |
// | |
// UEFI device path prefix: | |
// | |
// PciRoot(0x0)/Pci(0x7,0x0)/Scsi(0x2,0x3) | |
// -- if PCI function is 0 or absent | |
// PciRoot(0x0)/Pci(0x7,0x3)/Scsi(0x2,0x3) | |
// -- if PCI function is present and nonzero | |
// | |
UINT64 TargetLun[2]; | |
TargetLun[1] = 0; | |
NumEntries = ARRAY_SIZE (TargetLun); | |
if (ParseUnitAddressHexList ( | |
OfwNode[FirstNonBridge + 2].UnitAddress, | |
TargetLun, | |
&NumEntries | |
) != RETURN_SUCCESS | |
) { | |
return RETURN_UNSUPPORTED; | |
} | |
Written = UnicodeSPrintAsciiFormat ( | |
Translated, | |
*TranslatedSize * sizeof (*Translated), // BufferSize in bytes | |
"PciRoot(0x%x)%s/Pci(0x%Lx,0x%Lx)/Scsi(0x%Lx,0x%Lx)", | |
PciRoot, | |
Bridges, | |
PciDevFun[0], | |
PciDevFun[1], | |
TargetLun[0], | |
TargetLun[1] | |
); | |
} else if (NumNodes >= FirstNonBridge + 2 && | |
SubstringEq (OfwNode[FirstNonBridge + 0].DriverName, "pci8086,5845") && | |
SubstringEq (OfwNode[FirstNonBridge + 1].DriverName, "namespace") | |
) { | |
// | |
// OpenFirmware device path (NVMe device): | |
// | |
// /pci@i0cf8/pci8086,5845@6[,1]/namespace@1,0 | |
// ^ ^ ^ ^ ^ | |
// | | | | Extended Unique Identifier | |
// | | | | (EUI-64), big endian interp. | |
// | | | namespace ID | |
// | PCI slot & function holding NVMe controller | |
// PCI root at system bus port, PIO | |
// | |
// UEFI device path: | |
// | |
// PciRoot(0x0)/Pci(0x6,0x1)/NVMe(0x1,00-00-00-00-00-00-00-00) | |
// ^ ^ | |
// | octets of the EUI-64 | |
// | in address order | |
// namespace ID | |
// | |
UINT64 Namespace[2]; | |
UINTN RequiredEntries; | |
UINT8 *Eui64; | |
RequiredEntries = ARRAY_SIZE (Namespace); | |
NumEntries = RequiredEntries; | |
if (ParseUnitAddressHexList ( | |
OfwNode[FirstNonBridge + 1].UnitAddress, | |
Namespace, | |
&NumEntries | |
) != RETURN_SUCCESS || | |
NumEntries != RequiredEntries || | |
Namespace[0] == 0 || | |
Namespace[0] >= MAX_UINT32 | |
) { | |
return RETURN_UNSUPPORTED; | |
} | |
Eui64 = (UINT8 *)&Namespace[1]; | |
Written = UnicodeSPrintAsciiFormat ( | |
Translated, | |
*TranslatedSize * sizeof (*Translated), // BufferSize in bytes | |
"PciRoot(0x%x)%s/Pci(0x%Lx,0x%Lx)/" | |
"NVMe(0x%Lx,%02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x)", | |
PciRoot, | |
Bridges, | |
PciDevFun[0], | |
PciDevFun[1], | |
Namespace[0], | |
Eui64[7], Eui64[6], Eui64[5], Eui64[4], | |
Eui64[3], Eui64[2], Eui64[1], Eui64[0] | |
); | |
} else if (NumNodes >= FirstNonBridge + 2 && | |
SubstringEq (OfwNode[FirstNonBridge + 0].DriverName, "usb") && | |
SubstringEq (OfwNode[FirstNonBridge + 1].DriverName, "storage")) { | |
// | |
// OpenFirmware device path (usb-storage device in XHCI port): | |
// | |
// /pci@i0cf8/usb@3[,1]/storage@2/channel@0/disk@0,0 | |
// ^ ^ ^ ^ ^ ^ ^ | |
// | | | | fixed fixed | |
// | | | XHCI port number, 1-based | |
// | | PCI function corresponding to XHCI (optional) | |
// | PCI slot holding XHCI | |
// PCI root at system bus port, PIO | |
// | |
// UEFI device path prefix: | |
// | |
// PciRoot(0x0)/Pci(0x3,0x1)/USB(0x1,0x0) | |
// ^ ^ | |
// | XHCI port number in 0-based notation | |
// 0x0 if PCI function is 0, or absent from OFW | |
// | |
RETURN_STATUS ParseStatus; | |
UINT64 OneBasedXhciPort; | |
NumEntries = 1; | |
ParseStatus = ParseUnitAddressHexList ( | |
OfwNode[FirstNonBridge + 1].UnitAddress, | |
&OneBasedXhciPort, | |
&NumEntries | |
); | |
if (RETURN_ERROR (ParseStatus) || OneBasedXhciPort == 0) { | |
return RETURN_UNSUPPORTED; | |
} | |
Written = UnicodeSPrintAsciiFormat ( | |
Translated, | |
*TranslatedSize * sizeof (*Translated), // BufferSize in bytes | |
"PciRoot(0x%x)%s/Pci(0x%Lx,0x%Lx)/USB(0x%Lx,0x0)", | |
PciRoot, | |
Bridges, | |
PciDevFun[0], | |
PciDevFun[1], | |
OneBasedXhciPort - 1 | |
); | |
} else { | |
// | |
// Generic OpenFirmware device path for PCI devices: | |
// | |
// /pci@i0cf8/ethernet@3[,2] | |
// ^ ^ | |
// | PCI slot[, function] holding Ethernet card | |
// PCI root at system bus port, PIO | |
// | |
// UEFI device path prefix (dependent on presence of nonzero PCI function): | |
// | |
// PciRoot(0x0)/Pci(0x3,0x0) | |
// PciRoot(0x0)/Pci(0x3,0x2) | |
// | |
Written = UnicodeSPrintAsciiFormat ( | |
Translated, | |
*TranslatedSize * sizeof (*Translated), // BufferSize in bytes | |
"PciRoot(0x%x)%s/Pci(0x%Lx,0x%Lx)", | |
PciRoot, | |
Bridges, | |
PciDevFun[0], | |
PciDevFun[1] | |
); | |
} | |
// | |
// There's no way to differentiate between "completely used up without | |
// truncation" and "truncated", so treat the former as the latter, and return | |
// success only for "some room left unused". | |
// | |
if (Written + 1 < *TranslatedSize) { | |
*TranslatedSize = Written; | |
return RETURN_SUCCESS; | |
} | |
return RETURN_BUFFER_TOO_SMALL; | |
} | |
// | |
// A type providing easy raw access to the base address of a virtio-mmio | |
// transport. | |
// | |
typedef union { | |
UINT64 Uint64; | |
UINT8 Raw[8]; | |
} VIRTIO_MMIO_BASE_ADDRESS; | |
/** | |
Translate an MMIO-like array of OpenFirmware device nodes to a UEFI device | |
path fragment. | |
@param[in] OfwNode Array of OpenFirmware device nodes to | |
translate, constituting the beginning of an | |
OpenFirmware device path. | |
@param[in] NumNodes Number of elements in OfwNode. | |
@param[out] Translated Destination array receiving the UEFI path | |
fragment, allocated by the caller. If the | |
return value differs from RETURN_SUCCESS, its | |
contents is indeterminate. | |
@param[in out] TranslatedSize On input, the number of CHAR16's in | |
Translated. On RETURN_SUCCESS this parameter | |
is assigned the number of non-NUL CHAR16's | |
written to Translated. In case of other return | |
values, TranslatedSize is indeterminate. | |
@retval RETURN_SUCCESS Translation successful. | |
@retval RETURN_BUFFER_TOO_SMALL The translation does not fit into the number | |
of bytes provided. | |
@retval RETURN_UNSUPPORTED The array of OpenFirmware device nodes can't | |
be translated in the current implementation. | |
**/ | |
STATIC | |
RETURN_STATUS | |
TranslateMmioOfwNodes ( | |
IN CONST OFW_NODE *OfwNode, | |
IN UINTN NumNodes, | |
OUT CHAR16 *Translated, | |
IN OUT UINTN *TranslatedSize | |
) | |
{ | |
VIRTIO_MMIO_BASE_ADDRESS VirtioMmioBase; | |
CHAR16 VenHwString[60 + 1]; | |
UINTN NumEntries; | |
UINTN Written; | |
// | |
// Get the base address of the virtio-mmio transport. | |
// | |
if (NumNodes < REQUIRED_MMIO_OFW_NODES || | |
!SubstringEq (OfwNode[0].DriverName, "virtio-mmio") | |
) { | |
return RETURN_UNSUPPORTED; | |
} | |
NumEntries = 1; | |
if (ParseUnitAddressHexList ( | |
OfwNode[0].UnitAddress, | |
&VirtioMmioBase.Uint64, | |
&NumEntries | |
) != RETURN_SUCCESS | |
) { | |
return RETURN_UNSUPPORTED; | |
} | |
UnicodeSPrintAsciiFormat (VenHwString, sizeof VenHwString, | |
"VenHw(%g,%02X%02X%02X%02X%02X%02X%02X%02X)", &gVirtioMmioTransportGuid, | |
VirtioMmioBase.Raw[0], VirtioMmioBase.Raw[1], VirtioMmioBase.Raw[2], | |
VirtioMmioBase.Raw[3], VirtioMmioBase.Raw[4], VirtioMmioBase.Raw[5], | |
VirtioMmioBase.Raw[6], VirtioMmioBase.Raw[7]); | |
if (NumNodes >= 2 && | |
SubstringEq (OfwNode[1].DriverName, "disk")) { | |
// | |
// OpenFirmware device path (virtio-blk disk): | |
// | |
// /virtio-mmio@000000000a003c00/disk@0,0 | |
// ^ ^ ^ | |
// | fixed | |
// base address of virtio-mmio register block | |
// | |
// UEFI device path prefix: | |
// | |
// <VenHwString> | |
// | |
Written = UnicodeSPrintAsciiFormat ( | |
Translated, | |
*TranslatedSize * sizeof (*Translated), // BufferSize in bytes | |
"%s", | |
VenHwString | |
); | |
} else if (NumNodes >= 3 && | |
SubstringEq (OfwNode[1].DriverName, "channel") && | |
SubstringEq (OfwNode[2].DriverName, "disk")) { | |
// | |
// OpenFirmware device path (virtio-scsi disk): | |
// | |
// /virtio-mmio@000000000a003a00/channel@0/disk@2,3 | |
// ^ ^ ^ ^ | |
// | | | LUN | |
// | | target | |
// | channel (unused, fixed 0) | |
// base address of virtio-mmio register block | |
// | |
// UEFI device path prefix: | |
// | |
// <VenHwString>/Scsi(0x2,0x3) | |
// | |
UINT64 TargetLun[2]; | |
TargetLun[1] = 0; | |
NumEntries = ARRAY_SIZE (TargetLun); | |
if (ParseUnitAddressHexList ( | |
OfwNode[2].UnitAddress, | |
TargetLun, | |
&NumEntries | |
) != RETURN_SUCCESS | |
) { | |
return RETURN_UNSUPPORTED; | |
} | |
Written = UnicodeSPrintAsciiFormat ( | |
Translated, | |
*TranslatedSize * sizeof (*Translated), // BufferSize in bytes | |
"%s/Scsi(0x%Lx,0x%Lx)", | |
VenHwString, | |
TargetLun[0], | |
TargetLun[1] | |
); | |
} else if (NumNodes >= 2 && | |
SubstringEq (OfwNode[1].DriverName, "ethernet-phy")) { | |
// | |
// OpenFirmware device path (virtio-net NIC): | |
// | |
// /virtio-mmio@000000000a003e00/ethernet-phy@0 | |
// ^ ^ | |
// | fixed | |
// base address of virtio-mmio register block | |
// | |
// UEFI device path prefix: | |
// | |
// <VenHwString> | |
// | |
Written = UnicodeSPrintAsciiFormat ( | |
Translated, | |
*TranslatedSize * sizeof (*Translated), // BufferSize in bytes | |
"%s", | |
VenHwString | |
); | |
} else { | |
return RETURN_UNSUPPORTED; | |
} | |
// | |
// There's no way to differentiate between "completely used up without | |
// truncation" and "truncated", so treat the former as the latter, and return | |
// success only for "some room left unused". | |
// | |
if (Written + 1 < *TranslatedSize) { | |
*TranslatedSize = Written; | |
return RETURN_SUCCESS; | |
} | |
return RETURN_BUFFER_TOO_SMALL; | |
} | |
/** | |
Translate an array of OpenFirmware device nodes to a UEFI device path | |
fragment. | |
@param[in] OfwNode Array of OpenFirmware device nodes to | |
translate, constituting the beginning of an | |
OpenFirmware device path. | |
@param[in] NumNodes Number of elements in OfwNode. | |
@param[in] ExtraPciRoots An EXTRA_ROOT_BUS_MAP object created with | |
CreateExtraRootBusMap(), to be used for | |
translating positions of extra root buses to | |
bus numbers. | |
@param[out] Translated Destination array receiving the UEFI path | |
fragment, allocated by the caller. If the | |
return value differs from RETURN_SUCCESS, its | |
contents is indeterminate. | |
@param[in out] TranslatedSize On input, the number of CHAR16's in | |
Translated. On RETURN_SUCCESS this parameter | |
is assigned the number of non-NUL CHAR16's | |
written to Translated. In case of other return | |
values, TranslatedSize is indeterminate. | |
@retval RETURN_SUCCESS Translation successful. | |
@retval RETURN_BUFFER_TOO_SMALL The translation does not fit into the number | |
of bytes provided. | |
@retval RETURN_UNSUPPORTED The array of OpenFirmware device nodes can't | |
be translated in the current implementation. | |
@retval RETURN_PROTOCOL_ERROR The array of OpenFirmware device nodes has | |
been (partially) recognized, but it contains | |
a logic error / doesn't match system state. | |
**/ | |
STATIC | |
RETURN_STATUS | |
TranslateOfwNodes ( | |
IN CONST OFW_NODE *OfwNode, | |
IN UINTN NumNodes, | |
IN CONST EXTRA_ROOT_BUS_MAP *ExtraPciRoots, | |
OUT CHAR16 *Translated, | |
IN OUT UINTN *TranslatedSize | |
) | |
{ | |
RETURN_STATUS Status; | |
Status = RETURN_UNSUPPORTED; | |
if (FeaturePcdGet (PcdQemuBootOrderPciTranslation)) { | |
Status = TranslatePciOfwNodes (OfwNode, NumNodes, ExtraPciRoots, | |
Translated, TranslatedSize); | |
} | |
if (Status == RETURN_UNSUPPORTED && | |
FeaturePcdGet (PcdQemuBootOrderMmioTranslation)) { | |
Status = TranslateMmioOfwNodes (OfwNode, NumNodes, Translated, | |
TranslatedSize); | |
} | |
return Status; | |
} | |
/** | |
Translate an OpenFirmware device path fragment to a UEFI device path | |
fragment, and advance in the input string. | |
@param[in out] Ptr Address of the pointer pointing to the start | |
of the path string. After successful | |
translation (RETURN_SUCCESS) or at least | |
successful parsing (RETURN_UNSUPPORTED, | |
RETURN_BUFFER_TOO_SMALL), *Ptr is set to the | |
byte immediately following the consumed | |
characters. In other error cases, it points to | |
the byte that caused the error. | |
@param[in] ExtraPciRoots An EXTRA_ROOT_BUS_MAP object created with | |
CreateExtraRootBusMap(), to be used for | |
translating positions of extra root buses to | |
bus numbers. | |
@param[out] Translated Destination array receiving the UEFI path | |
fragment, allocated by the caller. If the | |
return value differs from RETURN_SUCCESS, its | |
contents is indeterminate. | |
@param[in out] TranslatedSize On input, the number of CHAR16's in | |
Translated. On RETURN_SUCCESS this parameter | |
is assigned the number of non-NUL CHAR16's | |
written to Translated. In case of other return | |
values, TranslatedSize is indeterminate. | |
@retval RETURN_SUCCESS Translation successful. | |
@retval RETURN_BUFFER_TOO_SMALL The OpenFirmware device path was parsed | |
successfully, but its translation did not | |
fit into the number of bytes provided. | |
Further calls to this function are | |
possible. | |
@retval RETURN_UNSUPPORTED The OpenFirmware device path was parsed | |
successfully, but it can't be translated in | |
the current implementation. Further calls | |
to this function are possible. | |
@retval RETURN_PROTOCOL_ERROR The OpenFirmware device path has been | |
(partially) recognized, but it contains a | |
logic error / doesn't match system state. | |
Further calls to this function are | |
possible. | |
@retval RETURN_NOT_FOUND Translation terminated. On input, *Ptr was | |
pointing to the empty string or "HALT". On | |
output, *Ptr points to the empty string | |
(ie. "HALT" is consumed transparently when | |
present). | |
@retval RETURN_INVALID_PARAMETER Parse error. This is a permanent error. | |
**/ | |
STATIC | |
RETURN_STATUS | |
TranslateOfwPath ( | |
IN OUT CONST CHAR8 **Ptr, | |
IN CONST EXTRA_ROOT_BUS_MAP *ExtraPciRoots, | |
OUT CHAR16 *Translated, | |
IN OUT UINTN *TranslatedSize | |
) | |
{ | |
UINTN NumNodes; | |
RETURN_STATUS Status; | |
OFW_NODE Node[EXAMINED_OFW_NODES]; | |
BOOLEAN IsFinal; | |
OFW_NODE Skip; | |
IsFinal = FALSE; | |
NumNodes = 0; | |
if (AsciiStrCmp (*Ptr, "HALT") == 0) { | |
*Ptr += 4; | |
Status = RETURN_NOT_FOUND; | |
} else { | |
Status = ParseOfwNode (Ptr, &Node[NumNodes], &IsFinal); | |
} | |
if (Status == RETURN_NOT_FOUND) { | |
DEBUG ((DEBUG_VERBOSE, "%a: no more nodes\n", __FUNCTION__)); | |
return RETURN_NOT_FOUND; | |
} | |
while (Status == RETURN_SUCCESS && !IsFinal) { | |
++NumNodes; | |
Status = ParseOfwNode ( | |
Ptr, | |
(NumNodes < EXAMINED_OFW_NODES) ? &Node[NumNodes] : &Skip, | |
&IsFinal | |
); | |
} | |
switch (Status) { | |
case RETURN_SUCCESS: | |
++NumNodes; | |
break; | |
case RETURN_INVALID_PARAMETER: | |
DEBUG ((DEBUG_VERBOSE, "%a: parse error\n", __FUNCTION__)); | |
return RETURN_INVALID_PARAMETER; | |
default: | |
ASSERT (0); | |
} | |
Status = TranslateOfwNodes ( | |
Node, | |
NumNodes < EXAMINED_OFW_NODES ? NumNodes : EXAMINED_OFW_NODES, | |
ExtraPciRoots, | |
Translated, | |
TranslatedSize); | |
switch (Status) { | |
case RETURN_SUCCESS: | |
DEBUG ((DEBUG_VERBOSE, "%a: success: \"%s\"\n", __FUNCTION__, Translated)); | |
break; | |
case RETURN_BUFFER_TOO_SMALL: | |
DEBUG ((DEBUG_VERBOSE, "%a: buffer too small\n", __FUNCTION__)); | |
break; | |
case RETURN_UNSUPPORTED: | |
DEBUG ((DEBUG_VERBOSE, "%a: unsupported\n", __FUNCTION__)); | |
break; | |
case RETURN_PROTOCOL_ERROR: | |
DEBUG ((DEBUG_VERBOSE, "%a: logic error / system state mismatch\n", | |
__FUNCTION__)); | |
break; | |
default: | |
ASSERT (0); | |
} | |
return Status; | |
} | |
/** | |
Connect devices based on the boot order retrieved from QEMU. | |
Attempt to retrieve the "bootorder" fw_cfg file from QEMU. Translate the | |
OpenFirmware device paths therein to UEFI device path fragments. Connect the | |
devices identified by the UEFI devpath prefixes as narrowly as possible, then | |
connect all their child devices, recursively. | |
If this function fails, then platform BDS should fall back to | |
EfiBootManagerConnectAll(), or some other method for connecting any expected | |
boot devices. | |
@retval RETURN_SUCCESS The "bootorder" fw_cfg file has been | |
parsed, and the referenced device-subtrees | |
have been connected. | |
@retval RETURN_UNSUPPORTED QEMU's fw_cfg is not supported. | |
@retval RETURN_NOT_FOUND Empty or nonexistent "bootorder" fw_cfg | |
file. | |
@retval RETURN_INVALID_PARAMETER Parse error in the "bootorder" fw_cfg file. | |
@retval RETURN_OUT_OF_RESOURCES Memory allocation failed. | |
@return Error statuses propagated from underlying | |
functions. | |
**/ | |
RETURN_STATUS | |
EFIAPI | |
ConnectDevicesFromQemu ( | |
VOID | |
) | |
{ | |
RETURN_STATUS Status; | |
FIRMWARE_CONFIG_ITEM FwCfgItem; | |
UINTN FwCfgSize; | |
CHAR8 *FwCfg; | |
EFI_STATUS EfiStatus; | |
EXTRA_ROOT_BUS_MAP *ExtraPciRoots; | |
CONST CHAR8 *FwCfgPtr; | |
UINTN NumConnected; | |
UINTN TranslatedSize; | |
CHAR16 Translated[TRANSLATION_OUTPUT_SIZE]; | |
Status = QemuFwCfgFindFile ("bootorder", &FwCfgItem, &FwCfgSize); | |
if (RETURN_ERROR (Status)) { | |
return Status; | |
} | |
if (FwCfgSize == 0) { | |
return RETURN_NOT_FOUND; | |
} | |
FwCfg = AllocatePool (FwCfgSize); | |
if (FwCfg == NULL) { | |
return RETURN_OUT_OF_RESOURCES; | |
} | |
QemuFwCfgSelectItem (FwCfgItem); | |
QemuFwCfgReadBytes (FwCfgSize, FwCfg); | |
if (FwCfg[FwCfgSize - 1] != '\0') { | |
Status = RETURN_INVALID_PARAMETER; | |
goto FreeFwCfg; | |
} | |
DEBUG ((DEBUG_VERBOSE, "%a: FwCfg:\n", __FUNCTION__)); | |
DEBUG ((DEBUG_VERBOSE, "%a\n", FwCfg)); | |
DEBUG ((DEBUG_VERBOSE, "%a: FwCfg: <end>\n", __FUNCTION__)); | |
if (FeaturePcdGet (PcdQemuBootOrderPciTranslation)) { | |
EfiStatus = CreateExtraRootBusMap (&ExtraPciRoots); | |
if (EFI_ERROR (EfiStatus)) { | |
Status = (RETURN_STATUS)EfiStatus; | |
goto FreeFwCfg; | |
} | |
} else { | |
ExtraPciRoots = NULL; | |
} | |
// | |
// Translate each OpenFirmware path to a UEFI devpath prefix. | |
// | |
FwCfgPtr = FwCfg; | |
NumConnected = 0; | |
TranslatedSize = ARRAY_SIZE (Translated); | |
Status = TranslateOfwPath (&FwCfgPtr, ExtraPciRoots, Translated, | |
&TranslatedSize); | |
while (!RETURN_ERROR (Status)) { | |
EFI_DEVICE_PATH_PROTOCOL *DevicePath; | |
EFI_HANDLE Controller; | |
// | |
// Convert the UEFI devpath prefix to binary representation. | |
// | |
ASSERT (Translated[TranslatedSize] == L'\0'); | |
DevicePath = ConvertTextToDevicePath (Translated); | |
if (DevicePath == NULL) { | |
Status = RETURN_OUT_OF_RESOURCES; | |
goto FreeExtraPciRoots; | |
} | |
// | |
// Advance along DevicePath, connecting the nodes individually, and asking | |
// drivers not to produce sibling nodes. Retrieve the controller handle | |
// associated with the full DevicePath -- this is the device that QEMU's | |
// OFW devpath refers to. | |
// | |
EfiStatus = EfiBootManagerConnectDevicePath (DevicePath, &Controller); | |
FreePool (DevicePath); | |
if (EFI_ERROR (EfiStatus)) { | |
Status = (RETURN_STATUS)EfiStatus; | |
goto FreeExtraPciRoots; | |
} | |
// | |
// Because QEMU's OFW devpaths have lesser expressive power than UEFI | |
// devpaths (i.e., DevicePath is considered a prefix), connect the tree | |
// rooted at Controller, recursively. If no children are produced | |
// (EFI_NOT_FOUND), that's OK. | |
// | |
EfiStatus = gBS->ConnectController (Controller, NULL, NULL, TRUE); | |
if (EFI_ERROR (EfiStatus) && EfiStatus != EFI_NOT_FOUND) { | |
Status = (RETURN_STATUS)EfiStatus; | |
goto FreeExtraPciRoots; | |
} | |
++NumConnected; | |
// | |
// Move to the next OFW devpath. | |
// | |
TranslatedSize = ARRAY_SIZE (Translated); | |
Status = TranslateOfwPath (&FwCfgPtr, ExtraPciRoots, Translated, | |
&TranslatedSize); | |
} | |
if (Status == RETURN_NOT_FOUND && NumConnected > 0) { | |
DEBUG ((DEBUG_INFO, "%a: %Lu OpenFirmware device path(s) connected\n", | |
__FUNCTION__, (UINT64)NumConnected)); | |
Status = RETURN_SUCCESS; | |
} | |
FreeExtraPciRoots: | |
if (ExtraPciRoots != NULL) { | |
DestroyExtraRootBusMap (ExtraPciRoots); | |
} | |
FreeFwCfg: | |
FreePool (FwCfg); | |
return Status; | |
} | |
/** | |
Convert the UEFI DevicePath to full text representation with DevPathToText, | |
then match the UEFI device path fragment in Translated against it. | |
@param[in] Translated UEFI device path fragment, translated from | |
OpenFirmware format, to search for. | |
@param[in] TranslatedLength The length of Translated in CHAR16's. | |
@param[in] DevicePath Boot option device path whose textual rendering | |
to search in. | |
@param[in] DevPathToText Binary-to-text conversion protocol for DevicePath. | |
@retval TRUE If Translated was found at the beginning of DevicePath after | |
converting the latter to text. | |
@retval FALSE If DevicePath was NULL, or it could not be converted, or there | |
was no match. | |
**/ | |
STATIC | |
BOOLEAN | |
Match ( | |
IN CONST CHAR16 *Translated, | |
IN UINTN TranslatedLength, | |
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath | |
) | |
{ | |
CHAR16 *Converted; | |
BOOLEAN Result; | |
VOID *FileBuffer; | |
UINTN FileSize; | |
EFI_DEVICE_PATH_PROTOCOL *AbsDevicePath; | |
CHAR16 *AbsConverted; | |
BOOLEAN Shortform; | |
EFI_DEVICE_PATH_PROTOCOL *Node; | |
Converted = ConvertDevicePathToText ( | |
DevicePath, | |
FALSE, // DisplayOnly | |
FALSE // AllowShortcuts | |
); | |
if (Converted == NULL) { | |
return FALSE; | |
} | |
Result = FALSE; | |
Shortform = FALSE; | |
// | |
// Expand the short-form device path to full device path | |
// | |
if ((DevicePathType (DevicePath) == MEDIA_DEVICE_PATH) && | |
(DevicePathSubType (DevicePath) == MEDIA_HARDDRIVE_DP)) { | |
// | |
// Harddrive shortform device path | |
// | |
Shortform = TRUE; | |
} else if ((DevicePathType (DevicePath) == MEDIA_DEVICE_PATH) && | |
(DevicePathSubType (DevicePath) == MEDIA_FILEPATH_DP)) { | |
// | |
// File-path shortform device path | |
// | |
Shortform = TRUE; | |
} else if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) && | |
(DevicePathSubType (DevicePath) == MSG_URI_DP)) { | |
// | |
// URI shortform device path | |
// | |
Shortform = TRUE; | |
} else { | |
for ( Node = DevicePath | |
; !IsDevicePathEnd (Node) | |
; Node = NextDevicePathNode (Node) | |
) { | |
if ((DevicePathType (Node) == MESSAGING_DEVICE_PATH) && | |
((DevicePathSubType (Node) == MSG_USB_CLASS_DP) || | |
(DevicePathSubType (Node) == MSG_USB_WWID_DP))) { | |
Shortform = TRUE; | |
break; | |
} | |
} | |
} | |
// | |
// Attempt to expand any relative UEFI device path to | |
// an absolute device path first. | |
// | |
if (Shortform) { | |
FileBuffer = EfiBootManagerGetLoadOptionBuffer ( | |
DevicePath, &AbsDevicePath, &FileSize | |
); | |
if (FileBuffer == NULL) { | |
goto Exit; | |
} | |
FreePool (FileBuffer); | |
AbsConverted = ConvertDevicePathToText (AbsDevicePath, FALSE, FALSE); | |
FreePool (AbsDevicePath); | |
if (AbsConverted == NULL) { | |
goto Exit; | |
} | |
DEBUG ((DEBUG_VERBOSE, | |
"%a: expanded relative device path \"%s\" for prefix matching\n", | |
__FUNCTION__, Converted)); | |
FreePool (Converted); | |
Converted = AbsConverted; | |
} | |
// | |
// Is Translated a prefix of Converted? | |
// | |
Result = (BOOLEAN)(StrnCmp (Converted, Translated, TranslatedLength) == 0); | |
DEBUG (( | |
DEBUG_VERBOSE, | |
"%a: against \"%s\": %a\n", | |
__FUNCTION__, | |
Converted, | |
Result ? "match" : "no match" | |
)); | |
Exit: | |
FreePool (Converted); | |
return Result; | |
} | |
/** | |
Append some of the unselected active boot options to the boot order. | |
This function should accommodate any further policy changes in "boot option | |
survival". Currently we're adding back everything that starts with neither | |
PciRoot() nor HD() nor a virtio-mmio VenHw() node. | |
@param[in,out] BootOrder The structure holding the boot order to | |
complete. The caller is responsible for | |
initializing (and potentially populating) it | |
before calling this function. | |
@param[in,out] ActiveOption The array of active boot options to scan. | |
Entries marked as Appended will be skipped. | |
Those of the rest that satisfy the survival | |
policy will be added to BootOrder with | |
BootOrderAppend(). | |
@param[in] ActiveCount Number of elements in ActiveOption. | |
@retval RETURN_SUCCESS BootOrder has been extended with any eligible boot | |
options. | |
@return Error codes returned by BootOrderAppend(). | |
**/ | |
STATIC | |
RETURN_STATUS | |
BootOrderComplete ( | |
IN OUT BOOT_ORDER *BootOrder, | |
IN OUT ACTIVE_OPTION *ActiveOption, | |
IN UINTN ActiveCount | |
) | |
{ | |
RETURN_STATUS Status; | |
UINTN Idx; | |
Status = RETURN_SUCCESS; | |
Idx = 0; | |
while (!RETURN_ERROR (Status) && Idx < ActiveCount) { | |
if (!ActiveOption[Idx].Appended) { | |
CONST EFI_BOOT_MANAGER_LOAD_OPTION *Current; | |
CONST EFI_DEVICE_PATH_PROTOCOL *FirstNode; | |
Current = ActiveOption[Idx].BootOption; | |
FirstNode = Current->FilePath; | |
if (FirstNode != NULL) { | |
CHAR16 *Converted; | |
STATIC CHAR16 ConvFallBack[] = L"<unable to convert>"; | |
BOOLEAN Keep; | |
Converted = ConvertDevicePathToText (FirstNode, FALSE, FALSE); | |
if (Converted == NULL) { | |
Converted = ConvFallBack; | |
} | |
Keep = TRUE; | |
if (DevicePathType(FirstNode) == MEDIA_DEVICE_PATH && | |
DevicePathSubType(FirstNode) == MEDIA_HARDDRIVE_DP) { | |
// | |
// drop HD() | |
// | |
Keep = FALSE; | |
} else if (DevicePathType(FirstNode) == ACPI_DEVICE_PATH && | |
DevicePathSubType(FirstNode) == ACPI_DP) { | |
ACPI_HID_DEVICE_PATH *Acpi; | |
Acpi = (ACPI_HID_DEVICE_PATH *) FirstNode; | |
if ((Acpi->HID & PNP_EISA_ID_MASK) == PNP_EISA_ID_CONST && | |
EISA_ID_TO_NUM (Acpi->HID) == 0x0a03) { | |
// | |
// drop PciRoot() if we enabled the user to select PCI-like boot | |
// options, by providing translation for such OFW device path | |
// fragments | |
// | |
Keep = !FeaturePcdGet (PcdQemuBootOrderPciTranslation); | |
} | |
} else if (DevicePathType(FirstNode) == HARDWARE_DEVICE_PATH && | |
DevicePathSubType(FirstNode) == HW_VENDOR_DP) { | |
VENDOR_DEVICE_PATH *VenHw; | |
VenHw = (VENDOR_DEVICE_PATH *)FirstNode; | |
if (CompareGuid (&VenHw->Guid, &gVirtioMmioTransportGuid)) { | |
// | |
// drop virtio-mmio if we enabled the user to select boot options | |
// referencing such device paths | |
// | |
Keep = !FeaturePcdGet (PcdQemuBootOrderMmioTranslation); | |
} | |
} | |
if (Keep) { | |
Status = BootOrderAppend (BootOrder, &ActiveOption[Idx]); | |
if (!RETURN_ERROR (Status)) { | |
DEBUG ((DEBUG_VERBOSE, "%a: keeping \"%s\"\n", __FUNCTION__, | |
Converted)); | |
} | |
} else { | |
DEBUG ((DEBUG_VERBOSE, "%a: dropping \"%s\"\n", __FUNCTION__, | |
Converted)); | |
} | |
if (Converted != ConvFallBack) { | |
FreePool (Converted); | |
} | |
} | |
} | |
++Idx; | |
} | |
return Status; | |
} | |
/** | |
Delete Boot#### variables that stand for such active boot options that have | |
been dropped (ie. have not been selected by either matching or "survival | |
policy"). | |
@param[in] ActiveOption The array of active boot options to scan. Each | |
entry not marked as appended will trigger the | |
deletion of the matching Boot#### variable. | |
@param[in] ActiveCount Number of elements in ActiveOption. | |
**/ | |
STATIC | |
VOID | |
PruneBootVariables ( | |
IN CONST ACTIVE_OPTION *ActiveOption, | |
IN UINTN ActiveCount | |
) | |
{ | |
UINTN Idx; | |
for (Idx = 0; Idx < ActiveCount; ++Idx) { | |
if (!ActiveOption[Idx].Appended) { | |
CHAR16 VariableName[9]; | |
UnicodeSPrintAsciiFormat (VariableName, sizeof VariableName, "Boot%04x", | |
ActiveOption[Idx].BootOption->OptionNumber); | |
// | |
// "The space consumed by the deleted variable may not be available until | |
// the next power cycle", but that's good enough. | |
// | |
gRT->SetVariable (VariableName, &gEfiGlobalVariableGuid, | |
0, // Attributes, 0 means deletion | |
0, // DataSize, 0 means deletion | |
NULL // Data | |
); | |
} | |
} | |
} | |
/** | |
Set the boot order based on configuration retrieved from QEMU. | |
Attempt to retrieve the "bootorder" fw_cfg file from QEMU. Translate the | |
OpenFirmware device paths therein to UEFI device path fragments. Match the | |
translated fragments against the current list of boot options, and rewrite | |
the BootOrder NvVar so that it corresponds to the order described in fw_cfg. | |
Platform BDS should call this function after connecting any expected boot | |
devices and calling EfiBootManagerRefreshAllBootOption (). | |
@retval RETURN_SUCCESS BootOrder NvVar rewritten. | |
@retval RETURN_UNSUPPORTED QEMU's fw_cfg is not supported. | |
@retval RETURN_NOT_FOUND Empty or nonexistent "bootorder" fw_cfg | |
file, or no match found between the | |
"bootorder" fw_cfg file and BootOptionList. | |
@retval RETURN_INVALID_PARAMETER Parse error in the "bootorder" fw_cfg file. | |
@retval RETURN_OUT_OF_RESOURCES Memory allocation failed. | |
@return Values returned by gBS->LocateProtocol () | |
or gRT->SetVariable (). | |
**/ | |
RETURN_STATUS | |
EFIAPI | |
SetBootOrderFromQemu ( | |
VOID | |
) | |
{ | |
RETURN_STATUS Status; | |
FIRMWARE_CONFIG_ITEM FwCfgItem; | |
UINTN FwCfgSize; | |
CHAR8 *FwCfg; | |
CONST CHAR8 *FwCfgPtr; | |
BOOT_ORDER BootOrder; | |
ACTIVE_OPTION *ActiveOption; | |
UINTN ActiveCount; | |
EXTRA_ROOT_BUS_MAP *ExtraPciRoots; | |
UINTN TranslatedSize; | |
CHAR16 Translated[TRANSLATION_OUTPUT_SIZE]; | |
EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions; | |
UINTN BootOptionCount; | |
Status = QemuFwCfgFindFile ("bootorder", &FwCfgItem, &FwCfgSize); | |
if (Status != RETURN_SUCCESS) { | |
return Status; | |
} | |
if (FwCfgSize == 0) { | |
return RETURN_NOT_FOUND; | |
} | |
FwCfg = AllocatePool (FwCfgSize); | |
if (FwCfg == NULL) { | |
return RETURN_OUT_OF_RESOURCES; | |
} | |
QemuFwCfgSelectItem (FwCfgItem); | |
QemuFwCfgReadBytes (FwCfgSize, FwCfg); | |
if (FwCfg[FwCfgSize - 1] != '\0') { | |
Status = RETURN_INVALID_PARAMETER; | |
goto ErrorFreeFwCfg; | |
} | |
DEBUG ((DEBUG_VERBOSE, "%a: FwCfg:\n", __FUNCTION__)); | |
DEBUG ((DEBUG_VERBOSE, "%a\n", FwCfg)); | |
DEBUG ((DEBUG_VERBOSE, "%a: FwCfg: <end>\n", __FUNCTION__)); | |
FwCfgPtr = FwCfg; | |
BootOrder.Produced = 0; | |
BootOrder.Allocated = 1; | |
BootOrder.Data = AllocatePool ( | |
BootOrder.Allocated * sizeof (*BootOrder.Data) | |
); | |
if (BootOrder.Data == NULL) { | |
Status = RETURN_OUT_OF_RESOURCES; | |
goto ErrorFreeFwCfg; | |
} | |
BootOptions = EfiBootManagerGetLoadOptions ( | |
&BootOptionCount, LoadOptionTypeBoot | |
); | |
if (BootOptions == NULL) { | |
Status = RETURN_NOT_FOUND; | |
goto ErrorFreeBootOrder; | |
} | |
Status = CollectActiveOptions ( | |
BootOptions, BootOptionCount, &ActiveOption, &ActiveCount | |
); | |
if (RETURN_ERROR (Status)) { | |
goto ErrorFreeBootOptions; | |
} | |
if (FeaturePcdGet (PcdQemuBootOrderPciTranslation)) { | |
Status = CreateExtraRootBusMap (&ExtraPciRoots); | |
if (EFI_ERROR (Status)) { | |
goto ErrorFreeActiveOption; | |
} | |
} else { | |
ExtraPciRoots = NULL; | |
} | |
// | |
// translate each OpenFirmware path | |
// | |
TranslatedSize = ARRAY_SIZE (Translated); | |
Status = TranslateOfwPath (&FwCfgPtr, ExtraPciRoots, Translated, | |
&TranslatedSize); | |
while (Status == RETURN_SUCCESS || | |
Status == RETURN_UNSUPPORTED || | |
Status == RETURN_PROTOCOL_ERROR || | |
Status == RETURN_BUFFER_TOO_SMALL) { | |
if (Status == RETURN_SUCCESS) { | |
UINTN Idx; | |
// | |
// match translated OpenFirmware path against all active boot options | |
// | |
for (Idx = 0; Idx < ActiveCount; ++Idx) { | |
if (!ActiveOption[Idx].Appended && | |
Match ( | |
Translated, | |
TranslatedSize, // contains length, not size, in CHAR16's here | |
ActiveOption[Idx].BootOption->FilePath | |
) | |
) { | |
// | |
// match found, store ID and continue with next OpenFirmware path | |
// | |
Status = BootOrderAppend (&BootOrder, &ActiveOption[Idx]); | |
if (Status != RETURN_SUCCESS) { | |
goto ErrorFreeExtraPciRoots; | |
} | |
} | |
} // scanned all active boot options | |
} // translation successful | |
TranslatedSize = ARRAY_SIZE (Translated); | |
Status = TranslateOfwPath (&FwCfgPtr, ExtraPciRoots, Translated, | |
&TranslatedSize); | |
} // scanning of OpenFirmware paths done | |
if (Status == RETURN_NOT_FOUND && BootOrder.Produced > 0) { | |
// | |
// No more OpenFirmware paths, some matches found: rewrite BootOrder NvVar. | |
// Some of the active boot options that have not been selected over fw_cfg | |
// should be preserved at the end of the boot order. | |
// | |
Status = BootOrderComplete (&BootOrder, ActiveOption, ActiveCount); | |
if (RETURN_ERROR (Status)) { | |
goto ErrorFreeExtraPciRoots; | |
} | |
// | |
// See Table 10 in the UEFI Spec 2.3.1 with Errata C for the required | |
// attributes. | |
// | |
Status = gRT->SetVariable ( | |
L"BootOrder", | |
&gEfiGlobalVariableGuid, | |
EFI_VARIABLE_NON_VOLATILE | | |
EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
EFI_VARIABLE_RUNTIME_ACCESS, | |
BootOrder.Produced * sizeof (*BootOrder.Data), | |
BootOrder.Data | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "%a: setting BootOrder: %r\n", __FUNCTION__, | |
Status)); | |
goto ErrorFreeExtraPciRoots; | |
} | |
DEBUG ((DEBUG_INFO, "%a: setting BootOrder: success\n", __FUNCTION__)); | |
PruneBootVariables (ActiveOption, ActiveCount); | |
} | |
ErrorFreeExtraPciRoots: | |
if (ExtraPciRoots != NULL) { | |
DestroyExtraRootBusMap (ExtraPciRoots); | |
} | |
ErrorFreeActiveOption: | |
FreePool (ActiveOption); | |
ErrorFreeBootOptions: | |
EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount); | |
ErrorFreeBootOrder: | |
FreePool (BootOrder.Data); | |
ErrorFreeFwCfg: | |
FreePool (FwCfg); | |
return Status; | |
} | |
/** | |
Calculate the number of seconds we should be showing the FrontPage progress | |
bar for. | |
@return The TimeoutDefault argument for PlatformBdsEnterFrontPage(). | |
**/ | |
UINT16 | |
EFIAPI | |
GetFrontPageTimeoutFromQemu ( | |
VOID | |
) | |
{ | |
FIRMWARE_CONFIG_ITEM BootMenuWaitItem; | |
UINTN BootMenuWaitSize; | |
QemuFwCfgSelectItem (QemuFwCfgItemBootMenu); | |
if (QemuFwCfgRead16 () == 0) { | |
// | |
// The user specified "-boot menu=off", or didn't specify "-boot | |
// menu=(on|off)" at all. Return the platform default. | |
// | |
return PcdGet16 (PcdPlatformBootTimeOut); | |
} | |
if (RETURN_ERROR (QemuFwCfgFindFile ("etc/boot-menu-wait", &BootMenuWaitItem, | |
&BootMenuWaitSize)) || | |
BootMenuWaitSize != sizeof (UINT16)) { | |
// | |
// "-boot menu=on" was specified without "splash-time=N". In this case, | |
// return three seconds if the platform default would cause us to skip the | |
// front page, and return the platform default otherwise. | |
// | |
UINT16 Timeout; | |
Timeout = PcdGet16 (PcdPlatformBootTimeOut); | |
if (Timeout == 0) { | |
Timeout = 3; | |
} | |
return Timeout; | |
} | |
// | |
// "-boot menu=on,splash-time=N" was specified, where N is in units of | |
// milliseconds. The Intel BDS Front Page progress bar only supports whole | |
// seconds, round N up. | |
// | |
QemuFwCfgSelectItem (BootMenuWaitItem); | |
return (UINT16)((QemuFwCfgRead16 () + 999) / 1000); | |
} |