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>
diff --git a/cmd/bootefi.c b/cmd/bootefi.c
index b93d8c6..f1d7d8b 100644
--- a/cmd/bootefi.c
+++ b/cmd/bootefi.c
@@ -297,6 +297,8 @@
 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 @@
 		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 @@
 	}
 
 	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 @@
 		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 07b913d..7af3f16 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -207,12 +207,17 @@
  * 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 a72cbe1..6d2dc2d 100644
--- a/lib/efi_loader/efi_boottime.c
+++ b/lib/efi_loader/efi_boottime.c
@@ -2626,6 +2626,9 @@
 
 	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)) {
 		/*
@@ -2670,6 +2673,41 @@
 }
 
 /**
+ * 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
  * @exit_status:    status code
@@ -2709,6 +2747,15 @@
 	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);