efi_loader: Add disk interfaces
authorAlexander Graf <agraf@suse.de>
Fri, 4 Mar 2016 00:10:02 +0000 (01:10 +0100)
committerTom Rini <trini@konsulko.com>
Tue, 15 Mar 2016 22:03:11 +0000 (18:03 -0400)
A EFI applications usually want to access storage devices to load data from.

This patch adds support for EFI disk interfaces. It loops through all block
storage interfaces known to U-Boot and creates an EFI object for each existing
one. EFI applications can then through these objects call U-Boot's read and
write functions.

Signed-off-by: Alexander Graf <agraf@suse.de>
Reviewed-by: Simon Glass <sjg@chromium.org>
Tested-by: Simon Glass <sjg@chromium.org>
[trini: Update for various DM changes since posting]
Signed-off-by: Tom Rini <trini@konsulko.com>
include/efi_loader.h
lib/efi_loader/efi_disk.c [new file with mode: 0644]

index 8b3aaddd595ae6bf75a9f0f81024945f2da37426..be3c28abcd53a8fd752819c2a42e3fa0c7173d14 100644 (file)
@@ -87,6 +87,8 @@ struct efi_object {
 /* This list contains all UEFI objects we know of */
 extern struct list_head efi_obj_list;
 
+/* Called by bootefi to make all disk storage accessible as EFI objects */
+int efi_disk_register(void);
 /*
  * Stub implementation for a protocol opener that just returns the handle as
  * interface
diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
new file mode 100644 (file)
index 0000000..6f543ff
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ *  EFI application disk support
+ *
+ *  Copyright (c) 2016 Alexander Graf
+ *
+ *  SPDX-License-Identifier:     GPL-2.0+
+ */
+
+#include <common.h>
+#include <efi_loader.h>
+#include <inttypes.h>
+#include <part.h>
+#include <malloc.h>
+
+static const efi_guid_t efi_block_io_guid = BLOCK_IO_GUID;
+
+struct efi_disk_obj {
+       /* Generic EFI object parent class data */
+       struct efi_object parent;
+       /* EFI Interface callback struct for block I/O */
+       struct efi_block_io ops;
+       /* U-Boot ifname for block device */
+       const char *ifname;
+       /* U-Boot dev_index for block device */
+       int dev_index;
+       /* EFI Interface Media descriptor struct, referenced by ops */
+       struct efi_block_io_media media;
+       /* EFI device path to this block device */
+       struct efi_device_path_file_path *dp;
+};
+
+static void ascii2unicode(u16 *unicode, char *ascii)
+{
+       while (*ascii)
+               *(unicode++) = *(ascii++);
+}
+
+static efi_status_t efi_disk_open_block(void *handle, efi_guid_t *protocol,
+                       void **protocol_interface, void *agent_handle,
+                       void *controller_handle, uint32_t attributes)
+{
+       struct efi_disk_obj *diskobj = handle;
+
+       *protocol_interface = &diskobj->ops;
+
+       return EFI_SUCCESS;
+}
+
+static efi_status_t efi_disk_open_dp(void *handle, efi_guid_t *protocol,
+                       void **protocol_interface, void *agent_handle,
+                       void *controller_handle, uint32_t attributes)
+{
+       struct efi_disk_obj *diskobj = handle;
+
+       *protocol_interface = diskobj->dp;
+
+       return EFI_SUCCESS;
+}
+
+static efi_status_t EFIAPI efi_disk_reset(struct efi_block_io *this,
+                       char extended_verification)
+{
+       EFI_ENTRY("%p, %x", this, extended_verification);
+       return EFI_EXIT(EFI_DEVICE_ERROR);
+}
+
+enum efi_disk_direction {
+       EFI_DISK_READ,
+       EFI_DISK_WRITE,
+};
+
+static efi_status_t EFIAPI efi_disk_rw_blocks(struct efi_block_io *this,
+                       u32 media_id, u64 lba, unsigned long buffer_size,
+                       void *buffer, enum efi_disk_direction direction)
+{
+       struct efi_disk_obj *diskobj;
+       struct blk_desc *desc;
+       int blksz;
+       int blocks;
+       unsigned long n;
+
+       EFI_ENTRY("%p, %x, %"PRIx64", %lx, %p", this, media_id, lba,
+                 buffer_size, buffer);
+
+       diskobj = container_of(this, struct efi_disk_obj, ops);
+       if (!(desc = blk_get_dev(diskobj->ifname, diskobj->dev_index)))
+               return EFI_EXIT(EFI_DEVICE_ERROR);
+       blksz = desc->blksz;
+       blocks = buffer_size / blksz;
+
+#ifdef DEBUG_EFI
+       printf("EFI: %s:%d blocks=%x lba=%"PRIx64" blksz=%x dir=%d\n", __func__,
+              __LINE__, blocks, lba, blksz, direction);
+#endif
+
+       /* We only support full block access */
+       if (buffer_size & (blksz - 1))
+               return EFI_EXIT(EFI_DEVICE_ERROR);
+
+       if (direction == EFI_DISK_READ)
+               n = desc->block_read(desc, lba, blocks, buffer);
+       else
+               n = desc->block_write(desc, lba, blocks, buffer);
+
+       /* We don't do interrupts, so check for timers cooperatively */
+       efi_timer_check();
+
+#ifdef DEBUG_EFI
+       printf("EFI: %s:%d n=%lx blocks=%x\n", __func__, __LINE__, n, blocks);
+#endif
+       if (n != blocks)
+               return EFI_EXIT(EFI_DEVICE_ERROR);
+
+       return EFI_EXIT(EFI_SUCCESS);
+}
+
+static efi_status_t efi_disk_read_blocks(struct efi_block_io *this,
+                       u32 media_id, u64 lba, unsigned long buffer_size,
+                       void *buffer)
+{
+       return efi_disk_rw_blocks(this, media_id, lba, buffer_size, buffer,
+                                 EFI_DISK_READ);
+}
+
+static efi_status_t efi_disk_write_blocks(struct efi_block_io *this,
+                       u32 media_id, u64 lba, unsigned long buffer_size,
+                       void *buffer)
+{
+       return efi_disk_rw_blocks(this, media_id, lba, buffer_size, buffer,
+                                 EFI_DISK_WRITE);
+}
+
+static efi_status_t EFIAPI efi_disk_flush_blocks(struct efi_block_io *this)
+{
+       /* We always write synchronously */
+       EFI_ENTRY("%p", this);
+       return EFI_EXIT(EFI_SUCCESS);
+}
+
+static const struct efi_block_io block_io_disk_template = {
+       .reset = &efi_disk_reset,
+       .read_blocks = &efi_disk_read_blocks,
+       .write_blocks = &efi_disk_write_blocks,
+       .flush_blocks = &efi_disk_flush_blocks,
+};
+
+/*
+ * U-Boot doesn't have a list of all online disk devices. So when running our
+ * EFI payload, we scan through all of the potentially available ones and
+ * store them in our object pool.
+ *
+ * This gets called from do_bootefi_exec().
+ */
+int efi_disk_register(void)
+{
+       const struct block_drvr *cur_drvr;
+       int i;
+       int disks = 0;
+
+       /* Search for all available disk devices */
+       for (cur_drvr = block_drvr; cur_drvr->name; cur_drvr++) {
+               printf("Scanning disks on %s...\n", cur_drvr->name);
+               for (i = 0; i < 4; i++) {
+                       struct blk_desc *desc;
+                       struct efi_disk_obj *diskobj;
+                       struct efi_device_path_file_path *dp;
+                       int objlen = sizeof(*diskobj) + (sizeof(*dp) * 2);
+                       char devname[16] = { 0 }; /* dp->str is u16[16] long */
+
+                       desc = blk_get_dev(cur_drvr->name, i);
+                       if (!desc)
+                               continue;
+                       if (desc->type == DEV_TYPE_UNKNOWN)
+                               continue;
+
+                       diskobj = calloc(1, objlen);
+
+                       /* Fill in object data */
+                       diskobj->parent.protocols[0].guid = &efi_block_io_guid;
+                       diskobj->parent.protocols[0].open = efi_disk_open_block;
+                       diskobj->parent.protocols[1].guid = &efi_guid_device_path;
+                       diskobj->parent.protocols[1].open = efi_disk_open_dp;
+                       diskobj->parent.handle = diskobj;
+                       diskobj->ops = block_io_disk_template;
+                       diskobj->ifname = cur_drvr->name;
+                       diskobj->dev_index = i;
+
+                       /* Fill in EFI IO Media info (for read/write callbacks) */
+                       diskobj->media.removable_media = desc->removable;
+                       diskobj->media.media_present = 1;
+                       diskobj->media.block_size = desc->blksz;
+                       diskobj->media.io_align = desc->blksz;
+                       diskobj->media.last_block = desc->lba;
+                       diskobj->ops.media = &diskobj->media;
+
+                       /* Fill in device path */
+                       dp = (void*)&diskobj[1];
+                       diskobj->dp = dp;
+                       dp[0].dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE;
+                       dp[0].dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH;
+                       dp[0].dp.length = sizeof(*dp);
+                       snprintf(devname, sizeof(devname), "%s%d",
+                                cur_drvr->name, i);
+                       ascii2unicode(dp[0].str, devname);
+
+                       dp[1].dp.type = DEVICE_PATH_TYPE_END;
+                       dp[1].dp.sub_type = DEVICE_PATH_SUB_TYPE_END;
+                       dp[1].dp.length = sizeof(*dp);
+
+                       /* Hook up to the device list */
+                       list_add_tail(&diskobj->parent.link, &efi_obj_list);
+                       disks++;
+               }
+       }
+       printf("Found %d disks\n", disks);
+
+       return 0;
+}