diff mbox series

[kvm-unit-tests,v2,09/18] lib/efi: Add support for loading the initrd

Message ID 20240227192109.487402-29-andrew.jones@linux.dev (mailing list archive)
State New, archived
Headers show
Series arm64: EFI improvements | expand

Commit Message

Andrew Jones Feb. 27, 2024, 7:21 p.m. UTC
When loading non-efi tests with QEMU's '-kernel' option we also load
an environ with the '-initrd' option. Now that efi tests can also be
loaded with the '-kernel' option also provide the '-initrd' environ.
For EFI, we use the EFI_LOAD_FILE2_PROTOCOL_GUID protocol to load
LINUX_EFI_INITRD_MEDIA_GUID. Each architecture which wants to use the
initrd for the environ will need to call setup_env() on the initrd
data. As usual, the new efi function is heavily influenced by Linux's
implementation.

Signed-off-by: Andrew Jones <andrew.jones@linux.dev>
---
 lib/efi.c       | 65 +++++++++++++++++++++++++++++++++++++++++++++++++
 lib/linux/efi.h | 27 ++++++++++++++++++++
 2 files changed, 92 insertions(+)

Comments

Nikos Nikoleris March 4, 2024, 7:44 a.m. UTC | #1
On 27/02/2024 19:21, Andrew Jones wrote:
> When loading non-efi tests with QEMU's '-kernel' option we also load
> an environ with the '-initrd' option. Now that efi tests can also be
> loaded with the '-kernel' option also provide the '-initrd' environ.
> For EFI, we use the EFI_LOAD_FILE2_PROTOCOL_GUID protocol to load
> LINUX_EFI_INITRD_MEDIA_GUID. Each architecture which wants to use the
> initrd for the environ will need to call setup_env() on the initrd
> data. As usual, the new efi function is heavily influenced by Linux's
> implementation.
> 
> Signed-off-by: Andrew Jones <andrew.jones@linux.dev>

Reviewed-by: Nikos Nikoleris <nikos.nikoleris@arm.com>

Thanks,

Nikos

