diff --git a/OvmfPkg/Library/PlatformBdsLib/BdsPlatform.c b/OvmfPkg/Library/PlatformBdsLib/BdsPlatform.c index cd09b2781e..d6e1e93399 100644 --- a/OvmfPkg/Library/PlatformBdsLib/BdsPlatform.c +++ b/OvmfPkg/Library/PlatformBdsLib/BdsPlatform.c @@ -13,6 +13,7 @@ **/ #include "BdsPlatform.h" +#include "QemuBootOrder.h" // @@ -1138,6 +1139,8 @@ Returns: BdsLibConnectAll (); BdsLibEnumerateAllBootOption (BootOptionList); + SetBootOrderFromQemu (BootOptionList); + // // Please uncomment above ConnectAll and EnumerateAll code and remove following first boot // checking code in real production tip. diff --git a/OvmfPkg/Library/PlatformBdsLib/PlatformBdsLib.inf b/OvmfPkg/Library/PlatformBdsLib/PlatformBdsLib.inf index eb773377cb..81602f5e61 100644 --- a/OvmfPkg/Library/PlatformBdsLib/PlatformBdsLib.inf +++ b/OvmfPkg/Library/PlatformBdsLib/PlatformBdsLib.inf @@ -29,7 +29,9 @@ [Sources] BdsPlatform.c PlatformData.c + QemuBootOrder.c BdsPlatform.h + QemuBootOrder.h [Packages] MdePkg/MdePkg.dec @@ -47,6 +49,7 @@ GenericBdsLib PciLib NvVarsFileLib + QemuFwCfgLib [Pcd] gEfiIntelFrameworkModulePkgTokenSpaceGuid.PcdPlatformBootTimeOut diff --git a/OvmfPkg/Library/PlatformBdsLib/QemuBootOrder.c b/OvmfPkg/Library/PlatformBdsLib/QemuBootOrder.c new file mode 100644 index 0000000000..2273d829c1 --- /dev/null +++ b/OvmfPkg/Library/PlatformBdsLib/QemuBootOrder.c @@ -0,0 +1,969 @@ +/** @file + Rewrite the BootOrder NvVar based on QEMU's "bootorder" fw_cfg file. + + Copyright (C) 2012, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + OpenFirmware to UEFI device path translation output buffer size in CHAR16's. +**/ +#define TRANSLATION_OUTPUT_SIZE 0x100 + + +/** + Number of nodes in OpenFirmware device paths that is required and examined. +**/ +#define FIXED_OFW_NODES 4 + + +/** + 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 (Pos == Substring.Len && *Chr == '\0'); +} + + +/** + + Parse a comma-separated list of hexadecimal integers into the elements of an + UINT32 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 UINT32 *Result, + IN OUT UINTN *NumResults + ) +{ + UINTN Entry; // number of entry currently being parsed + UINT32 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 > 0xFFFFFFF) { + return RETURN_INVALID_PARAMETER; + } + EntryVal = (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; + + +/** + + Append BootOptionId 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] BootOptionId The value to append to the array. + + + @retval RETURN_SUCCESS BootOptionId appended. + + @retval RETURN_OUT_OF_RESOURCES Memory reallocation failed. + +**/ +STATIC +RETURN_STATUS +BootOrderAppend ( + IN OUT BOOT_ORDER *BootOrder, + IN UINT16 BootOptionId + ) +{ + 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++] = BootOptionId; + 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][] + + A single trailing character is consumed but not returned. A trailing + 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 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[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 +TranslateOfwNodes ( + IN CONST OFW_NODE *OfwNode, + IN UINTN NumNodes, + OUT CHAR16 *Translated, + IN OUT UINTN *TranslatedSize + ) +{ + UINT32 PciDevFun[2]; + UINTN NumEntries; + UINTN Written; + + // + // Get PCI device and optional PCI function. Assume a single PCI root. + // + if (NumNodes < FIXED_OFW_NODES || + !SubstringEq (OfwNode[0].DriverName, "pci") + ) { + return RETURN_UNSUPPORTED; + } + PciDevFun[1] = 0; + NumEntries = sizeof (PciDevFun) / sizeof (PciDevFun[0]); + if (ParseUnitAddressHexList ( + OfwNode[1].UnitAddress, + PciDevFun, + &NumEntries + ) != RETURN_SUCCESS + ) { + return RETURN_UNSUPPORTED; + } + + if (SubstringEq (OfwNode[1].DriverName, "ide") && + SubstringEq (OfwNode[2].DriverName, "drive") && + SubstringEq (OfwNode[3].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 + // + UINT32 Secondary; + UINT32 Slave; + + NumEntries = 1; + if (ParseUnitAddressHexList ( + OfwNode[2].UnitAddress, + &Secondary, + &NumEntries + ) != RETURN_SUCCESS || + Secondary > 1 || + ParseUnitAddressHexList ( + OfwNode[3].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(0x0)/Pci(0x%x,0x%x)/Ata(%a,%a,0x0)", + PciDevFun[0], + PciDevFun[1], + Secondary ? "Secondary" : "Primary", + Slave ? "Slave" : "Master" + ); + } else if (SubstringEq (OfwNode[1].DriverName, "isa") && + SubstringEq (OfwNode[2].DriverName, "fdc") && + SubstringEq (OfwNode[3].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 + // + UINT32 AcpiUid; + + NumEntries = 1; + if (ParseUnitAddressHexList ( + OfwNode[3].UnitAddress, + &AcpiUid, + &NumEntries + ) != RETURN_SUCCESS || + AcpiUid > 1 + ) { + return RETURN_UNSUPPORTED; + } + + Written = UnicodeSPrintAsciiFormat ( + Translated, + *TranslatedSize * sizeof (*Translated), // BufferSize in bytes + "PciRoot(0x0)/Pci(0x%x,0x%x)/Floppy(0x%x)", + PciDevFun[0], + PciDevFun[1], + AcpiUid + ); + } 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 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[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_NOT_FOUND Translation terminated, *Ptr was (and is) + pointing to an empty string. + + @retval RETURN_INVALID_PARAMETER Parse error. This is a permanent error. + +**/ +STATIC +RETURN_STATUS +TranslateOfwPath ( + IN OUT CONST CHAR8 **Ptr, + OUT CHAR16 *Translated, + IN OUT UINTN *TranslatedSize + ) +{ + UINTN NumNodes; + RETURN_STATUS Status; + OFW_NODE Node[FIXED_OFW_NODES]; + BOOLEAN IsFinal; + OFW_NODE Skip; + + NumNodes = 0; + 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 < FIXED_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 < FIXED_OFW_NODES ? NumNodes : FIXED_OFW_NODES, + 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; + + 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, + IN CONST EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *DevPathToText + ) +{ + CHAR16 *Converted; + BOOLEAN Result; + + Converted = DevPathToText->ConvertDevicePathToText ( + DevicePath, + FALSE, // DisplayOnly + FALSE // AllowShortcuts + ); + if (Converted == NULL) { + return FALSE; + } + + // + // Is Translated a prefix of Converted? + // + Result = (StrnCmp (Converted, Translated, TranslatedLength) == 0); + DEBUG (( + DEBUG_VERBOSE, + "%a: against \"%s\": %a\n", + __FUNCTION__, + Converted, + Result ? "match" : "no match" + )); + FreePool (Converted); + return Result; +} + + +/** + + 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; + + EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *DevPathToText; + + FIRMWARE_CONFIG_ITEM FwCfgItem; + UINTN FwCfgSize; + CHAR8 *FwCfg; + CONST CHAR8 *FwCfgPtr; + + BOOT_ORDER BootOrder; + + UINTN TranslatedSize; + CHAR16 Translated[TRANSLATION_OUTPUT_SIZE]; + + Status = gBS->LocateProtocol ( + &gEfiDevicePathToTextProtocolGuid, + NULL, // optional registration key + (VOID **) &DevPathToText + ); + if (Status != EFI_SUCCESS) { + return Status; + } + + 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: \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; + } + + // + // translate each OpenFirmware path + // + TranslatedSize = sizeof (Translated) / sizeof (Translated[0]); + Status = TranslateOfwPath (&FwCfgPtr, Translated, &TranslatedSize); + while (Status == RETURN_SUCCESS || + Status == RETURN_UNSUPPORTED || + Status == RETURN_BUFFER_TOO_SMALL) { + if (Status == RETURN_SUCCESS) { + CONST LIST_ENTRY *Link; + + // + // match translated OpenFirmware path against all enumerated boot options + // + for (Link = BootOptionList->ForwardLink; Link != BootOptionList; + Link = Link->ForwardLink) { + CONST BDS_COMMON_OPTION *BootOption; + + BootOption = CR ( + Link, + BDS_COMMON_OPTION, + Link, + BDS_LOAD_OPTION_SIGNATURE + ); + if (IS_LOAD_OPTION_TYPE (BootOption->Attribute, LOAD_OPTION_ACTIVE) && + Match ( + Translated, + TranslatedSize, // contains length, not size, in CHAR16's here + BootOption->DevicePath, + DevPathToText + ) + ) { + // + // match found, store ID and continue with next OpenFirmware path + // + Status = BootOrderAppend (&BootOrder, BootOption->BootCurrent); + if (Status != RETURN_SUCCESS) { + goto ErrorFreeBootOrder; + } + break; + } + } // scanned all enumerated boot options + } // translation successful + + TranslatedSize = sizeof (Translated) / sizeof (Translated[0]); + Status = TranslateOfwPath (&FwCfgPtr, 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. + // 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 + ); + DEBUG (( + DEBUG_INFO, + "%a: setting BootOrder: %a\n", + __FUNCTION__, + Status == EFI_SUCCESS ? "success" : "error" + )); + } + +ErrorFreeBootOrder: + FreePool (BootOrder.Data); + +ErrorFreeFwCfg: + FreePool (FwCfg); + + return Status; +} diff --git a/OvmfPkg/Library/PlatformBdsLib/QemuBootOrder.h b/OvmfPkg/Library/PlatformBdsLib/QemuBootOrder.h new file mode 100644 index 0000000000..8d4ca2b111 --- /dev/null +++ b/OvmfPkg/Library/PlatformBdsLib/QemuBootOrder.h @@ -0,0 +1,53 @@ +/** @file + Rewrite the BootOrder NvVar based on QEMU's "bootorder" fw_cfg file -- + include file. + + Copyright (C) 2012, 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 +#include + + +/** + + 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 + );