[PATCH 04/10] efi_loader: capsule: add capsule_on_disk support

Heinrich Schuchardt xypron.glpk at gmx.de
Mon Apr 27 22:28:35 CEST 2020


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;
>



More information about the U-Boot mailing list