[efi] Support EFI_BLOCK_IO2 protocol
Implements new EFI block device that uses async AML emmc drivers and
support both EFI_BLOCK_IO and EFI_BLOCK_IO2 protocol.
Bug: 347715147
Change-Id: I14abbdfd4ce7ab44fdd61b0461a00993f7e5628d
Reviewed-on: https://turquoise-internal-review.googlesource.com/c/third_party/u-boot/+/866471
Reviewed-by: David Pursell <dpursell@google.com>
GitOrigin-RevId: 8f381fdaa7f33266262792b91187bf5612f7f171
diff --git a/include/efi_api.h b/include/efi_api.h
index 8f5ef5f..c5f6fb0 100644
--- a/include/efi_api.h
+++ b/include/efi_api.h
@@ -752,6 +752,31 @@
efi_status_t (EFIAPI *flush_blocks)(struct efi_block_io *this);
};
+#define EFI_BLOCK_IO2_PROTOCOL_GUID \
+ EFI_GUID(0xa77b2472, 0xe282, 0x4e9f, 0xa2, 0x45, 0xc2, 0xc0, 0xe2, \
+ 0x7b, 0xbc, 0xc1)
+
+typedef struct {
+ struct efi_event *event;
+ efi_status_t transaction_status;
+} efi_block_io2_token_t;
+
+struct efi_block_io2 {
+ struct efi_block_io_media *media;
+ efi_status_t(EFIAPI *reset)(struct efi_block_io2 *this,
+ bool extended_verification);
+ efi_status_t(EFIAPI *read_blocks_ex)(struct efi_block_io2 *this,
+ uint32_t media_id, uint64_t lba,
+ efi_block_io2_token_t *token,
+ size_t buffer_size, void *buffer);
+ efi_status_t(EFIAPI *write_blocks_ex)(struct efi_block_io2 *this,
+ uint32_t media_id, uint64_t lba,
+ efi_block_io2_token_t *token,
+ size_t buffer_size, void *buffer);
+ efi_status_t(EFIAPI *flush_blocks_ex)(struct efi_block_io2 *this,
+ efi_block_io2_token_t *token);
+};
+
struct simple_text_output_mode {
s32 max_mode;
s32 mode;
diff --git a/include/efi_loader.h b/include/efi_loader.h
index 865c9ae..edf1b79 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -274,6 +274,7 @@
#endif
/* GUID of the EFI_BLOCK_IO_PROTOCOL */
extern const efi_guid_t efi_block_io_guid;
+extern const efi_guid_t efi_block_io2_guid;
extern const efi_guid_t efi_global_variable_guid;
extern const efi_guid_t efi_guid_console_control;
extern const efi_guid_t efi_guid_device_path;
@@ -566,6 +567,8 @@
efi_status_t efi_console_register(void);
/* Called by efi_init_obj_list() to proble all block devices */
efi_status_t efi_disks_register(void);
+/* Called by efi_init_obj_list() to proble all Amlogic SD eMMC devices */
+efi_status_t efi_aml_sd_disk_register(void);
/* Called by efi_init_obj_list() to install EFI_RNG_PROTOCOL */
efi_status_t efi_rng_register(void);
/* Called by efi_init_obj_list() to install EFI_TCG2_PROTOCOL */
diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile
index fc3526e..ec52fa7 100644
--- a/lib/efi_loader/Makefile
+++ b/lib/efi_loader/Makefile
@@ -90,6 +90,7 @@
obj-$(CONFIG_EFI_ECPT) += efi_conformance.o
obj-y += efi_android_boot.o
+obj-y += efi_aml_sd_emmc.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_aml_sd_emmc.c b/lib/efi_loader/efi_aml_sd_emmc.c
new file mode 100644
index 0000000..b5a19df
--- /dev/null
+++ b/lib/efi_loader/efi_aml_sd_emmc.c
@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// Copyright 2024 Google LLC
+
+#include <common.h>
+#include <efi_loader.h>
+#include <malloc.h>
+#include <mmc.h>
+
+#include <aml_sd_emmc.h>
+
+#define VENDOR_PATH_DATA_LEN 16
+
+// Represents the EFI device path for a hardware partition on a Amlogic SD
+// eMMC.
+struct efi_aml_sd_disk_hwpart_device_path {
+ struct efi_device_path_vendor path;
+ char vendor_defined_data[VENDOR_PATH_DATA_LEN];
+ struct efi_device_path end;
+};
+
+// Contains input parameters for block read/write.
+struct efi_io2_request {
+ void *buffer;
+ uint64_t blk_off;
+ uint64_t blk_cnt;
+ bool is_write;
+ efi_block_io2_token_t *token;
+ bool io_started;
+};
+
+// Represents a hardware partition on an Amlogic SD eMMC device.
+struct efi_aml_sd_disk_hwpart {
+ struct efi_object header;
+ struct efi_aml_sd_disk *disk;
+ int hwpart;
+ struct efi_aml_sd_disk_hwpart_device_path dp;
+ struct efi_block_io_media media;
+ struct efi_block_io ops;
+ struct efi_block_io2 ops_io2;
+ struct efi_io2_request io_request;
+};
+
+// Represents an Amlogic SD eMMC device.
+struct efi_aml_sd_disk {
+ struct mmc *mmc;
+ int dev;
+ efi_block_io2_token_t *io2_token;
+ struct efi_event *timer_event;
+ struct efi_aml_sd_disk_hwpart hwparts[3];
+ int curr_hwpart; // current hardware partition to process.
+ efi_status_t status;
+};
+
+// Sets an EFI_BLOCK_IO2 request to the finished state. Sets status and
+// notifies completion.
+static void efi_io2_finish(struct efi_io2_request *request, efi_status_t status)
+{
+ if (!request->buffer || !request->token) {
+ return;
+ }
+
+ if (request->token->event) {
+ request->token->event->is_signaled = true;
+ }
+ request->token->transaction_status = status;
+ // Resets everything to stop referencing user buffer.
+ memset(request, 0, sizeof(*request));
+}
+
+// Aborts all IO with the error code.
+static void abort_all_ios(struct efi_aml_sd_disk *disk, efi_status_t err) {
+ for (size_t i = 0; i < ARRAY_SIZE(disk->hwparts); i++) {
+ efi_io2_finish(&disk->hwparts[i].io_request, err);
+ }
+}
+
+// Sets disk to an error state and aborts all IO with the error code.
+static void handle_driver_error(struct efi_aml_sd_disk *disk, efi_status_t err)
+{
+ disk->status = err;
+ abort_all_ios(disk, err);
+}
+
+// Processes pending IO requests.
+static void efi_disk_poll(struct efi_aml_sd_disk *disk)
+{
+ // We don't support recovering from previous driver error.
+ if (disk->status != EFI_SUCCESS) {
+ return;
+ }
+ size_t num_hwparts = ARRAY_SIZE(disk->hwparts);
+ // Process the IO request of currently scheduled hardwared partition.
+ struct efi_io2_request *request =
+ &disk->hwparts[disk->curr_hwpart].io_request;
+ // If the hardware partition doesn't have a pending request, find the next
+ // one.
+ for (size_t i = 0; i < num_hwparts && !request->buffer; i++) {
+ disk->curr_hwpart = (disk->curr_hwpart + 1) % num_hwparts;
+ request = &disk->hwparts[disk->curr_hwpart].io_request;
+ }
+ // Returns early if none has any IO request.
+ if (!request->buffer) {
+ return;
+ }
+ // Processes/updates the request.
+ if (!request->io_started) {
+ if (aml_sd_is_busy(disk->mmc)) {
+ return;
+ }
+ // Initiates the read/write if it has not started.
+ mmc_select_hwpart(disk->dev, disk->curr_hwpart);
+ if (request->is_write ?
+ aml_sd_write_async(disk->mmc, request->blk_off,
+ request->blk_cnt,
+ request->buffer) :
+ aml_sd_read_async(disk->mmc, request->blk_off,
+ request->blk_cnt,
+ request->buffer)) {
+ handle_driver_error(disk, EFI_DEVICE_ERROR);
+ } else {
+ request->io_started = true;
+ }
+ } else {
+ // If started, polls emmc status.
+ if (aml_sd_poll_io(disk->mmc)) {
+ handle_driver_error(disk, EFI_DEVICE_ERROR);
+ } else if (!aml_sd_is_busy(disk->mmc)) {
+ efi_io2_finish(request, EFI_SUCCESS);
+ // Sets to the next hardware partition to poll.
+ // This ensures fairness and prevents one busy block device from
+ // getting scheduled all the time.
+ disk->curr_hwpart = (disk->curr_hwpart + 1) % num_hwparts;
+ }
+ }
+}
+
+// Common helper used by `read_blocks_ex` and `write_blocks_ex`.
+static efi_status_t EFIAPI rw_blocks_ex(struct efi_block_io2 *this,
+ uint32_t media_id, uint64_t lba,
+ efi_block_io2_token_t *token,
+ size_t buffer_size, void *buffer,
+ bool is_write)
+{
+ if (!this) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ struct efi_aml_sd_disk_hwpart *disk_hwpart =
+ container_of(this, struct efi_aml_sd_disk_hwpart, ops_io2);
+
+ // Checks parameters.
+ uint64_t blk_cnt = buffer_size / this->media->block_size;
+ uint64_t end = lba + blk_cnt;
+ if (buffer_size % this->media->block_size) {
+ return EFI_BAD_BUFFER_SIZE;
+ } else if (end < lba || end > this->media->last_block + 1 ||
+ ((size_t)buffer % (size_t)this->media->io_align)) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ EFI_ENTRY("%p, %x, %llx, %zx, %p", this, media_id, lba, buffer_size,
+ buffer);
+
+ // Poll the eMMC, in case this device has an IO but is already finished.
+ // This will clear the request and therefore allow us to serve the new one
+ // instead of returnning `EFI_OUT_OF_RESOURCES`
+ efi_timer_check();
+
+ // If there is already a pending request, returns EFI_OUT_OF_RESOURCES.
+ if (disk_hwpart->io_request.buffer) {
+ return EFI_EXIT(EFI_OUT_OF_RESOURCES);
+ }
+
+ disk_hwpart->io_request.buffer = buffer;
+ disk_hwpart->io_request.blk_off = lba;
+ disk_hwpart->io_request.blk_cnt = blk_cnt;
+ disk_hwpart->io_request.is_write = is_write;
+ disk_hwpart->io_request.token = token;
+ disk_hwpart->io_request.io_started = false;
+ // Perform a poll to catch early errors.
+ efi_timer_check();
+
+ // If operation is blocking, waits for the IO to complete.
+ if (token->event == NULL) {
+ // `efi_disk_poll` resets buffer pointer to NULL once completed.
+ while (disk_hwpart->io_request.buffer) {
+ efi_timer_check();
+ }
+ }
+
+ return EFI_EXIT(disk_hwpart->disk->status);
+}
+
+// `EFI_BLOCK_IO2_PROTOCOL.read_blocks_ex`.
+static efi_status_t EFIAPI read_blocks_ex(struct efi_block_io2 *this,
+ uint32_t media_id, uint64_t lba,
+ efi_block_io2_token_t *token,
+ size_t buffer_size, void *buffer)
+{
+ return rw_blocks_ex(this, media_id, lba, token, buffer_size, buffer,
+ false);
+}
+
+// `EFI_BLOCK_IO2_PROTOCOL.write_blocks_ex`.
+static efi_status_t EFIAPI write_blocks_ex(struct efi_block_io2 *this,
+ uint32_t media_id, uint64_t lba,
+ efi_block_io2_token_t *token,
+ size_t buffer_size, void *buffer)
+{
+ return rw_blocks_ex(this, media_id, lba, token, buffer_size, buffer,
+ true);
+}
+
+// `EFI_BLOCK_IO2_PROTOCOL.flush_blocks`.
+static efi_status_t EFIAPI flush_blocks_ex(struct efi_block_io2 *this,
+ efi_block_io2_token_t *token)
+{
+ EFI_ENTRY("%p", this);
+ token->transaction_status = EFI_SUCCESS;
+ if (token->event) {
+ token->event->is_signaled = true;
+ }
+ return EFI_EXIT(EFI_SUCCESS);
+}
+
+// `EFI_BLOCK_IO2_PROTOCOL.reset`.
+static efi_status_t EFIAPI reset_io2(struct efi_block_io2 *this,
+ bool extended_verification)
+{
+ if (!this) {
+ return EFI_INVALID_PARAMETER;
+ }
+ EFI_ENTRY("%p, %x", this, extended_verification);
+ struct efi_aml_sd_disk_hwpart *disk_hwpart =
+ container_of(this, struct efi_aml_sd_disk_hwpart, ops_io2);
+ struct efi_aml_sd_disk *disk = disk_hwpart->disk;
+ // Waits until the current mmc operation is finished. Disregards result.
+ aml_sd_sync(disk->mmc);
+ // Aborts all IO.
+ abort_all_ios(disk, EFI_ABORTED);
+ return EFI_EXIT(EFI_SUCCESS);
+}
+
+// Common helper used by `read_blocks` and `write_blocks`.
+static efi_status_t EFIAPI rw_blocks(struct efi_block_io *this,
+ uint32_t media_id, uint64_t lba,
+ size_t buffer_size, void *buffer,
+ bool is_write)
+{
+ if (!this) {
+ return EFI_INVALID_PARAMETER;
+ }
+ // Redirects to `EFI_BLOCK_IO2_PROTOCOL` APIs.
+ struct efi_aml_sd_disk_hwpart *disk_hwpart =
+ container_of(this, struct efi_aml_sd_disk_hwpart, ops);
+ efi_block_io2_token_t token = {
+ .event = NULL,
+ .transaction_status = EFI_NOT_READY,
+ };
+ return is_write ? write_blocks_ex(&disk_hwpart->ops_io2, media_id, lba,
+ &token, buffer_size, buffer) :
+ read_blocks_ex(&disk_hwpart->ops_io2, media_id, lba,
+ &token, buffer_size, buffer);
+}
+
+// `EFI_BLOCK_IO_PROTOCOL.read_blocks`.
+static efi_status_t EFIAPI read_blocks(struct efi_block_io *this, u32 media_id,
+ u64 lba, efi_uintn_t buffer_size,
+ void *buffer)
+{
+ return rw_blocks(this, media_id, lba, buffer_size, buffer, false);
+}
+
+// `EFI_BLOCK_IO_PROTOCOL.write_blocks`.
+static efi_status_t EFIAPI write_blocks(struct efi_block_io *this, u32 media_id,
+ u64 lba, efi_uintn_t buffer_size,
+ void *buffer)
+{
+ return rw_blocks(this, media_id, lba, buffer_size, buffer, true);
+}
+
+// `EFI_BLOCK_IO_PROTOCOL.reset`.
+static efi_status_t EFIAPI reset(struct efi_block_io *this,
+ char extended_verification)
+{
+ if (!this) {
+ return EFI_INVALID_PARAMETER;
+ }
+ // Redirects to `EFI_BLOCK_IO2_PROTOCOL` APIs.
+ struct efi_aml_sd_disk_hwpart *disk_hwpart =
+ container_of(this, struct efi_aml_sd_disk_hwpart, ops);
+ return reset_io2(&disk_hwpart->ops_io2, extended_verification);
+}
+
+// `EFI_BLOCK_IO_PROTOCOL.flush_blocks`.
+static efi_status_t EFIAPI flush_blocks(struct efi_block_io *this)
+{
+ if (!this) {
+ return EFI_INVALID_PARAMETER;
+ }
+ // Redirects to `EFI_BLOCK_IO2_PROTOCOL` APIs.
+ struct efi_aml_sd_disk_hwpart *disk_hwpart =
+ container_of(this, struct efi_aml_sd_disk_hwpart, ops);
+ efi_block_io2_token_t token = {
+ .event = NULL,
+ .transaction_status = EFI_NOT_READY,
+ };
+ return flush_blocks_ex(&disk_hwpart->ops_io2, &token);
+}
+
+// A timer event notification function that polls and updates block device
+// status.
+static void EFIAPI aml_sd_disk_timer_notify(struct efi_event *event,
+ void *context)
+{
+ EFI_ENTRY("%p, %p", event, context);
+ efi_disk_poll(context);
+ EFI_EXIT(EFI_SUCCESS);
+}
+
+efi_status_t efi_aml_sd_disk_register(void)
+{
+ struct mmc *mmc = find_mmc_device(CONFIG_FASTBOOT_FLASH_MMC_DEV);
+ if (!is_aml_sd_mmc(mmc)) {
+ return EFI_UNSUPPORTED;
+ }
+
+ printf("Adding EFI Amlogic SD eMMC disk..\n");
+
+ struct efi_aml_sd_disk *disk =
+ calloc(1, sizeof(struct efi_aml_sd_disk));
+ if (!disk)
+ return EFI_OUT_OF_RESOURCES;
+ disk->mmc = mmc;
+ disk->dev = CONFIG_FASTBOOT_FLASH_MMC_DEV;
+
+ // Creates a block device for boot0/boot1/user hardware partitions.
+ for (size_t hwpart = 0; hwpart < ARRAY_SIZE(disk->hwparts); hwpart++) {
+ struct efi_aml_sd_disk_hwpart *disk_hwpart =
+ &disk->hwparts[hwpart];
+ // Adds to the device list
+ efi_add_handle(&disk_hwpart->header);
+ disk_hwpart->disk = disk;
+ disk_hwpart->hwpart = hwpart;
+ // Creates a vendor type device path.
+ disk_hwpart->dp.path.dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE;
+ disk_hwpart->dp.path.dp.sub_type =
+ DEVICE_PATH_SUB_TYPE_VENDOR_PATH;
+ disk_hwpart->dp.path.dp.length =
+ sizeof(disk_hwpart->dp.path) + VENDOR_PATH_DATA_LEN;
+ snprintf(disk_hwpart->dp.vendor_defined_data,
+ VENDOR_PATH_DATA_LEN, "hwpart-%ld", hwpart);
+ disk_hwpart->dp.end.type = DEVICE_PATH_TYPE_END;
+ disk_hwpart->dp.end.sub_type = DEVICE_PATH_SUB_TYPE_END;
+ // Populates `EFI_BLOCK_IO_MEDIA`
+ disk_hwpart->media.media_id = 1; // Don't care.
+ disk_hwpart->media.removable_media = 0;
+ disk_hwpart->media.media_present = 1;
+ disk_hwpart->media.logical_partition = 0;
+ disk_hwpart->media.read_only = 0;
+ disk_hwpart->media.write_caching = 0;
+ disk_hwpart->media.block_size = mmc->write_bl_len;
+ disk_hwpart->media.io_align = mmc->write_bl_len;
+ uint64_t total_blocks =
+ (hwpart == 0 ? mmc->capacity_user : mmc->capacity_boot);
+ total_blocks /= mmc->write_bl_len;
+ disk_hwpart->media.last_block = max(1ULL, total_blocks) - 1;
+ // Populates `EFI_BLOCK_IO2_PROTOCOL`.
+ disk_hwpart->ops_io2.media = &disk_hwpart->media;
+ disk_hwpart->ops_io2.reset = reset_io2;
+ disk_hwpart->ops_io2.read_blocks_ex = read_blocks_ex;
+ disk_hwpart->ops_io2.write_blocks_ex = write_blocks_ex;
+ disk_hwpart->ops_io2.flush_blocks_ex = flush_blocks_ex;
+ // Populates `EFI_BLOCK_IO_PROTOCOL`.
+ disk_hwpart->ops.revision = 1;
+ disk_hwpart->ops.media = &disk_hwpart->media;
+ disk_hwpart->ops.reset = reset;
+ disk_hwpart->ops.read_blocks = read_blocks;
+ disk_hwpart->ops.write_blocks = write_blocks;
+ disk_hwpart->ops.flush_blocks = flush_blocks;
+ // Installs protocols
+ struct efi_object *handle = &disk_hwpart->header;
+ efi_status_t ret = efi_install_multiple_protocol_interfaces(
+ &handle, &efi_guid_device_path,
+ &disk_hwpart->dp.path.dp, &efi_block_io_guid,
+ &disk_hwpart->ops, &efi_block_io2_guid,
+ &disk_hwpart->ops_io2, NULL);
+ if (ret != EFI_SUCCESS) {
+ return ret;
+ }
+ }
+ // Creates a timer event so that the notify function can get triggered
+ // whenever the EFI boot service processes any event.
+ efi_status_t ret = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL,
+ TPL_NOTIFY,
+ aml_sd_disk_timer_notify, disk,
+ NULL, &disk->timer_event);
+ if (ret != EFI_SUCCESS) {
+ printf("Failed to create timer event for EFI AML disk\n");
+ return ret;
+ }
+ // Set timer period to 0 to trigger the event in every timer cycle.
+ // This is equivalent to invoking `aml_sd_disk_timer_notify` every time
+ // boot service checks any event i.e. in `efi_timer_check()`.
+ ret = efi_set_timer(disk->timer_event, EFI_TIMER_PERIODIC, 0);
+ if (ret != EFI_SUCCESS) {
+ printf("Failed to set timer for EFI AML disk\n");
+ return ret;
+ }
+
+ return EFI_SUCCESS;
+}
diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c
index 1002505..c34224e 100644
--- a/lib/efi_loader/efi_disk.c
+++ b/lib/efi_loader/efi_disk.c
@@ -26,6 +26,7 @@
};
const efi_guid_t efi_block_io_guid = EFI_BLOCK_IO_PROTOCOL_GUID;
+const efi_guid_t efi_block_io2_guid = EFI_BLOCK_IO2_PROTOCOL_GUID;
const efi_guid_t efi_system_partition_guid = PARTITION_SYSTEM_GUID;
/**
diff --git a/lib/efi_loader/efi_setup.c b/lib/efi_loader/efi_setup.c
index 14a934c..f395697 100644
--- a/lib/efi_loader/efi_setup.c
+++ b/lib/efi_loader/efi_setup.c
@@ -239,7 +239,10 @@
* Probe block devices to find the ESP.
* efi_disks_register() must be called before efi_init_variables().
*/
- ret = efi_disks_register();
+ // Modified for Vim3: We use custom driver implementation that supports
+ // EFI_BLOCK_IO2_PROTOCOL.
+ // ret = efi_disks_register();
+ ret = efi_aml_sd_disk_register();
if (ret != EFI_SUCCESS)
goto out;