Add GBL Fastboot customization protocol

Add definition and implementation for the GBL Fastboot Customization
protocol.

Most methods are stubbed out and return EFI_UNSUPPORTED, and the
methods that are not stubbed out have simple implementations that are
mostly useful for testing against.

Includes selftest to verify the local session methods do basic
parameter checking and can provide a facsimile of a local session.

Bug: b/397442490

Change-Id: I236c6ee15218a2d230287649b3cb2bb24519e06c
diff --git a/cuttlefish.fragment b/cuttlefish.fragment
index 528cee6..4672d21 100644
--- a/cuttlefish.fragment
+++ b/cuttlefish.fragment
@@ -77,5 +77,6 @@
 CONFIG_EFI_LOADER=y
 CONFIG_EFI_GBL_IMAGE_LOADING=y
 CONFIG_EFI_GBL_AB_PROTOCOL=y
+CONFIG_EFI_GBL_FASTBOOT_PROTOCOL=y
 CONFIG_EFI_GBL_OS_CONFIGURATION_PROTOCOL=y
 CONFIG_EFI_DT_FIXUP=n
diff --git a/include/efi_api.h b/include/efi_api.h
index 841f5e2..ba07264 100644
--- a/include/efi_api.h
+++ b/include/efi_api.h
@@ -473,6 +473,10 @@
 	EFI_GUID(0x9a7a7db4, 0x614b, 0x4a08, 0x3d, \
 		 0xf9, 0x00, 0x6f, 0x49, 0xb0, 0xd8, 0x0c)
 
+#define EFI_GBL_FASTBOOT_PROTOCOL_GUID \
+	EFI_GUID(0xc67e48a0, 0x5eb8, 0x4127, 0xbe, \
+		 0x89, 0xdf, 0x2e, 0xd9, 0x3d, 0x8a, 0x9a)
+
 #define EFI_GBL_OS_CONFIGURATION_PROTOCOL_GUID \
 	EFI_GUID(0xdda0d135, 0xaa5b, 0x42ff, 0x85, \
 		 0xac, 0xe3, 0xad, 0x6e, 0xfb, 0x46, 0x19)
