QemuNewBootOrderLib will be changed to work with MdeModulePkg/BDS.

Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Ruiyu Ni <ruiyu...@intel.com>
Reviewed-by: Laszlo Ersek <ler...@redhat.com>
---
 .../Library/QemuNewBootOrderLib/ExtraRootBusMap.c  |  313 ++++
 .../Library/QemuNewBootOrderLib/ExtraRootBusMap.h  |   40 +
 .../Library/QemuNewBootOrderLib/QemuBootOrderLib.c | 1891 ++++++++++++++++++++
 .../QemuNewBootOrderLib/QemuBootOrderLib.inf       |   68 +
 4 files changed, 2312 insertions(+)
 create mode 100644 OvmfPkg/Library/QemuNewBootOrderLib/ExtraRootBusMap.c
 create mode 100644 OvmfPkg/Library/QemuNewBootOrderLib/ExtraRootBusMap.h
 create mode 100644 OvmfPkg/Library/QemuNewBootOrderLib/QemuBootOrderLib.c
 create mode 100644 OvmfPkg/Library/QemuNewBootOrderLib/QemuBootOrderLib.inf

diff --git a/OvmfPkg/Library/QemuNewBootOrderLib/ExtraRootBusMap.c 
b/OvmfPkg/Library/QemuNewBootOrderLib/ExtraRootBusMap.c
new file mode 100644
index 0000000..ec42214
--- /dev/null
+++ b/OvmfPkg/Library/QemuNewBootOrderLib/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/QemuNewBootOrderLib/ExtraRootBusMap.h 
b/OvmfPkg/Library/QemuNewBootOrderLib/ExtraRootBusMap.h
new file mode 100644
index 0000000..e2dbc38
--- /dev/null
+++ b/OvmfPkg/Library/QemuNewBootOrderLib/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/QemuNewBootOrderLib/QemuBootOrderLib.c 
b/OvmfPkg/Library/QemuNewBootOrderLib/QemuBootOrderLib.c
new file mode 100644
index 0000000..15065b7
--- /dev/null
+++ b/OvmfPkg/Library/QemuNewBootOrderLib/QemuBootOrderLib.c
@@ -0,0 +1,1891 @@
+/** @file
+  Rewrite the BootOrder NvVar based on QEMU's "bootorder" fw_cfg file.
+
+  Copyright (C) 2012 - 2014, Red Hat, Inc.
+  Copyright (c) 2013, 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/GenericBdsLib.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 BDS_COMMON_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++] =
+                                         ActiveOption->BootOption->BootCurrent;
+  ActiveOption->Appended = TRUE;
+  return RETURN_SUCCESS;
+}
+
+
+/**
+
+  Create an array of ACTIVE_OPTION elements for a boot option list.
+
+  @param[in]  BootOptionList  A boot option list, created with
+                              BdsLibEnumerateAllBootOption().
+
+  @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
+                                   BootOptionList.
+
+  @retval RETURN_OUT_OF_RESOURCES  Memory allocation failed.
+
+**/
+STATIC
+RETURN_STATUS
+CollectActiveOptions (
+  IN   CONST LIST_ENTRY *BootOptionList,
+  OUT  ACTIVE_OPTION    **ActiveOption,
+  OUT  UINTN            *Count
+  )
+{
+  UINTN ScanMode;
+
+  *ActiveOption = NULL;
+
+  //
+  // Scan the list twice:
+  // - count active entries,
+  // - store links to active entries.
+  //
+  for (ScanMode = 0; ScanMode < 2; ++ScanMode) {
+    CONST LIST_ENTRY *Link;
+
+    Link = BootOptionList->ForwardLink;
+    *Count = 0;
+    while (Link != BootOptionList) {
+      CONST BDS_COMMON_OPTION *Current;
+
+      Current = CR (Link, BDS_COMMON_OPTION, Link, BDS_LOAD_OPTION_SIGNATURE);
+      if (IS_LOAD_OPTION_TYPE (Current->Attribute, LOAD_OPTION_ACTIVE)) {
+        if (ScanMode == 1) {
+          (*ActiveOption)[*Count].BootOption = Current;
+          (*ActiveOption)[*Count].Appended   = FALSE;
+        }
+        ++*Count;
+      }
+      Link = Link->ForwardLink;
+    }
+
+    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,0x0,0x0)
+    //                                   ^   ^   ^
+    //                                   |   |   LUN (always 0 on Q35)
+    //                                   |   port multiplier port number,
+    //                                   |   always 0 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,0x0,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  CONST EFI_DEVICE_PATH_PROTOCOL         *DevicePath
+  )
+{
+  CHAR16  *Converted;
+  BOOLEAN Result;
+
+  Converted = ConvertDevicePathToText (
+                DevicePath,
+                FALSE, // DisplayOnly
+                FALSE  // AllowShortcuts
+                );
+  if (Converted == NULL) {
+    return FALSE;
+  }
+
+  //
+  // Attempt to expand any relative UEFI device path starting with HD() to an
+  // absolute device path first. The logic imitates BdsLibBootViaBootOption().
+  // We don't have to free the absolute device path,
+  // BdsExpandPartitionPartialDevicePathToFull() has internal caching.
+  //
+  Result = FALSE;
+  if (DevicePathType (DevicePath) == MEDIA_DEVICE_PATH &&
+      DevicePathSubType (DevicePath) == MEDIA_HARDDRIVE_DP) {
+    EFI_DEVICE_PATH_PROTOCOL *AbsDevicePath;
+    CHAR16                   *AbsConverted;
+
+    AbsDevicePath = BdsExpandPartitionPartialDevicePathToFull (
+                      (HARDDRIVE_DEVICE_PATH *) DevicePath);
+    if (AbsDevicePath == NULL) {
+      goto Exit;
+    }
+    AbsConverted = ConvertDevicePathToText (AbsDevicePath, FALSE, FALSE);
+    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 BDS_COMMON_OPTION        *Current;
+      CONST EFI_DEVICE_PATH_PROTOCOL *FirstNode;
+
+      Current = ActiveOption[Idx].BootOption;
+      FirstNode = Current->DevicePath;
+      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->BootCurrent);
+
+      //
+      // "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 BootOptionList, and rewrite the BootOrder NvVar
+  so that it corresponds to the order described in fw_cfg.
+
+  @param[in] BootOptionList  A boot option list, created with
+                             BdsLibEnumerateAllBootOption ().
+
+
+  @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 (
+  IN  CONST LIST_ENTRY *BootOptionList
+  )
+{
+  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];
+
+  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;
+  }
+
+  Status = CollectActiveOptions (BootOptionList, &ActiveOption, &ActiveCount);
+  if (RETURN_ERROR (Status)) {
+    goto ErrorFreeBootOrder;
+  }
+
+  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->DevicePath
+              )
+            ) {
+          //
+          // 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);
+
+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/QemuNewBootOrderLib/QemuBootOrderLib.inf 
b/OvmfPkg/Library/QemuNewBootOrderLib/QemuBootOrderLib.inf
new file mode 100644
index 0000000..9374a61
--- /dev/null
+++ b/OvmfPkg/Library/QemuNewBootOrderLib/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                      = QemuNewBootOrderLib
+  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
+  IntelFrameworkModulePkg/IntelFrameworkModulePkg.dec
+  OvmfPkg/OvmfPkg.dec
+
+[LibraryClasses]
+  QemuFwCfgLib
+  DebugLib
+  MemoryAllocationLib
+  GenericBdsLib
+  UefiBootServicesTableLib
+  UefiRuntimeServicesTableLib
+  BaseLib
+  PrintLib
+  DevicePathLib
+  BaseMemoryLib
+  OrderedCollectionLib
+
+[Guids]
+  gEfiGlobalVariableGuid
+  gVirtioMmioTransportGuid
+
+[FeaturePcd]
+  gUefiOvmfPkgTokenSpaceGuid.PcdQemuBootOrderPciTranslation
+  gUefiOvmfPkgTokenSpaceGuid.PcdQemuBootOrderMmioTranslation
+
+[Pcd]
+  gEfiMdePkgTokenSpaceGuid.PcdPlatformBootTimeOut
+
+[Protocols]
+  gEfiDevicePathProtocolGuid                            ## CONSUMES
+  gEfiPciRootBridgeIoProtocolGuid                       ## CONSUMES
-- 
2.7.0.windows.1

_______________________________________________
edk2-devel mailing list
edk2-devel@lists.01.org
https://lists.01.org/mailman/listinfo/edk2-devel

Reply via email to