[PATCH 4/8 v2] efi_loader: Introduce helper functions for EFI

Heinrich Schuchardt xypron.glpk at gmx.de
Wed Dec 30 20:29:54 CET 2020


On 12/30/20 4:07 PM, Ilias Apalodimas wrote:
> A following patch introduces a different logic for loading initrd's
> based on the EFI_LOAD_FILE2_PROTOCOL.
> Since similar logic can be applied in the future for other system files
> (i.e DTBs), let's add some helper functions which will retrieve and
> parse file paths stored in EFI variables.
>
> Signed-off-by: Ilias Apalodimas <ilias.apalodimas at linaro.org>
> ---
>   include/efi_helper.h        |  28 ++++++
>   lib/efi_loader/efi_helper.c | 180 ++++++++++++++++++++++++++++++++++++
>   2 files changed, 208 insertions(+)
>   create mode 100644 include/efi_helper.h
>   create mode 100644 lib/efi_loader/efi_helper.c
>
> diff --git a/include/efi_helper.h b/include/efi_helper.h
> new file mode 100644
> index 000000000000..5d3a43364619
> --- /dev/null
> +++ b/include/efi_helper.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (c) 2020, Linaro Limited
> + */
> +
> +#if !defined _EFI_HELPER_H_
> +#define _EFI_HELPER_H
> +
> +#include <efi.h>
> +#include <efi_api.h>
> +
> +/*
> + * @dev:	device string i.e 'mmc'
> + * @part:	partition string i.e '0:2'
> + * @filename:	name of the file
> + */
> +struct load_file_info {
> +	char dev[32];

if_typename_str[] contains all available strings. 8 bytes are long
enough. The value can be validated with blk_get_device_by_str().

> +	char part[16];

Windows allows 128 partitions per device. I would not expect millions of
devices. 8 bytes should be long enough.

> +	char filename[256];

This is a path not a file name. Please, adjust the parameter name.

In Windows the maximum length of a path is 260 characters. But does such
a limit exist in UEFI?

In U-Boot we have:
include/ext_common.h:33:#define EXT2_PATH_MAX 4096

So you cannot assume that the path is shorter then 4096 bytes.

I think the best thing to do is to use strdup() and then put 0x00 at the
end of each string part. How about:

struct load_file_info {
	/*
	 * duplicated filespec, to be freed after usage
	 * 0x00 separated device, part, path
	 */
	char *filespec;
	char *part;
	char *filepath;
}

So filespec would point to a buffer containing:

device\0   part\0   \path\file\0    \0
|          |        |- filepath points here
|          |- part points here
|- filespec points here

> +};
> +
> +loff_t get_file_size(const struct load_file_info *file_loc,
> +		     efi_status_t *status);
> +efi_status_t efi_get_fp_from_var(const char *name, struct load_file_info *loc);
> +void *get_var_efi(u16 *name, const efi_guid_t *vendor, efi_uintn_t *size);
> +
> +#endif
> diff --git a/lib/efi_loader/efi_helper.c b/lib/efi_loader/efi_helper.c
> new file mode 100644
> index 000000000000..e5919343a5cc
> --- /dev/null
> +++ b/lib/efi_loader/efi_helper.c
> @@ -0,0 +1,180 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (c) 2020, Linaro Limited
> + */
> +
> +#include <common.h>
> +#include <env.h>
> +#include <malloc.h>
> +#include <dm.h>
> +#include <fs.h>
> +#include <efi_helper.h>
> +#include <efi_loader.h>
> +#include <efi_variable.h>
> +
> +/**
> + * get_file_size() - retrieve the size of initramfs, set efi status on error
> + *
> + * @dev:			device to read from, e.g. "mmc"
> + * @part:			device partition, e.g. "0:1"
> + * @file:			name of file
> + * @status:			EFI exit code in case of failure
> + *
> + * Return:			size of file
> + */
> +loff_t get_file_size(const struct load_file_info *info, efi_status_t *status)
> +{
> +	loff_t sz = 0;
> +	int ret;
> +
> +	ret = fs_set_blk_dev(info->dev, info->part, FS_TYPE_ANY);
> +	if (ret) {
> +		*status = EFI_NO_MEDIA;
> +		goto out;
> +	}
> +
> +	ret = fs_size(info->filename, &sz);
> +	if (ret) {
> +		sz = 0;
> +		*status = EFI_NOT_FOUND;
> +		goto out;
> +	}
> +
> +out:
> +	return sz;
> +}
> +
> +/*
> + * string_to_load_args() - Fill in a struct load_file_info with the file info
> + *			   parsed from an EFI variable
> + *
> + * @args:	value of the EFI variable i.e "mmc 0 initrd"
> + * @info:	struct to fill in with file specific info
> + *
> + * Return:	Status code
> + */
> +static efi_status_t string_to_load_args(char *args, struct load_file_info *info)
> +{
> +	efi_status_t status = EFI_SUCCESS;
> +	char *p;
> +
> +	/*
> +	 * expect a string with three space separated parts:
> +	 * - block device type, e.g. "mmc"
> +	 * - device and partition identifier, e.g. "0:1"
> +	 * - file path on the block device, e.g. "/boot/initrd.cpio.gz"
> +	 */

Shouldn't you skip over leading spaces?

> +	p = strsep(&args, " ");
> +	if (!p) {
> +		status = EFI_NO_MEDIA;
> +		goto out;
> +	}
> +	strncpy(info->dev, p, sizeof(info->dev));
> +	info->dev[sizeof(info->dev) - 1] = 0;
> +
> +	p = strsep(&args, " ");
> +	if (!p) {
> +		status = EFI_NO_MEDIA;
> +		goto out;
> +	}

Don't make any assumptions about the number of spaces between the file
specification parts:

"     host      0:1       /dir1/dir12/filename     "

We should use strdup() and make_argv().

> +	strncpy(info->part, p, sizeof(info->part));
> +	info->part[sizeof(info->part) - 1] = 0;
> +
> +	p = strsep(&args, " ");
> +	if (!p) {
> +		status = EFI_NOT_FOUND;
> +		goto out;
> +	}
> +	strncpy(info->filename, p, sizeof(info->filename));
> +	info->filename[sizeof(info->filename) - 1] = 0;
> +
> +out:
> +	return status;
> +}
> +
> +/**
> + * get_var_efi() - read value of an EFI variable
> + *
> + * @name:	variable name
> + * @start:	vendor GUID
> + * @size:	size of allocated buffer
> + *
> + * Return:	buffer with variable data or NULL
> + */
> +void *get_var_efi(u16 *name, const efi_guid_t *vendor, efi_uintn_t *size)
> +{
> +	efi_status_t ret;
> +	void *buf = NULL;
> +
> +	*size = 0;
> +	ret = efi_get_variable_int(name, vendor, NULL, size, buf, NULL);
> +	if (ret == EFI_BUFFER_TOO_SMALL) {
> +		buf = malloc(*size);
> +		ret = efi_get_variable_int(name, vendor, NULL, size, buf, NULL);
> +	}
> +
> +	if (ret != EFI_SUCCESS) {
> +		free(buf);
> +		*size = 0;
> +		return NULL;
> +	}
> +
> +	return buf;
> +}
> +
> +/**
> + * efi_get_fp_from_var() - Retrieve a file path from an EFI variable
> + *
> + * @name:	variable name
> + * @info:	struct to fill in with file specific info
> + */
> +efi_status_t efi_get_fp_from_var(const char *name, struct load_file_info *info)
> +{
> +	efi_uintn_t boot_cur_size;
> +	void *var_value = NULL;
> +	efi_uintn_t size;
> +	efi_status_t ret;
> +	u16 boot_cur;
> +	u16 var_name[16];
> +	u16 *pos;
> +
> +	memset(info, 0, sizeof(*info));
> +
> +	boot_cur_size = sizeof(boot_cur);
> +	ret = efi_get_variable_int(L"BootCurrent",

Please, put some more text into the function description indicating the
usage of BootCurrent.

> +				   &efi_global_variable_guid, NULL,
> +				   &boot_cur_size, &boot_cur, NULL);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	pos = efi_create_indexed_name(var_name, sizeof(var_name), name,
> +				      boot_cur);
> +	if (!pos) {
> +		ret = EFI_OUT_OF_RESOURCES;
> +		goto out;
> +	}
> +
> +	var_value = get_var_efi(var_name, &efi_global_variable_guid, &size);
> +	if (!var_value) {
> +		ret = EFI_NOT_FOUND;
> +		goto out;
> +	}
> +
> +	ret = string_to_load_args(var_value, info);
> +	if (ret != EFI_SUCCESS)
> +		goto out;
> +
> +	if (fs_set_blk_dev(info->dev, info->part, FS_TYPE_ANY)) {
> +		ret = EFI_NO_MEDIA;
> +		goto out;
> +	}
> +
> +	if (!fs_exists(info->filename)) {
> +		ret = EFI_NOT_FOUND;
> +		goto out;
> +	}
> +
> +out:
> +	free(var_value);
> +	return ret;
> +}
>

Best regards

Heinrich


More information about the U-Boot mailing list