MdeModulePkg/UefiBootManagerLib: Separate boot description functions.
[mirror_edk2.git] / MdeModulePkg / Library / UefiBootManagerLib / BmBootDescription.c
CommitLineData
1f2e80af
RN
1/** @file\r
2 Library functions which relate with boot option description.\r
3\r
4Copyright (c) 2011 - 2016, Intel Corporation. All rights reserved.<BR>\r
5(C) Copyright 2015 Hewlett Packard Enterprise Development LP<BR>\r
6This program and the accompanying materials\r
7are licensed and made available under the terms and conditions of the BSD License\r
8which accompanies this distribution. The full text of the license may be found at\r
9http://opensource.org/licenses/bsd-license.php\r
10\r
11THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
12WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
13\r
14**/\r
15\r
16#include "InternalBm.h"\r
17\r
18#define VENDOR_IDENTIFICATION_OFFSET 3\r
19#define VENDOR_IDENTIFICATION_LENGTH 8\r
20#define PRODUCT_IDENTIFICATION_OFFSET 11\r
21#define PRODUCT_IDENTIFICATION_LENGTH 16\r
22\r
23CONST UINT16 mBmUsbLangId = 0x0409; // English\r
24CHAR16 mBmUefiPrefix[] = L"UEFI ";\r
25\r
26LIST_ENTRY mPlatformBootDescriptionHandlers = INITIALIZE_LIST_HEAD_VARIABLE (mPlatformBootDescriptionHandlers);\r
27\r
28/**\r
29 For a bootable Device path, return its boot type.\r
30\r
31 @param DevicePath The bootable device Path to check\r
32\r
33 @retval AcpiFloppyBoot If given device path contains ACPI_DEVICE_PATH type device path node\r
34 which HID is floppy device.\r
35 @retval MessageAtapiBoot If given device path contains MESSAGING_DEVICE_PATH type device path node\r
36 and its last device path node's subtype is MSG_ATAPI_DP.\r
37 @retval MessageSataBoot If given device path contains MESSAGING_DEVICE_PATH type device path node\r
38 and its last device path node's subtype is MSG_SATA_DP.\r
39 @retval MessageScsiBoot If given device path contains MESSAGING_DEVICE_PATH type device path node\r
40 and its last device path node's subtype is MSG_SCSI_DP.\r
41 @retval MessageUsbBoot If given device path contains MESSAGING_DEVICE_PATH type device path node\r
42 and its last device path node's subtype is MSG_USB_DP.\r
43 @retval MessageNetworkBoot If given device path contains MESSAGING_DEVICE_PATH type device path node\r
44 and its last device path node's subtype is MSG_MAC_ADDR_DP, MSG_VLAN_DP,\r
45 MSG_IPv4_DP or MSG_IPv6_DP.\r
46 @retval MessageHttpBoot If given device path contains MESSAGING_DEVICE_PATH type device path node\r
47 and its last device path node's subtype is MSG_URI_DP.\r
48 @retval UnsupportedBoot If tiven device path doesn't match the above condition, it's not supported.\r
49\r
50**/\r
51BM_BOOT_TYPE\r
52BmDevicePathType (\r
53 IN EFI_DEVICE_PATH_PROTOCOL *DevicePath\r
54 )\r
55{\r
56 EFI_DEVICE_PATH_PROTOCOL *Node;\r
57 EFI_DEVICE_PATH_PROTOCOL *NextNode;\r
58\r
59 ASSERT (DevicePath != NULL);\r
60\r
61 for (Node = DevicePath; !IsDevicePathEndType (Node); Node = NextDevicePathNode (Node)) {\r
62 switch (DevicePathType (Node)) {\r
63\r
64 case ACPI_DEVICE_PATH:\r
65 if (EISA_ID_TO_NUM (((ACPI_HID_DEVICE_PATH *) Node)->HID) == 0x0604) {\r
66 return BmAcpiFloppyBoot;\r
67 }\r
68 break;\r
69\r
70 case HARDWARE_DEVICE_PATH:\r
71 if (DevicePathSubType (Node) == HW_CONTROLLER_DP) {\r
72 return BmHardwareDeviceBoot;\r
73 }\r
74 break;\r
75\r
76 case MESSAGING_DEVICE_PATH:\r
77 //\r
78 // Skip LUN device node\r
79 //\r
80 NextNode = Node;\r
81 do {\r
82 NextNode = NextDevicePathNode (NextNode);\r
83 } while (\r
84 (DevicePathType (NextNode) == MESSAGING_DEVICE_PATH) &&\r
85 (DevicePathSubType(NextNode) == MSG_DEVICE_LOGICAL_UNIT_DP)\r
86 );\r
87\r
88 //\r
89 // If the device path not only point to driver device, it is not a messaging device path,\r
90 //\r
91 if (!IsDevicePathEndType (NextNode)) {\r
92 continue;\r
93 }\r
94\r
95 switch (DevicePathSubType (Node)) {\r
96 case MSG_ATAPI_DP:\r
97 return BmMessageAtapiBoot;\r
98 break;\r
99\r
100 case MSG_SATA_DP:\r
101 return BmMessageSataBoot;\r
102 break;\r
103\r
104 case MSG_USB_DP:\r
105 return BmMessageUsbBoot;\r
106 break;\r
107\r
108 case MSG_SCSI_DP:\r
109 return BmMessageScsiBoot;\r
110 break;\r
111\r
112 case MSG_MAC_ADDR_DP:\r
113 case MSG_VLAN_DP:\r
114 case MSG_IPv4_DP:\r
115 case MSG_IPv6_DP:\r
116 return BmMessageNetworkBoot;\r
117 break;\r
118\r
119 case MSG_URI_DP:\r
120 return BmMessageHttpBoot;\r
121 break;\r
122 }\r
123 }\r
124 }\r
125\r
126 return BmMiscBoot;\r
127}\r
128\r
129/**\r
130 Eliminate the extra spaces in the Str to one space.\r
131\r
132 @param Str Input string info.\r
133**/\r
134VOID\r
135BmEliminateExtraSpaces (\r
136 IN CHAR16 *Str\r
137 )\r
138{\r
139 UINTN Index;\r
140 UINTN ActualIndex;\r
141\r
142 for (Index = 0, ActualIndex = 0; Str[Index] != L'\0'; Index++) {\r
143 if ((Str[Index] != L' ') || ((ActualIndex > 0) && (Str[ActualIndex - 1] != L' '))) {\r
144 Str[ActualIndex++] = Str[Index];\r
145 }\r
146 }\r
147 Str[ActualIndex] = L'\0';\r
148}\r
149\r
150/**\r
151 Try to get the controller's ATA/ATAPI description.\r
152\r
153 @param Handle Controller handle.\r
154\r
155 @return The description string.\r
156**/\r
157CHAR16 *\r
158BmGetDescriptionFromDiskInfo (\r
159 IN EFI_HANDLE Handle\r
160 )\r
161{\r
162 UINTN Index;\r
163 EFI_STATUS Status;\r
164 EFI_DISK_INFO_PROTOCOL *DiskInfo;\r
165 UINT32 BufferSize;\r
166 EFI_ATAPI_IDENTIFY_DATA IdentifyData;\r
167 EFI_SCSI_INQUIRY_DATA InquiryData;\r
168 CHAR16 *Description;\r
169 UINTN Length;\r
170 CONST UINTN ModelNameLength = 40;\r
171 CONST UINTN SerialNumberLength = 20;\r
172 CHAR8 *StrPtr;\r
173 UINT8 Temp;\r
174\r
175 Description = NULL;\r
176\r
177 Status = gBS->HandleProtocol (\r
178 Handle,\r
179 &gEfiDiskInfoProtocolGuid,\r
180 (VOID **) &DiskInfo\r
181 );\r
182 if (EFI_ERROR (Status)) {\r
183 return NULL;\r
184 }\r
185\r
186 if (CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoAhciInterfaceGuid) ||\r
187 CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoIdeInterfaceGuid)) {\r
188 BufferSize = sizeof (EFI_ATAPI_IDENTIFY_DATA);\r
189 Status = DiskInfo->Identify (\r
190 DiskInfo,\r
191 &IdentifyData,\r
192 &BufferSize\r
193 );\r
194 if (!EFI_ERROR (Status)) {\r
195 Description = AllocateZeroPool ((ModelNameLength + SerialNumberLength + 2) * sizeof (CHAR16));\r
196 ASSERT (Description != NULL);\r
197 for (Index = 0; Index + 1 < ModelNameLength; Index += 2) {\r
198 Description[Index] = (CHAR16) IdentifyData.ModelName[Index + 1];\r
199 Description[Index + 1] = (CHAR16) IdentifyData.ModelName[Index];\r
200 }\r
201\r
202 Length = Index;\r
203 Description[Length++] = L' ';\r
204\r
205 for (Index = 0; Index + 1 < SerialNumberLength; Index += 2) {\r
206 Description[Length + Index] = (CHAR16) IdentifyData.SerialNo[Index + 1];\r
207 Description[Length + Index + 1] = (CHAR16) IdentifyData.SerialNo[Index];\r
208 }\r
209 Length += Index;\r
210 Description[Length++] = L'\0';\r
211 ASSERT (Length == ModelNameLength + SerialNumberLength + 2);\r
212\r
213 BmEliminateExtraSpaces (Description);\r
214 }\r
215 } else if (CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoScsiInterfaceGuid)) {\r
216 BufferSize = sizeof (EFI_SCSI_INQUIRY_DATA);\r
217 Status = DiskInfo->Inquiry (\r
218 DiskInfo,\r
219 &InquiryData,\r
220 &BufferSize\r
221 );\r
222 if (!EFI_ERROR (Status)) {\r
223 Description = AllocateZeroPool ((VENDOR_IDENTIFICATION_LENGTH + PRODUCT_IDENTIFICATION_LENGTH + 2) * sizeof (CHAR16));\r
224 ASSERT (Description != NULL);\r
225\r
226 //\r
227 // Per SCSI spec, EFI_SCSI_INQUIRY_DATA.Reserved_5_95[3 - 10] save the Verdor identification\r
228 // EFI_SCSI_INQUIRY_DATA.Reserved_5_95[11 - 26] save the product identification,\r
229 // Here combine the vendor identification and product identification to the description.\r
230 //\r
231 StrPtr = (CHAR8 *) (&InquiryData.Reserved_5_95[VENDOR_IDENTIFICATION_OFFSET]);\r
232 Temp = StrPtr[VENDOR_IDENTIFICATION_LENGTH];\r
233 StrPtr[VENDOR_IDENTIFICATION_LENGTH] = '\0';\r
234 AsciiStrToUnicodeStr (StrPtr, Description);\r
235 StrPtr[VENDOR_IDENTIFICATION_LENGTH] = Temp;\r
236\r
237 //\r
238 // Add one space at the middle of vendor information and product information.\r
239 //\r
240 Description[VENDOR_IDENTIFICATION_LENGTH] = L' ';\r
241\r
242 StrPtr = (CHAR8 *) (&InquiryData.Reserved_5_95[PRODUCT_IDENTIFICATION_OFFSET]);\r
243 StrPtr[PRODUCT_IDENTIFICATION_LENGTH] = '\0';\r
244 AsciiStrToUnicodeStr (StrPtr, Description + VENDOR_IDENTIFICATION_LENGTH + 1);\r
245\r
246 BmEliminateExtraSpaces (Description);\r
247 }\r
248 }\r
249\r
250 return Description;\r
251}\r
252\r
253/**\r
254 Try to get the controller's USB description.\r
255\r
256 @param Handle Controller handle.\r
257\r
258 @return The description string.\r
259**/\r
260CHAR16 *\r
261BmGetUsbDescription (\r
262 IN EFI_HANDLE Handle\r
263 )\r
264{\r
265 EFI_STATUS Status;\r
266 EFI_USB_IO_PROTOCOL *UsbIo;\r
267 CHAR16 NullChar;\r
268 CHAR16 *Manufacturer;\r
269 CHAR16 *Product;\r
270 CHAR16 *SerialNumber;\r
271 CHAR16 *Description;\r
272 EFI_USB_DEVICE_DESCRIPTOR DevDesc;\r
273 UINTN DescMaxSize;\r
274\r
275 Status = gBS->HandleProtocol (\r
276 Handle,\r
277 &gEfiUsbIoProtocolGuid,\r
278 (VOID **) &UsbIo\r
279 );\r
280 if (EFI_ERROR (Status)) {\r
281 return NULL;\r
282 }\r
283\r
284 NullChar = L'\0';\r
285\r
286 Status = UsbIo->UsbGetDeviceDescriptor (UsbIo, &DevDesc);\r
287 if (EFI_ERROR (Status)) {\r
288 return NULL;\r
289 }\r
290\r
291 Status = UsbIo->UsbGetStringDescriptor (\r
292 UsbIo,\r
293 mBmUsbLangId,\r
294 DevDesc.StrManufacturer,\r
295 &Manufacturer\r
296 );\r
297 if (EFI_ERROR (Status)) {\r
298 Manufacturer = &NullChar;\r
299 }\r
300\r
301 Status = UsbIo->UsbGetStringDescriptor (\r
302 UsbIo,\r
303 mBmUsbLangId,\r
304 DevDesc.StrProduct,\r
305 &Product\r
306 );\r
307 if (EFI_ERROR (Status)) {\r
308 Product = &NullChar;\r
309 }\r
310\r
311 Status = UsbIo->UsbGetStringDescriptor (\r
312 UsbIo,\r
313 mBmUsbLangId,\r
314 DevDesc.StrSerialNumber,\r
315 &SerialNumber\r
316 );\r
317 if (EFI_ERROR (Status)) {\r
318 SerialNumber = &NullChar;\r
319 }\r
320\r
321 if ((Manufacturer == &NullChar) &&\r
322 (Product == &NullChar) &&\r
323 (SerialNumber == &NullChar)\r
324 ) {\r
325 return NULL;\r
326 }\r
327\r
328 DescMaxSize = StrSize (Manufacturer) + StrSize (Product) + StrSize (SerialNumber);\r
329 Description = AllocateZeroPool (DescMaxSize);\r
330 ASSERT (Description != NULL);\r
331 StrCatS (Description, DescMaxSize/sizeof(CHAR16), Manufacturer);\r
332 StrCatS (Description, DescMaxSize/sizeof(CHAR16), L" ");\r
333\r
334 StrCatS (Description, DescMaxSize/sizeof(CHAR16), Product);\r
335 StrCatS (Description, DescMaxSize/sizeof(CHAR16), L" ");\r
336\r
337 StrCatS (Description, DescMaxSize/sizeof(CHAR16), SerialNumber);\r
338\r
339 if (Manufacturer != &NullChar) {\r
340 FreePool (Manufacturer);\r
341 }\r
342 if (Product != &NullChar) {\r
343 FreePool (Product);\r
344 }\r
345 if (SerialNumber != &NullChar) {\r
346 FreePool (SerialNumber);\r
347 }\r
348\r
349 BmEliminateExtraSpaces (Description);\r
350\r
351 return Description;\r
352}\r
353\r
354/**\r
355 Return the boot description for the controller based on the type.\r
356\r
357 @param Handle Controller handle.\r
358\r
359 @return The description string.\r
360**/\r
361CHAR16 *\r
362BmGetMiscDescription (\r
363 IN EFI_HANDLE Handle\r
364 )\r
365{\r
366 EFI_STATUS Status;\r
367 CHAR16 *Description;\r
368 EFI_BLOCK_IO_PROTOCOL *BlockIo;\r
369 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs;\r
370\r
371 switch (BmDevicePathType (DevicePathFromHandle (Handle))) {\r
372 case BmAcpiFloppyBoot:\r
373 Description = L"Floppy";\r
374 break;\r
375\r
376 case BmMessageAtapiBoot:\r
377 case BmMessageSataBoot:\r
378 Status = gBS->HandleProtocol (Handle, &gEfiBlockIoProtocolGuid, (VOID **) &BlockIo);\r
379 ASSERT_EFI_ERROR (Status);\r
380 //\r
381 // Assume a removable SATA device should be the DVD/CD device\r
382 //\r
383 Description = BlockIo->Media->RemovableMedia ? L"DVD/CDROM" : L"Hard Drive";\r
384 break;\r
385\r
386 case BmMessageUsbBoot:\r
387 Description = L"USB Device";\r
388 break;\r
389\r
390 case BmMessageScsiBoot:\r
391 Description = L"SCSI Device";\r
392 break;\r
393\r
394 case BmHardwareDeviceBoot:\r
395 Status = gBS->HandleProtocol (Handle, &gEfiBlockIoProtocolGuid, (VOID **) &BlockIo);\r
396 if (!EFI_ERROR (Status)) {\r
397 Description = BlockIo->Media->RemovableMedia ? L"Removable Disk" : L"Hard Drive";\r
398 } else {\r
399 Description = L"Misc Device";\r
400 }\r
401 break;\r
402\r
403 case BmMessageNetworkBoot:\r
404 Description = L"Network";\r
405 break;\r
406\r
407 case BmMessageHttpBoot:\r
408 Description = L"Http";\r
409 break;\r
410\r
411 default:\r
412 Status = gBS->HandleProtocol (Handle, &gEfiSimpleFileSystemProtocolGuid, (VOID **) &Fs);\r
413 if (!EFI_ERROR (Status)) {\r
414 Description = L"Non-Block Boot Device";\r
415 } else {\r
416 Description = L"Misc Device";\r
417 }\r
418 break;\r
419 }\r
420\r
421 return AllocateCopyPool (StrSize (Description), Description);\r
422}\r
423\r
424/**\r
425 Register the platform provided boot description handler.\r
426\r
427 @param Handler The platform provided boot description handler\r
428\r
429 @retval EFI_SUCCESS The handler was registered successfully.\r
430 @retval EFI_ALREADY_STARTED The handler was already registered.\r
431 @retval EFI_OUT_OF_RESOURCES There is not enough resource to perform the registration.\r
432**/\r
433EFI_STATUS\r
434EFIAPI\r
435EfiBootManagerRegisterBootDescriptionHandler (\r
436 IN EFI_BOOT_MANAGER_BOOT_DESCRIPTION_HANDLER Handler\r
437 )\r
438{\r
439 LIST_ENTRY *Link;\r
440 BM_BOOT_DESCRIPTION_ENTRY *Entry;\r
441\r
442 for ( Link = GetFirstNode (&mPlatformBootDescriptionHandlers)\r
443 ; !IsNull (&mPlatformBootDescriptionHandlers, Link)\r
444 ; Link = GetNextNode (&mPlatformBootDescriptionHandlers, Link)\r
445 ) {\r
446 Entry = CR (Link, BM_BOOT_DESCRIPTION_ENTRY, Link, BM_BOOT_DESCRIPTION_ENTRY_SIGNATURE);\r
447 if (Entry->Handler == Handler) {\r
448 return EFI_ALREADY_STARTED;\r
449 }\r
450 }\r
451\r
452 Entry = AllocatePool (sizeof (BM_BOOT_DESCRIPTION_ENTRY));\r
453 if (Entry == NULL) {\r
454 return EFI_OUT_OF_RESOURCES;\r
455 }\r
456\r
457 Entry->Signature = BM_BOOT_DESCRIPTION_ENTRY_SIGNATURE;\r
458 Entry->Handler = Handler;\r
459 InsertTailList (&mPlatformBootDescriptionHandlers, &Entry->Link);\r
460 return EFI_SUCCESS;\r
461}\r
462\r
463BM_GET_BOOT_DESCRIPTION mBmBootDescriptionHandlers[] = {\r
464 BmGetUsbDescription,\r
465 BmGetDescriptionFromDiskInfo,\r
466 BmGetMiscDescription\r
467};\r
468\r
469/**\r
470 Return the boot description for the controller.\r
471\r
472 @param Handle Controller handle.\r
473\r
474 @return The description string.\r
475**/\r
476CHAR16 *\r
477BmGetBootDescription (\r
478 IN EFI_HANDLE Handle\r
479 )\r
480{\r
481 LIST_ENTRY *Link;\r
482 BM_BOOT_DESCRIPTION_ENTRY *Entry;\r
483 CHAR16 *Description;\r
484 CHAR16 *DefaultDescription;\r
485 CHAR16 *Temp;\r
486 UINTN Index;\r
487\r
488 //\r
489 // Firstly get the default boot description\r
490 //\r
491 DefaultDescription = NULL;\r
492 for (Index = 0; Index < sizeof (mBmBootDescriptionHandlers) / sizeof (mBmBootDescriptionHandlers[0]); Index++) {\r
493 DefaultDescription = mBmBootDescriptionHandlers[Index] (Handle);\r
494 if (DefaultDescription != NULL) {\r
495 //\r
496 // Avoid description confusion between UEFI & Legacy boot option by adding "UEFI " prefix\r
497 // ONLY for core provided boot description handler.\r
498 //\r
499 Temp = AllocatePool (StrSize (DefaultDescription) + sizeof (mBmUefiPrefix));\r
500 ASSERT (Temp != NULL);\r
501 StrCpyS (Temp, (StrSize (DefaultDescription) + sizeof (mBmUefiPrefix)) / sizeof (CHAR16), mBmUefiPrefix);\r
502 StrCatS (Temp, (StrSize (DefaultDescription) + sizeof (mBmUefiPrefix)) / sizeof (CHAR16), DefaultDescription);\r
503 FreePool (DefaultDescription);\r
504 DefaultDescription = Temp;\r
505 break;\r
506 }\r
507 }\r
508 ASSERT (DefaultDescription != NULL);\r
509\r
510 //\r
511 // Secondly query platform for the better boot description\r
512 //\r
513 for ( Link = GetFirstNode (&mPlatformBootDescriptionHandlers)\r
514 ; !IsNull (&mPlatformBootDescriptionHandlers, Link)\r
515 ; Link = GetNextNode (&mPlatformBootDescriptionHandlers, Link)\r
516 ) {\r
517 Entry = CR (Link, BM_BOOT_DESCRIPTION_ENTRY, Link, BM_BOOT_DESCRIPTION_ENTRY_SIGNATURE);\r
518 Description = Entry->Handler (Handle, DefaultDescription);\r
519 if (Description != NULL) {\r
520 FreePool (DefaultDescription);\r
521 return Description;\r
522 }\r
523 }\r
524\r
525 return DefaultDescription;\r
526}\r
527\r
528/**\r
529 Enumerate all boot option descriptions and append " 2"/" 3"/... to make\r
530 unique description.\r
531\r
532 @param BootOptions Array of boot options.\r
533 @param BootOptionCount Count of boot options.\r
534**/\r
535VOID\r
536BmMakeBootOptionDescriptionUnique (\r
537 EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions,\r
538 UINTN BootOptionCount\r
539 )\r
540{\r
541 UINTN Base;\r
542 UINTN Index;\r
543 UINTN DescriptionSize;\r
544 UINTN MaxSuffixSize;\r
545 BOOLEAN *Visited;\r
546 UINTN MatchCount;\r
547\r
548 if (BootOptionCount == 0) {\r
549 return;\r
550 }\r
551\r
552 //\r
553 // Calculate the maximum buffer size for the number suffix.\r
554 // The initial sizeof (CHAR16) is for the blank space before the number.\r
555 //\r
556 MaxSuffixSize = sizeof (CHAR16);\r
557 for (Index = BootOptionCount; Index != 0; Index = Index / 10) {\r
558 MaxSuffixSize += sizeof (CHAR16);\r
559 }\r
560\r
561 Visited = AllocateZeroPool (sizeof (BOOLEAN) * BootOptionCount);\r
562 ASSERT (Visited != NULL);\r
563\r
564 for (Base = 0; Base < BootOptionCount; Base++) {\r
565 if (!Visited[Base]) {\r
566 MatchCount = 1;\r
567 Visited[Base] = TRUE;\r
568 DescriptionSize = StrSize (BootOptions[Base].Description);\r
569 for (Index = Base + 1; Index < BootOptionCount; Index++) {\r
570 if (!Visited[Index] && StrCmp (BootOptions[Base].Description, BootOptions[Index].Description) == 0) {\r
571 Visited[Index] = TRUE;\r
572 MatchCount++;\r
573 FreePool (BootOptions[Index].Description);\r
574 BootOptions[Index].Description = AllocatePool (DescriptionSize + MaxSuffixSize);\r
575 UnicodeSPrint (\r
576 BootOptions[Index].Description, DescriptionSize + MaxSuffixSize,\r
577 L"%s %d",\r
578 BootOptions[Base].Description, MatchCount\r
579 );\r
580 }\r
581 }\r
582 }\r
583 }\r
584\r
585 FreePool (Visited);\r
586}\r