diff options
author | Tom Rini <trini@konsulko.com> | 2020-02-29 08:01:07 -0500 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2020-02-29 08:01:07 -0500 |
commit | 5045289820835ce0baf5d7cea86f9fdc6170d189 (patch) | |
tree | 5461a194898326e044d0087345aeea59d7220190 | |
parent | 1e85aaf3723f0ecd06fcf62e2d2482749e1995d6 (diff) | |
parent | 71a7de4467030362ef2582c355c086eb5fc4143f (diff) |
Merge tag 'efi-2020-04-rc4-2' of https://gitlab.denx.de/u-boot/custodians/u-boot-efi
Pull request for UEFI sub-system for efi-2020-04-rc4 (2)
In Linux next-20200228 patches have been merged to load an initial ramdisk
using an EFI_LOAD_FILE2_PROTOCOL provided by the firmware. See commit
ec93fc371f01 ("efi/libstub: Add support for loading the initrd from a
device path"). The idea behind it is that the firmware should be
responsible for validating the initrd in a secure boot setup.
This pull-request comprises a patch series which let's U-Boot provide an
initial implementation of the EFI_LOAD_FILE2_PROTOCOL providing the initrd.
-rw-r--r-- | cmd/efidebug.c | 4 | ||||
-rw-r--r-- | doc/api/efi.rst | 9 | ||||
-rw-r--r-- | doc/uefi/uefi.rst | 12 | ||||
-rw-r--r-- | include/efi_api.h | 17 | ||||
-rw-r--r-- | include/efi_load_initrd.h | 25 | ||||
-rw-r--r-- | include/efi_loader.h | 1 | ||||
-rw-r--r-- | lib/efi_loader/Kconfig | 15 | ||||
-rw-r--r-- | lib/efi_loader/Makefile | 1 | ||||
-rw-r--r-- | lib/efi_loader/efi_load_initrd.c | 198 | ||||
-rw-r--r-- | lib/efi_loader/efi_setup.c | 5 | ||||
-rw-r--r-- | lib/efi_selftest/Makefile | 1 | ||||
-rw-r--r-- | lib/efi_selftest/efi_selftest_load_initrd.c | 220 |
12 files changed, 508 insertions, 0 deletions
diff --git a/cmd/efidebug.c b/cmd/efidebug.c index 510e258b12a..21dfd44fcc9 100644 --- a/cmd/efidebug.c +++ b/cmd/efidebug.c @@ -244,6 +244,10 @@ static const struct { EFI_HII_CONFIG_ROUTING_PROTOCOL_GUID, }, { + "Load File2", + EFI_LOAD_FILE2_PROTOCOL_GUID, + }, + { "Simple Network", EFI_SIMPLE_NETWORK_PROTOCOL_GUID, }, diff --git a/doc/api/efi.rst b/doc/api/efi.rst index bc593826082..631c0ceb1df 100644 --- a/doc/api/efi.rst +++ b/doc/api/efi.rst @@ -125,6 +125,15 @@ Graphical output protocol .. kernel-doc:: lib/efi_loader/efi_gop.c :internal: +Load file 2 protocol +~~~~~~~~~~~~~~~~~~~~ + +The load file 2 protocol can be used by the Linux kernel to load the initial +RAM disk. U-Boot can be configured to provide an implementation. + +.. kernel-doc:: lib/efi_loader/efi_load_initrd.c + :internal: + Network protocols ~~~~~~~~~~~~~~~~~ diff --git a/doc/uefi/uefi.rst b/doc/uefi/uefi.rst index a8fd886d6b5..cfe2d84a4c6 100644 --- a/doc/uefi/uefi.rst +++ b/doc/uefi/uefi.rst @@ -356,6 +356,18 @@ This driver is only available if U-Boot is configured with:: CONFIG_BLK=y CONFIG_PARTITIONS=y +Miscellaneous +------------- + +Load file 2 protocol +~~~~~~~~~~~~~~~~~~~~ + +The load file 2 protocol can be used by the Linux kernel to load the initial +RAM disk. U-Boot can be configured to provide an implementation with:: + + EFI_LOAD_FILE2_INITRD=y + EFI_INITRD_FILESPEC=interface dev:part path_to_initrd + Links ----- diff --git a/include/efi_api.h b/include/efi_api.h index b7b68cb7a1f..3d1a6beeeac 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -331,6 +331,14 @@ struct efi_runtime_services { EFI_GUID(0xeb9d2d31, 0x2d88, 0x11d3, \ 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d) +#define EFI_LOAD_FILE_PROTOCOL_GUID \ + EFI_GUID(0x56ec3091, 0x954c, 0x11d2, \ + 0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b) + +#define EFI_LOAD_FILE2_PROTOCOL_GUID \ + EFI_GUID(0x4006c0c1, 0xfcb3, 0x403e, \ + 0x99, 0x6d, 0x4a, 0x6c, 0x87, 0x24, 0xe0, 0x6d) + struct efi_configuration_table { efi_guid_t guid; void *table; @@ -486,6 +494,7 @@ struct efi_device_path_nvme { #define DEVICE_PATH_TYPE_MEDIA_DEVICE 0x04 # define DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH 0x01 # define DEVICE_PATH_SUB_TYPE_CDROM_PATH 0x02 +# define DEVICE_PATH_SUB_TYPE_VENDOR_PATH 0x03 # define DEVICE_PATH_SUB_TYPE_FILE_PATH 0x04 struct efi_device_path_hard_drive_path { @@ -1619,6 +1628,14 @@ struct efi_unicode_collation_protocol { char *supported_languages; }; +struct efi_load_file_protocol { + efi_status_t (EFIAPI *load_file)(struct efi_load_file_protocol *this, + struct efi_device_path *file_path, + bool boot_policy, + efi_uintn_t *buffer_size, + void *buffer); +}; + /* Boot manager load options */ #define LOAD_OPTION_ACTIVE 0x00000001 #define LOAD_OPTION_FORCE_RECONNECT 0x00000002 diff --git a/include/efi_load_initrd.h b/include/efi_load_initrd.h new file mode 100644 index 00000000000..478ae807c68 --- /dev/null +++ b/include/efi_load_initrd.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2020, Linaro Limited + */ + +#if !defined _EFI_LOAD_INITRD_H_ +#define _EFI_LOAD_INITRD_H_ + +#include <efi.h> +#include <efi_api.h> + +/* + * Vendor GUID used by Linux to identify the handle with the + * EFI_LOAD_FILE2_PROTOCOL and load an initial ramdisk. + */ +#define EFI_INITRD_MEDIA_GUID \ + EFI_GUID(0x5568e427, 0x68fc, 0x4f3d, \ + 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68) + +struct efi_initrd_dp { + struct efi_device_path_vendor vendor; + struct efi_device_path end; +} __packed; + +#endif diff --git a/include/efi_loader.h b/include/efi_loader.h index d4c59b54c48..8e343798339 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -378,6 +378,7 @@ efi_status_t efi_gop_register(void); efi_status_t efi_net_register(void); /* Called by bootefi to make the watchdog available */ efi_status_t efi_watchdog_register(void); +efi_status_t efi_initrd_register(void); /* Called by bootefi to make SMBIOS tables available */ /** * efi_acpi_register() - write out ACPI tables diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig index 76f43eca95f..9890144d416 100644 --- a/lib/efi_loader/Kconfig +++ b/lib/efi_loader/Kconfig @@ -130,4 +130,19 @@ config EFI_RNG_PROTOCOL Provide a EFI_RNG_PROTOCOL implementation using the hardware random number generator of the platform. +config EFI_LOAD_FILE2_INITRD + bool "EFI_FILE_LOAD2_PROTOCOL for Linux initial ramdisk" + default n + help + Expose a EFI_FILE_LOAD2_PROTOCOL that the Linux UEFI stub can + use to load the initial ramdisk. Once this is enabled using + initrd=<ramdisk> will stop working. + +config EFI_INITRD_FILESPEC + string "initramfs path" + default "host 0:1 initrd" + depends on EFI_LOAD_FILE2_INITRD + help + Full path of the initramfs file, e.g. mmc 0:2 initramfs.cpio.gz. + endif diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index 04dc8648512..9b3b7044733 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -43,3 +43,4 @@ obj-$(CONFIG_NET) += efi_net.o obj-$(CONFIG_GENERATE_ACPI_TABLE) += efi_acpi.o obj-$(CONFIG_GENERATE_SMBIOS_TABLE) += efi_smbios.o obj-$(CONFIG_EFI_RNG_PROTOCOL) += efi_rng.o +obj-$(CONFIG_EFI_LOAD_FILE2_INITRD) += efi_load_initrd.o diff --git a/lib/efi_loader/efi_load_initrd.c b/lib/efi_loader/efi_load_initrd.c new file mode 100644 index 00000000000..574a83d7e30 --- /dev/null +++ b/lib/efi_loader/efi_load_initrd.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020, Linaro Limited + */ + +#include <common.h> +#include <env.h> +#include <malloc.h> +#include <mapmem.h> +#include <dm.h> +#include <fs.h> +#include <efi_loader.h> +#include <efi_load_initrd.h> + +static const efi_guid_t efi_guid_load_file2_protocol = + EFI_LOAD_FILE2_PROTOCOL_GUID; + +static efi_status_t EFIAPI +efi_load_file2_initrd(struct efi_load_file_protocol *this, + struct efi_device_path *file_path, bool boot_policy, + efi_uintn_t *buffer_size, void *buffer); + +static const struct efi_load_file_protocol efi_lf2_protocol = { + .load_file = efi_load_file2_initrd, +}; + +/* + * Device path defined by Linux to identify the handle providing the + * EFI_LOAD_FILE2_PROTOCOL used for loading the initial ramdisk. + */ +static const struct efi_initrd_dp dp = { + .vendor = { + { + DEVICE_PATH_TYPE_MEDIA_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR_PATH, + sizeof(dp.vendor), + }, + EFI_INITRD_MEDIA_GUID, + }, + .end = { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(dp.end), + } +}; + +/** + * get_file_size() - retrieve the size of initramfs, set efi status on error + * + * @dev: device to read from. i.e "mmc" + * @part: device partition. i.e "0:1" + * @file: name fo file + * @status: EFI exit code in case of failure + * + * Return: size of file + */ +static loff_t get_file_size(const char *dev, const char *part, const char *file, + efi_status_t *status) +{ + loff_t sz = 0; + int ret; + + ret = fs_set_blk_dev(dev, part, FS_TYPE_ANY); + if (ret) { + *status = EFI_NO_MEDIA; + goto out; + } + + ret = fs_size(file, &sz); + if (ret) { + sz = 0; + *status = EFI_NOT_FOUND; + goto out; + } + +out: + return sz; +} + +/** + * load_file2() - get information about random number generation + * + * This function implement the LoadFile2() service in order to load an initram + * disk requested by the Linux kernel stub. + * See the UEFI spec for details. + * + * @this: loadfile2 protocol instance + * @file_path: relative path of the file. "" in this case + * @boot_policy: must be false for Loadfile2 + * @buffer_size: size of allocated buffer + * @buffer: buffer to load the file + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_load_file2_initrd(struct efi_load_file_protocol *this, + struct efi_device_path *file_path, bool boot_policy, + efi_uintn_t *buffer_size, void *buffer) +{ + const char *filespec = CONFIG_EFI_INITRD_FILESPEC; + efi_status_t status = EFI_NOT_FOUND; + loff_t file_sz = 0, read_sz = 0; + char *dev, *part, *file; + char *s; + int ret; + + EFI_ENTRY("%p, %p, %d, %p, %p", this, file_path, boot_policy, + buffer_size, buffer); + + s = strdup(filespec); + if (!s) + goto out; + + if (!this || this != &efi_lf2_protocol || + !buffer_size) { + status = EFI_INVALID_PARAMETER; + goto out; + } + + if (file_path->type != dp.end.type || + file_path->sub_type != dp.end.sub_type) { + status = EFI_INVALID_PARAMETER; + goto out; + } + + if (boot_policy) { + status = EFI_UNSUPPORTED; + goto out; + } + + /* expect something like 'mmc 0:1 initrd.cpio.gz' */ + dev = strsep(&s, " "); + if (!dev) + goto out; + part = strsep(&s, " "); + if (!part) + goto out; + file = strsep(&s, " "); + if (!file) + goto out; + + file_sz = get_file_size(dev, part, file, &status); + if (!file_sz) + goto out; + + if (!buffer || *buffer_size < file_sz) { + status = EFI_BUFFER_TOO_SMALL; + *buffer_size = file_sz; + } else { + ret = fs_set_blk_dev(dev, part, FS_TYPE_ANY); + if (ret) { + status = EFI_NO_MEDIA; + goto out; + } + + ret = fs_read(file, map_to_sysmem(buffer), 0, *buffer_size, + &read_sz); + if (ret || read_sz != file_sz) + goto out; + *buffer_size = read_sz; + + status = EFI_SUCCESS; + } + +out: + free(s); + return EFI_EXIT(status); +} + +/** + * efi_initrd_register() - Register a handle and loadfile2 protocol + * + * This function creates a new handle and installs a linux specific GUID + * to handle initram disk loading during boot. + * See the UEFI spec for details. + * + * Return: status code + */ +efi_status_t efi_initrd_register(void) +{ + efi_handle_t efi_initrd_handle = NULL; + efi_status_t ret; + + /* + * Set up the handle with the EFI_LOAD_FILE2_PROTOCOL which Linux may + * use to load the initial ramdisk. + */ + ret = EFI_CALL(efi_install_multiple_protocol_interfaces + (&efi_initrd_handle, + /* initramfs */ + &efi_guid_device_path, &dp, + /* LOAD_FILE2 */ + &efi_guid_load_file2_protocol, + (void *)&efi_lf2_protocol, + NULL)); + + return ret; +} diff --git a/lib/efi_loader/efi_setup.c b/lib/efi_loader/efi_setup.c index 2060307b053..b458093dfbd 100644 --- a/lib/efi_loader/efi_setup.c +++ b/lib/efi_loader/efi_setup.c @@ -155,6 +155,11 @@ efi_status_t efi_init_obj_list(void) if (ret != EFI_SUCCESS) goto out; #endif +#ifdef CONFIG_EFI_LOAD_FILE2_INITRD + ret = efi_initrd_register(); + if (ret != EFI_SUCCESS) + goto out; +#endif #ifdef CONFIG_NET ret = efi_net_register(); if (ret != EFI_SUCCESS) diff --git a/lib/efi_selftest/Makefile b/lib/efi_selftest/Makefile index 3ad96e1cbf0..cf132c372e1 100644 --- a/lib/efi_selftest/Makefile +++ b/lib/efi_selftest/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_CPU_V7) += efi_selftest_unaligned.o obj-$(CONFIG_EFI_LOADER_HII) += efi_selftest_hii.o obj-$(CONFIG_EFI_RNG_PROTOCOL) += efi_selftest_rng.o obj-$(CONFIG_EFI_GET_TIME) += efi_selftest_rtc.o +obj-$(CONFIG_EFI_LOAD_FILE2_INITRD) += efi_selftest_load_initrd.o ifeq ($(CONFIG_GENERATE_ACPI_TABLE),) obj-y += efi_selftest_fdt.o diff --git a/lib/efi_selftest/efi_selftest_load_initrd.c b/lib/efi_selftest/efi_selftest_load_initrd.c new file mode 100644 index 00000000000..e16163caca8 --- /dev/null +++ b/lib/efi_selftest/efi_selftest_load_initrd.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * efi_selftest_load_initrd + * + * Copyright (c) 2020 Ilias Apalodimas <ilias.apalodimas@linaro.org> + * + * This test checks the FileLoad2 protocol. + * A known file is read from the file system and verified. + * + * An example usage - given a file image with a file system in partition 1 + * holding file initrd - is: + * + * * Configure the sandbox with + * + * CONFIG_EFI_SELFTEST=y + * CONFIG_EFI_LOAD_FILE2_INITRD=y + * CONFIG_EFI_INITRD_FILESPEC="host 0:1 initrd" + * + * * Run ./u-boot and execute + * + * host bind 0 image + * setenv efi_selftest load initrd + * bootefi selftest + * + * This would provide a test output like: + * + * Testing EFI API implementation + * + * Selected test: 'load initrd' + * + * Setting up 'load initrd' + * Setting up 'load initrd' succeeded + * + * Executing 'load initrd' + * Loaded 12378613 bytes + * CRC32 2997478465 + * + * Now the size and CRC32 can be compared to the provided file. + */ + +#include <efi_selftest.h> +#include <efi_loader.h> +#include <efi_load_initrd.h> + +static struct efi_boot_services *boottime; + +static struct efi_initrd_dp dp = { + .vendor = { + { + DEVICE_PATH_TYPE_MEDIA_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR_PATH, + sizeof(dp.vendor), + }, + EFI_INITRD_MEDIA_GUID, + }, + .end = { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(dp.end), + } +}; + +static struct efi_initrd_dp dp_invalid = { + .vendor = { + { + DEVICE_PATH_TYPE_MEDIA_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR_PATH, + sizeof(dp.vendor), + }, + EFI_INITRD_MEDIA_GUID, + }, + .end = { + 0x8f, /* invalid */ + 0xfe, /* invalid */ + sizeof(dp.end), + } +}; + +static int setup(const efi_handle_t handle, + const struct efi_system_table *systable) +{ + boottime = systable->boottime; + + return EFI_ST_SUCCESS; +} + +static int execute(void) +{ + efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID; + struct efi_load_file_protocol *lf2; + struct efi_device_path *dp2, *dp2_invalid; + efi_status_t status; + efi_handle_t handle; + char buffer[64]; + efi_uintn_t buffer_size; + void *buf; + u32 crc32; + + memset(buffer, 0, sizeof(buffer)); + + dp2 = (struct efi_device_path *)&dp; + status = boottime->locate_device_path(&lf2_proto_guid, &dp2, &handle); + if (status != EFI_SUCCESS) { + efi_st_error("Unable to locate device path\n"); + return EFI_ST_FAILURE; + } + + status = boottime->handle_protocol(handle, &lf2_proto_guid, + (void **)&lf2); + if (status != EFI_SUCCESS) { + efi_st_error("Unable to locate protocol\n"); + return EFI_ST_FAILURE; + } + + /* Case 1: + * buffer_size can't be NULL + * protocol can't be NULL + */ + status = lf2->load_file(lf2, dp2, false, NULL, &buffer); + if (status != EFI_INVALID_PARAMETER) { + efi_st_error("Buffer size can't be NULL\n"); + return EFI_ST_FAILURE; + } + buffer_size = sizeof(buffer); + status = lf2->load_file(NULL, dp2, false, &buffer_size, &buffer); + if (status != EFI_INVALID_PARAMETER) { + efi_st_error("Protocol can't be NULL\n"); + return EFI_ST_FAILURE; + } + + /* + * Case 2: Match end node type/sub-type on device path + */ + dp2_invalid = (struct efi_device_path *)&dp_invalid; + buffer_size = sizeof(buffer); + status = lf2->load_file(lf2, dp2_invalid, false, &buffer_size, &buffer); + if (status != EFI_INVALID_PARAMETER) { + efi_st_error("Invalid device path type must return EFI_INVALID_PARAMETER\n"); + return EFI_ST_FAILURE; + } + + status = lf2->load_file(lf2, dp2_invalid, false, &buffer_size, &buffer); + if (status != EFI_INVALID_PARAMETER) { + efi_st_error("Invalid device path sub-type must return EFI_INVALID_PARAMETER\n"); + return EFI_ST_FAILURE; + } + + /* + * Case 3: + * BootPolicy 'true' must return EFI_UNSUPPORTED + */ + buffer_size = sizeof(buffer); + status = lf2->load_file(lf2, dp2, true, &buffer_size, &buffer); + if (status != EFI_UNSUPPORTED) { + efi_st_error("BootPolicy true must return EFI_UNSUPPORTED\n"); + return EFI_ST_FAILURE; + } + + /* + * Case: Pass buffer size as zero, firmware must return + * EFI_BUFFER_TOO_SMALL and an appropriate size + */ + buffer_size = 0; + status = lf2->load_file(lf2, dp2, false, &buffer_size, NULL); + if (status != EFI_BUFFER_TOO_SMALL || !buffer_size) { + efi_st_printf("buffer_size: %u\n", (unsigned int)buffer_size); + efi_st_printf("status: %x\n", (unsigned int)status); + efi_st_error("Buffer size not updated\n"); + return EFI_ST_FAILURE; + } + + /* + * Case: Pass buffer size as smaller than the file_size, + * firmware must return * EFI_BUFFER_TOO_SMALL and an appropriate size + */ + buffer_size = 1; + status = lf2->load_file(lf2, dp2, false, &buffer_size, &buffer); + if (status != EFI_BUFFER_TOO_SMALL || buffer_size <= 1) { + efi_st_error("Buffer size not updated\n"); + return EFI_ST_FAILURE; + } + + status = boottime->allocate_pool(EFI_BOOT_SERVICES_DATA, buffer_size, + &buf); + if (status != EFI_SUCCESS) { + efi_st_error("Cannot allocate buffer\n"); + return EFI_ST_FAILURE; + } + + /* Case: Pass correct buffer, load the file and verify checksum*/ + status = lf2->load_file(lf2, dp2, false, &buffer_size, buf); + if (status != EFI_SUCCESS) { + efi_st_error("Loading initrd failed\n"); + return EFI_ST_FAILURE; + } + + efi_st_printf("Loaded %u bytes\n", (unsigned int)buffer_size); + status = boottime->calculate_crc32(buf, buffer_size, &crc32); + if (status != EFI_SUCCESS) { + efi_st_error("Could not determine CRC32\n"); + return EFI_ST_FAILURE; + } + efi_st_printf("CRC32 %u\n", (unsigned int)crc32); + + status = boottime->free_pool(buf); + if (status != EFI_SUCCESS) { + efi_st_error("Cannot free buffer\n"); + return EFI_ST_FAILURE; + } + + return EFI_ST_SUCCESS; +} + +EFI_UNIT_TEST(load_initrd) = { + .name = "load initrd", + .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, + .setup = setup, + .execute = execute, + .on_request = true, +}; |