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 <[email protected]>
---
 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",
-- 
2.34.1

Reply via email to