
This commit adds the EFI_RAM_DISK_PROTOCOL implementation. User can mount the distro installer by registering the memory mapped ISO image through EFI_RAM_DISK_PROTOCOL.
Note that the installation process may not proceed after the distro installer calls ExitBootServices() since there is no hand-off process for the block device of memory mapped ISO image.
Signed-off-by: Masahisa Kojima masahisa.kojima@linaro.org --- include/efi_api.h | 13 ++ include/efi_loader.h | 4 + lib/efi_driver/efi_uclass.c | 7 +- lib/efi_loader/Kconfig | 6 + lib/efi_loader/Makefile | 1 + lib/efi_loader/efi_ram_disk.c | 334 ++++++++++++++++++++++++++++++++++ lib/efi_loader/efi_setup.c | 6 + lib/uuid.c | 4 + 8 files changed, 373 insertions(+), 2 deletions(-) create mode 100644 lib/efi_loader/efi_ram_disk.c
diff --git a/include/efi_api.h b/include/efi_api.h index 4ee4a1b5e9..3982ab89bc 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -764,6 +764,19 @@ struct efi_block_io { efi_status_t (EFIAPI *flush_blocks)(struct efi_block_io *this); };
+#define EFI_RAM_DISK_PROTOCOL_GUID \ + EFI_GUID(0xab38a0df, 0x6873, 0x44a9, \ + 0x87, 0xe6, 0xd4, 0xeb, 0x56, 0x14, 0x84, 0x49) + +struct efi_ram_disk_protocol { + /* "register" is a reserved keyword in C, use "disk_register" instead */ + efi_status_t(EFIAPI *disk_register)( + u64 ram_disk_base, u64 ram_disk_size, efi_guid_t *ram_disk_type, + struct efi_device_path *parent_device_path, + struct efi_device_path **device_path); + efi_status_t (EFIAPI *unregister)(struct efi_device_path *device_path); +}; + struct simple_text_output_mode { s32 max_mode; s32 mode; diff --git a/include/efi_loader.h b/include/efi_loader.h index 604fd765f7..70c8c83099 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -333,6 +333,8 @@ extern const efi_guid_t smbios_guid; /*GUID of console */ extern const efi_guid_t efi_guid_text_input_protocol; extern const efi_guid_t efi_guid_text_output_protocol; +/* GUID of Ram Disk protocol */ +extern const efi_guid_t efi_guid_ram_disk_protocol;
extern char __efi_runtime_start[], __efi_runtime_stop[]; extern char __efi_runtime_rel_start[], __efi_runtime_rel_stop[]; @@ -1159,4 +1161,6 @@ efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int */ void efi_add_known_memory(void);
+efi_status_t efi_ram_disk_register(void); + #endif /* _EFI_LOADER_H */ diff --git a/lib/efi_driver/efi_uclass.c b/lib/efi_driver/efi_uclass.c index 45f9351988..f7597811b7 100644 --- a/lib/efi_driver/efi_uclass.c +++ b/lib/efi_driver/efi_uclass.c @@ -44,8 +44,11 @@ static efi_status_t check_node_type(efi_handle_t handle) /* Get the last node */ const struct efi_device_path *node = efi_dp_last_node(dp); /* We do not support partitions as controller */ - if (!node || node->type == DEVICE_PATH_TYPE_MEDIA_DEVICE) - ret = EFI_UNSUPPORTED; + if (!node || node->type == DEVICE_PATH_TYPE_MEDIA_DEVICE) { + /* We support RAM disk as controller */ + if (node->sub_type != DEVICE_PATH_SUB_TYPE_RAM_DISK_PATH) + ret = EFI_UNSUPPORTED; + } } return ret; } diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig index c5835e6ef6..1a81b65688 100644 --- a/lib/efi_loader/Kconfig +++ b/lib/efi_loader/Kconfig @@ -433,4 +433,10 @@ config EFI_RISCV_BOOT_PROTOCOL replace the transfer via the device-tree. The latter is not possible on systems using ACPI.
+config EFI_RAM_DISK_PROTOCOL + bool "EFI_RAM_DISK_PROTOCOL support" + depends on PARTITIONS + help + Provide a EFI_RAM_DISK_PROTOCOL implementation to register the + RAM disk and create the block device. endif diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index 1a8c8d7cab..4197f594a2 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -86,6 +86,7 @@ obj-$(CONFIG_EFI_RISCV_BOOT_PROTOCOL) += efi_riscv.o obj-$(CONFIG_EFI_LOAD_FILE2_INITRD) += efi_load_initrd.o obj-$(CONFIG_EFI_SIGNATURE_SUPPORT) += efi_signature.o obj-$(CONFIG_EFI_ECPT) += efi_conformance.o +obj-$(CONFIG_EFI_RAM_DISK_PROTOCOL) += efi_ram_disk.o
EFI_VAR_SEED_FILE := $(subst $",,$(CONFIG_EFI_VAR_SEED_FILE)) $(obj)/efi_var_seed.o: $(srctree)/$(EFI_VAR_SEED_FILE) diff --git a/lib/efi_loader/efi_ram_disk.c b/lib/efi_loader/efi_ram_disk.c new file mode 100644 index 0000000000..10a6944aea --- /dev/null +++ b/lib/efi_loader/efi_ram_disk.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * RAM Disk Protocol + * + * Copyright (c) 2023, Linaro Limited + */ + +#include <efi_driver.h> +#include <malloc.h> +#include <linux/unaligned/le_byteshift.h> +#include <linux/unaligned/generic.h> + +#define MEM_BLOCK_SIZE_SHIFT 9 /* 512 bytes */ + +const efi_guid_t efi_guid_ram_disk_protocol = EFI_RAM_DISK_PROTOCOL_GUID; +static struct list_head obj_list; + +/** + * struct efi_ram_disk_obj - ram disk protocol object + * + * @ops: EFI disk I/O protocol interface + * @media: block I/O media information + * @dp: device path + * @image: image base address + * @image_size: image size + * @list: list structure + */ +struct efi_ram_disk_obj { + struct efi_block_io ops; + struct efi_block_io_media media; + struct efi_device_path *dp; + u8 *image; + u64 image_size; + struct list_head list; +}; + +/* + * reset - Reset service of the block IO protocol + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * @extended_verification: extended verification + * Return: status code + */ +static efi_status_t EFIAPI reset(struct efi_block_io *this, + char extended_verification) +{ + return EFI_SUCCESS; +} + +/* + * read_blocks - Read service of the block IO protocol + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * @media_id: id of the medium to be read from + * @lba: starting logical block for reading + * @buffer_size: size of the read buffer + * @buffer: pointer to the destination buffer + * Return: status code + */ +static efi_status_t EFIAPI read_blocks(struct efi_block_io *this, u32 media_id, + u64 lba, efi_uintn_t buffer_size, + void *buffer) +{ + u8 *start; + struct efi_ram_disk_obj *ram_disk_obj; + + if (!this || !buffer) + return EFI_INVALID_PARAMETER; + + if (!buffer_size) + return EFI_SUCCESS; + + /* TODO: check for media changes */ + if (media_id != this->media->media_id) + return EFI_MEDIA_CHANGED; + + if (!this->media->media_present) + return EFI_NO_MEDIA; + + EFI_ENTRY("%p, %x, %llx, %zx, %p", this, media_id, lba, + buffer_size, buffer); + + ram_disk_obj = container_of(this, struct efi_ram_disk_obj, ops); + + if ((lba << MEM_BLOCK_SIZE_SHIFT) + buffer_size > + ram_disk_obj->image_size) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + start = ram_disk_obj->image + (lba << MEM_BLOCK_SIZE_SHIFT); + memmove(buffer, start, buffer_size); + + return EFI_EXIT(EFI_SUCCESS); +} + +/* + * write_blocks - Write service of the block IO protocol + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * @media_id: id of the medium to be written to + * @lba: starting logical block for writing + * @buffer_size: size of the write buffer + * @buffer: pointer to the source buffer + * Return: status code + */ +static efi_status_t EFIAPI write_blocks(struct efi_block_io *this, u32 media_id, + u64 lba, efi_uintn_t buffer_size, + void *buffer) +{ + u8 *start; + struct efi_ram_disk_obj *ram_disk_obj; + + if (!this || !buffer) + return EFI_INVALID_PARAMETER; + + if (this->media->read_only) + return EFI_WRITE_PROTECTED; + + if (!buffer_size) + return EFI_SUCCESS; + + /* TODO: check for media changes */ + if (media_id != this->media->media_id) + return EFI_MEDIA_CHANGED; + + if (!this->media->media_present) + return EFI_NO_MEDIA; + + EFI_ENTRY("%p, %x, %llx, %zx, %p", this, media_id, lba, + buffer_size, buffer); + + ram_disk_obj = container_of(this, struct efi_ram_disk_obj, ops); + + if ((lba << MEM_BLOCK_SIZE_SHIFT) + buffer_size > + ram_disk_obj->image_size) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + start = ram_disk_obj->image + (lba << MEM_BLOCK_SIZE_SHIFT); + memmove(start, buffer, buffer_size); + + return EFI_EXIT(EFI_SUCCESS); +} + +/* + * flush_blocks - Flush service of the block IO protocol + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * Return: status code + */ +static efi_status_t EFIAPI flush_blocks(struct efi_block_io *this) +{ + return EFI_SUCCESS; +} + +/* + * ram_disk_register - Register service of the RAM disk protocol + * + * @ram_disk_base: The base address of registered RAM disk + * @ram_disk_size: The size of registered RAM disk + * @ram_disk_type: The type of registered RAM disk + * @parent_device_path: Pointer to the parent device path + * @device_path: Pointer to the device path + * Return: status code + */ +static efi_status_t EFIAPI +ram_disk_register(u64 ram_disk_base, u64 ram_disk_size, + efi_guid_t *ram_disk_type, + struct efi_device_path *parent_device_path, + struct efi_device_path **device_path) +{ + efi_status_t ret; + efi_handle_t disk_handle = NULL; + struct efi_device_path *dp; + struct efi_device_path end_node; + struct efi_ram_disk_obj *ram_disk_obj; + struct efi_device_path_ram_disk_path ram_disk_node; + + EFI_ENTRY("%llu %llu %pUs %p, %p", ram_disk_base, ram_disk_size, + ram_disk_type, parent_device_path, device_path); + + if (!ram_disk_size || !ram_disk_type || !device_path) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + *device_path = NULL; + + ram_disk_obj = calloc(1, sizeof(*ram_disk_obj)); + if (!ram_disk_obj) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + ram_disk_obj->image = (u8 *)ram_disk_base; + ram_disk_obj->image_size = ram_disk_size; + ram_disk_obj->ops.media = &ram_disk_obj->media; + + ram_disk_obj->ops.media->block_size = 1 << MEM_BLOCK_SIZE_SHIFT; + ram_disk_obj->ops.media->last_block = + (ram_disk_size >> MEM_BLOCK_SIZE_SHIFT) - 1; + ram_disk_obj->ops.media->media_present = 1; + + ram_disk_obj->ops.reset = reset; + ram_disk_obj->ops.read_blocks = read_blocks; + ram_disk_obj->ops.write_blocks = write_blocks; + ram_disk_obj->ops.flush_blocks = flush_blocks; + + dp = calloc(1, sizeof(struct efi_device_path_ram_disk_path) + + sizeof(struct efi_device_path)); + if (!dp) { + free(ram_disk_obj); + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + } + + ram_disk_node.dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + ram_disk_node.dp.sub_type = DEVICE_PATH_SUB_TYPE_RAM_DISK_PATH; + ram_disk_node.dp.length = sizeof(struct efi_device_path_ram_disk_path); + put_unaligned_le64(ram_disk_base, &ram_disk_node.starting_address); + put_unaligned_le64(ram_disk_base + ram_disk_size - 1, &ram_disk_node.ending_address); + guidcpy(&ram_disk_node.disk_type_guid, ram_disk_type); + ram_disk_node.disk_instance = 0; + memcpy(dp, &ram_disk_node, sizeof(struct efi_device_path_ram_disk_path)); + + end_node.type = DEVICE_PATH_TYPE_END; + end_node.sub_type = DEVICE_PATH_SUB_TYPE_END; + end_node.length = sizeof(struct efi_device_path); + memcpy((char *)dp + sizeof(struct efi_device_path_ram_disk_path), + &end_node, sizeof(struct efi_device_path)); + + ram_disk_obj->dp = efi_dp_append(parent_device_path, dp); + free(dp); + if (!ram_disk_obj->dp) { + free(ram_disk_obj); + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + } + + if (efi_dp_find_obj(ram_disk_obj->dp, NULL, NULL)) { + log_err("Already Started\n"); + ret = EFI_ALREADY_STARTED; + goto err; + } + + ret = efi_install_multiple_protocol_interfaces( + &disk_handle, &efi_guid_device_path, ram_disk_obj->dp, + &efi_block_io_guid, &ram_disk_obj->ops, NULL); + if (ret != EFI_SUCCESS) { + log_err("InstallProtocolInterface failed\n"); + goto err; + } + + ret = EFI_CALL(systab.boottime->connect_controller(disk_handle, NULL, + NULL, 1)); + if (ret != EFI_SUCCESS) { + log_err("ConnectController failed\n"); + goto err; + } + + *device_path = ram_disk_obj->dp; + list_add(&ram_disk_obj->list, &obj_list); + + return EFI_EXIT(ret); +err: + efi_free_pool(ram_disk_obj->dp); + free(ram_disk_obj); + + return EFI_EXIT(ret); +} + +/* + * ram_disk_unregister - Unregister service of the RAM disk protocol + * + * @device_path: Pointer to the device path + * Return: status code + */ +static efi_status_t EFIAPI +ram_disk_unregister(struct efi_device_path *device_path) +{ + int ret; + efi_handle_t disk_handle; + struct list_head *pos, *n; + struct efi_ram_disk_obj *entry; + struct efi_ram_disk_obj *ram_disk_obj = NULL; + + EFI_ENTRY("%p", device_path); + + if (!device_path) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + list_for_each_safe(pos, n, &obj_list) { + entry = list_entry(pos, struct efi_ram_disk_obj, list); + if (!efi_dp_match(device_path, entry->dp)) { + ram_disk_obj = entry; + break; + } + } + + if (!ram_disk_obj) + return EFI_EXIT(EFI_NOT_FOUND); + + disk_handle = efi_dp_find_obj(device_path, &efi_block_io_guid, NULL); + if (!disk_handle) + return EFI_EXIT(EFI_NOT_FOUND); + + ret = efi_uninstall_multiple_protocol_interfaces( + disk_handle, &efi_guid_device_path, ram_disk_obj->dp, + &efi_block_io_guid, &ram_disk_obj->ops, NULL); + if (ret != EFI_SUCCESS) + log_err("UninstallProtocolInterface failed\n"); + + list_del(&ram_disk_obj->list); + efi_free_pool(ram_disk_obj->dp); + free(ram_disk_obj); + + return EFI_EXIT(ret); +} + +static const struct efi_ram_disk_protocol efi_ram_disk_protocol = { + .disk_register = ram_disk_register, + .unregister = ram_disk_unregister, +}; + +/** + * efi_ram_disk_register() - register EFI_RAM_DISK_PROTOCOL + * + * Return: status code + */ +efi_status_t efi_ram_disk_register(void) +{ + efi_status_t ret; + + ret = efi_add_protocol(efi_root, &efi_guid_ram_disk_protocol, + (void *)&efi_ram_disk_protocol); + if (ret != EFI_SUCCESS) + log_err("Cannot install EFI_RAM_DISK_PROTOCOL\n"); + + INIT_LIST_HEAD(&obj_list); + + return ret; +} diff --git a/lib/efi_loader/efi_setup.c b/lib/efi_loader/efi_setup.c index 877f3878d6..8c430d4a9c 100644 --- a/lib/efi_loader/efi_setup.c +++ b/lib/efi_loader/efi_setup.c @@ -344,6 +344,12 @@ efi_status_t efi_init_obj_list(void) if (ret != EFI_SUCCESS) goto out;
+ if (IS_ENABLED(CONFIG_EFI_RAM_DISK_PROTOCOL)) { + ret = efi_ram_disk_register(); + if (ret != EFI_SUCCESS) + goto out; + } + /* Initialize EFI runtime services */ ret = efi_reset_system_init(); if (ret != EFI_SUCCESS) diff --git a/lib/uuid.c b/lib/uuid.c index 96e1af3c8b..9827588186 100644 --- a/lib/uuid.c +++ b/lib/uuid.c @@ -195,6 +195,10 @@ static const struct { "Firmware Management", EFI_FIRMWARE_MANAGEMENT_PROTOCOL_GUID }, + { + "Ram Disk", + EFI_RAM_DISK_PROTOCOL_GUID + }, /* Configuration table GUIDs */ { "ACPI table",