From mboxrd@z Thu Jan 1 00:00:00 1970 From: Heinrich Schuchardt Date: Thu, 23 Jul 2020 17:50:09 +0200 Subject: [PATCH v4 07/16] efi_loader: capsule: add capsule_on_disk support In-Reply-To: <20200722060539.15168-8-takahiro.akashi@linaro.org> References: <20200722060539.15168-1-takahiro.akashi@linaro.org> <20200722060539.15168-8-takahiro.akashi@linaro.org> Message-ID: <2e06544d-4afe-0747-9303-cd17a1f7ad59@gmx.de> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit To: u-boot@lists.denx.de On 22.07.20 08:05, 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 | 16 ++ > lib/efi_loader/Kconfig | 22 ++ > lib/efi_loader/efi_capsule.c | 510 +++++++++++++++++++++++++++++++++++ > lib/efi_loader/efi_setup.c | 8 + > 5 files changed, 560 insertions(+) > > diff --git a/common/main.c b/common/main.c > index 62ab3344e529..71fb749be4f4 100644 > --- a/common/main.c > +++ b/common/main.c > @@ -16,6 +16,7 @@ > #include > #include > #include > +#include > > static void run_preboot_environment_command(void) > { > @@ -50,6 +51,9 @@ void main_loop(void) > if (IS_ENABLED(CONFIG_USE_PREBOOT)) > run_preboot_environment_command(); > > + 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 a754fb0ed460..7e00bf3b33f3 100644 > --- a/include/efi_loader.h > +++ b/include/efi_loader.h > @@ -808,6 +808,18 @@ efi_status_t EFIAPI efi_query_capsule_caps( > u64 *maximum_capsule_size, > u32 *reset_type); > > +#ifdef CONFIG_EFI_CAPSULE_ON_DISK This #ifdef seems unnecessary. No code will invoke efi_launch_capsules() if CONFIG_EFI_CAPSULE_ON_DISK is not set. > +#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 */ > @@ -824,6 +836,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 ee9ebe348ad9..6e35cbe64c7f 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 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 cfe422bee924..2a224546dd11 100644 > --- a/lib/efi_loader/efi_capsule.c > +++ b/lib/efi_loader/efi_capsule.c > @@ -10,10 +10,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 number > * > @@ -166,3 +172,507 @@ 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_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, &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. If none of the Boot* variables is defined we should still be able to do a capsule update. Please, use efi_system_partition as fallback. > + * > + * 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_CALL(efi_get_variable(L"BootNext", Please, avoid EFI_CALL. Use efi_get_variable_int(). > + (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); We have this type of conversion in multiple places. Both for BootXXXX as well as for CapsuleXXXX. Both in capsule updates as well as in try_load_entry(). Please, provide a libary function. > + > + 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; > +} > + > +/** > + * 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, 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; > +} > + > +/** > + * 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(u16 *filename, 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, 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(u16 *filename) 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, 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; > + 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)); The variable must be read-only. Please, use efi_set_variable_int. The variable should be set in efi_setup.c. It does not depend on the execution of capsule updates. > + > + /* 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, The variable must be read-only. Please, use efi_set_variable_int. Best regards Heinrich > + 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 2fc0c5d091b8..a0eb81f079e1 100644 > --- a/lib/efi_loader/efi_setup.c > +++ b/lib/efi_loader/efi_setup.c > @@ -132,6 +132,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 | > @@ -243,6 +247,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; >