diff --git a/include/efi_gbl_fastboot.h b/include/efi_gbl_fastboot.h
new file mode 100644
index 0000000..d6a2ae9
--- /dev/null
+++ b/include/efi_gbl_fastboot.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ * Copyright (C) 2025 The Android Open Source Project
+ */
+
+#ifndef __EFI_GBL_FASTBOOT_H__
+#define __EFI_GBL_FASTBOOT_H__
+
+#include <efi.h>
+#include <efi_api.h>
+
+#define GBL_EFI_FASTBOOT_SERIAL_NUMBER_MAX_LEN_UTF8 32
+
+struct gbl_efi_fastboot_policy {
+	// Indicates whether device can be unlocked
+	bool can_unlock;
+	// Device firmware supports 'critical' partition locking
+	bool has_critical_lock;
+	// Indicates whether device allows booting from image loaded directly from
+	// RAM.
+	bool can_ram_boot;
+};
+
+// Callback function pointer passed to GblEfiFastbootProtocol.get_var_all.
+//
+// context: Caller specific context.
+// args: An array of NULL-terminated strings that contains the variable name
+//       followed by additional arguments if any.
+// val: A NULL-terminated string representing the value.
+typedef void (*get_var_all_callback)(void *context, const char *const *args,
+				     size_t num_args, const char *val);
+
+// Firmware can read the given partition and send its data to fastboot client.
+#define GBL_EFI_FASTBOOT_PARTITION_READ (0x1 << 0)
+// Firmware can overwrite the given partition.
+#define GBL_EFI_FASTBOOT_PARTITION_WRITE (0x1 << 1)
+// Firmware can erase the given partition.
+#define GBL_EFI_FASTBOOT_PARTITION_ERASE (0x1 << 2)
+
+// All device partitions are locked.
+#define GBL_EFI_FASTBOOT_LOCKED (0x1 << 0)
+// All 'critical' device partitions are locked.
+#define GBL_EFI_FASTBOOT_CRITICAL_LOCKED (0x1 << 1)
+
+extern const efi_guid_t efi_gbl_fastboot_guid;
+
+struct gbl_efi_fastboot_protocol {
+	// Revision of the protocol supported.
+	u32 version;
+	// Null-terminated UTF-8 encoded string
+	char serial_number[GBL_EFI_FASTBOOT_SERIAL_NUMBER_MAX_LEN_UTF8];
+
+	// Fastboot variable methods
+	efi_status_t(EFIAPI *get_var)(struct gbl_efi_fastboot_protocol *this,
+				      const char *const *args, size_t num_args,
+				      char *buf, size_t *bufsize);
+	efi_status_t(EFIAPI *get_var_all)(struct gbl_efi_fastboot_protocol *self,
+					  void *ctx, get_var_all_callback cb);
+
+	// Fastboot oem function methods
+	efi_status_t(EFIAPI *run_oem_function)(
+		struct gbl_efi_fastboot_protocol *this, const char *command,
+		size_t command_len, char *buf, size_t *bufsize);
+
+	// Device lock methods
+	efi_status_t(EFIAPI *get_policy)(struct gbl_efi_fastboot_protocol *this,
+					 struct gbl_efi_fastboot_policy *policy);
+	efi_status_t(EFIAPI *set_lock)(struct gbl_efi_fastboot_protocol *this,
+				       u64 lock_state);
+	efi_status_t(EFIAPI *clear_lock)(struct gbl_efi_fastboot_protocol *this,
+					 u64 lock_state);
+	// Local session methods
+	efi_status_t(EFIAPI *start_local_session)(
+		struct gbl_efi_fastboot_protocol *this, void **ctx);
+	efi_status_t(EFIAPI *update_local_session)(
+		struct gbl_efi_fastboot_protocol *this, void *ctx, char *buf,
+		size_t *bufsize);
+	efi_status_t(EFIAPI *close_local_session)(
+		struct gbl_efi_fastboot_protocol *this, void *ctx);
+	// Misc methods
+	efi_status_t(EFIAPI *get_partition_permissions)(
+		struct gbl_efi_fastboot_protocol *this, const char *part_name,
+		size_t part_name_len, u64 *permissions);
+	efi_status_t(EFIAPI *wipe_user_data)(
+		struct gbl_efi_fastboot_protocol *this);
+	bool(EFIAPI *should_enter_fastboot)(
+		struct gbl_efi_fastboot_protocol *this);
+};
+
+efi_status_t efi_gbl_fastboot_register(void);
+
+#endif /* __EFI_GBL_FASTBOOT_H__ */
diff --git a/include/efi_loader.h b/include/efi_loader.h
index 7daca0a..f265c1b 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -195,6 +195,35 @@
 	} while(0)
 
 /*
+ * Enter the u-boot world from UEFI without logging the entry.
+ * This can be useful for functions called in hot loops
+ * that would otherwise swamp any useful logging information.
+ * Preserve the signature of EFI_ENTRY to make it easy to switch
+ * between EFI_ENTRY and EFI_ENTRY_NO_LOG.
+ * Call __efi_nesting_inc even though the entry is not logged to preserve
+ * matching increments and decrements to the indentation level.
+ */
+#define EFI_ENTRY_NO_LOG(format, ...) do {          \
+	assert(__efi_entry_check());                \
+        __efi_nesting_inc();                        \
+	} while(0)
+
+/*
+ * Exit the u-boot world back to UEFI without logging the exit.
+ * This can be useful for functions called in hot loops
+ * that would otherwise swamp any useful logging information.
+ * Preserve the signature of EFI_EXIT to make it easy to switch
+ * between EFI_EXIT and EFI_EXIT_NO_LOG.
+ * Call __efi_nesting_dec even though the entry is not logged to preserve
+ * matching increments and decrements to the indentation level.
+ */
+#define EFI_EXIT_NO_LOG(ret) ({       \
+	__efi_nesting_dec();          \
+	assert(__efi_exit_check());   \
+	ret;                          \
+	})
+
+/*
  * Exit the u-boot world back to UEFI:
  */
 #define EFI_EXIT(ret) ({ \
