[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;