--- stand/efi/boot1/boot1.c.orig 2018-12-07 09:02:34.000000000 +0900 +++ stand/efi/boot1/boot1.c 2019-07-07 16:52:22.406821000 +0900 @@ -36,6 +36,8 @@ static void efi_panic(EFI_STATUS s, const char *fmt, ...) __dead2 __printflike(2, 3); +static EFI_STATUS try_boot(const boot_module_t *mod, dev_info_t *dev, void *loaderbuf, size_t loadersize); + static const boot_module_t *boot_modules[] = { #ifdef EFI_ZFS_BOOT @@ -79,26 +81,6 @@ } /* - * nodes_match returns TRUE if the imgpath isn't NULL and the nodes match, - * FALSE otherwise. - */ -static BOOLEAN -nodes_match(EFI_DEVICE_PATH *imgpath, EFI_DEVICE_PATH *devpath) -{ - size_t len; - - if (imgpath == NULL || imgpath->Type != devpath->Type || - imgpath->SubType != devpath->SubType) - return (FALSE); - - len = DevicePathNodeLength(imgpath); - if (len != DevicePathNodeLength(devpath)) - return (FALSE); - - return (memcmp(imgpath, devpath, (size_t)len) == 0); -} - -/* * device_paths_match returns TRUE if the imgpath isn't NULL and all nodes * in imgpath and devpath match up to their respective occurrences of a * media node, FALSE otherwise. @@ -115,7 +97,7 @@ IsDevicePathType(devpath, MEDIA_DEVICE_PATH)) return (TRUE); - if (!nodes_match(imgpath, devpath)) + if (!efi_devpath_match_node(imgpath, devpath)) return (FALSE); imgpath = NextDevicePathNode(imgpath); @@ -125,20 +107,253 @@ return (FALSE); } +/* quick and dirty work for boot partition menu */ + +#define NUM_DEV_LIST 35 +#define SELECT_TIMEOUT 10 + +typedef struct { + const boot_module_t *modp; + dev_info_t *devinfop; +} moddev_t; + +static moddev_t dev_list[NUM_DEV_LIST]; + +static char * +humanize_number_kmgt(UINT64 size) { + static char buf2[6]; + UINT64 size0; + + if (size < 1024) + sprintf(buf2, "%luB", size); + else if (size0 = (size*10+512)/1024, size0 < 100) + sprintf(buf2, "%lu.%luK", size0 / 10, size0 % 10); + else if (size0 = (size+512)/1024, size0 < 1024) + sprintf(buf2, "%luK", size0); + else if (size0 = (size*10/1024+512)/1024, size0 < 100) + sprintf(buf2, "%lu.%luM", size0 / 10, size0 % 10); + else if (size0 = (size/1024+512)/1024, size0 < 1024) + sprintf(buf2, "%luM", size0); + else if (size0 = (size*10/1024/1024+512)/1024, size0 < 100) + sprintf(buf2, "%lu.%luG", size0 / 10, size0 % 10); + else if (size0 = (size/1024/1024+512)/1024, size0 < 1024) + sprintf(buf2, "%luG", size0); + else if (size0 = (size*10/1024/1024/1024+512)/1024, size0 < 100) + sprintf(buf2, "%lu.%luT", size0 / 10, size0 % 10); + else + sprintf(buf2, "%luT", (size/1024/1024/1024+512)/1024); + return(buf2); +} + +static char +idx2char(int i) +{ + return (i<10 ? '0'+i : 'a'+i-10); +} + +static int +char2idx(char c) +{ + return ((c >= '0' && c <= '9') ? c - '0' : + (c >= 'A' && c <= 'Z') ? c - 'A' + 10 : + (c >= 'a' && c <= 'z') ? c - 'a' + 10 : + -1 ); +} + +static void +move_to_tol() +{ + SIMPLE_TEXT_OUTPUT_INTERFACE *conout; + int x,y; + + conout = ST->ConOut; + x = conout->Mode->CursorColumn; + y = conout->Mode->CursorRow; + conout->SetCursorPosition(conout, 0, y); +} + +int +getchar(void) +{ + EFI_INPUT_KEY key; + EFI_STATUS status; + UINTN junk; + + /* Try to read a key stroke. We wait for one if none is pending. */ + status = ST->ConIn->ReadKeyStroke(ST->ConIn, &key); + if (status == EFI_NOT_READY) { + BS->WaitForEvent(1, &ST->ConIn->WaitForKey, &junk); + status = ST->ConIn->ReadKeyStroke(ST->ConIn, &key); + } + switch (key.ScanCode) { + case 0x17: /* ESC */ + return (0x1b); /* esc */ + } + + /* this can return */ + return (key.UnicodeChar); +} + /* - * devpath_last returns the last non-path end node in devpath. + * Output of EFI_DEVICE_PATH_TO_TEXT_PROTOCOL is not easy to read. + " I hope this looks better. */ -static EFI_DEVICE_PATH * -devpath_last(EFI_DEVICE_PATH *devpath) +static int +mediapath_node_str(char *buf, size_t size, EFI_DEVICE_PATH *devpath) { + switch (devpath->Type) { + case MEDIA_DEVICE_PATH: + switch (devpath->SubType) { + case MEDIA_CDROM_DP: { + CDROM_DEVICE_PATH *cdrom; - while (!IsDevicePathEnd(NextDevicePathNode(devpath))) - devpath = NextDevicePathNode(devpath); + cdrom = (CDROM_DEVICE_PATH *)(void *)devpath; + return snprintf(buf, size, "cdrom(%x)", + cdrom->BootEntry); + } + case MEDIA_HARDDRIVE_DP: { + HARDDRIVE_DEVICE_PATH *hd; - return (devpath); + hd = (HARDDRIVE_DEVICE_PATH *)(void *)devpath; + return snprintf(buf, size, "hd(p%d) (%s)", + hd->PartitionNumber, + humanize_number_kmgt(hd->PartitionSize * 512)); + } + default: + return snprintf(buf, size, "media(0x%02x)", + devpath->SubType); + } + } + + return snprintf(buf, size, "type(0x%02x, 0x%02x)", devpath->Type, + devpath->SubType); } /* + * mediapath_str is convenience method which returns the text description of + * mediapath using a static buffer, so it isn't thread safe! + */ +char * +mediapath_str(EFI_DEVICE_PATH *devpath) +{ + static char buf[256]; + + mediapath_node_str(buf, sizeof(buf), devpath); + + return buf; +} + +static int +list_devices(void) +{ + UINTN i, j; + dev_info_t *dev; + const boot_module_t *mod; + + j = 0; + for (i = 0; i < NUM_BOOT_MODULES; i++) { + if (boot_modules[i] == NULL) + continue; + mod = boot_modules[i]; + for (dev = mod->devices(); dev != NULL; dev = dev->next) { + dev_list[j].devinfop = dev; + dev_list[j].modp = mod; + j++; + if (j >= NUM_DEV_LIST) + break; + } + } + + return (j); +} + + +static int +get_sel(int ndevs,int time_left) +{ + int i; + int c, n; + EFI_STATUS status; + EFI_EVENT timer; + EFI_EVENT events[2]; + EFI_DEVICE_PATH *mediapath; + UINTN idx; + dev_info_t *dev; + CHAR16 *text; + + status = BS->CreateEvent(EVT_TIMER, 0, 0, NULL, &timer); + if (status != EFI_SUCCESS) { + printf("Can't allocate timer event.\n"); + return (-1); + } + printf(" 0: AutoSelected Partition (Default): 0\n"); + for (i = 0; i < ndevs; i++) { + if (dev_list[i].devinfop->preferred == TRUE) + c = '*'; + else + c = ' '; + mediapath = efi_devpath_to_media_path( + dev_list[i].devinfop->devpath); + printf(" %c%c: %s: %s: %c\n", c, idx2char(i+1), + dev_list[i].modp->name, + mediapath_str(mediapath), + idx2char(i+1)); + } + /* One alpha-num selection only. Is this big enough ?? */ + BS->SetTimer(timer, TimerPeriodic, 10000000); + events[0] = timer; + events[1] = ST->ConIn->WaitForKey; + + while (1) { + if (time_left > 0) { + printf("Select from 0 to %c. Timeout in %2d seconds," + " [Space] to pause : ", + idx2char(ndevs), time_left); + } + status = BS->WaitForEvent(2, events, &idx); + if (status != EFI_SUCCESS) { + BS->CloseEvent(timer); + return (-1); + } + if (idx == 0) { + time_left--; + if (time_left <=0) { + printf("\nTimeout. " + "Partition is AutoSelected.\n"); + n = 0; + break; + } else { + move_to_tol(); + } + } + if (idx == 1) { + c = getchar(); + if ((c == '\n') || (c == '\r')) { + putchar('\n'); + n = 0; + break; + } else if ((time_left > 0) && (c == ' ')) { + BS->SetTimer(timer, TimerCancel, 0); + time_left = -1; + printf("\nTimer stopeed.\n Please Key in: "); + } + n = char2idx(c); + if ((n >= 0) && (n <= ndevs)) { + BS->SetTimer(timer, TimerCancel, 0); + time_left = -1; + printf("\n %c is selected.\n", c); + break; + } else { + /* Invalid charecter. */ + move_to_tol(); + } + } + }; + BS->CloseEvent(timer); + return (n); +} + +/* * load_loader attempts to load the loader image data. * * It tries each module and its respective devices, identified by mod->probe, @@ -173,32 +388,96 @@ return (EFI_NOT_FOUND); } +static EFI_STATUS +select_loader(const boot_module_t **modp, dev_info_t **devinfop, + void **bufp, size_t *bufsize) +{ + int n; + int time_left; + EFI_STATUS status; + dev_info_t *dev; + const boot_module_t *mod; + int ndevs; + + ndevs = list_devices(); + if ((ndevs <= 0) || (ndevs >= NUM_DEV_LIST)) { + return (EFI_NOT_FOUND); + } else if (ndevs == 1) { + /* Only one condidate. */ + *modp = mod = dev_list[0].modp; + *devinfop = dev = dev_list[0].devinfop; + return (mod->load(PATH_LOADER_EFI, dev,bufp, bufsize)); + } + + /* Two or more candidate exist. */ + + /* Reset the console input. */ + ST->ConIn->Reset(ST->ConIn, TRUE); + + time_left = SELECT_TIMEOUT; + while (1) { + n = get_sel(ndevs, time_left); + if (n < 0) { + return (EFI_NOT_FOUND); + } else if (n == 0) { /* AutoSelect */ + break; + } else { + mod = dev_list[n-1].modp; + dev = dev_list[n-1].devinfop; + status = mod->load(PATH_LOADER_EFI,dev, bufp, bufsize); + if (status == EFI_SUCCESS) { + break; + } + printf("Failed to load '%s'\n", PATH_LOADER_EFI); + printf("Please select again: "); + time_left = -1; + } + } + if (n == 0) { /* AutoSelect */ + status = load_loader(modp, devinfop, bufp, bufsize, TRUE); + if (status != EFI_SUCCESS) { + status = load_loader(modp, devinfop, bufp, bufsize, FALSE); + } + } else { + *modp = dev_list[n-1].modp; + *devinfop = dev_list[n-1].devinfop; + status = EFI_SUCCESS; + } + return (status); +} + +void +sel_boot(void) +{ + size_t loadersize; + void *loaderbuf = NULL; + dev_info_t *dev; + const boot_module_t *mod; + EFI_STATUS status; + + status = select_loader(&mod, &dev, &loaderbuf, &loadersize); + if (status != EFI_SUCCESS) { + printf("Failed to load '%s'\n", PATH_LOADER_EFI); + return; + } + + try_boot(mod, dev, loaderbuf, loadersize); +} + /* * try_boot only returns if it fails to load the loader. If it succeeds * it simply boots, otherwise it returns the status of last EFI call. */ -static EFI_STATUS -try_boot(void) +EFI_STATUS +try_boot(const boot_module_t *mod, dev_info_t *dev, void *loaderbuf, size_t loadersize) { - size_t bufsize, loadersize, cmdsize; - void *buf, *loaderbuf; + size_t bufsize, cmdsize; + void *buf; char *cmd; - dev_info_t *dev; - const boot_module_t *mod; EFI_HANDLE loaderhandle; EFI_LOADED_IMAGE *loaded_image; EFI_STATUS status; - status = load_loader(&mod, &dev, &loaderbuf, &loadersize, TRUE); - if (status != EFI_SUCCESS) { - status = load_loader(&mod, &dev, &loaderbuf, &loadersize, - FALSE); - if (status != EFI_SUCCESS) { - printf("Failed to load '%s'\n", PATH_LOADER_EFI); - return (status); - } - } - /* * Read in and parse the command line from /boot.config or /boot/config, * if present. We'll pass it the next stage via a simple ASCII @@ -225,7 +504,7 @@ buf = NULL; } - if ((status = BS->LoadImage(TRUE, IH, devpath_last(dev->devpath), + if ((status = BS->LoadImage(TRUE, IH, efi_devpath_last_node(dev->devpath), loaderbuf, loadersize, &loaderhandle)) != EFI_SUCCESS) { printf("Failed to load image provided by %s, size: %zu, (%lu)\n", mod->name, loadersize, EFI_ERROR_CODE(status)); @@ -522,7 +801,7 @@ boot_modules[i]->status(); } - try_boot(); + sel_boot(); /* If we get here, we're out of luck... */ efi_panic(EFI_LOAD_ERROR, "No bootable partitions found!");