--- /dev/null
+/** @file\r
+ Rewrite the BootOrder NvVar based on QEMU's "bootorder" fw_cfg file.\r
+\r
+ Copyright (C) 2012, Red Hat, Inc.\r
+\r
+ This program and the accompanying materials are licensed and made available\r
+ under the terms and conditions of the BSD License which accompanies this\r
+ distribution. The full text of the license may be found at\r
+ http://opensource.org/licenses/bsd-license.php\r
+\r
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT\r
+ WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+**/\r
+\r
+#include <Library/QemuFwCfgLib.h>\r
+#include <Library/DebugLib.h>\r
+#include <Library/MemoryAllocationLib.h>\r
+#include <Library/GenericBdsLib.h>\r
+#include <Library/UefiBootServicesTableLib.h>\r
+#include <Library/UefiRuntimeServicesTableLib.h>\r
+#include <Library/BaseLib.h>\r
+#include <Library/PrintLib.h>\r
+#include <Protocol/DevicePathToText.h>\r
+#include <Guid/GlobalVariable.h>\r
+\r
+\r
+/**\r
+ OpenFirmware to UEFI device path translation output buffer size in CHAR16's.\r
+**/\r
+#define TRANSLATION_OUTPUT_SIZE 0x100\r
+\r
+\r
+/**\r
+ Number of nodes in OpenFirmware device paths that is required and examined.\r
+**/\r
+#define FIXED_OFW_NODES 4\r
+\r
+\r
+/**\r
+ Simple character classification routines, corresponding to POSIX class names\r
+ and ASCII encoding.\r
+**/\r
+STATIC\r
+BOOLEAN\r
+IsAlnum (\r
+ IN CHAR8 Chr\r
+ )\r
+{\r
+ return (('0' <= Chr && Chr <= '9') ||\r
+ ('A' <= Chr && Chr <= 'Z') ||\r
+ ('a' <= Chr && Chr <= 'z')\r
+ );\r
+}\r
+\r
+\r
+STATIC\r
+BOOLEAN\r
+IsDriverNamePunct (\r
+ IN CHAR8 Chr\r
+ )\r
+{\r
+ return (Chr == ',' || Chr == '.' || Chr == '_' ||\r
+ Chr == '+' || Chr == '-'\r
+ );\r
+}\r
+\r
+\r
+STATIC\r
+BOOLEAN\r
+IsPrintNotDelim (\r
+ IN CHAR8 Chr\r
+ )\r
+{\r
+ return (32 <= Chr && Chr <= 126 &&\r
+ Chr != '/' && Chr != '@' && Chr != ':');\r
+}\r
+\r
+\r
+/**\r
+ Utility types and functions.\r
+**/\r
+typedef struct {\r
+ CONST CHAR8 *Ptr; // not necessarily NUL-terminated\r
+ UINTN Len; // number of non-NUL characters\r
+} SUBSTRING;\r
+\r
+\r
+/**\r
+\r
+ Check if Substring and String have identical contents.\r
+\r
+ The function relies on the restriction that a SUBSTRING cannot have embedded\r
+ NULs either.\r
+\r
+ @param[in] Substring The SUBSTRING input to the comparison.\r
+\r
+ @param[in] String The ASCII string input to the comparison.\r
+\r
+\r
+ @return Whether the inputs have identical contents.\r
+\r
+**/\r
+STATIC\r
+BOOLEAN\r
+SubstringEq (\r
+ IN SUBSTRING Substring,\r
+ IN CONST CHAR8 *String\r
+ )\r
+{\r
+ UINTN Pos;\r
+ CONST CHAR8 *Chr;\r
+\r
+ Pos = 0;\r
+ Chr = String;\r
+\r
+ while (Pos < Substring.Len && Substring.Ptr[Pos] == *Chr) {\r
+ ++Pos;\r
+ ++Chr;\r
+ }\r
+\r
+ return (Pos == Substring.Len && *Chr == '\0');\r
+}\r
+\r
+\r
+/**\r
+\r
+ Parse a comma-separated list of hexadecimal integers into the elements of an\r
+ UINT32 array.\r
+\r
+ Whitespace, "0x" prefixes, leading or trailing commas, sequences of commas,\r
+ or an empty string are not allowed; they are rejected.\r
+\r
+ The function relies on ASCII encoding.\r
+\r
+ @param[in] UnitAddress The substring to parse.\r
+\r
+ @param[out] Result The array, allocated by the caller, to receive\r
+ the parsed values. This parameter may be NULL if\r
+ NumResults is zero on input.\r
+\r
+ @param[in out] NumResults On input, the number of elements allocated for\r
+ Result. On output, the number of elements it has\r
+ taken (or would have taken) to parse the string\r
+ fully.\r
+\r
+\r
+ @retval RETURN_SUCCESS UnitAddress has been fully parsed.\r
+ NumResults is set to the number of parsed\r
+ values; the corresponding elements have\r
+ been set in Result. The rest of Result's\r
+ elements are unchanged.\r
+\r
+ @retval RETURN_BUFFER_TOO_SMALL UnitAddress has been fully parsed.\r
+ NumResults is set to the number of parsed\r
+ values, but elements have been stored only\r
+ up to the input value of NumResults, which\r
+ is less than what has been parsed.\r
+\r
+ @retval RETURN_INVALID_PARAMETER Parse error. The contents of Results is\r
+ indeterminate. NumResults has not been\r
+ changed.\r
+\r
+**/\r
+STATIC\r
+RETURN_STATUS\r
+ParseUnitAddressHexList (\r
+ IN SUBSTRING UnitAddress,\r
+ OUT UINT32 *Result,\r
+ IN OUT UINTN *NumResults\r
+ )\r
+{\r
+ UINTN Entry; // number of entry currently being parsed\r
+ UINT32 EntryVal; // value being constructed for current entry\r
+ CHAR8 PrevChr; // UnitAddress character previously checked\r
+ UINTN Pos; // current position within UnitAddress\r
+ RETURN_STATUS Status;\r
+\r
+ Entry = 0;\r
+ EntryVal = 0;\r
+ PrevChr = ',';\r
+\r
+ for (Pos = 0; Pos < UnitAddress.Len; ++Pos) {\r
+ CHAR8 Chr;\r
+ INT8 Val;\r
+\r
+ Chr = UnitAddress.Ptr[Pos];\r
+ Val = ('a' <= Chr && Chr <= 'f') ? (Chr - 'a' + 10) :\r
+ ('A' <= Chr && Chr <= 'F') ? (Chr - 'A' + 10) :\r
+ ('0' <= Chr && Chr <= '9') ? (Chr - '0' ) :\r
+ -1;\r
+\r
+ if (Val >= 0) {\r
+ if (EntryVal > 0xFFFFFFF) {\r
+ return RETURN_INVALID_PARAMETER;\r
+ }\r
+ EntryVal = (EntryVal << 4) | Val;\r
+ } else if (Chr == ',') {\r
+ if (PrevChr == ',') {\r
+ return RETURN_INVALID_PARAMETER;\r
+ }\r
+ if (Entry < *NumResults) {\r
+ Result[Entry] = EntryVal;\r
+ }\r
+ ++Entry;\r
+ EntryVal = 0;\r
+ } else {\r
+ return RETURN_INVALID_PARAMETER;\r
+ }\r
+\r
+ PrevChr = Chr;\r
+ }\r
+\r
+ if (PrevChr == ',') {\r
+ return RETURN_INVALID_PARAMETER;\r
+ }\r
+ if (Entry < *NumResults) {\r
+ Result[Entry] = EntryVal;\r
+ Status = RETURN_SUCCESS;\r
+ } else {\r
+ Status = RETURN_BUFFER_TOO_SMALL;\r
+ }\r
+ ++Entry;\r
+\r
+ *NumResults = Entry;\r
+ return Status;\r
+}\r
+\r
+\r
+/**\r
+ A simple array of Boot Option ID's.\r
+**/\r
+typedef struct {\r
+ UINT16 *Data;\r
+ UINTN Allocated;\r
+ UINTN Produced;\r
+} BOOT_ORDER;\r
+\r
+\r
+/**\r
+\r
+ Append BootOptionId to BootOrder, reallocating the latter if needed.\r
+\r
+ @param[in out] BootOrder The structure pointing to the array and holding\r
+ allocation and usage counters.\r
+\r
+ @param[in] BootOptionId The value to append to the array.\r
+\r
+\r
+ @retval RETURN_SUCCESS BootOptionId appended.\r
+\r
+ @retval RETURN_OUT_OF_RESOURCES Memory reallocation failed.\r
+\r
+**/\r
+STATIC\r
+RETURN_STATUS\r
+BootOrderAppend (\r
+ IN OUT BOOT_ORDER *BootOrder,\r
+ IN UINT16 BootOptionId\r
+ )\r
+{\r
+ if (BootOrder->Produced == BootOrder->Allocated) {\r
+ UINTN AllocatedNew;\r
+ UINT16 *DataNew;\r
+\r
+ ASSERT (BootOrder->Allocated > 0);\r
+ AllocatedNew = BootOrder->Allocated * 2;\r
+ DataNew = ReallocatePool (\r
+ BootOrder->Allocated * sizeof (*BootOrder->Data),\r
+ AllocatedNew * sizeof (*DataNew),\r
+ BootOrder->Data\r
+ );\r
+ if (DataNew == NULL) {\r
+ return RETURN_OUT_OF_RESOURCES;\r
+ }\r
+ BootOrder->Allocated = AllocatedNew;\r
+ BootOrder->Data = DataNew;\r
+ }\r
+\r
+ BootOrder->Data[BootOrder->Produced++] = BootOptionId;\r
+ return RETURN_SUCCESS;\r
+}\r
+\r
+\r
+/**\r
+ OpenFirmware device path node\r
+**/\r
+typedef struct {\r
+ SUBSTRING DriverName;\r
+ SUBSTRING UnitAddress;\r
+ SUBSTRING DeviceArguments;\r
+} OFW_NODE;\r
+\r
+\r
+/**\r
+\r
+ Parse an OpenFirmware device path node into the caller-allocated OFW_NODE\r
+ structure, and advance in the input string.\r
+\r
+ The node format is mostly parsed after IEEE 1275-1994, 3.2.1.1 "Node names"\r
+ (a leading slash is expected and not returned):\r
+\r
+ /driver-name@unit-address[:device-arguments][<LF>]\r
+\r
+ A single trailing <LF> character is consumed but not returned. A trailing\r
+ <LF> or NUL character terminates the device path.\r
+\r
+ The function relies on ASCII encoding.\r
+\r
+ @param[in out] Ptr Address of the pointer pointing to the start of the\r
+ node string. After successful parsing *Ptr is set to\r
+ the byte immediately following the consumed\r
+ characters. On error it points to the byte that\r
+ caused the error. The input string is never modified.\r
+\r
+ @param[out] OfwNode The members of this structure point into the input\r
+ string, designating components of the node.\r
+ Separators are never included. If "device-arguments"\r
+ is missing, then DeviceArguments.Ptr is set to NULL.\r
+ All components that are present have nonzero length.\r
+\r
+ If the call doesn't succeed, the contents of this\r
+ structure is indeterminate.\r
+\r
+ @param[out] IsFinal In case of successul parsing, this parameter signals\r
+ whether the node just parsed is the final node in the\r
+ device path. The call after a final node will attempt\r
+ to start parsing the next path. If the call doesn't\r
+ succeed, then this parameter is not changed.\r
+\r
+\r
+ @retval RETURN_SUCCESS Parsing successful.\r
+\r
+ @retval RETURN_NOT_FOUND Parsing terminated. *Ptr was (and is)\r
+ pointing to an empty string.\r
+\r
+ @retval RETURN_INVALID_PARAMETER Parse error.\r
+\r
+**/\r
+STATIC\r
+RETURN_STATUS\r
+ParseOfwNode (\r
+ IN OUT CONST CHAR8 **Ptr,\r
+ OUT OFW_NODE *OfwNode,\r
+ OUT BOOLEAN *IsFinal\r
+ )\r
+{\r
+ //\r
+ // A leading slash is expected. End of string is tolerated.\r
+ //\r
+ switch (**Ptr) {\r
+ case '\0':\r
+ return RETURN_NOT_FOUND;\r
+\r
+ case '/':\r
+ ++*Ptr;\r
+ break;\r
+\r
+ default:\r
+ return RETURN_INVALID_PARAMETER;\r
+ }\r
+\r
+ //\r
+ // driver-name\r
+ //\r
+ OfwNode->DriverName.Ptr = *Ptr;\r
+ OfwNode->DriverName.Len = 0;\r
+ while (OfwNode->DriverName.Len < 32 &&\r
+ (IsAlnum (**Ptr) || IsDriverNamePunct (**Ptr))\r
+ ) {\r
+ ++*Ptr;\r
+ ++OfwNode->DriverName.Len;\r
+ }\r
+\r
+ if (OfwNode->DriverName.Len == 0 || OfwNode->DriverName.Len == 32) {\r
+ return RETURN_INVALID_PARAMETER;\r
+ }\r
+\r
+\r
+ //\r
+ // unit-address\r
+ //\r
+ if (**Ptr != '@') {\r
+ return RETURN_INVALID_PARAMETER;\r
+ }\r
+ ++*Ptr;\r
+\r
+ OfwNode->UnitAddress.Ptr = *Ptr;\r
+ OfwNode->UnitAddress.Len = 0;\r
+ while (IsPrintNotDelim (**Ptr)) {\r
+ ++*Ptr;\r
+ ++OfwNode->UnitAddress.Len;\r
+ }\r
+\r
+ if (OfwNode->UnitAddress.Len == 0) {\r
+ return RETURN_INVALID_PARAMETER;\r
+ }\r
+\r
+\r
+ //\r
+ // device-arguments, may be omitted\r
+ //\r
+ OfwNode->DeviceArguments.Len = 0;\r
+ if (**Ptr == ':') {\r
+ ++*Ptr;\r
+ OfwNode->DeviceArguments.Ptr = *Ptr;\r
+\r
+ while (IsPrintNotDelim (**Ptr)) {\r
+ ++*Ptr;\r
+ ++OfwNode->DeviceArguments.Len;\r
+ }\r
+\r
+ if (OfwNode->DeviceArguments.Len == 0) {\r
+ return RETURN_INVALID_PARAMETER;\r
+ }\r
+ }\r
+ else {\r
+ OfwNode->DeviceArguments.Ptr = NULL;\r
+ }\r
+\r
+ switch (**Ptr) {\r
+ case '\n':\r
+ ++*Ptr;\r
+ //\r
+ // fall through\r
+ //\r
+\r
+ case '\0':\r
+ *IsFinal = TRUE;\r
+ break;\r
+\r
+ case '/':\r
+ *IsFinal = FALSE;\r
+ break;\r
+\r
+ default:\r
+ return RETURN_INVALID_PARAMETER;\r
+ }\r
+\r
+ DEBUG ((\r
+ DEBUG_VERBOSE,\r
+ "%a: DriverName=\"%.*a\" UnitAddress=\"%.*a\" DeviceArguments=\"%.*a\"\n",\r
+ __FUNCTION__,\r
+ OfwNode->DriverName.Len, OfwNode->DriverName.Ptr,\r
+ OfwNode->UnitAddress.Len, OfwNode->UnitAddress.Ptr,\r
+ OfwNode->DeviceArguments.Len,\r
+ OfwNode->DeviceArguments.Ptr == NULL ? "" : OfwNode->DeviceArguments.Ptr\r
+ ));\r
+ return RETURN_SUCCESS;\r
+}\r
+\r
+\r
+/**\r
+\r
+ Translate an array of OpenFirmware device nodes to a UEFI device path\r
+ fragment.\r
+\r
+ @param[in] OfwNode Array of OpenFirmware device nodes to\r
+ translate, constituting the beginning of an\r
+ OpenFirmware device path.\r
+\r
+ @param[in] NumNodes Number of elements in OfwNode.\r
+\r
+ @param[out] Translated Destination array receiving the UEFI path\r
+ fragment, allocated by the caller. If the\r
+ return value differs from RETURN_SUCCESS, its\r
+ contents is indeterminate.\r
+\r
+ @param[in out] TranslatedSize On input, the number of CHAR16's in\r
+ Translated. On RETURN_SUCCESS this parameter\r
+ is assigned the number of non-NUL CHAR16's\r
+ written to Translated. In case of other return\r
+ values, TranslatedSize is indeterminate.\r
+\r
+\r
+ @retval RETURN_SUCCESS Translation successful.\r
+\r
+ @retval RETURN_BUFFER_TOO_SMALL The translation does not fit into the number\r
+ of bytes provided.\r
+\r
+ @retval RETURN_UNSUPPORTED The array of OpenFirmware device nodes can't\r
+ be translated in the current implementation.\r
+\r
+**/\r
+STATIC\r
+RETURN_STATUS\r
+TranslateOfwNodes (\r
+ IN CONST OFW_NODE *OfwNode,\r
+ IN UINTN NumNodes,\r
+ OUT CHAR16 *Translated,\r
+ IN OUT UINTN *TranslatedSize\r
+ )\r
+{\r
+ UINT32 PciDevFun[2];\r
+ UINTN NumEntries;\r
+ UINTN Written;\r
+\r
+ //\r
+ // Get PCI device and optional PCI function. Assume a single PCI root.\r
+ //\r
+ if (NumNodes < FIXED_OFW_NODES ||\r
+ !SubstringEq (OfwNode[0].DriverName, "pci")\r
+ ) {\r
+ return RETURN_UNSUPPORTED;\r
+ }\r
+ PciDevFun[1] = 0;\r
+ NumEntries = sizeof (PciDevFun) / sizeof (PciDevFun[0]);\r
+ if (ParseUnitAddressHexList (\r
+ OfwNode[1].UnitAddress,\r
+ PciDevFun,\r
+ &NumEntries\r
+ ) != RETURN_SUCCESS\r
+ ) {\r
+ return RETURN_UNSUPPORTED;\r
+ }\r
+\r
+ if (SubstringEq (OfwNode[1].DriverName, "ide") &&\r
+ SubstringEq (OfwNode[2].DriverName, "drive") &&\r
+ SubstringEq (OfwNode[3].DriverName, "disk")\r
+ ) {\r
+ //\r
+ // OpenFirmware device path (IDE disk, IDE CD-ROM):\r
+ //\r
+ // /pci@i0cf8/ide@1,1/drive@0/disk@0\r
+ // ^ ^ ^ ^ ^\r
+ // | | | | master or slave\r
+ // | | | primary or secondary\r
+ // | PCI slot & function holding IDE controller\r
+ // PCI root at system bus port, PIO\r
+ //\r
+ // UEFI device path:\r
+ //\r
+ // PciRoot(0x0)/Pci(0x1,0x1)/Ata(Primary,Master,0x0)\r
+ // ^\r
+ // fixed LUN\r
+ //\r
+ UINT32 Secondary;\r
+ UINT32 Slave;\r
+\r
+ NumEntries = 1;\r
+ if (ParseUnitAddressHexList (\r
+ OfwNode[2].UnitAddress,\r
+ &Secondary,\r
+ &NumEntries\r
+ ) != RETURN_SUCCESS ||\r
+ Secondary > 1 ||\r
+ ParseUnitAddressHexList (\r
+ OfwNode[3].UnitAddress,\r
+ &Slave,\r
+ &NumEntries // reuse after previous single-element call\r
+ ) != RETURN_SUCCESS ||\r
+ Slave > 1\r
+ ) {\r
+ return RETURN_UNSUPPORTED;\r
+ }\r
+\r
+ Written = UnicodeSPrintAsciiFormat (\r
+ Translated,\r
+ *TranslatedSize * sizeof (*Translated), // BufferSize in bytes\r
+ "PciRoot(0x0)/Pci(0x%x,0x%x)/Ata(%a,%a,0x0)",\r
+ PciDevFun[0],\r
+ PciDevFun[1],\r
+ Secondary ? "Secondary" : "Primary",\r
+ Slave ? "Slave" : "Master"\r
+ );\r
+ } else if (SubstringEq (OfwNode[1].DriverName, "isa") &&\r
+ SubstringEq (OfwNode[2].DriverName, "fdc") &&\r
+ SubstringEq (OfwNode[3].DriverName, "floppy")\r
+ ) {\r
+ //\r
+ // OpenFirmware device path (floppy disk):\r
+ //\r
+ // /pci@i0cf8/isa@1/fdc@03f0/floppy@0\r
+ // ^ ^ ^ ^\r
+ // | | | A: or B:\r
+ // | | ISA controller io-port (hex)\r
+ // | PCI slot holding ISA controller\r
+ // PCI root at system bus port, PIO\r
+ //\r
+ // UEFI device path:\r
+ //\r
+ // PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x0)\r
+ // ^\r
+ // ACPI UID\r
+ //\r
+ UINT32 AcpiUid;\r
+\r
+ NumEntries = 1;\r
+ if (ParseUnitAddressHexList (\r
+ OfwNode[3].UnitAddress,\r
+ &AcpiUid,\r
+ &NumEntries\r
+ ) != RETURN_SUCCESS ||\r
+ AcpiUid > 1\r
+ ) {\r
+ return RETURN_UNSUPPORTED;\r
+ }\r
+\r
+ Written = UnicodeSPrintAsciiFormat (\r
+ Translated,\r
+ *TranslatedSize * sizeof (*Translated), // BufferSize in bytes\r
+ "PciRoot(0x0)/Pci(0x%x,0x%x)/Floppy(0x%x)",\r
+ PciDevFun[0],\r
+ PciDevFun[1],\r
+ AcpiUid\r
+ );\r
+ } else {\r
+ return RETURN_UNSUPPORTED;\r
+ }\r
+\r
+ //\r
+ // There's no way to differentiate between "completely used up without\r
+ // truncation" and "truncated", so treat the former as the latter, and return\r
+ // success only for "some room left unused".\r
+ //\r
+ if (Written + 1 < *TranslatedSize) {\r
+ *TranslatedSize = Written;\r
+ return RETURN_SUCCESS;\r
+ }\r
+\r
+ return RETURN_BUFFER_TOO_SMALL;\r
+}\r
+\r
+\r
+/**\r
+\r
+ Translate an OpenFirmware device path fragment to a UEFI device path\r
+ fragment, and advance in the input string.\r
+\r
+ @param[in out] Ptr Address of the pointer pointing to the start\r
+ of the path string. After successful\r
+ translation (RETURN_SUCCESS) or at least\r
+ successful parsing (RETURN_UNSUPPORTED,\r
+ RETURN_BUFFER_TOO_SMALL), *Ptr is set to the\r
+ byte immediately following the consumed\r
+ characters. In other error cases, it points to\r
+ the byte that caused the error.\r
+\r
+ @param[out] Translated Destination array receiving the UEFI path\r
+ fragment, allocated by the caller. If the\r
+ return value differs from RETURN_SUCCESS, its\r
+ contents is indeterminate.\r
+\r
+ @param[in out] TranslatedSize On input, the number of CHAR16's in\r
+ Translated. On RETURN_SUCCESS this parameter\r
+ is assigned the number of non-NUL CHAR16's\r
+ written to Translated. In case of other return\r
+ values, TranslatedSize is indeterminate.\r
+\r
+\r
+ @retval RETURN_SUCCESS Translation successful.\r
+\r
+ @retval RETURN_BUFFER_TOO_SMALL The OpenFirmware device path was parsed\r
+ successfully, but its translation did not\r
+ fit into the number of bytes provided.\r
+ Further calls to this function are\r
+ possible.\r
+\r
+ @retval RETURN_UNSUPPORTED The OpenFirmware device path was parsed\r
+ successfully, but it can't be translated in\r
+ the current implementation. Further calls\r
+ to this function are possible.\r
+\r
+ @retval RETURN_NOT_FOUND Translation terminated, *Ptr was (and is)\r
+ pointing to an empty string.\r
+\r
+ @retval RETURN_INVALID_PARAMETER Parse error. This is a permanent error.\r
+\r
+**/\r
+STATIC\r
+RETURN_STATUS\r
+TranslateOfwPath (\r
+ IN OUT CONST CHAR8 **Ptr,\r
+ OUT CHAR16 *Translated,\r
+ IN OUT UINTN *TranslatedSize\r
+ )\r
+{\r
+ UINTN NumNodes;\r
+ RETURN_STATUS Status;\r
+ OFW_NODE Node[FIXED_OFW_NODES];\r
+ BOOLEAN IsFinal;\r
+ OFW_NODE Skip;\r
+\r
+ NumNodes = 0;\r
+ Status = ParseOfwNode (Ptr, &Node[NumNodes], &IsFinal);\r
+\r
+ if (Status == RETURN_NOT_FOUND) {\r
+ DEBUG ((DEBUG_VERBOSE, "%a: no more nodes\n", __FUNCTION__));\r
+ return RETURN_NOT_FOUND;\r
+ }\r
+\r
+ while (Status == RETURN_SUCCESS && !IsFinal) {\r
+ ++NumNodes;\r
+ Status = ParseOfwNode (\r
+ Ptr,\r
+ (NumNodes < FIXED_OFW_NODES) ? &Node[NumNodes] : &Skip,\r
+ &IsFinal\r
+ );\r
+ }\r
+\r
+ switch (Status) {\r
+ case RETURN_SUCCESS:\r
+ ++NumNodes;\r
+ break;\r
+\r
+ case RETURN_INVALID_PARAMETER:\r
+ DEBUG ((DEBUG_VERBOSE, "%a: parse error\n", __FUNCTION__));\r
+ return RETURN_INVALID_PARAMETER;\r
+\r
+ default:\r
+ ASSERT (0);\r
+ }\r
+\r
+ Status = TranslateOfwNodes (\r
+ Node,\r
+ NumNodes < FIXED_OFW_NODES ? NumNodes : FIXED_OFW_NODES,\r
+ Translated,\r
+ TranslatedSize);\r
+ switch (Status) {\r
+ case RETURN_SUCCESS:\r
+ DEBUG ((DEBUG_VERBOSE, "%a: success: \"%s\"\n", __FUNCTION__, Translated));\r
+ break;\r
+\r
+ case RETURN_BUFFER_TOO_SMALL:\r
+ DEBUG ((DEBUG_VERBOSE, "%a: buffer too small\n", __FUNCTION__));\r
+ break;\r
+\r
+ case RETURN_UNSUPPORTED:\r
+ DEBUG ((DEBUG_VERBOSE, "%a: unsupported\n", __FUNCTION__));\r
+ break;\r
+\r
+ default:\r
+ ASSERT (0);\r
+ }\r
+ return Status;\r
+}\r
+\r
+\r
+/**\r
+\r
+ Convert the UEFI DevicePath to full text representation with DevPathToText,\r
+ then match the UEFI device path fragment in Translated against it.\r
+\r
+ @param[in] Translated UEFI device path fragment, translated from\r
+ OpenFirmware format, to search for.\r
+\r
+ @param[in] TranslatedLength The length of Translated in CHAR16's.\r
+\r
+ @param[in] DevicePath Boot option device path whose textual rendering\r
+ to search in.\r
+\r
+ @param[in] DevPathToText Binary-to-text conversion protocol for DevicePath.\r
+\r
+\r
+ @retval TRUE If Translated was found at the beginning of DevicePath after\r
+ converting the latter to text.\r
+\r
+ @retval FALSE If DevicePath was NULL, or it could not be converted, or there\r
+ was no match.\r
+\r
+**/\r
+STATIC\r
+BOOLEAN\r
+Match (\r
+ IN CONST CHAR16 *Translated,\r
+ IN UINTN TranslatedLength,\r
+ IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath,\r
+ IN CONST EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *DevPathToText\r
+ )\r
+{\r
+ CHAR16 *Converted;\r
+ BOOLEAN Result;\r
+\r
+ Converted = DevPathToText->ConvertDevicePathToText (\r
+ DevicePath,\r
+ FALSE, // DisplayOnly\r
+ FALSE // AllowShortcuts\r
+ );\r
+ if (Converted == NULL) {\r
+ return FALSE;\r
+ }\r
+\r
+ //\r
+ // Is Translated a prefix of Converted?\r
+ //\r
+ Result = (StrnCmp (Converted, Translated, TranslatedLength) == 0);\r
+ DEBUG ((\r
+ DEBUG_VERBOSE,\r
+ "%a: against \"%s\": %a\n",\r
+ __FUNCTION__,\r
+ Converted,\r
+ Result ? "match" : "no match"\r
+ ));\r
+ FreePool (Converted);\r
+ return Result;\r
+}\r
+\r
+\r
+/**\r
+\r
+ Set the boot order based on configuration retrieved from QEMU.\r
+\r
+ Attempt to retrieve the "bootorder" fw_cfg file from QEMU. Translate the\r
+ OpenFirmware device paths therein to UEFI device path fragments. Match the\r
+ translated fragments against BootOptionList, and rewrite the BootOrder NvVar\r
+ so that it corresponds to the order described in fw_cfg.\r
+\r
+ @param[in] BootOptionList A boot option list, created with\r
+ BdsLibEnumerateAllBootOption ().\r
+\r
+\r
+ @retval RETURN_SUCCESS BootOrder NvVar rewritten.\r
+\r
+ @retval RETURN_UNSUPPORTED QEMU's fw_cfg is not supported.\r
+\r
+ @retval RETURN_NOT_FOUND Empty or nonexistent "bootorder" fw_cfg\r
+ file, or no match found between the\r
+ "bootorder" fw_cfg file and BootOptionList.\r
+\r
+ @retval RETURN_INVALID_PARAMETER Parse error in the "bootorder" fw_cfg file.\r
+\r
+ @retval RETURN_OUT_OF_RESOURCES Memory allocation failed.\r
+\r
+ @return Values returned by gBS->LocateProtocol ()\r
+ or gRT->SetVariable ().\r
+\r
+**/\r
+RETURN_STATUS\r
+SetBootOrderFromQemu (\r
+ IN CONST LIST_ENTRY *BootOptionList\r
+ )\r
+{\r
+ RETURN_STATUS Status;\r
+\r
+ EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *DevPathToText;\r
+\r
+ FIRMWARE_CONFIG_ITEM FwCfgItem;\r
+ UINTN FwCfgSize;\r
+ CHAR8 *FwCfg;\r
+ CONST CHAR8 *FwCfgPtr;\r
+\r
+ BOOT_ORDER BootOrder;\r
+\r
+ UINTN TranslatedSize;\r
+ CHAR16 Translated[TRANSLATION_OUTPUT_SIZE];\r
+\r
+ Status = gBS->LocateProtocol (\r
+ &gEfiDevicePathToTextProtocolGuid,\r
+ NULL, // optional registration key\r
+ (VOID **) &DevPathToText\r
+ );\r
+ if (Status != EFI_SUCCESS) {\r
+ return Status;\r
+ }\r
+\r
+ Status = QemuFwCfgFindFile ("bootorder", &FwCfgItem, &FwCfgSize);\r
+ if (Status != RETURN_SUCCESS) {\r
+ return Status;\r
+ }\r
+\r
+ if (FwCfgSize == 0) {\r
+ return RETURN_NOT_FOUND;\r
+ }\r
+\r
+ FwCfg = AllocatePool (FwCfgSize);\r
+ if (FwCfg == NULL) {\r
+ return RETURN_OUT_OF_RESOURCES;\r
+ }\r
+\r
+ QemuFwCfgSelectItem (FwCfgItem);\r
+ QemuFwCfgReadBytes (FwCfgSize, FwCfg);\r
+ if (FwCfg[FwCfgSize - 1] != '\0') {\r
+ Status = RETURN_INVALID_PARAMETER;\r
+ goto ErrorFreeFwCfg;\r
+ }\r
+\r
+ DEBUG ((DEBUG_VERBOSE, "%a: FwCfg:\n", __FUNCTION__));\r
+ DEBUG ((DEBUG_VERBOSE, "%a\n", FwCfg));\r
+ DEBUG ((DEBUG_VERBOSE, "%a: FwCfg: <end>\n", __FUNCTION__));\r
+ FwCfgPtr = FwCfg;\r
+\r
+ BootOrder.Produced = 0;\r
+ BootOrder.Allocated = 1;\r
+ BootOrder.Data = AllocatePool (\r
+ BootOrder.Allocated * sizeof (*BootOrder.Data)\r
+ );\r
+ if (BootOrder.Data == NULL) {\r
+ Status = RETURN_OUT_OF_RESOURCES;\r
+ goto ErrorFreeFwCfg;\r
+ }\r
+\r
+ //\r
+ // translate each OpenFirmware path\r
+ //\r
+ TranslatedSize = sizeof (Translated) / sizeof (Translated[0]);\r
+ Status = TranslateOfwPath (&FwCfgPtr, Translated, &TranslatedSize);\r
+ while (Status == RETURN_SUCCESS ||\r
+ Status == RETURN_UNSUPPORTED ||\r
+ Status == RETURN_BUFFER_TOO_SMALL) {\r
+ if (Status == RETURN_SUCCESS) {\r
+ CONST LIST_ENTRY *Link;\r
+\r
+ //\r
+ // match translated OpenFirmware path against all enumerated boot options\r
+ //\r
+ for (Link = BootOptionList->ForwardLink; Link != BootOptionList;\r
+ Link = Link->ForwardLink) {\r
+ CONST BDS_COMMON_OPTION *BootOption;\r
+\r
+ BootOption = CR (\r
+ Link,\r
+ BDS_COMMON_OPTION,\r
+ Link,\r
+ BDS_LOAD_OPTION_SIGNATURE\r
+ );\r
+ if (IS_LOAD_OPTION_TYPE (BootOption->Attribute, LOAD_OPTION_ACTIVE) &&\r
+ Match (\r
+ Translated,\r
+ TranslatedSize, // contains length, not size, in CHAR16's here\r
+ BootOption->DevicePath,\r
+ DevPathToText\r
+ )\r
+ ) {\r
+ //\r
+ // match found, store ID and continue with next OpenFirmware path\r
+ //\r
+ Status = BootOrderAppend (&BootOrder, BootOption->BootCurrent);\r
+ if (Status != RETURN_SUCCESS) {\r
+ goto ErrorFreeBootOrder;\r
+ }\r
+ break;\r
+ }\r
+ } // scanned all enumerated boot options\r
+ } // translation successful\r
+\r
+ TranslatedSize = sizeof (Translated) / sizeof (Translated[0]);\r
+ Status = TranslateOfwPath (&FwCfgPtr, Translated, &TranslatedSize);\r
+ } // scanning of OpenFirmware paths done\r
+\r
+ if (Status == RETURN_NOT_FOUND && BootOrder.Produced > 0) {\r
+ //\r
+ // No more OpenFirmware paths, some matches found: rewrite BootOrder NvVar.\r
+ // See Table 10 in the UEFI Spec 2.3.1 with Errata C for the required\r
+ // attributes.\r
+ //\r
+ Status = gRT->SetVariable (\r
+ L"BootOrder",\r
+ &gEfiGlobalVariableGuid,\r
+ EFI_VARIABLE_NON_VOLATILE |\r
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |\r
+ EFI_VARIABLE_RUNTIME_ACCESS,\r
+ BootOrder.Produced * sizeof (*BootOrder.Data),\r
+ BootOrder.Data\r
+ );\r
+ DEBUG ((\r
+ DEBUG_INFO,\r
+ "%a: setting BootOrder: %a\n",\r
+ __FUNCTION__,\r
+ Status == EFI_SUCCESS ? "success" : "error"\r
+ ));\r
+ }\r
+\r
+ErrorFreeBootOrder:\r
+ FreePool (BootOrder.Data);\r
+\r
+ErrorFreeFwCfg:\r
+ FreePool (FwCfg);\r
+\r
+ return Status;\r
+}\r