From mboxrd@z Thu Jan 1 00:00:00 1970 From: AKASHI Takahiro Date: Tue, 16 Feb 2021 20:38:20 +0900 Subject: [PATCH v10 02/11] efi_loader: capsule: add capsule_on_disk support In-Reply-To: <9252b629-fa89-566c-a45a-785cd67960b6@gmx.de> References: <20201130091218.66413-1-takahiro.akashi@linaro.org> <20201130091218.66413-3-takahiro.akashi@linaro.org> <9252b629-fa89-566c-a45a-785cd67960b6@gmx.de> Message-ID: <20210216113820.GA52363@laputa> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit To: u-boot@lists.denx.de On Tue, Feb 16, 2021 at 11:24:47AM +0100, Heinrich Schuchardt wrote: > On 30.11.20 10:12, 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 > > --- > > common/main.c | 4 + > > include/efi_loader.h | 9 + > > lib/efi_loader/Kconfig | 22 ++ > > lib/efi_loader/efi_capsule.c | 498 +++++++++++++++++++++++++++++++++++ > > lib/efi_loader/efi_setup.c | 8 + > > 5 files changed, 541 insertions(+) > > > > diff --git a/common/main.c b/common/main.c > > index 4b3cd302c3e2..ae5bcdb32f8b 100644 > > --- a/common/main.c > > +++ b/common/main.c > > @@ -16,6 +16,7 @@ > > #include > > #include > > #include > > +#include > > > > static void run_preboot_environment_command(void) > > { > > @@ -53,6 +54,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(); > > If my preboot command is > > load mmc 0:1 $kernel_addr_r EFI/boot/grubaa64.efi && > bootefi $kernel_addr_r Why do you want to run "bootefi" in "preboot" environment? > efi_lauch_capsule() will never be called. > > Why do you call efi_launch_capsule after > run_preboot_environment_command() and update_tftp()? Capsule update logically provides a similar function to update_tftp(). So I believed that the place near update_tftp() is the best. -Takahiro Akashi > efi_init_obj_list() is executed as last routine in board_init_r() just > before invoking run_main_loop(). > > Can't we simplify the coding by relying on calling efi_launch_capsule() > in efi_init_obj_list()? > > Best regards > > Heinrich > > > + > > 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 d22f7a43ad09..eb57e7455eb1 100644 > > --- a/include/efi_loader.h > > +++ b/include/efi_loader.h > > @@ -820,6 +820,11 @@ efi_status_t EFIAPI efi_query_capsule_caps( > > u64 *maximum_capsule_size, > > u32 *reset_type); > > > > +#define EFI_CAPSULE_DIR L"\\EFI\\UpdateCapsule\\" > > + > > +/* Hook at initialization */ > > +efi_status_t efi_launch_capsules(void); > > + > > #else /* CONFIG_IS_ENABLED(EFI_LOADER) */ > > > > /* Without CONFIG_EFI_LOADER we don't have a runtime section, stub it out */ > > @@ -836,6 +841,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 3ca396df3646..e1ac5ac055de 100644 > > --- a/lib/efi_loader/Kconfig > > +++ b/lib/efi_loader/Kconfig > > @@ -104,6 +104,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 n > > + 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 575fa75b0a2f..b3c7d1b735b6 100644 > > --- a/lib/efi_loader/efi_capsule.c > > +++ b/lib/efi_loader/efi_capsule.c > > @@ -11,10 +11,16 @@ > > #include > > #include > > #include > > +#include > > #include > > > > 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 > > + > > /** > > * get_last_capsule - get the last capsule index > > * > > @@ -163,3 +169,495 @@ efi_status_t EFIAPI efi_query_capsule_caps( > > out: > > return EFI_EXIT(ret); > > } > > + > > +#ifdef CONFIG_EFI_CAPSULE_ON_DISK > > +/** > > + * get_dp_device - retrieve a device path from boot variable > > + * @boot_var: Boot variable name > > + * @device_dp Device path > > + * > > + * Retrieve a device patch from boot variable, @boot_var. > > + * > > + * Return: status code > > + */ > > +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_get_variable_int(boot_var, &efi_global_variable_guid, > > + NULL, &size, NULL, NULL); > > + if (ret == EFI_BUFFER_TOO_SMALL) { > > + buf = malloc(size); > > + if (!buf) > > + return EFI_OUT_OF_RESOURCES; > > + ret = efi_get_variable_int(boot_var, &efi_global_variable_guid, > > + NULL, &size, buf, NULL); > > + } > > + if (ret != EFI_SUCCESS) > > + return ret; > > + > > + efi_deserialize_load_option(&lo, buf, &size); > > + > > + 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; > > +} > > + > > +/** > > + * device_is_present_and_system_part - check if a device exists > > + * @dp Device path > > + * > > + * Check if a device pointed to by the device path, @dp, exists and is > > + * located in UEFI system partition. > > + * > > + * Return: true - yes, false - no > > + */ > > +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); > > +} > > + > > +/** > > + * find_boot_device - identify the boot device > > + * > > + * Identify the boot device from boot-related variables as UEFI > > + * specification describes and put its handle into bootdev_root. > > + * > > + * Return: status code > > + */ > > +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_get_variable_int(L"BootNext", > > + (efi_guid_t *)&efi_global_variable_guid, > > + NULL, &size, &bootnext, NULL); > > + 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_get_variable_int(L"BootOrder", &efi_global_variable_guid, > > + NULL, &size, NULL, NULL); > > + if (ret == EFI_BUFFER_TOO_SMALL) { > > + boot_order = malloc(size); > > + if (!boot_order) { > > + ret = EFI_OUT_OF_RESOURCES; > > + goto out; > > + } > > + > > + ret = efi_get_variable_int(L"BootOrder", > > + &efi_global_variable_guid, > > + NULL, &size, boot_order, NULL); > > + } > > + 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; > > +} > > + > > +/** > > + * efi_capsule_scan_dir - traverse a capsule directory in boot device > > + * @files: Array of file names > > + * @num: Number of elements in @files > > + * > > + * Traverse a capsule directory in boot device. > > + * Called by initialization code, and returns an array of capsule file > > + * names in @files. > > + * > > + * Return: status code > > + */ > > +static efi_status_t efi_capsule_scan_dir(u16 ***files, unsigned int *num) > > +{ > > + struct efi_file_handle *dirh; > > + struct efi_file_info *dirent; > > + efi_uintn_t dirent_size, tmp_size; > > + unsigned 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; > > +} > > + > > +/** > > + * efi_capsule_read_file - read in a capsule file > > + * @filename: File name > > + * @capsule: Pointer to buffer for capsule > > + * > > + * Read a capsule file and put its content in @capsule. > > + * > > + * Return: status code > > + */ > > +static efi_status_t efi_capsule_read_file(const 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, (u16 *)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; > > +} > > + > > +/** > > + * efi_capsule_delete_file - delete a capsule file > > + * @filename: File name > > + * > > + * Delete a capsule file from capsule directory. > > + * > > + * Return: status code > > + */ > > +static efi_status_t efi_capsule_delete_file(const 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, (u16 *)filename, > > + EFI_FILE_MODE_READ, 0)); > > + /* ignore an error */ > > + EFI_CALL((*dirh->close)(dirh)); > > + > > + ret = EFI_CALL((*fh->delete)(fh)); > > + > > + return ret; > > +} > > + > > +/** > > + * efi_capsule_scan_done - reset a scan help function > > + * > > + * Reset a scan help function > > + */ > > +static void efi_capsule_scan_done(void) > > +{ > > + EFI_CALL((*bootdev_root->close)(bootdev_root)); > > + bootdev_root = NULL; > > +} > > + > > +/** > > + * arch_efi_load_capsule_drivers - initialize capsule drivers > > + * > > + * Architecture or board specific initialization routine > > + * > > + * Return: status code > > + */ > > +efi_status_t __weak arch_efi_load_capsule_drivers(void) > > +{ > > + return EFI_SUCCESS; > > +} > > + > > +/** > > + * efi_launch_capsule - launch capsules > > + * > > + * Launch all the capsules in system at boot time. > > + * Called by efi init code > > + * > > + * Return: status codde > > + */ > > +efi_status_t efi_launch_capsules(void) > > +{ > > + u64 os_indications; > > + efi_uintn_t size; > > + struct efi_capsule_header *capsule = NULL; > > + u16 **files; > > + unsigned int nfiles, index, i; > > + u16 variable_name16[12]; > > + efi_status_t ret; > > + > > + size = sizeof(os_indications); > > + ret = efi_get_variable_int(L"OsIndications", &efi_global_variable_guid, > > + NULL, &size, &os_indications, NULL); > > + if (ret != EFI_SUCCESS || > > + !(os_indications > > + & EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED)) > > + return EFI_SUCCESS; > > + > > + index = 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, ++index; i < nfiles; i++, index++) { > > + EFI_PRINT("capsule from %ls ...\n", files[i]); > > + if (index > 0xffff) > > + index = 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(index, 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); > > + > > + /* CapsuleLast */ > > + efi_create_indexed_name(variable_name16, "Capsule", index - 1); > > + efi_set_variable_int(L"CapsuleLast", &efi_guid_capsule_report, > > + EFI_VARIABLE_READ_ONLY | > > + EFI_VARIABLE_NON_VOLATILE | > > + EFI_VARIABLE_BOOTSERVICE_ACCESS | > > + EFI_VARIABLE_RUNTIME_ACCESS, > > + 22, variable_name16, false); > > + > > + 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 070ac6147c72..0735e4755b60 100644 > > --- a/lib/efi_loader/efi_setup.c > > +++ b/lib/efi_loader/efi_setup.c > > @@ -155,6 +155,10 @@ static efi_status_t efi_init_os_indications(void) > > os_indications_supported |= > > EFI_OS_INDICATIONS_CAPSULE_RESULT_VAR_SUPPORTED; > > > > + if (IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK)) > > + os_indications_supported |= > > + EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED; > > + > > return efi_set_variable_int(L"OsIndicationsSupported", > > &efi_global_variable_guid, > > EFI_VARIABLE_BOOTSERVICE_ACCESS | > > @@ -275,6 +279,10 @@ efi_status_t efi_init_obj_list(void) > > if (ret != EFI_SUCCESS) > > goto out; > > > > + /* Execute capsules after reboot */ > > + if (IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK) && > > + !IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK_EARLY)) > > + ret = efi_launch_capsules(); > > out: > > efi_obj_list_initialized = ret; > > return ret; > > >