diff mbox series

[2/4] efi_loader: add EFI_RAM_DISK_PROTOCOL implementation

Message ID 20230707040045.485790-3-masahisa.kojima@linaro.org
State New
Headers show
Series introduce EFI_RAM_DISK_PROTOCOL | expand

Commit Message

Masahisa Kojima July 7, 2023, 4 a.m. UTC
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 mbox series

Patch

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