diff --git a/lib/efi_driver/Kconfig b/lib/efi_driver/Kconfig
index 8803a0d..4cea642 100644
--- a/lib/efi_driver/Kconfig
+++ b/lib/efi_driver/Kconfig
@@ -17,10 +17,18 @@
 	  marking boot attempts, changing the currently active slot,
 	  and querying metadata about boot slots.
 
+config EFI_GBL_FASTBOOT_PROTOCOL
+	bool "Include GBL Fastboot customization protocol"
+	depends on EFI_LOADER
+	help
+	  Include the driver for the custom GBL Fastboot customization protocol.
+	  This protocol is used for defining custom fastboot variables,
+	  custom oem commands, and driving a device-local bootloader session.
+
 config EFI_GBL_OS_CONFIGURATION_PROTOCOL
 	bool "Include GBL Os Configuration protocol"
 	depends on EFI_LOADER
 	help
 	  Include the driver for the custom GBL os configuration protocol.
 	  This protocol is used to select base device trees and overlays
-	  and apply runtime fix-ups to kernel commandline and bootconfig.
\ No newline at end of file
+	  and apply runtime fix-ups to kernel commandline and bootconfig.
diff --git a/lib/efi_driver/Makefile b/lib/efi_driver/Makefile
index 557b7e5..fefb398 100644
--- a/lib/efi_driver/Makefile
+++ b/lib/efi_driver/Makefile
@@ -12,4 +12,5 @@
 
 obj-$(CONFIG_EFI_GBL_IMAGE_LOADING) += efi_gbl_image_loading_protocol.o
 obj-$(CONFIG_EFI_GBL_AB_PROTOCOL) += efi_gbl_ab_protocol.o
+obj-$(CONFIG_EFI_GBL_FASTBOOT_PROTOCOL) += efi_gbl_fastboot_protocol.o
 obj-$(CONFIG_EFI_GBL_OS_CONFIGURATION_PROTOCOL) += efi_gbl_os_configuration_protocol.o
