]> git.proxmox.com Git - mirror_edk2.git/blame - OvmfPkg/Library/PlatformBdsLib/QemuBootOrder.c
OvmfPkg: introduce virtio-scsi driver
[mirror_edk2.git] / OvmfPkg / Library / PlatformBdsLib / QemuBootOrder.c
CommitLineData
2cd086a6 1/** @file\r
2 Rewrite the BootOrder NvVar based on QEMU's "bootorder" fw_cfg file.\r
3\r
4 Copyright (C) 2012, Red Hat, Inc.\r
5\r
6 This program and the accompanying materials are licensed and made available\r
7 under the terms and conditions of the BSD License which accompanies this\r
8 distribution. The full text of the license may be found at\r
9 http://opensource.org/licenses/bsd-license.php\r
10\r
11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT\r
12 WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
13**/\r
14\r
15#include <Library/QemuFwCfgLib.h>\r
16#include <Library/DebugLib.h>\r
17#include <Library/MemoryAllocationLib.h>\r
18#include <Library/GenericBdsLib.h>\r
19#include <Library/UefiBootServicesTableLib.h>\r
20#include <Library/UefiRuntimeServicesTableLib.h>\r
21#include <Library/BaseLib.h>\r
22#include <Library/PrintLib.h>\r
23#include <Protocol/DevicePathToText.h>\r
24#include <Guid/GlobalVariable.h>\r
25\r
26\r
27/**\r
28 OpenFirmware to UEFI device path translation output buffer size in CHAR16's.\r
29**/\r
30#define TRANSLATION_OUTPUT_SIZE 0x100\r
31\r
32\r
33/**\r
ec01afdb 34 Numbers of nodes in OpenFirmware device paths that are required and examined.\r
2cd086a6 35**/\r
ec01afdb 36#define REQUIRED_OFW_NODES 2\r
37#define EXAMINED_OFW_NODES 4\r
2cd086a6 38\r
39\r
40/**\r
41 Simple character classification routines, corresponding to POSIX class names\r
42 and ASCII encoding.\r
43**/\r
44STATIC\r
45BOOLEAN\r
46IsAlnum (\r
47 IN CHAR8 Chr\r
48 )\r
49{\r
50 return (('0' <= Chr && Chr <= '9') ||\r
51 ('A' <= Chr && Chr <= 'Z') ||\r
52 ('a' <= Chr && Chr <= 'z')\r
53 );\r
54}\r
55\r
56\r
57STATIC\r
58BOOLEAN\r
59IsDriverNamePunct (\r
60 IN CHAR8 Chr\r
61 )\r
62{\r
63 return (Chr == ',' || Chr == '.' || Chr == '_' ||\r
64 Chr == '+' || Chr == '-'\r
65 );\r
66}\r
67\r
68\r
69STATIC\r
70BOOLEAN\r
71IsPrintNotDelim (\r
72 IN CHAR8 Chr\r
73 )\r
74{\r
75 return (32 <= Chr && Chr <= 126 &&\r
76 Chr != '/' && Chr != '@' && Chr != ':');\r
77}\r
78\r
79\r
80/**\r
81 Utility types and functions.\r
82**/\r
83typedef struct {\r
84 CONST CHAR8 *Ptr; // not necessarily NUL-terminated\r
85 UINTN Len; // number of non-NUL characters\r
86} SUBSTRING;\r
87\r
88\r
89/**\r
90\r
91 Check if Substring and String have identical contents.\r
92\r
93 The function relies on the restriction that a SUBSTRING cannot have embedded\r
94 NULs either.\r
95\r
96 @param[in] Substring The SUBSTRING input to the comparison.\r
97\r
98 @param[in] String The ASCII string input to the comparison.\r
99\r
100\r
101 @return Whether the inputs have identical contents.\r
102\r
103**/\r
104STATIC\r
105BOOLEAN\r
106SubstringEq (\r
107 IN SUBSTRING Substring,\r
108 IN CONST CHAR8 *String\r
109 )\r
110{\r
111 UINTN Pos;\r
112 CONST CHAR8 *Chr;\r
113\r
114 Pos = 0;\r
115 Chr = String;\r
116\r
117 while (Pos < Substring.Len && Substring.Ptr[Pos] == *Chr) {\r
118 ++Pos;\r
119 ++Chr;\r
120 }\r
121\r
dccf7678 122 return (BOOLEAN)(Pos == Substring.Len && *Chr == '\0');\r
2cd086a6 123}\r
124\r
125\r
126/**\r
127\r
128 Parse a comma-separated list of hexadecimal integers into the elements of an\r
129 UINT32 array.\r
130\r
131 Whitespace, "0x" prefixes, leading or trailing commas, sequences of commas,\r
132 or an empty string are not allowed; they are rejected.\r
133\r
134 The function relies on ASCII encoding.\r
135\r
136 @param[in] UnitAddress The substring to parse.\r
137\r
138 @param[out] Result The array, allocated by the caller, to receive\r
139 the parsed values. This parameter may be NULL if\r
140 NumResults is zero on input.\r
141\r
142 @param[in out] NumResults On input, the number of elements allocated for\r
143 Result. On output, the number of elements it has\r
144 taken (or would have taken) to parse the string\r
145 fully.\r
146\r
147\r
148 @retval RETURN_SUCCESS UnitAddress has been fully parsed.\r
149 NumResults is set to the number of parsed\r
150 values; the corresponding elements have\r
151 been set in Result. The rest of Result's\r
152 elements are unchanged.\r
153\r
154 @retval RETURN_BUFFER_TOO_SMALL UnitAddress has been fully parsed.\r
155 NumResults is set to the number of parsed\r
156 values, but elements have been stored only\r
157 up to the input value of NumResults, which\r
158 is less than what has been parsed.\r
159\r
160 @retval RETURN_INVALID_PARAMETER Parse error. The contents of Results is\r
161 indeterminate. NumResults has not been\r
162 changed.\r
163\r
164**/\r
165STATIC\r
166RETURN_STATUS\r
167ParseUnitAddressHexList (\r
168 IN SUBSTRING UnitAddress,\r
169 OUT UINT32 *Result,\r
170 IN OUT UINTN *NumResults\r
171 )\r
172{\r
173 UINTN Entry; // number of entry currently being parsed\r
174 UINT32 EntryVal; // value being constructed for current entry\r
175 CHAR8 PrevChr; // UnitAddress character previously checked\r
176 UINTN Pos; // current position within UnitAddress\r
177 RETURN_STATUS Status;\r
178\r
179 Entry = 0;\r
180 EntryVal = 0;\r
181 PrevChr = ',';\r
182\r
183 for (Pos = 0; Pos < UnitAddress.Len; ++Pos) {\r
184 CHAR8 Chr;\r
185 INT8 Val;\r
186\r
187 Chr = UnitAddress.Ptr[Pos];\r
188 Val = ('a' <= Chr && Chr <= 'f') ? (Chr - 'a' + 10) :\r
189 ('A' <= Chr && Chr <= 'F') ? (Chr - 'A' + 10) :\r
190 ('0' <= Chr && Chr <= '9') ? (Chr - '0' ) :\r
191 -1;\r
192\r
193 if (Val >= 0) {\r
194 if (EntryVal > 0xFFFFFFF) {\r
195 return RETURN_INVALID_PARAMETER;\r
196 }\r
197 EntryVal = (EntryVal << 4) | Val;\r
198 } else if (Chr == ',') {\r
199 if (PrevChr == ',') {\r
200 return RETURN_INVALID_PARAMETER;\r
201 }\r
202 if (Entry < *NumResults) {\r
203 Result[Entry] = EntryVal;\r
204 }\r
205 ++Entry;\r
206 EntryVal = 0;\r
207 } else {\r
208 return RETURN_INVALID_PARAMETER;\r
209 }\r
210\r
211 PrevChr = Chr;\r
212 }\r
213\r
214 if (PrevChr == ',') {\r
215 return RETURN_INVALID_PARAMETER;\r
216 }\r
217 if (Entry < *NumResults) {\r
218 Result[Entry] = EntryVal;\r
219 Status = RETURN_SUCCESS;\r
220 } else {\r
221 Status = RETURN_BUFFER_TOO_SMALL;\r
222 }\r
223 ++Entry;\r
224\r
225 *NumResults = Entry;\r
226 return Status;\r
227}\r
228\r
229\r
230/**\r
231 A simple array of Boot Option ID's.\r
232**/\r
233typedef struct {\r
234 UINT16 *Data;\r
235 UINTN Allocated;\r
236 UINTN Produced;\r
237} BOOT_ORDER;\r
238\r
239\r
240/**\r
241\r
242 Append BootOptionId to BootOrder, reallocating the latter if needed.\r
243\r
244 @param[in out] BootOrder The structure pointing to the array and holding\r
245 allocation and usage counters.\r
246\r
247 @param[in] BootOptionId The value to append to the array.\r
248\r
249\r
250 @retval RETURN_SUCCESS BootOptionId appended.\r
251\r
252 @retval RETURN_OUT_OF_RESOURCES Memory reallocation failed.\r
253\r
254**/\r
255STATIC\r
256RETURN_STATUS\r
257BootOrderAppend (\r
258 IN OUT BOOT_ORDER *BootOrder,\r
259 IN UINT16 BootOptionId\r
260 )\r
261{\r
262 if (BootOrder->Produced == BootOrder->Allocated) {\r
263 UINTN AllocatedNew;\r
264 UINT16 *DataNew;\r
265\r
266 ASSERT (BootOrder->Allocated > 0);\r
267 AllocatedNew = BootOrder->Allocated * 2;\r
268 DataNew = ReallocatePool (\r
269 BootOrder->Allocated * sizeof (*BootOrder->Data),\r
270 AllocatedNew * sizeof (*DataNew),\r
271 BootOrder->Data\r
272 );\r
273 if (DataNew == NULL) {\r
274 return RETURN_OUT_OF_RESOURCES;\r
275 }\r
276 BootOrder->Allocated = AllocatedNew;\r
277 BootOrder->Data = DataNew;\r
278 }\r
279\r
280 BootOrder->Data[BootOrder->Produced++] = BootOptionId;\r
281 return RETURN_SUCCESS;\r
282}\r
283\r
284\r
285/**\r
286 OpenFirmware device path node\r
287**/\r
288typedef struct {\r
289 SUBSTRING DriverName;\r
290 SUBSTRING UnitAddress;\r
291 SUBSTRING DeviceArguments;\r
292} OFW_NODE;\r
293\r
294\r
295/**\r
296\r
297 Parse an OpenFirmware device path node into the caller-allocated OFW_NODE\r
298 structure, and advance in the input string.\r
299\r
300 The node format is mostly parsed after IEEE 1275-1994, 3.2.1.1 "Node names"\r
301 (a leading slash is expected and not returned):\r
302\r
303 /driver-name@unit-address[:device-arguments][<LF>]\r
304\r
305 A single trailing <LF> character is consumed but not returned. A trailing\r
306 <LF> or NUL character terminates the device path.\r
307\r
308 The function relies on ASCII encoding.\r
309\r
310 @param[in out] Ptr Address of the pointer pointing to the start of the\r
311 node string. After successful parsing *Ptr is set to\r
312 the byte immediately following the consumed\r
313 characters. On error it points to the byte that\r
314 caused the error. The input string is never modified.\r
315\r
316 @param[out] OfwNode The members of this structure point into the input\r
317 string, designating components of the node.\r
318 Separators are never included. If "device-arguments"\r
319 is missing, then DeviceArguments.Ptr is set to NULL.\r
320 All components that are present have nonzero length.\r
321\r
322 If the call doesn't succeed, the contents of this\r
323 structure is indeterminate.\r
324\r
325 @param[out] IsFinal In case of successul parsing, this parameter signals\r
326 whether the node just parsed is the final node in the\r
327 device path. The call after a final node will attempt\r
328 to start parsing the next path. If the call doesn't\r
329 succeed, then this parameter is not changed.\r
330\r
331\r
332 @retval RETURN_SUCCESS Parsing successful.\r
333\r
334 @retval RETURN_NOT_FOUND Parsing terminated. *Ptr was (and is)\r
335 pointing to an empty string.\r
336\r
337 @retval RETURN_INVALID_PARAMETER Parse error.\r
338\r
339**/\r
340STATIC\r
341RETURN_STATUS\r
342ParseOfwNode (\r
343 IN OUT CONST CHAR8 **Ptr,\r
344 OUT OFW_NODE *OfwNode,\r
345 OUT BOOLEAN *IsFinal\r
346 )\r
347{\r
348 //\r
349 // A leading slash is expected. End of string is tolerated.\r
350 //\r
351 switch (**Ptr) {\r
352 case '\0':\r
353 return RETURN_NOT_FOUND;\r
354\r
355 case '/':\r
356 ++*Ptr;\r
357 break;\r
358\r
359 default:\r
360 return RETURN_INVALID_PARAMETER;\r
361 }\r
362\r
363 //\r
364 // driver-name\r
365 //\r
366 OfwNode->DriverName.Ptr = *Ptr;\r
367 OfwNode->DriverName.Len = 0;\r
368 while (OfwNode->DriverName.Len < 32 &&\r
369 (IsAlnum (**Ptr) || IsDriverNamePunct (**Ptr))\r
370 ) {\r
371 ++*Ptr;\r
372 ++OfwNode->DriverName.Len;\r
373 }\r
374\r
375 if (OfwNode->DriverName.Len == 0 || OfwNode->DriverName.Len == 32) {\r
376 return RETURN_INVALID_PARAMETER;\r
377 }\r
378\r
379\r
380 //\r
381 // unit-address\r
382 //\r
383 if (**Ptr != '@') {\r
384 return RETURN_INVALID_PARAMETER;\r
385 }\r
386 ++*Ptr;\r
387\r
388 OfwNode->UnitAddress.Ptr = *Ptr;\r
389 OfwNode->UnitAddress.Len = 0;\r
390 while (IsPrintNotDelim (**Ptr)) {\r
391 ++*Ptr;\r
392 ++OfwNode->UnitAddress.Len;\r
393 }\r
394\r
395 if (OfwNode->UnitAddress.Len == 0) {\r
396 return RETURN_INVALID_PARAMETER;\r
397 }\r
398\r
399\r
400 //\r
401 // device-arguments, may be omitted\r
402 //\r
403 OfwNode->DeviceArguments.Len = 0;\r
404 if (**Ptr == ':') {\r
405 ++*Ptr;\r
406 OfwNode->DeviceArguments.Ptr = *Ptr;\r
407\r
408 while (IsPrintNotDelim (**Ptr)) {\r
409 ++*Ptr;\r
410 ++OfwNode->DeviceArguments.Len;\r
411 }\r
412\r
413 if (OfwNode->DeviceArguments.Len == 0) {\r
414 return RETURN_INVALID_PARAMETER;\r
415 }\r
416 }\r
417 else {\r
418 OfwNode->DeviceArguments.Ptr = NULL;\r
419 }\r
420\r
421 switch (**Ptr) {\r
422 case '\n':\r
423 ++*Ptr;\r
424 //\r
425 // fall through\r
426 //\r
427\r
428 case '\0':\r
429 *IsFinal = TRUE;\r
430 break;\r
431\r
432 case '/':\r
433 *IsFinal = FALSE;\r
434 break;\r
435\r
436 default:\r
437 return RETURN_INVALID_PARAMETER;\r
438 }\r
439\r
440 DEBUG ((\r
441 DEBUG_VERBOSE,\r
442 "%a: DriverName=\"%.*a\" UnitAddress=\"%.*a\" DeviceArguments=\"%.*a\"\n",\r
443 __FUNCTION__,\r
444 OfwNode->DriverName.Len, OfwNode->DriverName.Ptr,\r
445 OfwNode->UnitAddress.Len, OfwNode->UnitAddress.Ptr,\r
446 OfwNode->DeviceArguments.Len,\r
447 OfwNode->DeviceArguments.Ptr == NULL ? "" : OfwNode->DeviceArguments.Ptr\r
448 ));\r
449 return RETURN_SUCCESS;\r
450}\r
451\r
452\r
453/**\r
454\r
455 Translate an array of OpenFirmware device nodes to a UEFI device path\r
456 fragment.\r
457\r
458 @param[in] OfwNode Array of OpenFirmware device nodes to\r
459 translate, constituting the beginning of an\r
460 OpenFirmware device path.\r
461\r
462 @param[in] NumNodes Number of elements in OfwNode.\r
463\r
464 @param[out] Translated Destination array receiving the UEFI path\r
465 fragment, allocated by the caller. If the\r
466 return value differs from RETURN_SUCCESS, its\r
467 contents is indeterminate.\r
468\r
469 @param[in out] TranslatedSize On input, the number of CHAR16's in\r
470 Translated. On RETURN_SUCCESS this parameter\r
471 is assigned the number of non-NUL CHAR16's\r
472 written to Translated. In case of other return\r
473 values, TranslatedSize is indeterminate.\r
474\r
475\r
476 @retval RETURN_SUCCESS Translation successful.\r
477\r
478 @retval RETURN_BUFFER_TOO_SMALL The translation does not fit into the number\r
479 of bytes provided.\r
480\r
481 @retval RETURN_UNSUPPORTED The array of OpenFirmware device nodes can't\r
482 be translated in the current implementation.\r
483\r
484**/\r
485STATIC\r
486RETURN_STATUS\r
487TranslateOfwNodes (\r
488 IN CONST OFW_NODE *OfwNode,\r
489 IN UINTN NumNodes,\r
490 OUT CHAR16 *Translated,\r
491 IN OUT UINTN *TranslatedSize\r
492 )\r
493{\r
494 UINT32 PciDevFun[2];\r
495 UINTN NumEntries;\r
496 UINTN Written;\r
497\r
498 //\r
499 // Get PCI device and optional PCI function. Assume a single PCI root.\r
500 //\r
ec01afdb 501 if (NumNodes < REQUIRED_OFW_NODES ||\r
2cd086a6 502 !SubstringEq (OfwNode[0].DriverName, "pci")\r
503 ) {\r
504 return RETURN_UNSUPPORTED;\r
505 }\r
506 PciDevFun[1] = 0;\r
507 NumEntries = sizeof (PciDevFun) / sizeof (PciDevFun[0]);\r
508 if (ParseUnitAddressHexList (\r
509 OfwNode[1].UnitAddress,\r
510 PciDevFun,\r
511 &NumEntries\r
512 ) != RETURN_SUCCESS\r
513 ) {\r
514 return RETURN_UNSUPPORTED;\r
515 }\r
516\r
ec01afdb 517 if (NumNodes >= 4 &&\r
518 SubstringEq (OfwNode[1].DriverName, "ide") &&\r
2cd086a6 519 SubstringEq (OfwNode[2].DriverName, "drive") &&\r
520 SubstringEq (OfwNode[3].DriverName, "disk")\r
521 ) {\r
522 //\r
523 // OpenFirmware device path (IDE disk, IDE CD-ROM):\r
524 //\r
525 // /pci@i0cf8/ide@1,1/drive@0/disk@0\r
526 // ^ ^ ^ ^ ^\r
527 // | | | | master or slave\r
528 // | | | primary or secondary\r
529 // | PCI slot & function holding IDE controller\r
530 // PCI root at system bus port, PIO\r
531 //\r
532 // UEFI device path:\r
533 //\r
534 // PciRoot(0x0)/Pci(0x1,0x1)/Ata(Primary,Master,0x0)\r
535 // ^\r
536 // fixed LUN\r
537 //\r
538 UINT32 Secondary;\r
539 UINT32 Slave;\r
540\r
541 NumEntries = 1;\r
542 if (ParseUnitAddressHexList (\r
543 OfwNode[2].UnitAddress,\r
544 &Secondary,\r
545 &NumEntries\r
546 ) != RETURN_SUCCESS ||\r
547 Secondary > 1 ||\r
548 ParseUnitAddressHexList (\r
549 OfwNode[3].UnitAddress,\r
550 &Slave,\r
551 &NumEntries // reuse after previous single-element call\r
552 ) != RETURN_SUCCESS ||\r
553 Slave > 1\r
554 ) {\r
555 return RETURN_UNSUPPORTED;\r
556 }\r
557\r
558 Written = UnicodeSPrintAsciiFormat (\r
559 Translated,\r
560 *TranslatedSize * sizeof (*Translated), // BufferSize in bytes\r
561 "PciRoot(0x0)/Pci(0x%x,0x%x)/Ata(%a,%a,0x0)",\r
562 PciDevFun[0],\r
563 PciDevFun[1],\r
564 Secondary ? "Secondary" : "Primary",\r
565 Slave ? "Slave" : "Master"\r
566 );\r
ec01afdb 567 } else if (NumNodes >= 4 &&\r
568 SubstringEq (OfwNode[1].DriverName, "isa") &&\r
2cd086a6 569 SubstringEq (OfwNode[2].DriverName, "fdc") &&\r
570 SubstringEq (OfwNode[3].DriverName, "floppy")\r
571 ) {\r
572 //\r
573 // OpenFirmware device path (floppy disk):\r
574 //\r
575 // /pci@i0cf8/isa@1/fdc@03f0/floppy@0\r
576 // ^ ^ ^ ^\r
577 // | | | A: or B:\r
578 // | | ISA controller io-port (hex)\r
579 // | PCI slot holding ISA controller\r
580 // PCI root at system bus port, PIO\r
581 //\r
582 // UEFI device path:\r
583 //\r
584 // PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x0)\r
585 // ^\r
586 // ACPI UID\r
587 //\r
588 UINT32 AcpiUid;\r
589\r
590 NumEntries = 1;\r
591 if (ParseUnitAddressHexList (\r
592 OfwNode[3].UnitAddress,\r
593 &AcpiUid,\r
594 &NumEntries\r
595 ) != RETURN_SUCCESS ||\r
596 AcpiUid > 1\r
597 ) {\r
598 return RETURN_UNSUPPORTED;\r
599 }\r
600\r
601 Written = UnicodeSPrintAsciiFormat (\r
602 Translated,\r
603 *TranslatedSize * sizeof (*Translated), // BufferSize in bytes\r
604 "PciRoot(0x0)/Pci(0x%x,0x%x)/Floppy(0x%x)",\r
605 PciDevFun[0],\r
606 PciDevFun[1],\r
607 AcpiUid\r
608 );\r
e06a4cd1 609 } else if (NumNodes >= 3 &&\r
610 SubstringEq (OfwNode[1].DriverName, "scsi") &&\r
611 SubstringEq (OfwNode[2].DriverName, "disk")\r
612 ) {\r
613 //\r
614 // OpenFirmware device path (virtio-blk disk):\r
615 //\r
616 // /pci@i0cf8/scsi@6[,3]/disk@0,0\r
617 // ^ ^ ^ ^ ^\r
618 // | | | fixed\r
619 // | | PCI function corresponding to disk (optional)\r
620 // | PCI slot holding disk\r
621 // PCI root at system bus port, PIO\r
622 //\r
623 // UEFI device path prefix:\r
624 //\r
625 // PciRoot(0x0)/Pci(0x6,0x0)/HD( -- if PCI function is 0 or absent\r
626 // PciRoot(0x0)/Pci(0x6,0x3)/HD( -- if PCI function is present and nonzero\r
627 //\r
628 Written = UnicodeSPrintAsciiFormat (\r
629 Translated,\r
630 *TranslatedSize * sizeof (*Translated), // BufferSize in bytes\r
631 "PciRoot(0x0)/Pci(0x%x,0x%x)/HD(",\r
632 PciDevFun[0],\r
633 PciDevFun[1]\r
634 );\r
cdde6ddf 635 } else {\r
636 return RETURN_UNSUPPORTED;\r
637 }\r
2cd086a6 638\r
639 //\r
640 // There's no way to differentiate between "completely used up without\r
641 // truncation" and "truncated", so treat the former as the latter, and return\r
642 // success only for "some room left unused".\r
643 //\r
644 if (Written + 1 < *TranslatedSize) {\r
645 *TranslatedSize = Written;\r
646 return RETURN_SUCCESS;\r
647 }\r
648\r
649 return RETURN_BUFFER_TOO_SMALL;\r
650}\r
651\r
652\r
653/**\r
654\r
655 Translate an OpenFirmware device path fragment to a UEFI device path\r
656 fragment, and advance in the input string.\r
657\r
658 @param[in out] Ptr Address of the pointer pointing to the start\r
659 of the path string. After successful\r
660 translation (RETURN_SUCCESS) or at least\r
661 successful parsing (RETURN_UNSUPPORTED,\r
662 RETURN_BUFFER_TOO_SMALL), *Ptr is set to the\r
663 byte immediately following the consumed\r
664 characters. In other error cases, it points to\r
665 the byte that caused the error.\r
666\r
667 @param[out] Translated Destination array receiving the UEFI path\r
668 fragment, allocated by the caller. If the\r
669 return value differs from RETURN_SUCCESS, its\r
670 contents is indeterminate.\r
671\r
672 @param[in out] TranslatedSize On input, the number of CHAR16's in\r
673 Translated. On RETURN_SUCCESS this parameter\r
674 is assigned the number of non-NUL CHAR16's\r
675 written to Translated. In case of other return\r
676 values, TranslatedSize is indeterminate.\r
677\r
678\r
679 @retval RETURN_SUCCESS Translation successful.\r
680\r
681 @retval RETURN_BUFFER_TOO_SMALL The OpenFirmware device path was parsed\r
682 successfully, but its translation did not\r
683 fit into the number of bytes provided.\r
684 Further calls to this function are\r
685 possible.\r
686\r
687 @retval RETURN_UNSUPPORTED The OpenFirmware device path was parsed\r
688 successfully, but it can't be translated in\r
689 the current implementation. Further calls\r
690 to this function are possible.\r
691\r
692 @retval RETURN_NOT_FOUND Translation terminated, *Ptr was (and is)\r
693 pointing to an empty string.\r
694\r
695 @retval RETURN_INVALID_PARAMETER Parse error. This is a permanent error.\r
696\r
697**/\r
698STATIC\r
699RETURN_STATUS\r
700TranslateOfwPath (\r
701 IN OUT CONST CHAR8 **Ptr,\r
702 OUT CHAR16 *Translated,\r
703 IN OUT UINTN *TranslatedSize\r
704 )\r
705{\r
706 UINTN NumNodes;\r
707 RETURN_STATUS Status;\r
ec01afdb 708 OFW_NODE Node[EXAMINED_OFW_NODES];\r
2cd086a6 709 BOOLEAN IsFinal;\r
710 OFW_NODE Skip;\r
711\r
712 NumNodes = 0;\r
713 Status = ParseOfwNode (Ptr, &Node[NumNodes], &IsFinal);\r
714\r
715 if (Status == RETURN_NOT_FOUND) {\r
716 DEBUG ((DEBUG_VERBOSE, "%a: no more nodes\n", __FUNCTION__));\r
717 return RETURN_NOT_FOUND;\r
718 }\r
719\r
720 while (Status == RETURN_SUCCESS && !IsFinal) {\r
721 ++NumNodes;\r
722 Status = ParseOfwNode (\r
723 Ptr,\r
ec01afdb 724 (NumNodes < EXAMINED_OFW_NODES) ? &Node[NumNodes] : &Skip,\r
2cd086a6 725 &IsFinal\r
726 );\r
727 }\r
728\r
729 switch (Status) {\r
730 case RETURN_SUCCESS:\r
731 ++NumNodes;\r
732 break;\r
733\r
734 case RETURN_INVALID_PARAMETER:\r
735 DEBUG ((DEBUG_VERBOSE, "%a: parse error\n", __FUNCTION__));\r
736 return RETURN_INVALID_PARAMETER;\r
737\r
738 default:\r
739 ASSERT (0);\r
740 }\r
741\r
742 Status = TranslateOfwNodes (\r
743 Node,\r
ec01afdb 744 NumNodes < EXAMINED_OFW_NODES ? NumNodes : EXAMINED_OFW_NODES,\r
2cd086a6 745 Translated,\r
746 TranslatedSize);\r
747 switch (Status) {\r
748 case RETURN_SUCCESS:\r
749 DEBUG ((DEBUG_VERBOSE, "%a: success: \"%s\"\n", __FUNCTION__, Translated));\r
750 break;\r
751\r
752 case RETURN_BUFFER_TOO_SMALL:\r
753 DEBUG ((DEBUG_VERBOSE, "%a: buffer too small\n", __FUNCTION__));\r
754 break;\r
755\r
756 case RETURN_UNSUPPORTED:\r
757 DEBUG ((DEBUG_VERBOSE, "%a: unsupported\n", __FUNCTION__));\r
758 break;\r
759\r
760 default:\r
761 ASSERT (0);\r
762 }\r
763 return Status;\r
764}\r
765\r
766\r
767/**\r
768\r
769 Convert the UEFI DevicePath to full text representation with DevPathToText,\r
770 then match the UEFI device path fragment in Translated against it.\r
771\r
772 @param[in] Translated UEFI device path fragment, translated from\r
773 OpenFirmware format, to search for.\r
774\r
775 @param[in] TranslatedLength The length of Translated in CHAR16's.\r
776\r
777 @param[in] DevicePath Boot option device path whose textual rendering\r
778 to search in.\r
779\r
780 @param[in] DevPathToText Binary-to-text conversion protocol for DevicePath.\r
781\r
782\r
783 @retval TRUE If Translated was found at the beginning of DevicePath after\r
784 converting the latter to text.\r
785\r
786 @retval FALSE If DevicePath was NULL, or it could not be converted, or there\r
787 was no match.\r
788\r
789**/\r
790STATIC\r
791BOOLEAN\r
792Match (\r
793 IN CONST CHAR16 *Translated,\r
794 IN UINTN TranslatedLength,\r
795 IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath,\r
796 IN CONST EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *DevPathToText\r
797 )\r
798{\r
799 CHAR16 *Converted;\r
800 BOOLEAN Result;\r
801\r
802 Converted = DevPathToText->ConvertDevicePathToText (\r
803 DevicePath,\r
804 FALSE, // DisplayOnly\r
805 FALSE // AllowShortcuts\r
806 );\r
807 if (Converted == NULL) {\r
808 return FALSE;\r
809 }\r
810\r
811 //\r
812 // Is Translated a prefix of Converted?\r
813 //\r
dccf7678 814 Result = (BOOLEAN)(StrnCmp (Converted, Translated, TranslatedLength) == 0);\r
2cd086a6 815 DEBUG ((\r
816 DEBUG_VERBOSE,\r
817 "%a: against \"%s\": %a\n",\r
818 __FUNCTION__,\r
819 Converted,\r
820 Result ? "match" : "no match"\r
821 ));\r
822 FreePool (Converted);\r
823 return Result;\r
824}\r
825\r
826\r
827/**\r
828\r
829 Set the boot order based on configuration retrieved from QEMU.\r
830\r
831 Attempt to retrieve the "bootorder" fw_cfg file from QEMU. Translate the\r
832 OpenFirmware device paths therein to UEFI device path fragments. Match the\r
833 translated fragments against BootOptionList, and rewrite the BootOrder NvVar\r
834 so that it corresponds to the order described in fw_cfg.\r
835\r
836 @param[in] BootOptionList A boot option list, created with\r
837 BdsLibEnumerateAllBootOption ().\r
838\r
839\r
840 @retval RETURN_SUCCESS BootOrder NvVar rewritten.\r
841\r
842 @retval RETURN_UNSUPPORTED QEMU's fw_cfg is not supported.\r
843\r
844 @retval RETURN_NOT_FOUND Empty or nonexistent "bootorder" fw_cfg\r
845 file, or no match found between the\r
846 "bootorder" fw_cfg file and BootOptionList.\r
847\r
848 @retval RETURN_INVALID_PARAMETER Parse error in the "bootorder" fw_cfg file.\r
849\r
850 @retval RETURN_OUT_OF_RESOURCES Memory allocation failed.\r
851\r
852 @return Values returned by gBS->LocateProtocol ()\r
853 or gRT->SetVariable ().\r
854\r
855**/\r
856RETURN_STATUS\r
857SetBootOrderFromQemu (\r
858 IN CONST LIST_ENTRY *BootOptionList\r
859 )\r
860{\r
861 RETURN_STATUS Status;\r
862\r
863 EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *DevPathToText;\r
864\r
865 FIRMWARE_CONFIG_ITEM FwCfgItem;\r
866 UINTN FwCfgSize;\r
867 CHAR8 *FwCfg;\r
868 CONST CHAR8 *FwCfgPtr;\r
869\r
870 BOOT_ORDER BootOrder;\r
871\r
872 UINTN TranslatedSize;\r
873 CHAR16 Translated[TRANSLATION_OUTPUT_SIZE];\r
874\r
875 Status = gBS->LocateProtocol (\r
876 &gEfiDevicePathToTextProtocolGuid,\r
877 NULL, // optional registration key\r
878 (VOID **) &DevPathToText\r
879 );\r
880 if (Status != EFI_SUCCESS) {\r
881 return Status;\r
882 }\r
883\r
884 Status = QemuFwCfgFindFile ("bootorder", &FwCfgItem, &FwCfgSize);\r
885 if (Status != RETURN_SUCCESS) {\r
886 return Status;\r
887 }\r
888\r
889 if (FwCfgSize == 0) {\r
890 return RETURN_NOT_FOUND;\r
891 }\r
892\r
893 FwCfg = AllocatePool (FwCfgSize);\r
894 if (FwCfg == NULL) {\r
895 return RETURN_OUT_OF_RESOURCES;\r
896 }\r
897\r
898 QemuFwCfgSelectItem (FwCfgItem);\r
899 QemuFwCfgReadBytes (FwCfgSize, FwCfg);\r
900 if (FwCfg[FwCfgSize - 1] != '\0') {\r
901 Status = RETURN_INVALID_PARAMETER;\r
902 goto ErrorFreeFwCfg;\r
903 }\r
904\r
905 DEBUG ((DEBUG_VERBOSE, "%a: FwCfg:\n", __FUNCTION__));\r
906 DEBUG ((DEBUG_VERBOSE, "%a\n", FwCfg));\r
907 DEBUG ((DEBUG_VERBOSE, "%a: FwCfg: <end>\n", __FUNCTION__));\r
908 FwCfgPtr = FwCfg;\r
909\r
910 BootOrder.Produced = 0;\r
911 BootOrder.Allocated = 1;\r
912 BootOrder.Data = AllocatePool (\r
913 BootOrder.Allocated * sizeof (*BootOrder.Data)\r
914 );\r
915 if (BootOrder.Data == NULL) {\r
916 Status = RETURN_OUT_OF_RESOURCES;\r
917 goto ErrorFreeFwCfg;\r
918 }\r
919\r
920 //\r
921 // translate each OpenFirmware path\r
922 //\r
923 TranslatedSize = sizeof (Translated) / sizeof (Translated[0]);\r
924 Status = TranslateOfwPath (&FwCfgPtr, Translated, &TranslatedSize);\r
925 while (Status == RETURN_SUCCESS ||\r
926 Status == RETURN_UNSUPPORTED ||\r
927 Status == RETURN_BUFFER_TOO_SMALL) {\r
928 if (Status == RETURN_SUCCESS) {\r
929 CONST LIST_ENTRY *Link;\r
930\r
931 //\r
932 // match translated OpenFirmware path against all enumerated boot options\r
933 //\r
934 for (Link = BootOptionList->ForwardLink; Link != BootOptionList;\r
935 Link = Link->ForwardLink) {\r
936 CONST BDS_COMMON_OPTION *BootOption;\r
937\r
938 BootOption = CR (\r
939 Link,\r
940 BDS_COMMON_OPTION,\r
941 Link,\r
942 BDS_LOAD_OPTION_SIGNATURE\r
943 );\r
944 if (IS_LOAD_OPTION_TYPE (BootOption->Attribute, LOAD_OPTION_ACTIVE) &&\r
945 Match (\r
946 Translated,\r
947 TranslatedSize, // contains length, not size, in CHAR16's here\r
948 BootOption->DevicePath,\r
949 DevPathToText\r
950 )\r
951 ) {\r
952 //\r
953 // match found, store ID and continue with next OpenFirmware path\r
954 //\r
955 Status = BootOrderAppend (&BootOrder, BootOption->BootCurrent);\r
956 if (Status != RETURN_SUCCESS) {\r
957 goto ErrorFreeBootOrder;\r
958 }\r
959 break;\r
960 }\r
961 } // scanned all enumerated boot options\r
962 } // translation successful\r
963\r
964 TranslatedSize = sizeof (Translated) / sizeof (Translated[0]);\r
965 Status = TranslateOfwPath (&FwCfgPtr, Translated, &TranslatedSize);\r
966 } // scanning of OpenFirmware paths done\r
967\r
968 if (Status == RETURN_NOT_FOUND && BootOrder.Produced > 0) {\r
969 //\r
970 // No more OpenFirmware paths, some matches found: rewrite BootOrder NvVar.\r
971 // See Table 10 in the UEFI Spec 2.3.1 with Errata C for the required\r
972 // attributes.\r
973 //\r
974 Status = gRT->SetVariable (\r
975 L"BootOrder",\r
976 &gEfiGlobalVariableGuid,\r
977 EFI_VARIABLE_NON_VOLATILE |\r
978 EFI_VARIABLE_BOOTSERVICE_ACCESS |\r
979 EFI_VARIABLE_RUNTIME_ACCESS,\r
980 BootOrder.Produced * sizeof (*BootOrder.Data),\r
981 BootOrder.Data\r
982 );\r
983 DEBUG ((\r
984 DEBUG_INFO,\r
985 "%a: setting BootOrder: %a\n",\r
986 __FUNCTION__,\r
987 Status == EFI_SUCCESS ? "success" : "error"\r
988 ));\r
989 }\r
990\r
991ErrorFreeBootOrder:\r
992 FreePool (BootOrder.Data);\r
993\r
994ErrorFreeFwCfg:\r
995 FreePool (FwCfg);\r
996\r
997 return Status;\r
998}\r