Message ID | 20200427094829.1140-5-takahiro.akashi@linaro.org |
---|---|
State | New |
Headers | show |
Series | efi_loader: add capsule update support | expand |
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.akashi at 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; >
Heinrich, On Mon, Apr 27, 2020 at 10:28:35PM +0200, Heinrich Schuchardt wrote: > 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.akashi at 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? With CONFIG_EFI_CAPSULE_ON_DISK_EARLY disabled, efi_launch_capsules() will be called in efi_init_obj_list() as you expect. See the code below in efi_setup.c. > > 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? Good point. My intent here was to split efi_launch_capsules() from efi_init_obj_list() so that users can start UEFI early for some reason without enabling capsule feature in the future. EFI_SETUP_EARLY is the hook for that. > > + > > 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? Okay. Thanks, -Takahiro Akashi > 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; > > >
On Tue, 28 Apr 2020 at 05:58, AKASHI Takahiro <takahiro.akashi at linaro.org> wrote: > Heinrich, > > On Mon, Apr 27, 2020 at 10:28:35PM +0200, Heinrich Schuchardt wrote: > > 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.akashi at 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? > > With CONFIG_EFI_CAPSULE_ON_DISK_EARLY disabled, > efi_launch_capsules() will be called in efi_init_obj_list() > as you expect. See the code below in efi_setup.c. > Instead of calling efi_launch_capsules in efi_init_obj_list, can we invoke the function explicitly through a dedicated command line, under the 'efidebug capsule' class of commands. I think that would be a cleaner approach, since efi_init_obj_list gets called for a lot of efi functions, which are unrelated to capsule update. -sughosh
On 4/30/20 2:52 PM, Sughosh Ganu wrote: > > On Tue, 28 Apr 2020 at 05:58, AKASHI Takahiro > <takahiro.akashi at linaro.org <mailto:takahiro.akashi at linaro.org>> wrote: > > Heinrich, > > On Mon, Apr 27, 2020 at 10:28:35PM +0200, Heinrich Schuchardt wrote: > > 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.akashi at linaro.org > <mailto:takahiro.akashi at 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? > > With CONFIG_EFI_CAPSULE_ON_DISK_EARLY disabled, > efi_launch_capsules() will be called in efi_init_obj_list() > as you expect. See the code below in efi_setup.c. > > > Instead of calling efi_launch_capsules in efi_init_obj_list, can we > invoke the function explicitly through a dedicated command line, under > the 'efidebug?capsule' class of commands.?I think that would be a > cleaner approach, since efi_init_obj_list gets called for a lot of efi > functions, which are unrelated to capsule update. Who would invoke that command line on an IoT device? My understanding of the UEFI spec is that capsule updates should be invoked automatically. I suggested to Takahiro to use the first EFI system partition that we find when scanning the available block devices to identify the boot device holding the capsules but he dismissed it as contradicting the UEFI spec. According to the UEFI 2.8 spec we have to first check BootNext and then BootOrder to find the boot option with the highest priority (just like the boot manager does). When analysing BootNext and BootOrder we have to ignore entries pointing to devices that are not present. This gives us the active boot entry. On the device identified by the FilePathList field of the active boot entry we look for the directory \EFI\UpdateCapsule. The UEFI spec says it does not require to check for other EFI system partitions. - This could mean it is not forbidden to check other EFI system partitions for update capsules. The problem with the UEFI spec is that it assumes that variables BootNext and BootOrder exist. If they do not exist, the UEFI spec gives no hint what to do. One way to solve this is to populate BootOrder with all block devices. This is exactly what my laptop does: BootOrder: 0001,0000,0016,0017,0018,0019,001A,001B Boot0000* Windows Boot Manager Boot0001* debian Boot0010 Setup Boot0011 Boot Menu Boot0012 Diagnostic Splash Screen Boot0013 Diagnostics Boot0014 Startup Interrupt Menu Boot0015 Rescue and Recovery Boot0016* USB CD Boot0017* USB FDD Boot0018* NVMe0 Boot0019* ATA HDD0 Boot001A* USB HDD Boot001B* PCI LAN Please, observe that this list contains entries USB CD, USB FDD, USB HDD that aren't or even never were physically present on my laptop. Another approach is just to wait until bootefi or bootm (for EFI FIT images) is invoked. After loading the boot image but before starting it we know the active boot device. This will reduce the code size because we do not have to implement the logic of the boot manager to analyze BootNext and BootOrder twice. Best regards Heinrich
On Thu, Apr 30, 2020 at 09:51:51PM +0200, Heinrich Schuchardt wrote: > On 4/30/20 2:52 PM, Sughosh Ganu wrote: > > > > On Tue, 28 Apr 2020 at 05:58, AKASHI Takahiro > > <takahiro.akashi at linaro.org <mailto:takahiro.akashi at linaro.org>> wrote: > > > > Heinrich, > > > > On Mon, Apr 27, 2020 at 10:28:35PM +0200, Heinrich Schuchardt wrote: > > > 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.akashi at linaro.org > > <mailto:takahiro.akashi at 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? > > > > With CONFIG_EFI_CAPSULE_ON_DISK_EARLY disabled, > > efi_launch_capsules() will be called in efi_init_obj_list() > > as you expect. See the code below in efi_setup.c. > > > > > > Instead of calling efi_launch_capsules in efi_init_obj_list, can we > > invoke the function explicitly through a dedicated command line, under > > the 'efidebug?capsule' class of commands.?I think that would be a > > cleaner approach, since efi_init_obj_list gets called for a lot of efi > > functions, which are unrelated to capsule update. > > Who would invoke that command line on an IoT device? > > My understanding of the UEFI spec is that capsule updates should be > invoked automatically. Right. We must ensure that capsule updates immediately must happen after reboot. > I suggested to Takahiro to use the first EFI system partition that we > find when scanning the available block devices to identify the boot > device holding the capsules but he dismissed it as contradicting the > UEFI spec. Yeah ... > According to the UEFI 2.8 spec we have to first check BootNext and then > BootOrder to find the boot option with the highest priority (just like > the boot manager does). When analysing BootNext and BootOrder we have to > ignore entries pointing to devices that are not present. This gives us > the active boot entry. > > On the device identified by the FilePathList field of the active boot > entry we look for the directory \EFI\UpdateCapsule. > > The UEFI spec says it does not require to check for other EFI system > partitions. - This could mean it is not forbidden to check other EFI > system partitions for update capsules. > > The problem with the UEFI spec is that it assumes that variables > BootNext and BootOrder exist. If they do not exist, the UEFI spec gives > no hint what to do. Thank you for detailed explanation instead of me! The UEFI specification sounds a bit odd, but I can't read it differently than my interpretation. > One way to solve this is to populate BootOrder with all block devices. > This is exactly what my laptop does: > > BootOrder: 0001,0000,0016,0017,0018,0019,001A,001B > Boot0000* Windows Boot Manager > Boot0001* debian > Boot0010 Setup > Boot0011 Boot Menu > Boot0012 Diagnostic Splash Screen > Boot0013 Diagnostics > Boot0014 Startup Interrupt Menu > Boot0015 Rescue and Recovery > Boot0016* USB CD > Boot0017* USB FDD > Boot0018* NVMe0 > Boot0019* ATA HDD0 > Boot001A* USB HDD > Boot001B* PCI LAN > > Please, observe that this list contains entries USB CD, USB FDD, USB HDD > that aren't or even never were physically present on my laptop. > > Another approach is just to wait until bootefi or bootm (for EFI FIT > images) is invoked. After loading the boot image but before starting it > we know the active boot device. This will reduce the code size because > we do not have to implement the logic of the boot manager to analyze > BootNext and BootOrder twice. I didn't take this approach because firmware update may affect not only UEFI subsystem but also other U-Boot functionality. This is why "capsule updates must happen immediately after reboot." Thanks, -Takahiro Akashi > Best regards > > Heinrich
On Fri, 1 May 2020 at 01:22, Heinrich Schuchardt <xypron.glpk at gmx.de> wrote: > On 4/30/20 2:52 PM, Sughosh Ganu wrote: > > > > On Tue, 28 Apr 2020 at 05:58, AKASHI Takahiro > > <takahiro.akashi at linaro.org <mailto:takahiro.akashi at linaro.org>> wrote: > > > > Heinrich, > > > > On Mon, Apr 27, 2020 at 10:28:35PM +0200, Heinrich Schuchardt wrote: > > > 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.akashi at linaro.org > > <mailto:takahiro.akashi at 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? > > > > With CONFIG_EFI_CAPSULE_ON_DISK_EARLY disabled, > > efi_launch_capsules() will be called in efi_init_obj_list() > > as you expect. See the code below in efi_setup.c. > > > > > > Instead of calling efi_launch_capsules in efi_init_obj_list, can we > > invoke the function explicitly through a dedicated command line, under > > the 'efidebug capsule' class of commands. I think that would be a > > cleaner approach, since efi_init_obj_list gets called for a lot of efi > > functions, which are unrelated to capsule update. > > Who would invoke that command line on an IoT device? > Understand your point, but the 'efidebug capsule' class of commands are anyways going to be used for debug purpose -- there is also an 'efidebug capsule update' command that is being added as part of this patch series. My point was that it is better to call the capsule update explicitly rather than through efi_init_obj_list, which also gets called for all types of unrelated commands like printenv -e. > My understanding of the UEFI spec is that capsule updates should be > invoked automatically. > Right, and in that case the capsule update should be invoked automatically at some point during the boot. Takahiro has added a patch which invokes the capsule update as part of the main_loop. In that case, why should it be invoked from efi_init_obj_list. What is the scenario where the capsule update is required to be invoked as part of the efi_init_obj_list function. -sughosh
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(); + 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. + 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 + */ +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;
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.akashi at 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(+)