Index: stand/efi/include/efi.h =================================================================== --- stand/efi/include/efi.h +++ stand/efi/include/efi.h @@ -69,7 +69,18 @@ /* * FreeBSD UUID */ +/* + * FreeBSD specific boot variables. + */ #define FREEBSD_BOOT_VAR_GUID \ { 0xCFEE69AD, 0xA0DE, 0x47A9, {0x93, 0xA8, 0xF6, 0x31, 0x06, 0xF8, 0xAE, 0x99} } +/* + * Special FreeBSD specific GUID that translates all set variables in this GUID + * to boot loader environment variables early. This translation is once at the start + * of loader.efi. There's no export at the end of the boot process to persis these + * The user must set theset up. + */ +#define FREEBSD_LOADER_ENV_GUID \ + { 0xd2bf216b, 0xbcc0, 0x402c, {0xb0, 0xed, 0xfe, 0x62, 0xcd, 0xcc, 0xd3, 0x32} } #endif Index: stand/efi/include/efichar.h =================================================================== --- stand/efi/include/efichar.h +++ stand/efi/include/efichar.h @@ -38,5 +38,6 @@ int ucs2_to_utf8(const efi_char *, char **); int utf8_to_ucs2(const char *, efi_char **, size_t *); int ucs2len(const efi_char *); +int is_ascii(uint8_t *data, size_t datasz); #endif /* _BOOT_EFI_EFICHAR_H_ */ Index: stand/efi/libefi/efichar.c =================================================================== --- stand/efi/libefi/efichar.c +++ stand/efi/libefi/efichar.c @@ -202,3 +202,20 @@ } return (EILSEQ); } + +int +is_ascii(uint8_t *data, size_t datasz) +{ + UINTN i; + + for (i = 0; i < datasz; i++) { + /* + * Quick hack to see if this ascii-ish string is printable + * range plus tab, cr and lf. + */ + if ((data[i] < 32 || data[i] > 126) + && data[i] != 9 && data[i] != 10 && data[i] != 13) + return (0); + } + return (1); +} Index: stand/efi/libefi/env.c =================================================================== --- stand/efi/libefi/env.c +++ stand/efi/libefi/env.c @@ -348,23 +348,10 @@ efi_print_other_value(uint8_t *data, UINTN datasz) { UINTN i; - bool is_ascii = true; printf(" = "); - for (i = 0; i < datasz - 1; i++) { - /* - * Quick hack to see if this ascii-ish string is printable - * range plus tab, cr and lf. - */ - if ((data[i] < 32 || data[i] > 126) - && data[i] != 9 && data[i] != 10 && data[i] != 13) { - is_ascii = false; - break; - } - } - if (data[datasz - 1] != '\0') - is_ascii = false; - if (is_ascii == true) { + + if (is_ascii(data, datasz - 1) && data[datasz - 1] == '\0') { printf("%s", data); if (pager_output("\n")) return (CMD_WARN); Index: stand/efi/loader/main.c =================================================================== --- stand/efi/loader/main.c +++ stand/efi/loader/main.c @@ -4,7 +4,7 @@ * All rights reserved. * * Copyright (c) 2018 Netflix, Inc. - * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -46,6 +46,7 @@ #include #include +#include #include #include @@ -73,6 +74,7 @@ EFI_GUID debugimg = DEBUG_IMAGE_INFO_TABLE_GUID; EFI_GUID fdtdtb = FDT_TABLE_GUID; EFI_GUID inputid = SIMPLE_TEXT_INPUT_PROTOCOL; +EFI_GUID ldr_env = FREEBSD_LOADER_ENV_GUID; /* * Number of seconds to wait for a keystroke before exiting with failure @@ -478,7 +480,26 @@ } /* - * Second choice: If we can find out image boot_info, and there's + * Second choice: If rootdev_uefi is set, translate that UEFI device + * path to the loader's internal name and use that. + */ + do { + rootdev = getenv("rootdev_uefi"); + if (rootdev == NULL) + break; + devpath = efi_name_to_devpath(rootdev); + if (devpath == NULL) + break; + dp = efiblk_get_pdinfo_by_device_path(devpath); + efi_devpath_free(devpath); + if (dp == NULL) + break; + set_currdev_pdinfo(dp); + return (0); + } while (0); + + /* + * Third choice: If we can find out image boot_info, and there's * a follow-on boot image in that boot_info, use that. In this * case root will be the partition specified in that image and * we'll load the kernel specified by the file path. Should there @@ -742,6 +763,129 @@ return (how); } +void +parse_loader_efi_config(EFI_HANDLE h, const char *env_fn) +{ + pdinfo_t *dp; + struct stat st; + int fd = -1; + char *env = NULL; + + dp = efiblk_get_pdinfo_by_handle(h); + if (dp == NULL) + return; + set_currdev_pdinfo(dp); + if (stat(env_fn, &st) != 0) + return; + fd = open(env_fn, O_RDONLY); + if (fd == -1) + return; + env = malloc(st.st_size + 1); + if (env == NULL) + goto out; + if (read(fd, env, st.st_size) != st.st_size) + goto out; + env[st.st_size] = '\0'; + boot_parse_cmdline(env); +out: + free(env); + close(fd); +} + +/* + * All variables with a GUID of FREEBSD_LOADER_ENV_GUID are added to + * the environment. The variables are converted from CHAR16 to utf8, + * though only ascii env values are well supported. Only string + * values are supported as well, since the unix envirnment interfaces + * the loader derives from is sting based. To simplify the + * implementation, only ASCII strings are supported. + */ +static void +env_from_uefi_env(void) +{ + EFI_STATUS status; + EFI_GUID varguid = ZERO_GUID; + UINTN varalloc, varsz, valuealloc, valuesz; + CHAR16 *varname, *newnm; + char *key, *value, *newval; + uint32_t s; + + varalloc = 1024; + varname = malloc(varalloc); + if (varname == NULL) { + printf("Can't allocate memory to get variables\n"); + return; + } + varname[0] = 0; + + /* NB: valuealloc is one less than true len for NUL */ + valuealloc = 1023; + value = malloc(valuealloc + 1); + if (value == NULL) { + printf("Can't allocate memory to get variables\n"); + free(varname); + return; + } + while (1) { + varsz = varalloc; + status = RS->GetNextVariableName(&varsz, varname, &varguid); + if (status == EFI_BUFFER_TOO_SMALL) { + newnm = realloc(varname, varsz); + if (newnm == NULL) + break; + varalloc = varsz; + varname = newnm; + continue; + } + if (status == EFI_NOT_FOUND) + break; + if (status != EFI_SUCCESS) + break; + uuid_equal((uuid_t *)&varguid, (uuid_t *)&ldr_env, &s); + if (s != uuid_s_ok) + continue; + if (ucs2_to_utf8(varname, &key) != 0) + continue; + if (!is_ascii(key, strlen(key))) { + free(key); + continue; + } + again: + valuesz = valuealloc; + status = efi_getenv(&varguid, key, value, &valuesz); + if (status == EFI_BUFFER_TOO_SMALL) { + newval = realloc(value, valuesz + 1); + if (newval == NULL) { + free(key); + break; + } + valuealloc = valuesz; + value = newval; + goto again; + } else if (status != EFI_SUCCESS) { + free(key); + continue; + } + + /* + * Accept either a ASCII string (all characters printable ascii) or an + * ASCII string witht he last character a NUL. Valid UCS2 strings are + * not accepted, nor converted. + */ + if (!is_ascii(value, valuesz - 1) || + (value[valuesz - 1] != '\0' && + !is_ascii(value + valuesz - 1, 1))) { + free(key); + continue; + } + value[valuesz] = '\0'; + env_setenv(key, EV_VOLATILE, value, NULL, NULL); + free(key); + } + free(varname); + free(value); +} + EFI_STATUS main(int argc, CHAR16 *argv[]) { @@ -766,6 +910,12 @@ archsw.arch_readin = efi_readin; archsw.arch_zfs_probe = efi_zfs_probe; + /* Assume the efi console, unless overriden */ + setenv("console", "efi", 1); + + /* Bring in the loader environment from UEFI vars */ + env_from_uefi_env(); + /* Get our loaded image protocol interface structure. */ BS->HandleProtocol(IH, &imgid, (VOID**)&boot_img); @@ -796,6 +946,34 @@ howto &= ~RB_PROBE; uhowto = parse_uefi_con_out(); + /* + * Scan the BLOCK IO MEDIA handles then + * march through the device switch probing for things. + */ + i = efipart_inithandles(); + if (i != 0 && i != ENOENT) { + printf("efipart_inithandles failed with ERRNO %d, expect " + "failures\n", i); + } + + for (i = 0; devsw[i] != NULL; i++) + if (devsw[i]->dv_init != NULL) + (devsw[i]->dv_init)(); + + /* + * Read additional environment variables from the boot device's + * /efi/freebsd/loader.env file. Any environment variable may be set + * there, which are subdly different than loader.conf variables. Only + * the 'simple' ones may be set so things like foo_laod="YES" won't work + * for two reasons. First, the parser is simplisitc and doesn't grok + * quotes. Second, because the variables that cause an action to happen + * are parsed by the lua, 4th or whatever code that's not yet + * loaded. This is relative to the root directory when loader.efi is + * loaded off the UFS root drive (when chain booted), or from the ESP + * when directly loaded by the BIOS. + */ + parse_loader_efi_config(img->DeviceHandle, "/efi/freebsd/loader.env"); + /* * We now have two notions of console. howto should be viewed as * overrides. If console is already set, don't set it again. @@ -852,20 +1030,6 @@ if ((s = getenv("fail_timeout")) != NULL) fail_timeout = strtol(s, NULL, 10); - /* - * Scan the BLOCK IO MEDIA handles then - * march through the device switch probing for things. - */ - i = efipart_inithandles(); - if (i != 0 && i != ENOENT) { - printf("efipart_inithandles failed with ERRNO %d, expect " - "failures\n", i); - } - - for (i = 0; devsw[i] != NULL; i++) - if (devsw[i]->dv_init != NULL) - (devsw[i]->dv_init)(); - printf("%s\n", bootprog_info); printf(" Command line arguments:"); for (i = 0; i < argc; i++) @@ -878,8 +1042,6 @@ ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); printf(" Console: %s (%#x)\n", getenv("console"), howto); - - /* Determine the devpath of our image so we can prefer it. */ text = efi_devpath_name(boot_img->FilePath); if (text != NULL) { @@ -898,36 +1060,41 @@ } } - uefi_boot_mgr = true; - boot_current = 0; - sz = sizeof(boot_current); - rv = efi_global_getenv("BootCurrent", &boot_current, &sz); - if (rv == EFI_SUCCESS) - printf(" BootCurrent: %04x\n", boot_current); - else { - boot_current = 0xffff; + if (getenv("uefi_ignore_boot_mgr") != NULL) { + printf(" Ignoring UEFI boot manager\n"); uefi_boot_mgr = false; - } + } else { + uefi_boot_mgr = true; + boot_current = 0; + sz = sizeof(boot_current); + rv = efi_global_getenv("BootCurrent", &boot_current, &sz); + if (rv == EFI_SUCCESS) + printf(" BootCurrent: %04x\n", boot_current); + else { + boot_current = 0xffff; + uefi_boot_mgr = false; + } - sz = sizeof(boot_order); - rv = efi_global_getenv("BootOrder", &boot_order, &sz); - if (rv == EFI_SUCCESS) { - printf(" BootOrder:"); - for (i = 0; i < sz / sizeof(boot_order[0]); i++) - printf(" %04x%s", boot_order[i], - boot_order[i] == boot_current ? "[*]" : ""); - printf("\n"); - is_last = boot_order[(sz / sizeof(boot_order[0])) - 1] == boot_current; - bosz = sz; - } else if (uefi_boot_mgr) { - /* - * u-boot doesn't set BootOrder, but otherwise participates in the - * boot manager protocol. So we fake it here and don't consider it - * a failure. - */ - bosz = sizeof(boot_order[0]); - boot_order[0] = boot_current; - is_last = true; + sz = sizeof(boot_order); + rv = efi_global_getenv("BootOrder", &boot_order, &sz); + if (rv == EFI_SUCCESS) { + printf(" BootOrder:"); + for (i = 0; i < sz / sizeof(boot_order[0]); i++) + printf(" %04x%s", boot_order[i], + boot_order[i] == boot_current ? "[*]" : ""); + printf("\n"); + is_last = boot_order[(sz / sizeof(boot_order[0])) - 1] == boot_current; + bosz = sz; + } else if (uefi_boot_mgr) { + /* + * u-boot doesn't set BootOrder, but otherwise participates in the + * boot manager protocol. So we fake it here and don't consider it + * a failure. + */ + bosz = sizeof(boot_order[0]); + boot_order[0] = boot_current; + is_last = true; + } } /* @@ -976,7 +1143,8 @@ * to try something different. */ if (find_currdev(uefi_boot_mgr, is_last, boot_info, bisz) != 0) - if (!interactive_interrupt("Failed to find bootable partition")) + if (uefi_boot_mgr && + !interactive_interrupt("Failed to find bootable partition")) return (EFI_NOT_FOUND); efi_init_environment();