2 Rewrite the BootOrder NvVar based on QEMU's "bootorder" fw_cfg file.
4 Copyright (C) 2012, Red Hat, Inc.
6 This program and the accompanying materials are licensed and made available
7 under the terms and conditions of the BSD License which accompanies this
8 distribution. The full text of the license may be found at
9 http://opensource.org/licenses/bsd-license.php
11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
12 WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
15 #include <Library/QemuFwCfgLib.h>
16 #include <Library/DebugLib.h>
17 #include <Library/MemoryAllocationLib.h>
18 #include <Library/GenericBdsLib.h>
19 #include <Library/UefiBootServicesTableLib.h>
20 #include <Library/UefiRuntimeServicesTableLib.h>
21 #include <Library/BaseLib.h>
22 #include <Library/PrintLib.h>
23 #include <Protocol/DevicePathToText.h>
24 #include <Guid/GlobalVariable.h>
28 OpenFirmware to UEFI device path translation output buffer size in CHAR16's.
30 #define TRANSLATION_OUTPUT_SIZE 0x100
34 Number of nodes in OpenFirmware device paths that is required and examined.
36 #define FIXED_OFW_NODES 4
40 Simple character classification routines, corresponding to POSIX class names
49 return (('0' <= Chr
&& Chr
<= '9') ||
50 ('A' <= Chr
&& Chr
<= 'Z') ||
51 ('a' <= Chr
&& Chr
<= 'z')
62 return (Chr
== ',' || Chr
== '.' || Chr
== '_' ||
63 Chr
== '+' || Chr
== '-'
74 return (32 <= Chr
&& Chr
<= 126 &&
75 Chr
!= '/' && Chr
!= '@' && Chr
!= ':');
80 Utility types and functions.
83 CONST CHAR8
*Ptr
; // not necessarily NUL-terminated
84 UINTN Len
; // number of non-NUL characters
90 Check if Substring and String have identical contents.
92 The function relies on the restriction that a SUBSTRING cannot have embedded
95 @param[in] Substring The SUBSTRING input to the comparison.
97 @param[in] String The ASCII string input to the comparison.
100 @return Whether the inputs have identical contents.
106 IN SUBSTRING Substring
,
107 IN CONST CHAR8
*String
116 while (Pos
< Substring
.Len
&& Substring
.Ptr
[Pos
] == *Chr
) {
121 return (Pos
== Substring
.Len
&& *Chr
== '\0');
127 Parse a comma-separated list of hexadecimal integers into the elements of an
130 Whitespace, "0x" prefixes, leading or trailing commas, sequences of commas,
131 or an empty string are not allowed; they are rejected.
133 The function relies on ASCII encoding.
135 @param[in] UnitAddress The substring to parse.
137 @param[out] Result The array, allocated by the caller, to receive
138 the parsed values. This parameter may be NULL if
139 NumResults is zero on input.
141 @param[in out] NumResults On input, the number of elements allocated for
142 Result. On output, the number of elements it has
143 taken (or would have taken) to parse the string
147 @retval RETURN_SUCCESS UnitAddress has been fully parsed.
148 NumResults is set to the number of parsed
149 values; the corresponding elements have
150 been set in Result. The rest of Result's
151 elements are unchanged.
153 @retval RETURN_BUFFER_TOO_SMALL UnitAddress has been fully parsed.
154 NumResults is set to the number of parsed
155 values, but elements have been stored only
156 up to the input value of NumResults, which
157 is less than what has been parsed.
159 @retval RETURN_INVALID_PARAMETER Parse error. The contents of Results is
160 indeterminate. NumResults has not been
166 ParseUnitAddressHexList (
167 IN SUBSTRING UnitAddress
,
169 IN OUT UINTN
*NumResults
172 UINTN Entry
; // number of entry currently being parsed
173 UINT32 EntryVal
; // value being constructed for current entry
174 CHAR8 PrevChr
; // UnitAddress character previously checked
175 UINTN Pos
; // current position within UnitAddress
176 RETURN_STATUS Status
;
182 for (Pos
= 0; Pos
< UnitAddress
.Len
; ++Pos
) {
186 Chr
= UnitAddress
.Ptr
[Pos
];
187 Val
= ('a' <= Chr
&& Chr
<= 'f') ? (Chr
- 'a' + 10) :
188 ('A' <= Chr
&& Chr
<= 'F') ? (Chr
- 'A' + 10) :
189 ('0' <= Chr
&& Chr
<= '9') ? (Chr
- '0' ) :
193 if (EntryVal
> 0xFFFFFFF) {
194 return RETURN_INVALID_PARAMETER
;
196 EntryVal
= (EntryVal
<< 4) | Val
;
197 } else if (Chr
== ',') {
198 if (PrevChr
== ',') {
199 return RETURN_INVALID_PARAMETER
;
201 if (Entry
< *NumResults
) {
202 Result
[Entry
] = EntryVal
;
207 return RETURN_INVALID_PARAMETER
;
213 if (PrevChr
== ',') {
214 return RETURN_INVALID_PARAMETER
;
216 if (Entry
< *NumResults
) {
217 Result
[Entry
] = EntryVal
;
218 Status
= RETURN_SUCCESS
;
220 Status
= RETURN_BUFFER_TOO_SMALL
;
230 A simple array of Boot Option ID's.
241 Append BootOptionId to BootOrder, reallocating the latter if needed.
243 @param[in out] BootOrder The structure pointing to the array and holding
244 allocation and usage counters.
246 @param[in] BootOptionId The value to append to the array.
249 @retval RETURN_SUCCESS BootOptionId appended.
251 @retval RETURN_OUT_OF_RESOURCES Memory reallocation failed.
257 IN OUT BOOT_ORDER
*BootOrder
,
258 IN UINT16 BootOptionId
261 if (BootOrder
->Produced
== BootOrder
->Allocated
) {
265 ASSERT (BootOrder
->Allocated
> 0);
266 AllocatedNew
= BootOrder
->Allocated
* 2;
267 DataNew
= ReallocatePool (
268 BootOrder
->Allocated
* sizeof (*BootOrder
->Data
),
269 AllocatedNew
* sizeof (*DataNew
),
272 if (DataNew
== NULL
) {
273 return RETURN_OUT_OF_RESOURCES
;
275 BootOrder
->Allocated
= AllocatedNew
;
276 BootOrder
->Data
= DataNew
;
279 BootOrder
->Data
[BootOrder
->Produced
++] = BootOptionId
;
280 return RETURN_SUCCESS
;
285 OpenFirmware device path node
288 SUBSTRING DriverName
;
289 SUBSTRING UnitAddress
;
290 SUBSTRING DeviceArguments
;
296 Parse an OpenFirmware device path node into the caller-allocated OFW_NODE
297 structure, and advance in the input string.
299 The node format is mostly parsed after IEEE 1275-1994, 3.2.1.1 "Node names"
300 (a leading slash is expected and not returned):
302 /driver-name@unit-address[:device-arguments][<LF>]
304 A single trailing <LF> character is consumed but not returned. A trailing
305 <LF> or NUL character terminates the device path.
307 The function relies on ASCII encoding.
309 @param[in out] Ptr Address of the pointer pointing to the start of the
310 node string. After successful parsing *Ptr is set to
311 the byte immediately following the consumed
312 characters. On error it points to the byte that
313 caused the error. The input string is never modified.
315 @param[out] OfwNode The members of this structure point into the input
316 string, designating components of the node.
317 Separators are never included. If "device-arguments"
318 is missing, then DeviceArguments.Ptr is set to NULL.
319 All components that are present have nonzero length.
321 If the call doesn't succeed, the contents of this
322 structure is indeterminate.
324 @param[out] IsFinal In case of successul parsing, this parameter signals
325 whether the node just parsed is the final node in the
326 device path. The call after a final node will attempt
327 to start parsing the next path. If the call doesn't
328 succeed, then this parameter is not changed.
331 @retval RETURN_SUCCESS Parsing successful.
333 @retval RETURN_NOT_FOUND Parsing terminated. *Ptr was (and is)
334 pointing to an empty string.
336 @retval RETURN_INVALID_PARAMETER Parse error.
342 IN OUT CONST CHAR8
**Ptr
,
343 OUT OFW_NODE
*OfwNode
,
348 // A leading slash is expected. End of string is tolerated.
352 return RETURN_NOT_FOUND
;
359 return RETURN_INVALID_PARAMETER
;
365 OfwNode
->DriverName
.Ptr
= *Ptr
;
366 OfwNode
->DriverName
.Len
= 0;
367 while (OfwNode
->DriverName
.Len
< 32 &&
368 (IsAlnum (**Ptr
) || IsDriverNamePunct (**Ptr
))
371 ++OfwNode
->DriverName
.Len
;
374 if (OfwNode
->DriverName
.Len
== 0 || OfwNode
->DriverName
.Len
== 32) {
375 return RETURN_INVALID_PARAMETER
;
383 return RETURN_INVALID_PARAMETER
;
387 OfwNode
->UnitAddress
.Ptr
= *Ptr
;
388 OfwNode
->UnitAddress
.Len
= 0;
389 while (IsPrintNotDelim (**Ptr
)) {
391 ++OfwNode
->UnitAddress
.Len
;
394 if (OfwNode
->UnitAddress
.Len
== 0) {
395 return RETURN_INVALID_PARAMETER
;
400 // device-arguments, may be omitted
402 OfwNode
->DeviceArguments
.Len
= 0;
405 OfwNode
->DeviceArguments
.Ptr
= *Ptr
;
407 while (IsPrintNotDelim (**Ptr
)) {
409 ++OfwNode
->DeviceArguments
.Len
;
412 if (OfwNode
->DeviceArguments
.Len
== 0) {
413 return RETURN_INVALID_PARAMETER
;
417 OfwNode
->DeviceArguments
.Ptr
= NULL
;
436 return RETURN_INVALID_PARAMETER
;
441 "%a: DriverName=\"%.*a\" UnitAddress=\"%.*a\" DeviceArguments=\"%.*a\"\n",
443 OfwNode
->DriverName
.Len
, OfwNode
->DriverName
.Ptr
,
444 OfwNode
->UnitAddress
.Len
, OfwNode
->UnitAddress
.Ptr
,
445 OfwNode
->DeviceArguments
.Len
,
446 OfwNode
->DeviceArguments
.Ptr
== NULL
? "" : OfwNode
->DeviceArguments
.Ptr
448 return RETURN_SUCCESS
;
454 Translate an array of OpenFirmware device nodes to a UEFI device path
457 @param[in] OfwNode Array of OpenFirmware device nodes to
458 translate, constituting the beginning of an
459 OpenFirmware device path.
461 @param[in] NumNodes Number of elements in OfwNode.
463 @param[out] Translated Destination array receiving the UEFI path
464 fragment, allocated by the caller. If the
465 return value differs from RETURN_SUCCESS, its
466 contents is indeterminate.
468 @param[in out] TranslatedSize On input, the number of CHAR16's in
469 Translated. On RETURN_SUCCESS this parameter
470 is assigned the number of non-NUL CHAR16's
471 written to Translated. In case of other return
472 values, TranslatedSize is indeterminate.
475 @retval RETURN_SUCCESS Translation successful.
477 @retval RETURN_BUFFER_TOO_SMALL The translation does not fit into the number
480 @retval RETURN_UNSUPPORTED The array of OpenFirmware device nodes can't
481 be translated in the current implementation.
487 IN CONST OFW_NODE
*OfwNode
,
489 OUT CHAR16
*Translated
,
490 IN OUT UINTN
*TranslatedSize
498 // Get PCI device and optional PCI function. Assume a single PCI root.
500 if (NumNodes
< FIXED_OFW_NODES
||
501 !SubstringEq (OfwNode
[0].DriverName
, "pci")
503 return RETURN_UNSUPPORTED
;
506 NumEntries
= sizeof (PciDevFun
) / sizeof (PciDevFun
[0]);
507 if (ParseUnitAddressHexList (
508 OfwNode
[1].UnitAddress
,
513 return RETURN_UNSUPPORTED
;
516 if (SubstringEq (OfwNode
[1].DriverName
, "ide") &&
517 SubstringEq (OfwNode
[2].DriverName
, "drive") &&
518 SubstringEq (OfwNode
[3].DriverName
, "disk")
521 // OpenFirmware device path (IDE disk, IDE CD-ROM):
523 // /pci@i0cf8/ide@1,1/drive@0/disk@0
525 // | | | | master or slave
526 // | | | primary or secondary
527 // | PCI slot & function holding IDE controller
528 // PCI root at system bus port, PIO
532 // PciRoot(0x0)/Pci(0x1,0x1)/Ata(Primary,Master,0x0)
540 if (ParseUnitAddressHexList (
541 OfwNode
[2].UnitAddress
,
544 ) != RETURN_SUCCESS
||
546 ParseUnitAddressHexList (
547 OfwNode
[3].UnitAddress
,
549 &NumEntries
// reuse after previous single-element call
550 ) != RETURN_SUCCESS
||
553 return RETURN_UNSUPPORTED
;
556 Written
= UnicodeSPrintAsciiFormat (
558 *TranslatedSize
* sizeof (*Translated
), // BufferSize in bytes
559 "PciRoot(0x0)/Pci(0x%x,0x%x)/Ata(%a,%a,0x0)",
562 Secondary
? "Secondary" : "Primary",
563 Slave
? "Slave" : "Master"
565 } else if (SubstringEq (OfwNode
[1].DriverName
, "isa") &&
566 SubstringEq (OfwNode
[2].DriverName
, "fdc") &&
567 SubstringEq (OfwNode
[3].DriverName
, "floppy")
570 // OpenFirmware device path (floppy disk):
572 // /pci@i0cf8/isa@1/fdc@03f0/floppy@0
575 // | | ISA controller io-port (hex)
576 // | PCI slot holding ISA controller
577 // PCI root at system bus port, PIO
581 // PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x0)
588 if (ParseUnitAddressHexList (
589 OfwNode
[3].UnitAddress
,
592 ) != RETURN_SUCCESS
||
595 return RETURN_UNSUPPORTED
;
598 Written
= UnicodeSPrintAsciiFormat (
600 *TranslatedSize
* sizeof (*Translated
), // BufferSize in bytes
601 "PciRoot(0x0)/Pci(0x%x,0x%x)/Floppy(0x%x)",
607 return RETURN_UNSUPPORTED
;
611 // There's no way to differentiate between "completely used up without
612 // truncation" and "truncated", so treat the former as the latter, and return
613 // success only for "some room left unused".
615 if (Written
+ 1 < *TranslatedSize
) {
616 *TranslatedSize
= Written
;
617 return RETURN_SUCCESS
;
620 return RETURN_BUFFER_TOO_SMALL
;
626 Translate an OpenFirmware device path fragment to a UEFI device path
627 fragment, and advance in the input string.
629 @param[in out] Ptr Address of the pointer pointing to the start
630 of the path string. After successful
631 translation (RETURN_SUCCESS) or at least
632 successful parsing (RETURN_UNSUPPORTED,
633 RETURN_BUFFER_TOO_SMALL), *Ptr is set to the
634 byte immediately following the consumed
635 characters. In other error cases, it points to
636 the byte that caused the error.
638 @param[out] Translated Destination array receiving the UEFI path
639 fragment, allocated by the caller. If the
640 return value differs from RETURN_SUCCESS, its
641 contents is indeterminate.
643 @param[in out] TranslatedSize On input, the number of CHAR16's in
644 Translated. On RETURN_SUCCESS this parameter
645 is assigned the number of non-NUL CHAR16's
646 written to Translated. In case of other return
647 values, TranslatedSize is indeterminate.
650 @retval RETURN_SUCCESS Translation successful.
652 @retval RETURN_BUFFER_TOO_SMALL The OpenFirmware device path was parsed
653 successfully, but its translation did not
654 fit into the number of bytes provided.
655 Further calls to this function are
658 @retval RETURN_UNSUPPORTED The OpenFirmware device path was parsed
659 successfully, but it can't be translated in
660 the current implementation. Further calls
661 to this function are possible.
663 @retval RETURN_NOT_FOUND Translation terminated, *Ptr was (and is)
664 pointing to an empty string.
666 @retval RETURN_INVALID_PARAMETER Parse error. This is a permanent error.
672 IN OUT CONST CHAR8
**Ptr
,
673 OUT CHAR16
*Translated
,
674 IN OUT UINTN
*TranslatedSize
678 RETURN_STATUS Status
;
679 OFW_NODE Node
[FIXED_OFW_NODES
];
684 Status
= ParseOfwNode (Ptr
, &Node
[NumNodes
], &IsFinal
);
686 if (Status
== RETURN_NOT_FOUND
) {
687 DEBUG ((DEBUG_VERBOSE
, "%a: no more nodes\n", __FUNCTION__
));
688 return RETURN_NOT_FOUND
;
691 while (Status
== RETURN_SUCCESS
&& !IsFinal
) {
693 Status
= ParseOfwNode (
695 (NumNodes
< FIXED_OFW_NODES
) ? &Node
[NumNodes
] : &Skip
,
705 case RETURN_INVALID_PARAMETER
:
706 DEBUG ((DEBUG_VERBOSE
, "%a: parse error\n", __FUNCTION__
));
707 return RETURN_INVALID_PARAMETER
;
713 Status
= TranslateOfwNodes (
715 NumNodes
< FIXED_OFW_NODES
? NumNodes
: FIXED_OFW_NODES
,
720 DEBUG ((DEBUG_VERBOSE
, "%a: success: \"%s\"\n", __FUNCTION__
, Translated
));
723 case RETURN_BUFFER_TOO_SMALL
:
724 DEBUG ((DEBUG_VERBOSE
, "%a: buffer too small\n", __FUNCTION__
));
727 case RETURN_UNSUPPORTED
:
728 DEBUG ((DEBUG_VERBOSE
, "%a: unsupported\n", __FUNCTION__
));
740 Convert the UEFI DevicePath to full text representation with DevPathToText,
741 then match the UEFI device path fragment in Translated against it.
743 @param[in] Translated UEFI device path fragment, translated from
744 OpenFirmware format, to search for.
746 @param[in] TranslatedLength The length of Translated in CHAR16's.
748 @param[in] DevicePath Boot option device path whose textual rendering
751 @param[in] DevPathToText Binary-to-text conversion protocol for DevicePath.
754 @retval TRUE If Translated was found at the beginning of DevicePath after
755 converting the latter to text.
757 @retval FALSE If DevicePath was NULL, or it could not be converted, or there
764 IN CONST CHAR16
*Translated
,
765 IN UINTN TranslatedLength
,
766 IN CONST EFI_DEVICE_PATH_PROTOCOL
*DevicePath
,
767 IN CONST EFI_DEVICE_PATH_TO_TEXT_PROTOCOL
*DevPathToText
773 Converted
= DevPathToText
->ConvertDevicePathToText (
775 FALSE
, // DisplayOnly
776 FALSE
// AllowShortcuts
778 if (Converted
== NULL
) {
783 // Is Translated a prefix of Converted?
785 Result
= (StrnCmp (Converted
, Translated
, TranslatedLength
) == 0);
788 "%a: against \"%s\": %a\n",
791 Result
? "match" : "no match"
793 FreePool (Converted
);
800 Set the boot order based on configuration retrieved from QEMU.
802 Attempt to retrieve the "bootorder" fw_cfg file from QEMU. Translate the
803 OpenFirmware device paths therein to UEFI device path fragments. Match the
804 translated fragments against BootOptionList, and rewrite the BootOrder NvVar
805 so that it corresponds to the order described in fw_cfg.
807 @param[in] BootOptionList A boot option list, created with
808 BdsLibEnumerateAllBootOption ().
811 @retval RETURN_SUCCESS BootOrder NvVar rewritten.
813 @retval RETURN_UNSUPPORTED QEMU's fw_cfg is not supported.
815 @retval RETURN_NOT_FOUND Empty or nonexistent "bootorder" fw_cfg
816 file, or no match found between the
817 "bootorder" fw_cfg file and BootOptionList.
819 @retval RETURN_INVALID_PARAMETER Parse error in the "bootorder" fw_cfg file.
821 @retval RETURN_OUT_OF_RESOURCES Memory allocation failed.
823 @return Values returned by gBS->LocateProtocol ()
824 or gRT->SetVariable ().
828 SetBootOrderFromQemu (
829 IN CONST LIST_ENTRY
*BootOptionList
832 RETURN_STATUS Status
;
834 EFI_DEVICE_PATH_TO_TEXT_PROTOCOL
*DevPathToText
;
836 FIRMWARE_CONFIG_ITEM FwCfgItem
;
839 CONST CHAR8
*FwCfgPtr
;
841 BOOT_ORDER BootOrder
;
843 UINTN TranslatedSize
;
844 CHAR16 Translated
[TRANSLATION_OUTPUT_SIZE
];
846 Status
= gBS
->LocateProtocol (
847 &gEfiDevicePathToTextProtocolGuid
,
848 NULL
, // optional registration key
849 (VOID
**) &DevPathToText
851 if (Status
!= EFI_SUCCESS
) {
855 Status
= QemuFwCfgFindFile ("bootorder", &FwCfgItem
, &FwCfgSize
);
856 if (Status
!= RETURN_SUCCESS
) {
860 if (FwCfgSize
== 0) {
861 return RETURN_NOT_FOUND
;
864 FwCfg
= AllocatePool (FwCfgSize
);
866 return RETURN_OUT_OF_RESOURCES
;
869 QemuFwCfgSelectItem (FwCfgItem
);
870 QemuFwCfgReadBytes (FwCfgSize
, FwCfg
);
871 if (FwCfg
[FwCfgSize
- 1] != '\0') {
872 Status
= RETURN_INVALID_PARAMETER
;
876 DEBUG ((DEBUG_VERBOSE
, "%a: FwCfg:\n", __FUNCTION__
));
877 DEBUG ((DEBUG_VERBOSE
, "%a\n", FwCfg
));
878 DEBUG ((DEBUG_VERBOSE
, "%a: FwCfg: <end>\n", __FUNCTION__
));
881 BootOrder
.Produced
= 0;
882 BootOrder
.Allocated
= 1;
883 BootOrder
.Data
= AllocatePool (
884 BootOrder
.Allocated
* sizeof (*BootOrder
.Data
)
886 if (BootOrder
.Data
== NULL
) {
887 Status
= RETURN_OUT_OF_RESOURCES
;
892 // translate each OpenFirmware path
894 TranslatedSize
= sizeof (Translated
) / sizeof (Translated
[0]);
895 Status
= TranslateOfwPath (&FwCfgPtr
, Translated
, &TranslatedSize
);
896 while (Status
== RETURN_SUCCESS
||
897 Status
== RETURN_UNSUPPORTED
||
898 Status
== RETURN_BUFFER_TOO_SMALL
) {
899 if (Status
== RETURN_SUCCESS
) {
900 CONST LIST_ENTRY
*Link
;
903 // match translated OpenFirmware path against all enumerated boot options
905 for (Link
= BootOptionList
->ForwardLink
; Link
!= BootOptionList
;
906 Link
= Link
->ForwardLink
) {
907 CONST BDS_COMMON_OPTION
*BootOption
;
913 BDS_LOAD_OPTION_SIGNATURE
915 if (IS_LOAD_OPTION_TYPE (BootOption
->Attribute
, LOAD_OPTION_ACTIVE
) &&
918 TranslatedSize
, // contains length, not size, in CHAR16's here
919 BootOption
->DevicePath
,
924 // match found, store ID and continue with next OpenFirmware path
926 Status
= BootOrderAppend (&BootOrder
, BootOption
->BootCurrent
);
927 if (Status
!= RETURN_SUCCESS
) {
928 goto ErrorFreeBootOrder
;
932 } // scanned all enumerated boot options
933 } // translation successful
935 TranslatedSize
= sizeof (Translated
) / sizeof (Translated
[0]);
936 Status
= TranslateOfwPath (&FwCfgPtr
, Translated
, &TranslatedSize
);
937 } // scanning of OpenFirmware paths done
939 if (Status
== RETURN_NOT_FOUND
&& BootOrder
.Produced
> 0) {
941 // No more OpenFirmware paths, some matches found: rewrite BootOrder NvVar.
942 // See Table 10 in the UEFI Spec 2.3.1 with Errata C for the required
945 Status
= gRT
->SetVariable (
947 &gEfiGlobalVariableGuid
,
948 EFI_VARIABLE_NON_VOLATILE
|
949 EFI_VARIABLE_BOOTSERVICE_ACCESS
|
950 EFI_VARIABLE_RUNTIME_ACCESS
,
951 BootOrder
.Produced
* sizeof (*BootOrder
.Data
),
956 "%a: setting BootOrder: %a\n",
958 Status
== EFI_SUCCESS
? "success" : "error"
963 FreePool (BootOrder
.Data
);