> ---
>   lib/efi.c       | 65 +++++++++++++++++++++++++++++++++++++++++++++++++
>   lib/linux/efi.h | 27 ++++++++++++++++++++
>   2 files changed, 92 insertions(+)
> 
> diff --git a/lib/efi.c b/lib/efi.c
> index 0785bd3e8916..edfcc80ef114 100644
> --- a/lib/efi.c
> +++ b/lib/efi.c
> @@ -14,6 +14,10 @@
>   #include "efi.h"
>   #include "libfdt/libfdt.h"
>   
> +/* From each arch */
> +extern char *initrd;
> +extern u32 initrd_size;
> +
>   /* From lib/argv.c */
>   extern int __argc, __envc;
>   extern char *__argv[100];
> @@ -303,6 +307,65 @@ static bool efi_get_fdt(efi_handle_t handle, struct efi_loaded_image_64 *image,
>   	return fdt_check_header(*fdt) == 0;
>   }
>   
> +static const struct {
> +	struct efi_vendor_dev_path	vendor;
> +	struct efi_generic_dev_path	end;
> +} __packed initrd_dev_path = {
> +	{
> +		{
> +			EFI_DEV_MEDIA,
> +			EFI_DEV_MEDIA_VENDOR,
> +			sizeof(struct efi_vendor_dev_path),
> +		},
> +		LINUX_EFI_INITRD_MEDIA_GUID
> +	}, {
> +		EFI_DEV_END_PATH,
> +		EFI_DEV_END_ENTIRE,
> +		sizeof(struct efi_generic_dev_path)
> +	}
> +};
> +
> +static void efi_load_initrd(void)
> +{
> +	efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
> +	efi_device_path_protocol_t *dp;
> +	efi_load_file2_protocol_t *lf2;
> +	efi_handle_t handle;
> +	efi_status_t status;
> +	unsigned long file_size = 0;
> +
> +	initrd = NULL;
> +	initrd_size = 0;
> +
> +	dp = (efi_device_path_protocol_t *)&initrd_dev_path;
> +	status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
> +	if (status != EFI_SUCCESS)
> +		return;
> +
> +	status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid, (void **)&lf2);
> +	assert(status == EFI_SUCCESS);
> +
> +	status = efi_call_proto(lf2, load_file, dp, false, &file_size, NULL);
> +	assert(status == EFI_BUFFER_TOO_SMALL);
> +
> +	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, file_size, (void **)&initrd);
> +	assert(status == EFI_SUCCESS);
> +
> +	status = efi_call_proto(lf2, load_file, dp, false, &file_size, (void *)initrd);
> +	assert(status == EFI_SUCCESS);
> +
> +	initrd_size = (u32)file_size;
> +
> +	/*
> +	 * UEFI appends initrd=initrd to the command line when an initrd is present.
> +	 * Remove it in order to avoid confusing unit tests.
> +	 */
> +	if (!strcmp(__argv[__argc - 1], "initrd=initrd")) {
> +		__argv[__argc - 1] = NULL;
> +		__argc -= 1;
> +	}
> +}
> +
>   efi_status_t efi_main(efi_handle_t handle, efi_system_table_t *sys_tab)
>   {
>   	int ret;
> @@ -341,6 +404,8 @@ efi_status_t efi_main(efi_handle_t handle, efi_system_table_t *sys_tab)
>   	}
>   	setup_args(cmdline_ptr);
>   
> +	efi_load_initrd();
> +
>   	efi_bootinfo.fdt_valid = efi_get_fdt(handle, image, &efi_bootinfo.fdt);
>   	/* Set up efi_bootinfo */
>   	efi_bootinfo.mem_map.map = &map;
> diff --git a/lib/linux/efi.h b/lib/linux/efi.h
> index 92d798f79767..8fa23ad078ce 100644
> --- a/lib/linux/efi.h
> +++ b/lib/linux/efi.h
> @@ -70,6 +70,9 @@ typedef guid_t efi_guid_t;
>   
>   #define LOADED_IMAGE_PROTOCOL_GUID EFI_GUID(0x5b1b31a1, 0x9562, 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)
> +#define LINUX_EFI_INITRD_MEDIA_GUID EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
> +
>   typedef struct {
>   	efi_guid_t guid;
>   	void *table;
> @@ -248,6 +251,12 @@ struct efi_generic_dev_path {
>   	u16				length;
>   } __packed;
>   
> +struct efi_vendor_dev_path {
> +	struct efi_generic_dev_path	header;
> +	efi_guid_t			vendorguid;
> +	u8				vendordata[];
> +} __packed;
> +
>   typedef struct efi_generic_dev_path efi_device_path_protocol_t;
>   
>   /*
> @@ -449,6 +458,19 @@ typedef struct _efi_simple_file_system_protocol efi_simple_file_system_protocol_
>   typedef struct _efi_file_protocol efi_file_protocol_t;
>   typedef efi_simple_file_system_protocol_t efi_file_io_interface_t;
>   typedef efi_file_protocol_t efi_file_t;
> +typedef union efi_load_file_protocol efi_load_file_protocol_t;
> +typedef union efi_load_file_protocol efi_load_file2_protocol_t;
> +
> +union efi_load_file_protocol {
> +	struct {
> +		efi_status_t (__efiapi *load_file)(efi_load_file_protocol_t *,
> +						   efi_device_path_protocol_t *,
> +						   bool, unsigned long *, void *);
> +	};
> +	struct {
> +		u32 load_file;
> +	} mixed_mode;
> +};
>   
>   typedef efi_status_t efi_simple_file_system_protocol_open_volume(
>   	efi_simple_file_system_protocol_t *this,
> @@ -544,7 +566,12 @@ typedef struct {
>   	efi_char16_t	file_name[1];
>   } efi_file_info_t;
>   
> +#define efi_fn_call(inst, func, ...) (inst)->func(__VA_ARGS__)
>   #define efi_bs_call(func, ...) efi_system_table->boottime->func(__VA_ARGS__)
>   #define efi_rs_call(func, ...) efi_system_table->runtime->func(__VA_ARGS__)
> +#define efi_call_proto(inst, func, ...) ({				\
> +		__typeof__(inst) __inst = (inst);			\
> +		efi_fn_call(__inst, func, __inst, ##__VA_ARGS__);	\
> +})
>   
>   #endif /* __LINUX_UEFI_H */
diff mbox series

Patch

diff --git a/lib/efi.c b/lib/efi.c
index 0785bd3e8916..edfcc80ef114 100644
--- a/lib/efi.c
+++ b/lib/efi.c
@@ -14,6 +14,10 @@ 
 #include "efi.h"
 #include "libfdt/libfdt.h"
 
+/* From each arch */
+extern char *initrd;
+extern u32 initrd_size;
+
 /* From lib/argv.c */
 extern int __argc, __envc;
 extern char *__argv[100];
@@ -303,6 +307,65 @@  static bool efi_get_fdt(efi_handle_t handle, struct efi_loaded_image_64 *image,
 	return fdt_check_header(*fdt) == 0;
 }
 
+static const struct {
+	struct efi_vendor_dev_path	vendor;
+	struct efi_generic_dev_path	end;
+} __packed initrd_dev_path = {
+	{
+		{
+			EFI_DEV_MEDIA,
+			EFI_DEV_MEDIA_VENDOR,
+			sizeof(struct efi_vendor_dev_path),
+		},
+		LINUX_EFI_INITRD_MEDIA_GUID
+	}, {
+		EFI_DEV_END_PATH,
+		EFI_DEV_END_ENTIRE,
+		sizeof(struct efi_generic_dev_path)
+	}
+};
+
+static void efi_load_initrd(void)
+{
+	efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
+	efi_device_path_protocol_t *dp;
+	efi_load_file2_protocol_t *lf2;
+	efi_handle_t handle;
+	efi_status_t status;
+	unsigned long file_size = 0;
+
+	initrd = NULL;
+	initrd_size = 0;
+
+	dp = (efi_device_path_protocol_t *)&initrd_dev_path;
+	status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
+	if (status != EFI_SUCCESS)
+		return;
+
+	status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid, (void **)&lf2);
+	assert(status == EFI_SUCCESS);
+
+	status = efi_call_proto(lf2, load_file, dp, false, &file_size, NULL);
+	assert(status == EFI_BUFFER_TOO_SMALL);
+
+	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, file_size, (void **)&initrd);
+	assert(status == EFI_SUCCESS);
+
+	status = efi_call_proto(lf2, load_file, dp, false, &file_size, (void *)initrd);
+	assert(status == EFI_SUCCESS);
+
+	initrd_size = (u32)file_size;
+
+	/*
+	 * UEFI appends initrd=initrd to the command line when an initrd is present.
+	 * Remove it in order to avoid confusing unit tests.
+	 */
+	if (!strcmp(__argv[__argc - 1], "initrd=initrd")) {
+		__argv[__argc - 1] = NULL;
+		__argc -= 1;
+	}
+}
+
 efi_status_t efi_main(efi_handle_t handle, efi_system_table_t *sys_tab)
 {
 	int ret;
@@ -341,6 +404,8 @@  efi_status_t efi_main(efi_handle_t handle, efi_system_table_t *sys_tab)
 	}
 	setup_args(cmdline_ptr);
 
