diff mbox series

[v4,07/16] efi_loader: capsule: add capsule_on_disk support

Message ID 20200722060539.15168-8-takahiro.akashi@linaro.org
State Superseded
Headers show
Series efi_loader: add capsule update support | expand

Commit Message

AKASHI Takahiro July 22, 2020, 6:05 a.m. UTC
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@linaro.org>

---
 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(+)

-- 
2.27.0

Comments

Heinrich Schuchardt July 23, 2020, 3:50 p.m. UTC | #1
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 <takahiro.akashi@linaro.org>

> ---

>  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 <init.h>

>  #include <net.h>

>  #include <version.h>

> +#include <efi_loader.h>

>

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

> +

>  /**

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

>
AKASHI Takahiro July 30, 2020, 2:13 a.m. UTC | #2
Heinrich,

On Thu, Jul 23, 2020 at 05:50:09PM +0200, Heinrich Schuchardt wrote:
> 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 <takahiro.akashi@linaro.org>

> > ---

> >  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 <init.h>

> >  #include <net.h>

> >  #include <version.h>

> > +#include <efi_loader.h>

> >

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


Okay, I agree that this kind of #ifdef be removed from a header.

> > +#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 <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

> > +

> >  /**

> >   * 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.


My implementation is fully compliant with 8.5.6.
Is there any additional requirement in UEFI specification?

> > + *

> > + * 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().


Sure.

> 

> > +					(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.


While I don't think it's really worth, but I will try.


> > +

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


Okay.

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


Okay.

> 

> > +{

> > +	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.


Okay.

> The variable should be set in efi_setup.c. It does not depend on the

> execution of capsule updates.


No.
Since efi_launch_capsules() is only called once (and CapsuleMax is only
useful if capsule updates are enabled), moving the code to efi_setup.c
doesn't make any difference.

> > +

> > +	/* 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.


Okay.

-Takahiro Akashi

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

> >

>
diff mbox series

Patch

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 <init.h>
 #include <net.h>
 #include <version.h>
+#include <efi_loader.h>
 
 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
+#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 <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
+
 /**
  * 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.
+ *
+ * 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",
+					(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;
+}
+
+/**
+ * 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,
+					  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)
+{
+	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));
+
+	/* 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 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;