]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - stand/efi/boot1/boot1.c
MFV r337744:
[FreeBSD/FreeBSD.git] / stand / efi / boot1 / boot1.c
1 /*-
2  * Copyright (c) 1998 Robert Nordier
3  * All rights reserved.
4  * Copyright (c) 2001 Robert Drehmel
5  * All rights reserved.
6  * Copyright (c) 2014 Nathan Whitehorn
7  * All rights reserved.
8  * Copyright (c) 2015 Eric McCorkle
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms are freely
12  * permitted provided that the above copyright notice and this
13  * paragraph and the following disclaimer are duplicated in all
14  * such forms.
15  *
16  * This software is provided "AS IS" and without any express or
17  * implied warranties, including, without limitation, the implied
18  * warranties of merchantability and fitness for a particular
19  * purpose.
20  */
21
22 #include <sys/cdefs.h>
23 __FBSDID("$FreeBSD$");
24
25 #include <sys/param.h>
26 #include <machine/elf.h>
27 #include <machine/stdarg.h>
28 #include <stand.h>
29
30 #include <efi.h>
31 #include <eficonsctl.h>
32 #include <efichar.h>
33
34 #include "boot_module.h"
35 #include "paths.h"
36
37 static void efi_panic(EFI_STATUS s, const char *fmt, ...) __dead2 __printflike(2, 3);
38
39 static const boot_module_t *boot_modules[] =
40 {
41 #ifdef EFI_ZFS_BOOT
42         &zfs_module,
43 #endif
44 #ifdef EFI_UFS_BOOT
45         &ufs_module
46 #endif
47 };
48
49 #define NUM_BOOT_MODULES        nitems(boot_modules)
50 /* The initial number of handles used to query EFI for partitions. */
51 #define NUM_HANDLES_INIT        24
52
53 static EFI_GUID BlockIoProtocolGUID = BLOCK_IO_PROTOCOL;
54 static EFI_GUID DevicePathGUID = DEVICE_PATH_PROTOCOL;
55 static EFI_GUID LoadedImageGUID = LOADED_IMAGE_PROTOCOL;
56 static EFI_GUID ConsoleControlGUID = EFI_CONSOLE_CONTROL_PROTOCOL_GUID;
57
58 /*
59  * Provide Malloc / Free backed by EFIs AllocatePool / FreePool which ensures
60  * memory is correctly aligned avoiding EFI_INVALID_PARAMETER returns from
61  * EFI methods.
62  */
63 void *
64 Malloc(size_t len, const char *file __unused, int line __unused)
65 {
66         void *out;
67
68         if (BS->AllocatePool(EfiLoaderData, len, &out) == EFI_SUCCESS)
69                 return (out);
70
71         return (NULL);
72 }
73
74 void
75 Free(void *buf, const char *file __unused, int line __unused)
76 {
77         if (buf != NULL)
78                 (void)BS->FreePool(buf);
79 }
80
81 /*
82  * nodes_match returns TRUE if the imgpath isn't NULL and the nodes match,
83  * FALSE otherwise.
84  */
85 static BOOLEAN
86 nodes_match(EFI_DEVICE_PATH *imgpath, EFI_DEVICE_PATH *devpath)
87 {
88         size_t len;
89
90         if (imgpath == NULL || imgpath->Type != devpath->Type ||
91             imgpath->SubType != devpath->SubType)
92                 return (FALSE);
93
94         len = DevicePathNodeLength(imgpath);
95         if (len != DevicePathNodeLength(devpath))
96                 return (FALSE);
97
98         return (memcmp(imgpath, devpath, (size_t)len) == 0);
99 }
100
101 /*
102  * device_paths_match returns TRUE if the imgpath isn't NULL and all nodes
103  * in imgpath and devpath match up to their respective occurrences of a
104  * media node, FALSE otherwise.
105  */
106 static BOOLEAN
107 device_paths_match(EFI_DEVICE_PATH *imgpath, EFI_DEVICE_PATH *devpath)
108 {
109
110         if (imgpath == NULL)
111                 return (FALSE);
112
113         while (!IsDevicePathEnd(imgpath) && !IsDevicePathEnd(devpath)) {
114                 if (IsDevicePathType(imgpath, MEDIA_DEVICE_PATH) &&
115                     IsDevicePathType(devpath, MEDIA_DEVICE_PATH))
116                         return (TRUE);
117
118                 if (!nodes_match(imgpath, devpath))
119                         return (FALSE);
120
121                 imgpath = NextDevicePathNode(imgpath);
122                 devpath = NextDevicePathNode(devpath);
123         }
124
125         return (FALSE);
126 }
127
128 /*
129  * devpath_last returns the last non-path end node in devpath.
130  */
131 static EFI_DEVICE_PATH *
132 devpath_last(EFI_DEVICE_PATH *devpath)
133 {
134
135         while (!IsDevicePathEnd(NextDevicePathNode(devpath)))
136                 devpath = NextDevicePathNode(devpath);
137
138         return (devpath);
139 }
140
141 /*
142  * load_loader attempts to load the loader image data.
143  *
144  * It tries each module and its respective devices, identified by mod->probe,
145  * in order until a successful load occurs at which point it returns EFI_SUCCESS
146  * and EFI_NOT_FOUND otherwise.
147  *
148  * Only devices which have preferred matching the preferred parameter are tried.
149  */
150 static EFI_STATUS
151 load_loader(const boot_module_t **modp, dev_info_t **devinfop, void **bufp,
152     size_t *bufsize, BOOLEAN preferred)
153 {
154         UINTN i;
155         dev_info_t *dev;
156         const boot_module_t *mod;
157
158         for (i = 0; i < NUM_BOOT_MODULES; i++) {
159                 mod = boot_modules[i];
160                 for (dev = mod->devices(); dev != NULL; dev = dev->next) {
161                         if (dev->preferred != preferred)
162                                 continue;
163
164                         if (mod->load(PATH_LOADER_EFI, dev, bufp, bufsize) ==
165                             EFI_SUCCESS) {
166                                 *devinfop = dev;
167                                 *modp = mod;
168                                 return (EFI_SUCCESS);
169                         }
170                 }
171         }
172
173         return (EFI_NOT_FOUND);
174 }
175
176 /*
177  * try_boot only returns if it fails to load the loader. If it succeeds
178  * it simply boots, otherwise it returns the status of last EFI call.
179  */
180 static EFI_STATUS
181 try_boot(void)
182 {
183         size_t bufsize, loadersize, cmdsize;
184         void *buf, *loaderbuf;
185         char *cmd;
186         dev_info_t *dev;
187         const boot_module_t *mod;
188         EFI_HANDLE loaderhandle;
189         EFI_LOADED_IMAGE *loaded_image;
190         EFI_STATUS status;
191
192         status = load_loader(&mod, &dev, &loaderbuf, &loadersize, TRUE);
193         if (status != EFI_SUCCESS) {
194                 status = load_loader(&mod, &dev, &loaderbuf, &loadersize,
195                     FALSE);
196                 if (status != EFI_SUCCESS) {
197                         printf("Failed to load '%s'\n", PATH_LOADER_EFI);
198                         return (status);
199                 }
200         }
201
202         /*
203          * Read in and parse the command line from /boot.config or /boot/config,
204          * if present. We'll pass it the next stage via a simple ASCII
205          * string. loader.efi has a hack for ASCII strings, so we'll use that to
206          * keep the size down here. We only try to read the alternate file if
207          * we get EFI_NOT_FOUND because all other errors mean that the boot_module
208          * had troubles with the filesystem. We could return early, but we'll let
209          * loading the actual kernel sort all that out. Since these files are
210          * optional, we don't report errors in trying to read them.
211          */
212         cmd = NULL;
213         cmdsize = 0;
214         status = mod->load(PATH_DOTCONFIG, dev, &buf, &bufsize);
215         if (status == EFI_NOT_FOUND)
216                 status = mod->load(PATH_CONFIG, dev, &buf, &bufsize);
217         if (status == EFI_SUCCESS) {
218                 cmdsize = bufsize + 1;
219                 cmd = malloc(cmdsize);
220                 if (cmd == NULL)
221                         goto errout;
222                 memcpy(cmd, buf, bufsize);
223                 cmd[bufsize] = '\0';
224                 free(buf);
225                 buf = NULL;
226         }
227
228         if ((status = BS->LoadImage(TRUE, IH, devpath_last(dev->devpath),
229             loaderbuf, loadersize, &loaderhandle)) != EFI_SUCCESS) {
230                 printf("Failed to load image provided by %s, size: %zu, (%lu)\n",
231                      mod->name, loadersize, EFI_ERROR_CODE(status));
232                 goto errout;
233         }
234
235         if ((status = BS->HandleProtocol(loaderhandle, &LoadedImageGUID,
236             (VOID**)&loaded_image)) != EFI_SUCCESS) {
237                 printf("Failed to query LoadedImage provided by %s (%lu)\n",
238                     mod->name, EFI_ERROR_CODE(status));
239                 goto errout;
240         }
241
242         if (cmd != NULL)
243                 printf("    command args: %s\n", cmd);
244
245         loaded_image->DeviceHandle = dev->devhandle;
246         loaded_image->LoadOptionsSize = cmdsize;
247         loaded_image->LoadOptions = cmd;
248
249         DPRINTF("Starting '%s' in 5 seconds...", PATH_LOADER_EFI);
250         DSTALL(1000000);
251         DPRINTF(".");
252         DSTALL(1000000);
253         DPRINTF(".");
254         DSTALL(1000000);
255         DPRINTF(".");
256         DSTALL(1000000);
257         DPRINTF(".");
258         DSTALL(1000000);
259         DPRINTF(".\n");
260
261         if ((status = BS->StartImage(loaderhandle, NULL, NULL)) !=
262             EFI_SUCCESS) {
263                 printf("Failed to start image provided by %s (%lu)\n",
264                     mod->name, EFI_ERROR_CODE(status));
265                 loaded_image->LoadOptionsSize = 0;
266                 loaded_image->LoadOptions = NULL;
267         }
268
269 errout:
270         if (cmd != NULL)
271                 free(cmd);
272         if (buf != NULL)
273                 free(buf);
274         if (loaderbuf != NULL)
275                 free(loaderbuf);
276
277         return (status);
278 }
279
280 /*
281  * probe_handle determines if the passed handle represents a logical partition
282  * if it does it uses each module in order to probe it and if successful it
283  * returns EFI_SUCCESS.
284  */
285 static EFI_STATUS
286 probe_handle(EFI_HANDLE h, EFI_DEVICE_PATH *imgpath, BOOLEAN *preferred)
287 {
288         dev_info_t *devinfo;
289         EFI_BLOCK_IO *blkio;
290         EFI_DEVICE_PATH *devpath;
291         EFI_STATUS status;
292         UINTN i;
293
294         /* Figure out if we're dealing with an actual partition. */
295         status = BS->HandleProtocol(h, &DevicePathGUID, (void **)&devpath);
296         if (status == EFI_UNSUPPORTED)
297                 return (status);
298
299         if (status != EFI_SUCCESS) {
300                 DPRINTF("\nFailed to query DevicePath (%lu)\n",
301                     EFI_ERROR_CODE(status));
302                 return (status);
303         }
304 #ifdef EFI_DEBUG
305         {
306                 CHAR16 *text = efi_devpath_name(devpath);
307                 DPRINTF("probing: %S\n", text);
308                 efi_free_devpath_name(text);
309         }
310 #endif
311         status = BS->HandleProtocol(h, &BlockIoProtocolGUID, (void **)&blkio);
312         if (status == EFI_UNSUPPORTED)
313                 return (status);
314
315         if (status != EFI_SUCCESS) {
316                 DPRINTF("\nFailed to query BlockIoProtocol (%lu)\n",
317                     EFI_ERROR_CODE(status));
318                 return (status);
319         }
320
321         if (!blkio->Media->LogicalPartition)
322                 return (EFI_UNSUPPORTED);
323
324         *preferred = device_paths_match(imgpath, devpath);
325
326         /* Run through each module, see if it can load this partition */
327         for (i = 0; i < NUM_BOOT_MODULES; i++) {
328                 devinfo = malloc(sizeof(*devinfo));
329                 if (devinfo == NULL) {
330                         DPRINTF("\nFailed to allocate devinfo\n");
331                         continue;
332                 }
333                 devinfo->dev = blkio;
334                 devinfo->devpath = devpath;
335                 devinfo->devhandle = h;
336                 devinfo->devdata = NULL;
337                 devinfo->preferred = *preferred;
338                 devinfo->next = NULL;
339
340                 status = boot_modules[i]->probe(devinfo);
341                 if (status == EFI_SUCCESS)
342                         return (EFI_SUCCESS);
343                 free(devinfo);
344         }
345
346         return (EFI_UNSUPPORTED);
347 }
348
349 /*
350  * probe_handle_status calls probe_handle and outputs the returned status
351  * of the call.
352  */
353 static void
354 probe_handle_status(EFI_HANDLE h, EFI_DEVICE_PATH *imgpath)
355 {
356         EFI_STATUS status;
357         BOOLEAN preferred;
358
359         preferred = FALSE;
360         status = probe_handle(h, imgpath, &preferred);
361         
362         DPRINTF("probe: ");
363         switch (status) {
364         case EFI_UNSUPPORTED:
365                 printf(".");
366                 DPRINTF(" not supported\n");
367                 break;
368         case EFI_SUCCESS:
369                 if (preferred) {
370                         printf("%c", '*');
371                         DPRINTF(" supported (preferred)\n");
372                 } else {
373                         printf("%c", '+');
374                         DPRINTF(" supported\n");
375                 }
376                 break;
377         default:
378                 printf("x");
379                 DPRINTF(" error (%lu)\n", EFI_ERROR_CODE(status));
380                 break;
381         }
382         DSTALL(500000);
383 }
384
385 EFI_STATUS
386 efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE *Xsystab)
387 {
388         EFI_HANDLE *handles;
389         EFI_LOADED_IMAGE *img;
390         EFI_DEVICE_PATH *imgpath;
391         EFI_STATUS status;
392         EFI_CONSOLE_CONTROL_PROTOCOL *ConsoleControl = NULL;
393         SIMPLE_TEXT_OUTPUT_INTERFACE *conout = NULL;
394         UINTN i, hsize, nhandles;
395         CHAR16 *text;
396         UINT16 boot_current;
397         size_t sz;
398         UINT16 boot_order[100];
399
400         /* Basic initialization*/
401         ST = Xsystab;
402         IH = Ximage;
403         BS = ST->BootServices;
404         RS = ST->RuntimeServices;
405
406         /* Set up the console, so printf works. */
407         status = BS->LocateProtocol(&ConsoleControlGUID, NULL,
408             (VOID **)&ConsoleControl);
409         if (status == EFI_SUCCESS)
410                 (void)ConsoleControl->SetMode(ConsoleControl,
411                     EfiConsoleControlScreenText);
412         /*
413          * Reset the console enable the cursor. Later we'll choose a better
414          * console size through GOP/UGA.
415          */
416         conout = ST->ConOut;
417         conout->Reset(conout, TRUE);
418         /* Explicitly set conout to mode 0, 80x25 */
419         conout->SetMode(conout, 0);
420         conout->EnableCursor(conout, TRUE);
421         conout->ClearScreen(conout);
422
423         printf("\n>> FreeBSD EFI boot block\n");
424         printf("   Loader path: %s\n\n", PATH_LOADER_EFI);
425         printf("   Initializing modules:");
426         for (i = 0; i < NUM_BOOT_MODULES; i++) {
427                 printf(" %s", boot_modules[i]->name);
428                 if (boot_modules[i]->init != NULL)
429                         boot_modules[i]->init();
430         }
431         putchar('\n');
432
433         /* Determine the devpath of our image so we can prefer it. */
434         status = BS->HandleProtocol(IH, &LoadedImageGUID, (VOID**)&img);
435         imgpath = NULL;
436         if (status == EFI_SUCCESS) {
437                 text = efi_devpath_name(img->FilePath);
438                 if (text != NULL) {
439                         printf("   Load Path: %S\n", text);
440                         efi_setenv_freebsd_wcs("Boot1Path", text);
441                         efi_free_devpath_name(text);
442                 }
443
444                 status = BS->HandleProtocol(img->DeviceHandle, &DevicePathGUID,
445                     (void **)&imgpath);
446                 if (status != EFI_SUCCESS) {
447                         DPRINTF("Failed to get image DevicePath (%lu)\n",
448                             EFI_ERROR_CODE(status));
449                 } else {
450                         text = efi_devpath_name(imgpath);
451                         if (text != NULL) {
452                                 printf("   Load Device: %S\n", text);
453                                 efi_setenv_freebsd_wcs("Boot1Dev", text);
454                                 efi_free_devpath_name(text);
455                         }
456                 }
457         }
458
459         boot_current = 0;
460         sz = sizeof(boot_current);
461         if (efi_global_getenv("BootCurrent", &boot_current, &sz) == EFI_SUCCESS) {
462                 printf("   BootCurrent: %04x\n", boot_current);
463
464                 sz = sizeof(boot_order);
465                 if (efi_global_getenv("BootOrder", &boot_order, &sz) == EFI_SUCCESS) {
466                         printf("   BootOrder:");
467                         for (i = 0; i < sz / sizeof(boot_order[0]); i++)
468                                 printf(" %04x%s", boot_order[i],
469                                     boot_order[i] == boot_current ? "[*]" : "");
470                         printf("\n");
471                 }
472         }
473
474 #ifdef TEST_FAILURE
475         /*
476          * For testing failover scenarios, it's nice to be able to fail fast.
477          * Define TEST_FAILURE to create a boot1.efi that always fails after
478          * reporting the boot manager protocol details.
479          */
480         BS->Exit(IH, EFI_OUT_OF_RESOURCES, 0, NULL);
481 #endif
482
483         /* Get all the device handles */
484         hsize = (UINTN)NUM_HANDLES_INIT * sizeof(EFI_HANDLE);
485         handles = malloc(hsize);
486         if (handles == NULL)
487                 printf("Failed to allocate %d handles\n", NUM_HANDLES_INIT);
488
489         status = BS->LocateHandle(ByProtocol, &BlockIoProtocolGUID, NULL,
490             &hsize, handles);
491         switch (status) {
492         case EFI_SUCCESS:
493                 break;
494         case EFI_BUFFER_TOO_SMALL:
495                 free(handles);
496                 handles = malloc(hsize);
497                 if (handles == NULL)
498                         efi_panic(EFI_OUT_OF_RESOURCES, "Failed to allocate %d handles\n",
499                             NUM_HANDLES_INIT);
500                 status = BS->LocateHandle(ByProtocol, &BlockIoProtocolGUID,
501                     NULL, &hsize, handles);
502                 if (status != EFI_SUCCESS)
503                         efi_panic(status, "Failed to get device handles\n");
504                 break;
505         default:
506                 efi_panic(status, "Failed to get device handles\n");
507                 break;
508         }
509
510         /* Scan all partitions, probing with all modules. */
511         nhandles = hsize / sizeof(*handles);
512         printf("   Probing %zu block devices...", nhandles);
513         DPRINTF("\n");
514
515         for (i = 0; i < nhandles; i++)
516                 probe_handle_status(handles[i], imgpath);
517         printf(" done\n");
518
519         /* Status summary. */
520         for (i = 0; i < NUM_BOOT_MODULES; i++) {
521                 printf("    ");
522                 boot_modules[i]->status();
523         }
524
525         try_boot();
526
527         /* If we get here, we're out of luck... */
528         efi_panic(EFI_LOAD_ERROR, "No bootable partitions found!");
529 }
530
531 /*
532  * add_device adds a device to the passed devinfo list.
533  */
534 void
535 add_device(dev_info_t **devinfop, dev_info_t *devinfo)
536 {
537         dev_info_t *dev;
538
539         if (*devinfop == NULL) {
540                 *devinfop = devinfo;
541                 return;
542         }
543
544         for (dev = *devinfop; dev->next != NULL; dev = dev->next)
545                 ;
546
547         dev->next = devinfo;
548 }
549
550 /*
551  * OK. We totally give up. Exit back to EFI with a sensible status so
552  * it can try the next option on the list.
553  */
554 static void
555 efi_panic(EFI_STATUS s, const char *fmt, ...)
556 {
557         va_list ap;
558
559         printf("panic: ");
560         va_start(ap, fmt);
561         vprintf(fmt, ap);
562         va_end(ap);
563         printf("\n");
564
565         BS->Exit(IH, s, 0, NULL);
566 }
567
568 void
569 putchar(int c)
570 {
571         CHAR16 buf[2];
572
573         if (c == '\n') {
574                 buf[0] = '\r';
575                 buf[1] = 0;
576                 ST->ConOut->OutputString(ST->ConOut, buf);
577         }
578         buf[0] = c;
579         buf[1] = 0;
580         ST->ConOut->OutputString(ST->ConOut, buf);
581 }