+	efi_load_initrd();
+
 	efi_bootinfo.fdt_valid = efi_get_fdt(handle, image, &efi_bootinfo.fdt);
 	/* Set up efi_bootinfo */
 	efi_bootinfo.mem_map.map = &map;
diff --git a/lib/linux/efi.h b/lib/linux/efi.h
index 92d798f79767..8fa23ad078ce 100644
--- a/lib/linux/efi.h
+++ b/lib/linux/efi.h
@@ -70,6 +70,9 @@  typedef guid_t efi_guid_t;
 
 #define LOADED_IMAGE_PROTOCOL_GUID EFI_GUID(0x5b1b31a1, 0x9562, 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)
+#define LINUX_EFI_INITRD_MEDIA_GUID EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
+
 typedef struct {
 	efi_guid_t guid;
 	void *table;
@@ -248,6 +251,12 @@  struct efi_generic_dev_path {
 	u16				length;
 } __packed;
 
+struct efi_vendor_dev_path {
+	struct efi_generic_dev_path	header;
+	efi_guid_t			vendorguid;
+	u8				vendordata[];
+} __packed;
+
 typedef struct efi_generic_dev_path efi_device_path_protocol_t;
 
 /*
@@ -449,6 +458,19 @@  typedef struct _efi_simple_file_system_protocol efi_simple_file_system_protocol_
 typedef struct _efi_file_protocol efi_file_protocol_t;
 typedef efi_simple_file_system_protocol_t efi_file_io_interface_t;
 typedef efi_file_protocol_t efi_file_t;
+typedef union efi_load_file_protocol efi_load_file_protocol_t;
+typedef union efi_load_file_protocol efi_load_file2_protocol_t;
+
+union efi_load_file_protocol {
+	struct {
+		efi_status_t (__efiapi *load_file)(efi_load_file_protocol_t *,
+						   efi_device_path_protocol_t *,
+						   bool, unsigned long *, void *);
+	};
+	struct {
+		u32 load_file;
+	} mixed_mode;
+};
 
 typedef efi_status_t efi_simple_file_system_protocol_open_volume(
 	efi_simple_file_system_protocol_t *this,
@@ -544,7 +566,12 @@  typedef struct {
 	efi_char16_t	file_name[1];
 } efi_file_info_t;
 
+#define efi_fn_call(inst, func, ...) (inst)->func(__VA_ARGS__)
 #define efi_bs_call(func, ...) efi_system_table->boottime->func(__VA_ARGS__)
 #define efi_rs_call(func, ...) efi_system_table->runtime->func(__VA_ARGS__)
+#define efi_call_proto(inst, func, ...) ({				\
+		__typeof__(inst) __inst = (inst);			\
+		efi_fn_call(__inst, func, __inst, ##__VA_ARGS__);	\
+})
 
 #endif /* __LINUX_UEFI_H */