From 556d8dc937f74fa8a5fa849b7e1393db3446f3b2 Mon Sep 17 00:00:00 2001
From: Heinrich Schuchardt <xypron.glpk@gmx.de>
Date: Tue, 30 Apr 2019 17:57:30 +0200
Subject: [PATCH] efi_loader: implement support of exit data

In case of a failure exit data may be passed to Exit() which in turn is
returned by StartImage().

Let the `bootefi` command print the exit data string in case of an error.

Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
---
 cmd/bootefi.c                 | 11 +++++---
 include/efi_loader.h          |  5 ++++
 lib/efi_loader/efi_boottime.c | 47 +++++++++++++++++++++++++++++++++++
 3 files changed, 60 insertions(+), 3 deletions(-)

diff --git a/cmd/bootefi.c b/cmd/bootefi.c
index b93d8c6a32..f1d7d8bc66 100644
--- a/cmd/bootefi.c
+++ b/cmd/bootefi.c
@@ -297,6 +297,8 @@ static efi_status_t efi_install_fdt(const char *fdt_opt)
 static efi_status_t do_bootefi_exec(efi_handle_t handle)
 {
 	efi_status_t ret;
+	efi_uintn_t exit_data_size = 0;
+	u16 *exit_data = NULL;
 
 	/* Transfer environment variable as load options */
 	ret = set_load_options(handle, "bootargs");
@@ -304,7 +306,12 @@ static efi_status_t do_bootefi_exec(efi_handle_t handle)
 		return ret;
 
 	/* Call our payload! */
-	ret = EFI_CALL(efi_start_image(handle, NULL, NULL));
+	ret = EFI_CALL(efi_start_image(handle, &exit_data_size, &exit_data));
+	printf("## Application terminated, r = %lu\n", ret & ~EFI_ERROR_MASK);
+	if (ret && exit_data) {
+		printf("## %ls\n", exit_data);
+		efi_free_pool(exit_data);
+	}
 
 	efi_restore_gd();
 
@@ -357,7 +364,6 @@ static int do_efibootmgr(const char *fdt_opt)
 	}
 
 	ret = do_bootefi_exec(handle);
-	printf("## Application terminated, r = %lu\n", ret & ~EFI_ERROR_MASK);
 
 	if (ret != EFI_SUCCESS)
 		return CMD_RET_FAILURE;
@@ -472,7 +478,6 @@ static int do_bootefi_image(const char *image_opt, const char *fdt_opt)
 		goto out;
 
 	ret = do_bootefi_exec(handle);
-	printf("## Application terminated, r = %lu\n", ret & ~EFI_ERROR_MASK);
 
 out:
 	if (mem_handle)
diff --git a/include/efi_loader.h b/include/efi_loader.h
index 07b913d256..7af3f16ef8 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -207,12 +207,17 @@ struct efi_object {
  * struct efi_loaded_image_obj - handle of a loaded image
  *
  * @header:		EFI object header
+ * @exit_status:	exit status passed to Exit()
+ * @exit_data_size:	exit data size passed to Exit()
+ * @exit_data:		exit data passed to Exit()
  * @exit_jmp:		long jump buffer for returning form started image
  * @entry:		entry address of the relocated image
  */
 struct efi_loaded_image_obj {
 	struct efi_object header;
 	efi_status_t exit_status;
+	efi_uintn_t *exit_data_size;
+	u16 **exit_data;
 	struct jmp_buf_data exit_jmp;
 	EFIAPI efi_status_t (*entry)(efi_handle_t image_handle,
 				     struct efi_system_table *st);
diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c
index a72cbe1559..6d2dc2dda3 100644
--- a/lib/efi_loader/efi_boottime.c
+++ b/lib/efi_loader/efi_boottime.c
@@ -2626,6 +2626,9 @@ efi_status_t EFIAPI efi_start_image(efi_handle_t image_handle,
 
 	efi_is_direct_boot = false;
 
+	image_obj->exit_data_size = exit_data_size;
+	image_obj->exit_data = exit_data;
+
 	/* call the image! */
 	if (setjmp(&image_obj->exit_jmp)) {
 		/*
@@ -2669,6 +2672,41 @@ efi_status_t EFIAPI efi_start_image(efi_handle_t image_handle,
 	return EFI_CALL(systab.boottime->exit(image_handle, ret, 0, NULL));
 }
 
+/**
+ * efi_update_exit_data() - fill exit data parameters of StartImage()
+ *
+ * @image_obj		image handle
+ * @exit_data_size	size of the exit data buffer
+ * @exit_data		buffer with data returned by UEFI payload
+ * Return:		status code
+ */
+static efi_status_t efi_update_exit_data(struct efi_loaded_image_obj *image_obj,
+					 efi_uintn_t exit_data_size,
+					 u16 *exit_data)
+{
+	efi_status_t ret;
+
+	/*
+	 * If exit_data is not provided to StartImage(), exit_data_size must be
+	 * ignored.
+	 */
+	if (!image_obj->exit_data)
+		return EFI_SUCCESS;
+	if (image_obj->exit_data_size)
+		*image_obj->exit_data_size = exit_data_size;
+	if (exit_data_size && exit_data) {
+		ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA,
+					exit_data_size,
+					(void **)image_obj->exit_data);
+		if (ret != EFI_SUCCESS)
+			return ret;
+		memcpy(*image_obj->exit_data, exit_data, exit_data_size);
+	} else {
+		image_obj->exit_data = NULL;
+	}
+	return EFI_SUCCESS;
+}
+
 /**
  * efi_exit() - leave an EFI application or driver
  * @image_handle:   handle of the application or driver that is exiting
@@ -2709,6 +2747,15 @@ static efi_status_t EFIAPI efi_exit(efi_handle_t image_handle,
 	if (ret != EFI_SUCCESS)
 		goto out;
 
+	/* Exit data is only foreseen in case of failure. */
+	if (exit_status != EFI_SUCCESS) {
+		ret = efi_update_exit_data(image_obj, exit_data_size,
+					   exit_data);
+		/* Exiting has priority. Don't return error to caller. */
+		if (ret != EFI_SUCCESS)
+			EFI_PRINT("%s: out of memory\n", __func__);
+	}
+
 	/* Make sure entry/exit counts for EFI world cross-overs match */
 	EFI_EXIT(exit_status);
 
-- 
2.39.5