On 4/27/20 11:48 AM, AKASHI Takahiro wrote: > Capsule data can be loaded into the system either via UpdateCapsule > runtime service or files on a file system (of boot device). > The latter case is called "capsules on disk", and actual updates will > take place at the next boot time. > > In this commit, we will support capsule on disk mechanism. > > Please note that U-Boot itself has no notion of "boot device" and > all the capsule files to be executed will be detected only if they > are located in a specific directory, \EFI\UpdateCapsule, on a device > that is identified as a boot device by "BootXXXX" variables. > > Signed-off-by: AKASHI Takahiro <takahiro.aka...@linaro.org> > --- > common/main.c | 4 + > include/efi_loader.h | 16 ++ > lib/efi_loader/Kconfig | 22 ++ > lib/efi_loader/efi_capsule.c | 449 +++++++++++++++++++++++++++++++++++ > lib/efi_loader/efi_setup.c | 9 + > 5 files changed, 500 insertions(+) > > diff --git a/common/main.c b/common/main.c > index 06d7ff56d60c..877ae63b708d 100644 > --- a/common/main.c > +++ b/common/main.c > @@ -14,6 +14,7 @@ > #include <env.h> > #include <init.h> > #include <version.h> > +#include <efi_loader.h> > > static void run_preboot_environment_command(void) > { > @@ -51,6 +52,9 @@ void main_loop(void) > if (IS_ENABLED(CONFIG_UPDATE_TFTP)) > update_tftp(0UL, NULL, NULL); > > + if (IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK_EARLY)) > + efi_launch_capsules(); > +
Can't we move this to efi_init_obj_list() and do away with CONFIG_EFI_CAPSULE_ON_DISK_EARLY? > s = bootdelay_process(); > if (cli_process_fdt(&s)) > cli_secure_boot_cmd(s); > diff --git a/include/efi_loader.h b/include/efi_loader.h > index 19ffc027c171..d49ebcad53ec 100644 > --- a/include/efi_loader.h > +++ b/include/efi_loader.h > @@ -793,6 +793,18 @@ efi_status_t EFIAPI efi_query_capsule_caps( > u32 *reset_type); > #endif /* CONFIG_EFI_HAVE_CAPSULE_SUPPORT */ > > +#ifdef CONFIG_EFI_CAPSULE_ON_DISK > +#define EFI_CAPSULE_DIR L"\\EFI\\UpdateCapsule\\" > + > +/* Hook at initialization */ > +efi_status_t efi_launch_capsules(void); > +#else > +static inline efi_status_t efi_launch_capsules(void) > +{ > + return EFI_SUCCESS; > +} > +#endif /* CONFIG_EFI_CAPSULE_ON_DISK */ > + > #else /* CONFIG_IS_ENABLED(EFI_LOADER) */ > > /* Without CONFIG_EFI_LOADER we don't have a runtime section, stub it out */ > @@ -809,6 +821,10 @@ static inline void efi_set_bootdev(const char *dev, > const char *devnr, > const char *path) { } > static inline void efi_net_set_dhcp_ack(void *pkt, int len) { } > static inline void efi_print_image_infos(void *pc) { } > +static inline efi_status_t efi_launch_capsules(void) > +{ > + return EFI_SUCCESS; > +} > > #endif /* CONFIG_IS_ENABLED(EFI_LOADER) */ > > diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig > index e2b08251f26a..b48b95a32e03 100644 > --- a/lib/efi_loader/Kconfig > +++ b/lib/efi_loader/Kconfig > @@ -56,6 +56,28 @@ config EFI_RUNTIME_UPDATE_CAPSULE > Select this option if you want to use UpdateCapsule and > QueryCapsuleCapabilities API's. > > +config EFI_CAPSULE_ON_DISK > + bool "Enable capsule-on-disk support" > + select EFI_HAVE_CAPSULE_SUPPORT > + default n > + help > + Select this option if you want to use capsule-on-disk feature, > + that is, capsules can be fetched and executed from files > + under a specific directory on UEFI system partition instead of > + via UpdateCapsule API. > + > +config EFI_CAPSULE_ON_DISK_EARLY > + bool "Initiate capsule-on-disk at U-Boot boottime" > + depends on EFI_CAPSULE_ON_DISK > + default y > + select EFI_SETUP_EARLY > + help > + Normally, without this option enabled, capsules will be > + executed only at the first time of invoking one of efi command. > + If this option is enabled, capsules will be enforced to be > + executed as part of U-Boot initialisation so that they will > + surely take place whatever is set to distro_bootcmd. Why do we need this Kconfig variable if we have EFI_SETUP_EARLY available? > + > config EFI_DEVICE_PATH_TO_TEXT > bool "Device path to text protocol" > default y > diff --git a/lib/efi_loader/efi_capsule.c b/lib/efi_loader/efi_capsule.c > index fb104bb92a6c..938129a41934 100644 > --- a/lib/efi_loader/efi_capsule.c > +++ b/lib/efi_loader/efi_capsule.c > @@ -10,10 +10,16 @@ > #include <efi_loader.h> > #include <fs.h> > #include <malloc.h> > +#include <mapmem.h> > #include <sort.h> > > const efi_guid_t efi_guid_capsule_report = EFI_CAPSULE_REPORT_GUID; > > +#ifdef CONFIG_EFI_CAPSULE_ON_DISK > +/* for file system access */ > +static struct efi_file_handle *bootdev_root; > +#endif > + > static __maybe_unused int get_last_capsule(void) > { > u16 value16[11]; /* "CapsuleXXXX": non-null-terminated */ > @@ -151,3 +157,446 @@ efi_status_t EFIAPI efi_query_capsule_caps( > out: > return EFI_EXIT(ret); > } > + > +#ifdef CONFIG_EFI_CAPSULE_ON_DISK > +static efi_status_t get_dp_device(u16 *boot_var, > + struct efi_device_path **device_dp) > +{ > + void *buf = NULL; > + efi_uintn_t size; > + struct efi_load_option lo; > + struct efi_device_path *file_dp; > + efi_status_t ret; > + > + size = 0; > + ret = EFI_CALL(efi_get_variable(boot_var, &efi_global_variable_guid, > + NULL, &size, NULL)); > + if (ret == EFI_BUFFER_TOO_SMALL) { > + buf = malloc(size); > + if (!buf) > + return EFI_OUT_OF_RESOURCES; > + ret = EFI_CALL(efi_get_variable(boot_var, > + &efi_global_variable_guid, > + NULL, &size, buf)); > + } > + if (ret != EFI_SUCCESS) > + return ret; > + > + efi_deserialize_load_option(&lo, buf); > + > + if (lo.attributes & LOAD_OPTION_ACTIVE) { > + efi_dp_split_file_path(lo.file_path, device_dp, &file_dp); > + efi_free_pool(file_dp); > + > + ret = EFI_SUCCESS; > + } else { > + ret = EFI_NOT_FOUND; > + } > + > + free(buf); > + > + return ret; > +} > + > +static bool device_is_present_and_system_part(struct efi_device_path *dp) > +{ > + efi_handle_t handle; > + > + handle = efi_dp_find_obj(dp, NULL); > + if (!handle) > + return false; > + > + return efi_disk_is_system_part(handle); > +} > + > +static efi_status_t find_boot_device(void) > +{ > + char boot_var[9]; > + u16 boot_var16[9], *p, bootnext, *boot_order = NULL; > + efi_uintn_t size; > + int i, num; > + struct efi_simple_file_system_protocol *volume; > + struct efi_device_path *boot_dev = NULL; > + efi_status_t ret; > + > + /* find active boot device in BootNext */ > + bootnext = 0; > + size = sizeof(bootnext); > + ret = EFI_CALL(efi_get_variable(L"BootNext", > + (efi_guid_t *)&efi_global_variable_guid, > + NULL, &size, &bootnext)); > + if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL) { > + /* BootNext does exist here */ > + if (ret == EFI_BUFFER_TOO_SMALL || size != sizeof(u16)) { > + printf("BootNext must be 16-bit integer\n"); > + goto skip; > + } > + sprintf((char *)boot_var, "Boot%04X", bootnext); > + p = boot_var16; > + utf8_utf16_strcpy(&p, boot_var); > + > + ret = get_dp_device(boot_var16, &boot_dev); > + if (ret == EFI_SUCCESS) { > + if (device_is_present_and_system_part(boot_dev)) { > + goto out; > + } else { > + efi_free_pool(boot_dev); > + boot_dev = NULL; > + } > + } > + } > + > +skip: > + /* find active boot device in BootOrder */ > + size = 0; > + ret = EFI_CALL(efi_get_variable(L"BootOrder", &efi_global_variable_guid, > + NULL, &size, NULL)); > + if (ret == EFI_BUFFER_TOO_SMALL) { > + boot_order = malloc(size); > + if (!boot_order) { > + ret = EFI_OUT_OF_RESOURCES; > + goto out; > + } > + > + ret = EFI_CALL(efi_get_variable( > + L"BootOrder", &efi_global_variable_guid, > + NULL, &size, boot_order)); > + } > + if (ret != EFI_SUCCESS) > + goto out; > + > + /* check in higher order */ > + num = size / sizeof(u16); > + for (i = 0; i < num; i++) { > + sprintf((char *)boot_var, "Boot%04X", boot_order[i]); > + p = boot_var16; > + utf8_utf16_strcpy(&p, boot_var); > + ret = get_dp_device(boot_var16, &boot_dev); > + if (ret != EFI_SUCCESS) > + continue; > + > + if (device_is_present_and_system_part(boot_dev)) > + break; > + > + efi_free_pool(boot_dev); > + boot_dev = NULL; > + } > +out: > + if (boot_dev) { > + u16 *path_str; > + > + path_str = efi_dp_str(boot_dev); > + EFI_PRINT("EFI Capsule: bootdev is %ls\n", path_str); > + efi_free_pool(path_str); > + > + volume = efi_fs_from_path(boot_dev); > + if (!volume) > + ret = EFI_DEVICE_ERROR; > + else > + ret = EFI_CALL(volume->open_volume(volume, > + &bootdev_root)); > + efi_free_pool(boot_dev); > + } else { > + ret = EFI_NOT_FOUND; > + } > + free(boot_order); > + > + return ret; > +} > + > +/* > + * Traverse a capsule directory in boot device > + * Called by initialization code, and returns an array of capsule file > + * names in @files > + */ > +static efi_status_t efi_capsule_scan_dir(u16 ***files, int *num) > +{ > + struct efi_file_handle *dirh; > + struct efi_file_info *dirent; > + efi_uintn_t dirent_size, tmp_size; > + int count; > + u16 **tmp_files; > + efi_status_t ret; > + > + ret = find_boot_device(); > + if (ret == EFI_NOT_FOUND) { > + EFI_PRINT("EFI Capsule: bootdev is not set\n"); > + *num = 0; > + return EFI_SUCCESS; > + } else if (ret != EFI_SUCCESS) { > + return EFI_DEVICE_ERROR; > + } > + > + /* count capsule files */ > + ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh, > + EFI_CAPSULE_DIR, > + EFI_FILE_MODE_READ, 0)); > + if (ret != EFI_SUCCESS) { > + *num = 0; > + return EFI_SUCCESS; > + } > + > + dirent_size = 256; > + dirent = malloc(dirent_size); > + if (!dirent) > + return EFI_OUT_OF_RESOURCES; > + > + count = 0; > + while (1) { > + tmp_size = dirent_size; > + ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent)); > + if (ret == EFI_BUFFER_TOO_SMALL) { > + dirent = realloc(dirent, tmp_size); > + if (!dirent) { > + ret = EFI_OUT_OF_RESOURCES; > + goto err; > + } > + dirent_size = tmp_size; > + ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent)); > + } > + if (ret != EFI_SUCCESS) > + goto err; > + if (!tmp_size) > + break; > + > + if (!(dirent->attribute & EFI_FILE_DIRECTORY) && > + u16_strcmp(dirent->file_name, L".") && > + u16_strcmp(dirent->file_name, L"..")) > + count++; > + } > + > + ret = EFI_CALL((*dirh->setpos)(dirh, 0)); > + if (ret != EFI_SUCCESS) > + goto err; > + > + /* make a list */ > + tmp_files = malloc(count * sizeof(*files)); > + if (!tmp_files) { > + ret = EFI_OUT_OF_RESOURCES; > + goto err; > + } > + > + count = 0; > + while (1) { > + tmp_size = dirent_size; > + ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent)); > + if (ret != EFI_SUCCESS) > + goto err; > + if (!tmp_size) > + break; > + > + if (!(dirent->attribute & EFI_FILE_DIRECTORY) && > + u16_strcmp(dirent->file_name, L".") && > + u16_strcmp(dirent->file_name, L"..")) > + tmp_files[count++] = u16_strdup(dirent->file_name); > + } > + /* ignore an error */ > + EFI_CALL((*dirh->close)(dirh)); > + > + /* in ascii order */ > + /* FIXME: u16 version of strcasecmp */ > + qsort(tmp_files, count, sizeof(*tmp_files), > + (int (*)(const void *, const void *))strcasecmp); > + *files = tmp_files; > + *num = count; > + ret = EFI_SUCCESS; > +err: > + free(dirent); > + > + return ret; > +} > + > +/* > + * Read in a capsule file > + */ > +static efi_status_t efi_capsule_read_file(u16 *filename, > + struct efi_capsule_header **capsule) > +{ > + struct efi_file_handle *dirh, *fh; > + struct efi_file_info *file_info = NULL; > + struct efi_capsule_header *buf = NULL; > + efi_uintn_t size; > + efi_status_t ret; > + > + ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh, > + EFI_CAPSULE_DIR, > + EFI_FILE_MODE_READ, 0)); > + if (ret != EFI_SUCCESS) > + return ret; > + ret = EFI_CALL((*dirh->open)(dirh, &fh, filename, > + EFI_FILE_MODE_READ, 0)); > + /* ignore an error */ > + EFI_CALL((*dirh->close)(dirh)); > + if (ret != EFI_SUCCESS) > + return ret; > + > + /* file size */ > + size = 0; > + ret = EFI_CALL((*fh->getinfo)(fh, &efi_file_info_guid, > + &size, file_info)); > + if (ret == EFI_BUFFER_TOO_SMALL) { > + file_info = malloc(size); > + if (!file_info) { > + ret = EFI_OUT_OF_RESOURCES; > + goto err; > + } > + ret = EFI_CALL((*fh->getinfo)(fh, &efi_file_info_guid, > + &size, file_info)); > + } > + if (ret != EFI_SUCCESS) > + goto err; > + size = file_info->file_size; > + free(file_info); > + buf = malloc(size); > + if (!buf) { > + ret = EFI_OUT_OF_RESOURCES; > + goto err; > + } > + > + /* fetch data */ > + ret = EFI_CALL((*fh->read)(fh, &size, buf)); > + if (ret == EFI_SUCCESS) { > + if (size >= buf->capsule_image_size) { > + *capsule = buf; > + } else { > + free(buf); > + ret = EFI_INVALID_PARAMETER; > + } > + } else { > + free(buf); > + } > +err: > + EFI_CALL((*fh->close)(fh)); > + > + return ret; > +} > + > +static efi_status_t efi_capsule_delete_file(u16 *filename) > +{ > + struct efi_file_handle *dirh, *fh; > + efi_status_t ret; > + > + ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh, > + EFI_CAPSULE_DIR, > + EFI_FILE_MODE_READ, 0)); > + if (ret != EFI_SUCCESS) > + return ret; > + ret = EFI_CALL((*dirh->open)(dirh, &fh, filename, > + EFI_FILE_MODE_READ, 0)); > + /* ignore an error */ > + EFI_CALL((*dirh->close)(dirh)); > + > + ret = EFI_CALL((*fh->delete)(fh)); > + > + return ret; > +} > + > +static void efi_capsule_scan_done(void) > +{ > + EFI_CALL((*bootdev_root->close)(bootdev_root)); > + bootdev_root = NULL; > +} > + > +efi_status_t __weak arch_efi_load_capsule_drivers(void) > +{ > + return EFI_SUCCESS; > +} > + > +/* > + * Launch all the capsules in system at boot time > + * > + * Called by efi init code > + */ Where are the function descriptions? https://www.kernel.org/doc/html/latest/doc-guide/kernel-doc.html#function-documentation Best regards Heinrich > +efi_status_t efi_launch_capsules(void) > +{ > + u64 os_indications; > + efi_uintn_t size; > + struct efi_capsule_header *capsule = NULL; > + u16 **files; > + int nfiles, num, i; > + char variable_name[12]; > + u16 variable_name16[12], *p; > + efi_status_t ret; > + > + size = sizeof(os_indications); > + ret = EFI_CALL(efi_get_variable(L"OsIndications", > + &efi_global_variable_guid, > + NULL, &size, &os_indications)); > + if (ret != EFI_SUCCESS || > + !(os_indications > + & EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED)) > + return EFI_SUCCESS; > + > + num = get_last_capsule(); > + > + /* Load capsule drivers */ > + ret = arch_efi_load_capsule_drivers(); > + if (ret != EFI_SUCCESS) > + return ret; > + > + /* > + * Find capsules on disk. > + * All the capsules are collected at the beginning because > + * capsule files will be removed instantly. > + */ > + nfiles = 0; > + files = NULL; > + ret = efi_capsule_scan_dir(&files, &nfiles); > + if (ret != EFI_SUCCESS) > + return ret; > + if (!nfiles) > + return EFI_SUCCESS; > + > + /* Launch capsules */ > + for (i = 0, ++num; i < nfiles; i++, num++) { > + EFI_PRINT("capsule from %ls ...\n", files[i]); > + if (num > 0xffff) > + num = 0; > + ret = efi_capsule_read_file(files[i], &capsule); > + if (ret == EFI_SUCCESS) { > + ret = EFI_CALL(efi_update_capsule(&capsule, 1, 0)); > + if (ret != EFI_SUCCESS) > + printf("EFI Capsule update failed at %ls\n", > + files[i]); > + > + free(capsule); > + } else { > + printf("EFI: reading capsule failed: %ls\n", > + files[i]); > + } > + /* create CapsuleXXXX */ > + set_capsule_result(num, capsule, ret); > + > + /* delete a capsule either in case of success or failure */ > + ret = efi_capsule_delete_file(files[i]); > + if (ret != EFI_SUCCESS) > + printf("EFI: deleting a capsule file failed: %ls\n", > + files[i]); > + } > + efi_capsule_scan_done(); > + > + for (i = 0; i < nfiles; i++) > + free(files[i]); > + free(files); > + > + /* CapsuleMax */ > + p = variable_name16; > + utf8_utf16_strncpy(&p, "CapsuleFFFF", 11); > + EFI_CALL(efi_set_variable(L"CapsuleMax", &efi_guid_capsule_report, > + EFI_VARIABLE_BOOTSERVICE_ACCESS | > + EFI_VARIABLE_RUNTIME_ACCESS, > + 22, variable_name16)); > + > + /* CapsuleLast */ > + sprintf(variable_name, "Capsule%04X", num - 1); > + p = variable_name16; > + utf8_utf16_strncpy(&p, variable_name, 11); > + EFI_CALL(efi_set_variable(L"CapsuleLast", &efi_guid_capsule_report, > + EFI_VARIABLE_NON_VOLATILE | > + EFI_VARIABLE_BOOTSERVICE_ACCESS | > + EFI_VARIABLE_RUNTIME_ACCESS, > + 22, variable_name16)); > + > + return ret; > +} > +#endif /* CONFIG_EFI_CAPSULE_ON_DISK */ > diff --git a/lib/efi_loader/efi_setup.c b/lib/efi_loader/efi_setup.c > index 8fe378bbfdfc..bb759976102a 100644 > --- a/lib/efi_loader/efi_setup.c > +++ b/lib/efi_loader/efi_setup.c > @@ -129,6 +129,10 @@ static efi_status_t efi_init_os_indications(void) > #ifdef CONFIG_EFI_HAVE_CAPSULE_SUPPORT > os_indications_supported |= > EFI_OS_INDICATIONS_CAPSULE_RESULT_VAR_SUPPORTED; > +#endif > +#ifdef CONFIG_EFI_CAPSULE_ON_DISK > + os_indications_supported |= > + EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED; > #endif > return EFI_CALL(efi_set_variable(L"OsIndicationsSupported", > &efi_global_variable_guid, > @@ -239,6 +243,11 @@ efi_status_t efi_init_obj_list(void) > if (ret != EFI_SUCCESS) > goto out; > > +#if defined(CONFIG_EFI_CAPSULE_ON_DISK) && \ > + !defined(CONFIG_EFI_CAPSULE_ON_DISK_EARLY) > + /* Execute capsules after reboot */ > + ret = efi_launch_capsules(); > +#endif > out: > efi_obj_list_initialized = ret; > return ret; >