summaryrefslogtreecommitdiffstats
path: root/OvmfPkg/Library/QemuBootOrderLib
diff options
context:
space:
mode:
authorLaszlo Ersek <lersek@redhat.com>2016-05-17 19:30:24 +0200
committerLaszlo Ersek <lersek@redhat.com>2016-05-25 12:25:28 +0200
commitd85c5e31ed2b550dd801f82e4ddb5f7583332098 (patch)
treec6e3cf3078c1de866fb5c0278c549ff921d8ea7d /OvmfPkg/Library/QemuBootOrderLib
parent2542feea2ecd4dc73ee54cb32c875839413eed05 (diff)
downloadedk2-d85c5e31ed2b550dd801f82e4ddb5f7583332098.tar.gz
edk2-d85c5e31ed2b550dd801f82e4ddb5f7583332098.tar.bz2
edk2-d85c5e31ed2b550dd801f82e4ddb5f7583332098.zip
OvmfPkg, ArmVirtPkg: rename QemuNewBootOrderLib to QemuBootOrderLib
This completes the transition to the new BDS. The FILE_GUID in "QemuBootOrderLib.inf" is intentionally not changed. Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org> Cc: Gary Ching-Pang Lin <glin@suse.com> Cc: Jordan Justen <jordan.l.justen@intel.com> Cc: Ruiyu Ni <ruiyu.ni@intel.com> Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Laszlo Ersek <lersek@redhat.com> Reviewed-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> Reviewed-by: Jordan Justen <jordan.l.justen@intel.com>
Diffstat (limited to 'OvmfPkg/Library/QemuBootOrderLib')
-rw-r--r--OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.c313
-rw-r--r--OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.h40
-rw-r--r--OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.c1944
-rw-r--r--OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.inf68
4 files changed, 2365 insertions, 0 deletions
diff --git a/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.c b/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.c
new file mode 100644
index 0000000000..ec422145c4
--- /dev/null
+++ b/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.c
@@ -0,0 +1,313 @@
+/** @file
+ Map positions of extra PCI root buses to bus numbers.
+
+ Copyright (C) 2015, Red Hat, Inc.
+
+ 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 <Library/DebugLib.h>
+#include <Library/DevicePathLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/OrderedCollectionLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Protocol/DevicePath.h>
+#include <Protocol/PciRootBridgeIo.h>
+
+#include "ExtraRootBusMap.h"
+
+//
+// The BusNumbers field is an array with Count elements. The elements increase
+// strictry monotonically. Zero is not an element (because the zero bus number
+// belongs to the "main" root bus, never to an extra root bus). Offset N in the
+// array maps the extra root bus with position (N+1) to its bus number (because
+// the root bus with position 0 is always the main root bus, therefore we don't
+// store it).
+//
+// If there are no extra root buses in the system, then Count is 0, and
+// BusNumbers is NULL.
+//
+struct EXTRA_ROOT_BUS_MAP_STRUCT {
+ UINT32 *BusNumbers;
+ UINTN Count;
+};
+
+
+/**
+ An ORDERED_COLLECTION_USER_COMPARE function that compares root bridge
+ protocol device paths based on UID.
+
+ @param[in] UserStruct1 Pointer to the first ACPI_HID_DEVICE_PATH.
+
+ @param[in] UserStruct2 Pointer to the second ACPI_HID_DEVICE_PATH.
+
+ @retval <0 If UserStruct1 compares less than UserStruct2.
+
+ @retval 0 If UserStruct1 compares equal to UserStruct2.
+
+ @retval >0 If UserStruct1 compares greater than UserStruct2.
+**/
+STATIC
+INTN
+EFIAPI
+RootBridgePathCompare (
+ IN CONST VOID *UserStruct1,
+ IN CONST VOID *UserStruct2
+ )
+{
+ CONST ACPI_HID_DEVICE_PATH *Acpi1;
+ CONST ACPI_HID_DEVICE_PATH *Acpi2;
+
+ Acpi1 = UserStruct1;
+ Acpi2 = UserStruct2;
+
+ return Acpi1->UID < Acpi2->UID ? -1 :
+ Acpi1->UID > Acpi2->UID ? 1 :
+ 0;
+}
+
+
+/**
+ An ORDERED_COLLECTION_KEY_COMPARE function that compares a root bridge
+ protocol device path against a UID.
+
+ @param[in] StandaloneKey Pointer to the bare UINT32 UID.
+
+ @param[in] UserStruct Pointer to the ACPI_HID_DEVICE_PATH with the
+ embedded UINT32 UID.
+
+ @retval <0 If StandaloneKey compares less than UserStruct's key.
+
+ @retval 0 If StandaloneKey compares equal to UserStruct's key.
+
+ @retval >0 If StandaloneKey compares greater than UserStruct's key.
+**/
+STATIC
+INTN
+EFIAPI
+RootBridgePathKeyCompare (
+ IN CONST VOID *StandaloneKey,
+ IN CONST VOID *UserStruct
+ )
+{
+ CONST UINT32 *Uid;
+ CONST ACPI_HID_DEVICE_PATH *Acpi;
+
+ Uid = StandaloneKey;
+ Acpi = UserStruct;
+
+ return *Uid < Acpi->UID ? -1 :
+ *Uid > Acpi->UID ? 1 :
+ 0;
+}
+
+
+/**
+ Create a structure that maps the relative positions of PCI root buses to bus
+ numbers.
+
+ In the "bootorder" fw_cfg file, QEMU refers to extra PCI root buses by their
+ positions, in relative root bus number order, not by their actual PCI bus
+ numbers. The ACPI HID device path nodes however that are associated with
+ PciRootBridgeIo protocol instances in the system have their UID fields set to
+ the bus numbers. Create a map that gives, for each extra PCI root bus's
+ position (ie. "serial number") its actual PCI bus number.
+
+ @param[out] ExtraRootBusMap The data structure implementing the map.
+
+ @retval EFI_SUCCESS ExtraRootBusMap has been populated.
+
+ @retval EFI_OUT_OF_RESOURCES Memory allocation failed.
+
+ @retval EFI_ALREADY_STARTED A duplicate root bus number has been found in
+ the system. (This should never happen.)
+
+ @return Error codes returned by
+ gBS->LocateHandleBuffer() and
+ gBS->HandleProtocol().
+
+**/
+EFI_STATUS
+CreateExtraRootBusMap (
+ OUT EXTRA_ROOT_BUS_MAP **ExtraRootBusMap
+ )
+{
+ EFI_STATUS Status;
+ UINTN NumHandles;
+ EFI_HANDLE *Handles;
+ ORDERED_COLLECTION *Collection;
+ EXTRA_ROOT_BUS_MAP *Map;
+ UINTN Idx;
+ ORDERED_COLLECTION_ENTRY *Entry, *Entry2;
+
+ //
+ // Handles and Collection are temporary / helper variables, while in Map we
+ // build the return value.
+ //
+
+ Status = gBS->LocateHandleBuffer (ByProtocol,
+ &gEfiPciRootBridgeIoProtocolGuid, NULL /* SearchKey */,
+ &NumHandles, &Handles);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Collection = OrderedCollectionInit (RootBridgePathCompare,
+ RootBridgePathKeyCompare);
+ if (Collection == NULL) {
+ Status = EFI_OUT_OF_RESOURCES;
+ goto FreeHandles;
+ }
+
+ Map = AllocateZeroPool (sizeof *Map);
+ if (Map == NULL) {
+ Status = EFI_OUT_OF_RESOURCES;
+ goto FreeCollection;
+ }
+
+ //
+ // Collect the ACPI device path protocols of the root bridges.
+ //
+ for (Idx = 0; Idx < NumHandles; ++Idx) {
+ EFI_DEVICE_PATH_PROTOCOL *DevicePath;
+
+ Status = gBS->HandleProtocol (Handles[Idx], &gEfiDevicePathProtocolGuid,
+ (VOID**)&DevicePath);
+ if (EFI_ERROR (Status)) {
+ goto FreeMap;
+ }
+
+ //
+ // Examine if the device path is an ACPI HID one, and if so, if UID is
+ // nonzero (ie. the root bridge that the bus number belongs to is "extra",
+ // not the main one). In that case, link the device path into Collection.
+ //
+ if (DevicePathType (DevicePath) == ACPI_DEVICE_PATH &&
+ DevicePathSubType (DevicePath) == ACPI_DP &&
+ ((ACPI_HID_DEVICE_PATH *)DevicePath)->HID == EISA_PNP_ID(0x0A03) &&
+ ((ACPI_HID_DEVICE_PATH *)DevicePath)->UID > 0) {
+ Status = OrderedCollectionInsert (Collection, NULL, DevicePath);
+ if (EFI_ERROR (Status)) {
+ goto FreeMap;
+ }
+ ++Map->Count;
+ }
+ }
+
+ if (Map->Count > 0) {
+ //
+ // At least one extra PCI root bus exists.
+ //
+ Map->BusNumbers = AllocatePool (Map->Count * sizeof *Map->BusNumbers);
+ if (Map->BusNumbers == NULL) {
+ Status = EFI_OUT_OF_RESOURCES;
+ goto FreeMap;
+ }
+ }
+
+ //
+ // Now collect the bus numbers of the extra PCI root buses into Map.
+ //
+ Idx = 0;
+ Entry = OrderedCollectionMin (Collection);
+ while (Idx < Map->Count) {
+ ACPI_HID_DEVICE_PATH *Acpi;
+
+ ASSERT (Entry != NULL);
+ Acpi = OrderedCollectionUserStruct (Entry);
+ Map->BusNumbers[Idx] = Acpi->UID;
+ DEBUG ((EFI_D_VERBOSE,
+ "%a: extra bus position 0x%Lx maps to bus number (UID) 0x%x\n",
+ __FUNCTION__, (UINT64)(Idx + 1), Acpi->UID));
+ ++Idx;
+ Entry = OrderedCollectionNext (Entry);
+ }
+ ASSERT (Entry == NULL);
+
+ *ExtraRootBusMap = Map;
+ Status = EFI_SUCCESS;
+
+ //
+ // Fall through in order to release temporaries.
+ //
+
+FreeMap:
+ if (EFI_ERROR (Status)) {
+ if (Map->BusNumbers != NULL) {
+ FreePool (Map->BusNumbers);
+ }
+ FreePool (Map);
+ }
+
+FreeCollection:
+ for (Entry = OrderedCollectionMin (Collection); Entry != NULL;
+ Entry = Entry2) {
+ Entry2 = OrderedCollectionNext (Entry);
+ OrderedCollectionDelete (Collection, Entry, NULL);
+ }
+ OrderedCollectionUninit (Collection);
+
+FreeHandles:
+ FreePool (Handles);
+
+ return Status;
+}
+
+
+/**
+ Release a map created with CreateExtraRootBusMap().
+
+ @param[in] ExtraRootBusMap The map to release.
+*/
+VOID
+DestroyExtraRootBusMap (
+ IN EXTRA_ROOT_BUS_MAP *ExtraRootBusMap
+ )
+{
+ if (ExtraRootBusMap->BusNumbers != NULL) {
+ FreePool (ExtraRootBusMap->BusNumbers);
+ }
+ FreePool (ExtraRootBusMap);
+}
+
+/**
+ Map the position (serial number) of an extra PCI root bus to its bus number.
+
+ @param[in] ExtraRootBusMap The map created with CreateExtraRootBusMap();
+
+ @param[in] RootBusPos The extra PCI root bus position to map.
+
+ @param[out] RootBusNr The bus number belonging to the extra PCI root
+ bus identified by RootBusPos.
+
+ @retval EFI_INVALID_PARAMETER RootBusPos is zero. The zero position
+ identifies the main root bus, whose bus number
+ is always zero, and is therefore never
+ maintained in ExtraRootBusMap.
+
+ @retval EFI_NOT_FOUND RootBusPos is not found in ExtraRootBusMap.
+
+ @retval EFI_SUCCESS Mapping successful.
+**/
+EFI_STATUS
+MapRootBusPosToBusNr (
+ IN CONST EXTRA_ROOT_BUS_MAP *ExtraRootBusMap,
+ IN UINT64 RootBusPos,
+ OUT UINT32 *RootBusNr
+ )
+{
+ if (RootBusPos == 0) {
+ return EFI_INVALID_PARAMETER;
+ }
+ if (RootBusPos > ExtraRootBusMap->Count) {
+ return EFI_NOT_FOUND;
+ }
+ *RootBusNr = ExtraRootBusMap->BusNumbers[RootBusPos - 1];
+ return EFI_SUCCESS;
+}
diff --git a/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.h b/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.h
new file mode 100644
index 0000000000..e2dbc38ed8
--- /dev/null
+++ b/OvmfPkg/Library/QemuBootOrderLib/ExtraRootBusMap.h
@@ -0,0 +1,40 @@
+/** @file
+ Map positions of extra PCI root buses to bus numbers.
+
+ Copyright (C) 2015, Red Hat, Inc.
+
+ 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.
+**/
+
+#ifndef __EXTRA_ROOT_BUS_MAP_H__
+#define __EXTRA_ROOT_BUS_MAP_H__
+
+/**
+ Incomplete ("opaque") data type implementing the map.
+**/
+typedef struct EXTRA_ROOT_BUS_MAP_STRUCT EXTRA_ROOT_BUS_MAP;
+
+EFI_STATUS
+CreateExtraRootBusMap (
+ OUT EXTRA_ROOT_BUS_MAP **ExtraRootBusMap
+ );
+
+VOID
+DestroyExtraRootBusMap (
+ IN EXTRA_ROOT_BUS_MAP *ExtraRootBusMap
+ );
+
+EFI_STATUS
+MapRootBusPosToBusNr (
+ IN CONST EXTRA_ROOT_BUS_MAP *ExtraRootBusMap,
+ IN UINT64 RootBusPos,
+ OUT UINT32 *RootBusNr
+ );
+
+#endif
diff --git a/OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.c b/OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.c
new file mode 100644
index 0000000000..86082301a8
--- /dev/null
+++ b/OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.c
@@ -0,0 +1,1944 @@
+/** @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>
+
+ 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 <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 successul 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 = sizeof (PciDevFun) / sizeof (PciDevFun[0]);
+ 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)/HD( -- if PCI function is 0 or absent
+ // PciRoot(0x0)/Pci(0x6,0x3)/HD( -- 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)/HD(",
+ 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 = sizeof (TargetLun) / sizeof (TargetLun[0]);
+ 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 = sizeof (Namespace) / sizeof (Namespace[0]);
+ 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 {
+ //
+ // 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>/HD(
+ //
+ Written = UnicodeSPrintAsciiFormat (
+ Translated,
+ *TranslatedSize * sizeof (*Translated), // BufferSize in bytes
+ "%s/HD(",
+ 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 = sizeof (TargetLun) / sizeof (TargetLun[0]);
+ 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 (dependent on presence of nonzero PCI function):
+ //
+ // <VenHwString>/MAC(
+ //
+ Written = UnicodeSPrintAsciiFormat (
+ Translated,
+ *TranslatedSize * sizeof (*Translated), // BufferSize in bytes
+ "%s/MAC(",
+ 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;
+}
+
+
+/**
+
+ 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 EfiBootManagerConnectAll () and
+ EfiBootManagerRefreshAllBootOption () return.
+
+ @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
+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 = sizeof (Translated) / sizeof (Translated[0]);
+ 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 (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;
+ }
+ break;
+ }
+ } // scanned all active boot options
+ } // translation successful
+
+ TranslatedSize = sizeof (Translated) / sizeof (Translated[0]);
+ 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
+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);
+}
diff --git a/OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.inf b/OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.inf
new file mode 100644
index 0000000000..eec938f198
--- /dev/null
+++ b/OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.inf
@@ -0,0 +1,68 @@
+## @file
+# Rewrite the BootOrder NvVar based on QEMU's "bootorder" fw_cfg file.
+#
+# Copyright (C) 2012 - 2014, Red Hat, Inc.
+# Copyright (c) 2007 - 2016, Intel Corporation. All rights reserved.<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.
+#
+##
+
+[Defines]
+ INF_VERSION = 0x00010005
+ BASE_NAME = QemuBootOrderLib
+ FILE_GUID = 1D677A58-C753-4AF1-B552-EFE142DF8F57
+ MODULE_TYPE = DXE_DRIVER
+ VERSION_STRING = 1.0
+ LIBRARY_CLASS = QemuBootOrderLib|DXE_DRIVER
+
+#
+# The following information is for reference only and not required by the build
+# tools.
+#
+# VALID_ARCHITECTURES = IA32 X64 IPF EBC ARM AARCH64
+#
+
+[Sources]
+ QemuBootOrderLib.c
+ ExtraRootBusMap.c
+
+[Packages]
+ MdePkg/MdePkg.dec
+ MdeModulePkg/MdeModulePkg.dec
+ OvmfPkg/OvmfPkg.dec
+
+[LibraryClasses]
+ QemuFwCfgLib
+ DebugLib
+ MemoryAllocationLib
+ UefiBootManagerLib
+ UefiBootServicesTableLib
+ UefiRuntimeServicesTableLib
+ BaseLib
+ PrintLib
+ DevicePathLib
+ BaseMemoryLib
+ OrderedCollectionLib
+
+[Guids]
+ gEfiGlobalVariableGuid
+ gVirtioMmioTransportGuid
+
+[FeaturePcd]
+ gUefiOvmfPkgTokenSpaceGuid.PcdQemuBootOrderPciTranslation
+ gUefiOvmfPkgTokenSpaceGuid.PcdQemuBootOrderMmioTranslation
+
+[Pcd]
+ gEfiMdePkgTokenSpaceGuid.PcdPlatformBootTimeOut
+
+[Protocols]
+ gEfiDevicePathProtocolGuid ## CONSUMES
+ gEfiPciRootBridgeIoProtocolGuid ## CONSUMES