diff --git a/lib/efi_driver/efi_gbl_fastboot_protocol.c b/lib/efi_driver/efi_gbl_fastboot_protocol.c
new file mode 100644
index 0000000..a0d2df7
--- /dev/null
+++ b/lib/efi_driver/efi_gbl_fastboot_protocol.c
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ * Copyright (C) 2025 The Android Open Source Project
+ */
+
+#include <efi.h>
+#include <efi_api.h>
+#include <efi_gbl_fastboot.h>
+#include <efi_loader.h>
+#include <log.h>
+
+const efi_guid_t efi_gbl_fastboot_guid = EFI_GBL_FASTBOOT_PROTOCOL_GUID;
+static struct gbl_efi_fastboot_protocol gbl_efi_fastboot_proto;
+
+// Deliberately simplified fastboot variable representation.
+struct fastboot_var {
+	// NULL terminated array of strings
+	// representing a variable-argument tuple.
+	char const *const *const args;
+	// String representation of the variable's value.
+	char const *const val;
+};
+
+// Array of fastboot variables with a NULL sentinel.
+static struct fastboot_var vars[] = {
+	{ .args = NULL, .val = NULL }, // Sentinel
+};
+
+size_t args_len(struct fastboot_var *var)
+{
+	size_t i = 0;
+	while (var->args[i]) {
+		i++;
+	}
+	return i;
+}
+
+static bool args_match_var(const char *const *args, size_t num_args,
+			   const struct fastboot_var *var)
+{
+	int i;
+	for (i = 0; i < num_args && var->args[i]; i++) {
+		if (strcmp(args[i], var->args[i])) {
+			return false;
+		}
+	}
+
+	return (i == num_args && !var->args[i]);
+}
+
+static efi_status_t EFIAPI get_var(struct gbl_efi_fastboot_protocol *this,
+				   const char *const *fb_args, size_t num_args,
+				   char *buf, size_t *bufsize)
+{
+	EFI_ENTRY("%p, %p, %lu, %p, %p", this, fb_args, num_args, buf, bufsize);
+	if (this != &gbl_efi_fastboot_proto || fb_args == NULL || buf == NULL ||
+	    bufsize == NULL) {
+		return EFI_EXIT(EFI_INVALID_PARAMETER);
+	}
+
+	for (struct fastboot_var *var = &vars[0]; var->val; var++) {
+		if (args_match_var(fb_args, num_args, var)) {
+			size_t val_len = strlen(var->val);
+			efi_status_t ret;
+			if (val_len <= *bufsize) {
+				memcpy(buf, var->val, val_len);
+				ret = EFI_SUCCESS;
+			} else {
+				ret = EFI_BUFFER_TOO_SMALL;
+			}
+			return EFI_EXIT(ret);
+		}
+	}
+
+	return EFI_EXIT(EFI_NOT_FOUND);
+}
+
+static efi_status_t EFIAPI get_var_all(struct gbl_efi_fastboot_protocol *this,
+				       void *ctx, get_var_all_callback cb)
+{
+	EFI_ENTRY("%p, %p, %p", this, ctx, cb);
+	if (this != &gbl_efi_fastboot_proto || cb == NULL) {
+		return EFI_EXIT(EFI_INVALID_PARAMETER);
+	}
+
+	for (struct fastboot_var *var = &vars[0]; var->args; var++) {
+		cb(ctx, var->args, args_len(var), var->val);
+	}
+
+	return EFI_EXIT(EFI_SUCCESS);
+}
+
+static efi_status_t EFIAPI
+run_oem_function(struct gbl_efi_fastboot_protocol *this, const char *command,
+		 size_t command_len, char *buf, size_t *bufsize)
+{
+	EFI_ENTRY("%p, %p, %lu, %p, %p", this, command, command_len, buf,
+		  bufsize);
+
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI get_policy(struct gbl_efi_fastboot_protocol *this,
+				      struct gbl_efi_fastboot_policy *policy)
+{
+	EFI_ENTRY("%p, %p", this, policy);
+
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI set_lock(struct gbl_efi_fastboot_protocol *this,
+				    u64 lock_state)
+{
+	EFI_ENTRY("%p, %lu", this, lock_state);
+
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI clear_lock(struct gbl_efi_fastboot_protocol *this,
+				      u64 lock_state)
+{
+	EFI_ENTRY("%p, %lu", this, lock_state);
+
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+// Structure to store local session context.
+struct fastboot_context {};
+static struct fastboot_context context;
+
+static efi_status_t EFIAPI
+start_local_session(struct gbl_efi_fastboot_protocol *this, void **ctx)
+{
+	EFI_ENTRY("%p, %p", this, ctx);
+	if (this != &gbl_efi_fastboot_proto || ctx == NULL) {
+		return EFI_EXIT(EFI_INVALID_PARAMETER);
+	}
+
+	*ctx = &context;
+
+	return EFI_EXIT(EFI_SUCCESS);
+}
+
+static efi_status_t EFIAPI
+update_local_session(struct gbl_efi_fastboot_protocol *this, void *ctx,
+		     char *buf, size_t *bufsize)
+{
+	EFI_ENTRY_NO_LOG("%p, %p, %p, %p", this, ctx, buf, bufsize);
+	struct fastboot_context *fb_ctx = (struct fastboot_context *)ctx;
+	if (this != &gbl_efi_fastboot_proto || fb_ctx != &context ||
+	    buf == NULL || bufsize == NULL) {
+		return EFI_EXIT(EFI_INVALID_PARAMETER);
+	}
+
+	*bufsize = 0;
+	return EFI_EXIT_NO_LOG(EFI_SUCCESS);
+}
+
+static efi_status_t EFIAPI
+close_local_session(struct gbl_efi_fastboot_protocol *this, void *ctx)
+{
+	EFI_ENTRY("%p, %p", this, ctx);
+	struct fastboot_context *fb_ctx = (struct fastboot_context *)ctx;
+	if (this != &gbl_efi_fastboot_proto || fb_ctx != &context) {
+		return EFI_EXIT(EFI_INVALID_PARAMETER);
+	}
+
+	return EFI_EXIT(EFI_SUCCESS);
+}
+
+static efi_status_t EFIAPI get_partition_permissions(
+	struct gbl_efi_fastboot_protocol *this, const char *part_name,
+	size_t part_name_len, u64 *permissions)
+{
+	EFI_ENTRY("%p, %p, %lu, %p", this, part_name, part_name_len,
+		  permissions);
+
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI wipe_user_data(struct gbl_efi_fastboot_protocol *this)
+{
+	EFI_ENTRY("%p", this);
+
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static bool EFIAPI should_enter_fastboot(struct gbl_efi_fastboot_protocol *this)
+{
+	EFI_ENTRY("%p", this);
+
+	return EFI_EXIT(false);
+}
+
+static struct gbl_efi_fastboot_protocol gbl_efi_fastboot_proto = {
+	.version = 1,
+	.serial_number = "cuttlefish-0xCAFED00D",
+	.get_var = get_var,
+	.get_var_all = get_var_all,
+	.run_oem_function = run_oem_function,
+	.get_policy = get_policy,
+	.set_lock = set_lock,
+	.clear_lock = clear_lock,
+	.start_local_session = start_local_session,
+	.update_local_session = update_local_session,
+	.close_local_session = close_local_session,
+	.get_partition_permissions = get_partition_permissions,
+	.wipe_user_data = wipe_user_data,
+	.should_enter_fastboot = should_enter_fastboot,
+};
+
+efi_status_t efi_gbl_fastboot_register(void)
+{
+	efi_status_t ret = efi_add_protocol(efi_root, &efi_gbl_fastboot_guid,
+					    &gbl_efi_fastboot_proto);
+	if (ret != EFI_SUCCESS) {
+		log_err("Failed to install GBL_EFI_FASTBOOT_PROTOCOL: 0x%lx\n",
+			ret);
+	}
+
+	return ret;
+}
diff --git a/lib/efi_loader/efi_setup.c b/lib/efi_loader/efi_setup.c
index f166042..4d79c82 100644
--- a/lib/efi_loader/efi_setup.c
+++ b/lib/efi_loader/efi_setup.c
@@ -10,6 +10,7 @@
 
 #include <efi_driver.h>
 #include <efi_gbl_ab.h>
+#include <efi_gbl_fastboot.h>
 #include <efi_gbl_image_loading.h>
 #include <efi_gbl_os_configuration.h>
 #include <efi_loader.h>
@@ -358,6 +359,14 @@
 		}
 	}
 
+	if (IS_ENABLED(CONFIG_EFI_GBL_FASTBOOT_PROTOCOL)) {
+		ret = efi_gbl_fastboot_register();
+		if (ret != EFI_SUCCESS) {
+			log_err("GBL Fastboot Protocol initialization error\n");
+			goto out;
+		}
+	}
+
 	if (IS_ENABLED(CONFIG_EFI_GBL_OS_CONFIGURATION_PROTOCOL)) {
 		ret = efi_gbl_os_config_register();
 		if (ret != EFI_SUCCESS) {
diff --git a/lib/efi_selftest/Makefile b/lib/efi_selftest/Makefile
index c4f3759..b7f3a05 100644
--- a/lib/efi_selftest/Makefile
+++ b/lib/efi_selftest/Makefile
@@ -50,6 +50,7 @@
 efi_selftest_watchdog.o
 
 obj-$(CONFIG_EFI_GBL_AB_PROTOCOL) += efi_selftest_gbl_ab_protocol.o
+obj-$(CONFIG_EFI_GBL_FASTBOOT_PROTOCOL) += efi_selftest_gbl_fastboot_protocol.o
 
 obj-$(CONFIG_EFI_ECPT) += efi_selftest_ecpt.o
 obj-$(CONFIG_NETDEVICES) += efi_selftest_snp.o
diff --git a/lib/efi_selftest/efi_selftest_gbl_fastboot_protocol.c b/lib/efi_selftest/efi_selftest_gbl_fastboot_protocol.c
new file mode 100644
index 0000000..cf82173
--- /dev/null
+++ b/lib/efi_selftest/efi_selftest_gbl_fastboot_protocol.c
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+ * Copyright (C) 2025 The Android Open Source Project
+ */
+
+#include <efi.h>
+#include <efi_api.h>
+#include <efi_gbl_fastboot.h>
+#include <efi_selftest.h>
+#include <string.h>
+
+static struct efi_boot_services *boot_services;
+static struct gbl_efi_fastboot_protocol *protocol;
+
+static int test_local_session(void)
+{
+	void *ctx;
+	efi_status_t res;
+
+	res = protocol->start_local_session(protocol, NULL);
+	if (res != EFI_INVALID_PARAMETER) {
+		efi_st_error(
+			"Call to start_local_session should have failed with NULL ctx\n");
+		return EFI_ST_FAILURE;
+	}
+
+	res = protocol->start_local_session(protocol, &ctx);
+	if (res != EFI_SUCCESS) {
+		efi_st_error(
+			"Call to start_local_session failed unexpectedly\n");
+		return EFI_ST_FAILURE;
+	}
+
+	char buf[32];
+	size_t bufsize = sizeof(buf);
+	res = protocol->update_local_session(protocol, NULL, buf, &bufsize);
+	if (res != EFI_INVALID_PARAMETER) {
+		efi_st_error(
+			"Call to update_local_session should have failed with NULL ctx\n");
+		return EFI_ST_FAILURE;
+	}
+
+	res = protocol->update_local_session(protocol, ctx, NULL, &bufsize);
+	if (res != EFI_INVALID_PARAMETER) {
+		efi_st_error(
+			"Call to update_local_session should have failed with NULL buffer\n");
+		return EFI_ST_FAILURE;
+	}
+
+	res = protocol->update_local_session(protocol, ctx, buf, NULL);
+	if (res != EFI_INVALID_PARAMETER) {
+		efi_st_error(
+			"Call to update_local_session should have failed with NULL bufsize\n");
+		return EFI_ST_FAILURE;
+	}
+
+	res = protocol->update_local_session(protocol, ctx, buf, &bufsize);
+	if (res != EFI_SUCCESS) {
+		efi_st_error(
+			"Call to update_local_session failed unexpectedly\n");
+		return EFI_ST_FAILURE;
+	}
+
+	res = protocol->close_local_session(protocol, NULL);
+	if (res != EFI_INVALID_PARAMETER) {
+		efi_st_error(
+			"Call to close_local_session should have failed with NULL ctx\n");
+		return EFI_ST_FAILURE;
+	}
+
+	res = protocol->close_local_session(protocol, ctx);
+	if (res != EFI_SUCCESS) {
+		efi_st_error(
+			"Call to close_local_session failed unexpectedly\n");
+		return EFI_ST_FAILURE;
+	}
+
+	return EFI_ST_SUCCESS;
+}
+
+static int setup(const efi_handle_t handle,
+		 const struct efi_system_table *systable)
+{
+	boot_services = systable->boottime;
+	efi_status_t res = boot_services->locate_protocol(
+		&efi_gbl_fastboot_guid, NULL, (void **)&protocol);
+	if (res != EFI_SUCCESS) {
+		protocol = NULL;
+		efi_st_error("Failed to locate GBL Fastboot protocol\n");
+		return EFI_ST_FAILURE;
+	}
+
+	return EFI_ST_SUCCESS;
+}
+
+static int execute(void)
+{
+	int res;
+	res = test_local_session();
+	if (res != EFI_ST_SUCCESS) {
+		return res;
+	}
+	return EFI_ST_SUCCESS;
+}
+
+static int teardown(void)
+{
+	return EFI_ST_SUCCESS;
+}
+
+EFI_UNIT_TEST(gbl_fastboot) = {
+	.name = "GBL Fastboot Protocol",
+	.phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
+	.setup = setup,
+	.execute = execute,
+	.teardown = teardown,
+};