linux-efi.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH V4 0/4] Handle EFI_INVALID_PARAMETER from ExitBootServices
@ 2016-08-15 19:22 Jeffrey Hugo
       [not found] ` <1471288933-31810-1-git-send-email-jhugo-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
  0 siblings, 1 reply; 6+ messages in thread
From: Jeffrey Hugo @ 2016-08-15 19:22 UTC (permalink / raw)
  To: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	matt-mF/unelCI9GS6iBeEJttW/XRex20P6io
  Cc: ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A,
	timur-sgV2jX0FEOL9JmXXK+q4OQ, Jeffrey Hugo

According to the UEFI spec, the UEFI OS Loader (aka the stub) should
transition from UEFI to the OS by getting the current memory map from UEFI,
then calling ExitBootServices.  The spec states that ExitBootServices may
return EFI_INVALID_PARAMETER if the memory map reference provided by the stub
is not current, ie UEFI handled some event prior to ExitBootServices which
modified the map.  The spec states that to handle this scenario, the stub shall
get the updated map, and invoke ExitBootServices again.  The spec also states
that once ExitBootServices is invoked, even if it returns error, the only
APIs the stub is allowed to invoke is GetMemoryMap and ExitBootServices.

The EFI_INVALID_PARAMETER scenario has been seen in the wild previously in
x86 but the fix - d3768d885c6c ("x86, efi: retry ExitBootServices() on failure")
still violates the spec.  The FDT code does not handle this scenario, but
instances of it are now observed.

This patch series aims to provide a spec complaint solution that can be reused
in all stubs, thus preventing each arch or variant from reinventing the wheel
and likely getting it wrong.

[V4]
-Fix x86 boot error reported by Matt Fleming
-Update change logs to indicate real world occurances per Matt Fleming
-Make the callback function non-optional per Matt Fleming
-Reduce parameter list of efi_get_memory_map() per Matt Fleming
-Reduce parameter list of efi_exit_boot_services per Matt Fleming
-Define callback function signature with a typedef per Matt Fleming

[V3]
-Remove old extra headroom allocation per Matt Fleming
-Create and use headroom check wrapper per Matt Fleming

[V2]
-Define EFI_MMAP_NR_SLACK_SLOTS per Mark Rutland
-Use desc_size as firmware may exceed the defined struct size per Ard Biesheuvel

[V1]
-Allocate headspace on the memory map buffer for reuse per Ard Biesheuvel
-Create a shared helper address the issue universally per Matt Fleming

Jeffrey Hugo (4):
  efi/libstub: Allocate headspace in efi_get_memory_map()
  efi/libstub: Introduce ExitBootServices helper
  efi/libstub: Use efi_exit_boot_services() in FDT
  x86/efi: Use efi_exit_boot_services()

 arch/x86/boot/compressed/eboot.c               | 132 +++++++++++++------------
 drivers/firmware/efi/libstub/efi-stub-helper.c | 129 ++++++++++++++++++++++--
 drivers/firmware/efi/libstub/fdt.c             |  52 +++++++---
 drivers/firmware/efi/libstub/random.c          |   3 +-
 include/linux/efi.h                            |  22 ++++-
 5 files changed, 249 insertions(+), 89 deletions(-)

-- 
Qualcomm Datacenter Technologies as an affiliate of Qualcomm Technologies, Inc.
Qualcomm Technologies, Inc. is a member of the
Code Aurora Forum, a Linux Foundation Collaborative Project.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH V4 1/4] efi/libstub: Allocate headspace in efi_get_memory_map()
       [not found] ` <1471288933-31810-1-git-send-email-jhugo-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
@ 2016-08-15 19:22   ` Jeffrey Hugo
  2016-08-15 19:22   ` [PATCH V4 2/4] efi/libstub: Introduce ExitBootServices helper Jeffrey Hugo
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Jeffrey Hugo @ 2016-08-15 19:22 UTC (permalink / raw)
  To: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	matt-mF/unelCI9GS6iBeEJttW/XRex20P6io
  Cc: ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A,
	timur-sgV2jX0FEOL9JmXXK+q4OQ, Jeffrey Hugo

efi_get_memory_map() allocates a buffer to store the memory map that it
retrieves.  This buffer may need to be reused by the client after
ExitBootServices() is called, at which point allocations are not longer
permitted.  To support this usecase, provide the allocated buffer size back
to the client, and allocate some additional headroom to account for any
reasonable growth in the map that is likely to happen between the call to
efi_get_memory_map() and the client reusing the buffer.

Signed-off-by: Jeffrey Hugo <jhugo-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
---
 arch/x86/boot/compressed/eboot.c               | 12 +++-
 drivers/firmware/efi/libstub/efi-stub-helper.c | 96 ++++++++++++++++++--------
 drivers/firmware/efi/libstub/fdt.c             | 17 +++--
 drivers/firmware/efi/libstub/random.c          | 12 +++-
 include/linux/efi.h                            | 15 ++--
 5 files changed, 107 insertions(+), 45 deletions(-)

diff --git a/arch/x86/boot/compressed/eboot.c b/arch/x86/boot/compressed/eboot.c
index 52fef60..31d0ca5 100644
--- a/arch/x86/boot/compressed/eboot.c
+++ b/arch/x86/boot/compressed/eboot.c
@@ -1010,7 +1010,7 @@ static efi_status_t exit_boot(struct boot_params *boot_params,
 			      void *handle, bool is64)
 {
 	struct efi_info *efi = &boot_params->efi_info;
-	unsigned long map_sz, key, desc_size;
+	unsigned long map_sz, key, desc_size, buff_size;
 	efi_memory_desc_t *mem_map;
 	struct setup_data *e820ext;
 	const char *signature;
@@ -1021,14 +1021,20 @@ static efi_status_t exit_boot(struct boot_params *boot_params,
 	bool called_exit = false;
 	u8 nr_entries;
 	int i;
+	efi_boottime_memory_map_t map;
 
 	nr_desc = 0;
 	e820ext = NULL;
 	e820ext_size = 0;
+	map.map = &mem_map;
+	map.map_size = &map_sz;
+	map.desc_size = &desc_size;
+	map.desc_ver = &desc_version;
+	map.key_ptr = &key;
+	map.buff_size = &buff_size;
 
 get_map:
-	status = efi_get_memory_map(sys_table, &mem_map, &map_sz, &desc_size,
-				    &desc_version, &key);
+	status = efi_get_memory_map(sys_table, &map);
 
 	if (status != EFI_SUCCESS)
 		return status;
diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
index 3bd127f9..bbb5166 100644
--- a/drivers/firmware/efi/libstub/efi-stub-helper.c
+++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
@@ -41,6 +41,8 @@ static unsigned long __chunk_size = EFI_READ_CHUNK_SIZE;
 #define EFI_ALLOC_ALIGN		EFI_PAGE_SIZE
 #endif
 
+#define EFI_MMAP_NR_SLACK_SLOTS	8
+
 struct file_info {
 	efi_file_handle_t *handle;
 	u64 size;
@@ -63,49 +65,62 @@ void efi_printk(efi_system_table_t *sys_table_arg, char *str)
 	}
 }
 
+static inline bool mmap_has_headroom(unsigned long buff_size,
+				     unsigned long map_size,
+				     unsigned long desc_size)
+{
+	unsigned long slack = buff_size - map_size;
+
+	return slack / desc_size >= EFI_MMAP_NR_SLACK_SLOTS;
+}
+
 efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg,
-				efi_memory_desc_t **map,
-				unsigned long *map_size,
-				unsigned long *desc_size,
-				u32 *desc_ver,
-				unsigned long *key_ptr)
+				efi_boottime_memory_map_t *map)
 {
 	efi_memory_desc_t *m = NULL;
 	efi_status_t status;
 	unsigned long key;
 	u32 desc_version;
 
-	*map_size = sizeof(*m) * 32;
+	*map->desc_size = sizeof(*m);
+	*map->map_size = *map->desc_size * 32;
+	*map->buff_size = *map->map_size;
 again:
-	/*
-	 * Add an additional efi_memory_desc_t because we're doing an
-	 * allocation which may be in a new descriptor region.
-	 */
-	*map_size += sizeof(*m);
 	status = efi_call_early(allocate_pool, EFI_LOADER_DATA,
-				*map_size, (void **)&m);
+				*map->map_size, (void **)&m);
 	if (status != EFI_SUCCESS)
 		goto fail;
 
-	*desc_size = 0;
+	*map->desc_size = 0;
 	key = 0;
-	status = efi_call_early(get_memory_map, map_size, m,
-				&key, desc_size, &desc_version);
-	if (status == EFI_BUFFER_TOO_SMALL) {
+	status = efi_call_early(get_memory_map, map->map_size, m,
+				&key, map->desc_size, &desc_version);
+	if (status == EFI_BUFFER_TOO_SMALL ||
+	    !mmap_has_headroom(*map->buff_size, *map->map_size,
+			       *map->desc_size)) {
 		efi_call_early(free_pool, m);
+		/*
+		 * Make sure there is some entries of headroom so that the
+		 * buffer can be reused for a new map after allocations are
+		 * no longer permitted.  Its unlikely that the map will grow to
+		 * exceed this headroom once we are ready to trigger
+		 * ExitBootServices()
+		 */
+		*map->map_size += *map->desc_size * EFI_MMAP_NR_SLACK_SLOTS;
+		*map->buff_size = *map->map_size;
 		goto again;
 	}
 
 	if (status != EFI_SUCCESS)
 		efi_call_early(free_pool, m);
 
-	if (key_ptr && status == EFI_SUCCESS)
-		*key_ptr = key;
-	if (desc_ver && status == EFI_SUCCESS)
-		*desc_ver = desc_version;
+	if (map->key_ptr && status == EFI_SUCCESS)
+		*map->key_ptr = key;
+	if (map->desc_ver && status == EFI_SUCCESS)
+		*map->desc_ver = desc_version;
 
 fail:
-	*map = m;
+	*map->map = m;
 	return status;
 }
 
@@ -113,13 +128,20 @@ fail:
 unsigned long get_dram_base(efi_system_table_t *sys_table_arg)
 {
 	efi_status_t status;
-	unsigned long map_size;
+	unsigned long map_size, buff_size;
 	unsigned long membase  = EFI_ERROR;
 	struct efi_memory_map map;
 	efi_memory_desc_t *md;
+	efi_boottime_memory_map_t boot_map;
 
-	status = efi_get_memory_map(sys_table_arg, (efi_memory_desc_t **)&map.map,
-				    &map_size, &map.desc_size, NULL, NULL);
+	boot_map.map = (efi_memory_desc_t **)&map.map;
+	boot_map.map_size = &map_size;
+	boot_map.desc_size = &map.desc_size;
+	boot_map.desc_ver = NULL;
+	boot_map.key_ptr = NULL;
+	boot_map.buff_size = &buff_size;
+
+	status = efi_get_memory_map(sys_table_arg, &boot_map);
 	if (status != EFI_SUCCESS)
 		return membase;
 
@@ -144,15 +166,22 @@ efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg,
 			    unsigned long size, unsigned long align,
 			    unsigned long *addr, unsigned long max)
 {
-	unsigned long map_size, desc_size;
+	unsigned long map_size, desc_size, buff_size;
 	efi_memory_desc_t *map;
 	efi_status_t status;
 	unsigned long nr_pages;
 	u64 max_addr = 0;
 	int i;
+	efi_boottime_memory_map_t boot_map;
+
+	boot_map.map = &map;
+	boot_map.map_size = &map_size;
+	boot_map.desc_size = &desc_size;
+	boot_map.desc_ver = NULL;
+	boot_map.key_ptr = NULL;
+	boot_map.buff_size = &buff_size;
 
-	status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size,
-				    NULL, NULL);
+	status = efi_get_memory_map(sys_table_arg, &boot_map);
 	if (status != EFI_SUCCESS)
 		goto fail;
 
@@ -230,14 +259,21 @@ efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg,
 			   unsigned long size, unsigned long align,
 			   unsigned long *addr)
 {
-	unsigned long map_size, desc_size;
+	unsigned long map_size, desc_size, buff_size;
 	efi_memory_desc_t *map;
 	efi_status_t status;
 	unsigned long nr_pages;
 	int i;
+	efi_boottime_memory_map_t boot_map;
+
+	boot_map.map = &map;
+	boot_map.map_size = &map_size;
+	boot_map.desc_size = &desc_size;
+	boot_map.desc_ver = NULL;
+	boot_map.key_ptr = NULL;
+	boot_map.buff_size = &buff_size;
 
-	status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size,
-				    NULL, NULL);
+	status = efi_get_memory_map(sys_table_arg, &boot_map);
 	if (status != EFI_SUCCESS)
 		goto fail;
 
diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c
index e58abfa..e94846d 100644
--- a/drivers/firmware/efi/libstub/fdt.c
+++ b/drivers/firmware/efi/libstub/fdt.c
@@ -175,13 +175,21 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 					    unsigned long fdt_addr,
 					    unsigned long fdt_size)
 {
-	unsigned long map_size, desc_size;
+	unsigned long map_size, desc_size, buff_size;
 	u32 desc_ver;
 	unsigned long mmap_key;
 	efi_memory_desc_t *memory_map, *runtime_map;
 	unsigned long new_fdt_size;
 	efi_status_t status;
 	int runtime_entry_count = 0;
+	efi_boottime_memory_map_t map;
+
+	map.map = &runtime_map;
+	map.map_size = &map_size;
+	map.desc_size = &desc_size;
+	map.desc_ver = &desc_ver;
+	map.key_ptr = &mmap_key;
+	map.buff_size = &buff_size;
 
 	/*
 	 * Get a copy of the current memory map that we will use to prepare
@@ -189,8 +197,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 	 * subsequent allocations adding entries, since they could not affect
 	 * the number of EFI_MEMORY_RUNTIME regions.
 	 */
-	status = efi_get_memory_map(sys_table, &runtime_map, &map_size,
-				    &desc_size, &desc_ver, &mmap_key);
+	status = efi_get_memory_map(sys_table, &map);
 	if (status != EFI_SUCCESS) {
 		pr_efi_err(sys_table, "Unable to retrieve UEFI memory map.\n");
 		return status;
@@ -199,6 +206,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 	pr_efi(sys_table,
 	       "Exiting boot services and installing virtual address map...\n");
 
+	map.map = &memory_map;
 	/*
 	 * Estimate size of new FDT, and allocate memory for it. We
 	 * will allocate a bigger buffer if this ends up being too
@@ -218,8 +226,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 		 * we can get the memory map key  needed for
 		 * exit_boot_services().
 		 */
-		status = efi_get_memory_map(sys_table, &memory_map, &map_size,
-					    &desc_size, &desc_ver, &mmap_key);
+		status = efi_get_memory_map(sys_table, &map);
 		if (status != EFI_SUCCESS)
 			goto fail_free_new_fdt;
 
diff --git a/drivers/firmware/efi/libstub/random.c b/drivers/firmware/efi/libstub/random.c
index 53f6d3f..46c14ee 100644
--- a/drivers/firmware/efi/libstub/random.c
+++ b/drivers/firmware/efi/libstub/random.c
@@ -73,12 +73,20 @@ efi_status_t efi_random_alloc(efi_system_table_t *sys_table_arg,
 			      unsigned long random_seed)
 {
 	unsigned long map_size, desc_size, total_slots = 0, target_slot;
+	unsigned long buff_size;
 	efi_status_t status;
 	efi_memory_desc_t *memory_map;
 	int map_offset;
+	efi_boottime_memory_map_t map;
 
-	status = efi_get_memory_map(sys_table_arg, &memory_map, &map_size,
-				    &desc_size, NULL, NULL);
+	map.map = &memory_map;
+	map.map_size = &map_size;
+	map.desc_size = &desc_size;
+	map.desc_ver = NULL;
+	map.key_ptr = NULL;
+	map.buff_size = &buff_size;
+
+	status = efi_get_memory_map(sys_table_arg, &map);
 	if (status != EFI_SUCCESS)
 		return status;
 
diff --git a/include/linux/efi.h b/include/linux/efi.h
index f196dd0..61400a1 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -118,6 +118,15 @@ typedef struct {
 	u32 imagesize;
 } efi_capsule_header_t;
 
+typedef struct {
+	efi_memory_desc_t **map;
+	unsigned long *map_size;
+	unsigned long *desc_size;
+	u32 *desc_ver;
+	unsigned long *key_ptr;
+	unsigned long *buff_size;
+} efi_boottime_memory_map_t;
+
 /*
  * EFI capsule flags
  */
@@ -1430,11 +1439,7 @@ char *efi_convert_cmdline(efi_system_table_t *sys_table_arg,
 			  efi_loaded_image_t *image, int *cmd_line_len);
 
 efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg,
-				efi_memory_desc_t **map,
-				unsigned long *map_size,
-				unsigned long *desc_size,
-				u32 *desc_ver,
-				unsigned long *key_ptr);
+				efi_boottime_memory_map_t *map);
 
 efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg,
 			   unsigned long size, unsigned long align,
-- 
Qualcomm Datacenter Technologies as an affiliate of Qualcomm Technologies, Inc.
Qualcomm Technologies, Inc. is a member of the
Code Aurora Forum, a Linux Foundation Collaborative Project.

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH V4 2/4] efi/libstub: Introduce ExitBootServices helper
       [not found] ` <1471288933-31810-1-git-send-email-jhugo-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
  2016-08-15 19:22   ` [PATCH V4 1/4] efi/libstub: Allocate headspace in efi_get_memory_map() Jeffrey Hugo
@ 2016-08-15 19:22   ` Jeffrey Hugo
  2016-08-15 19:22   ` [PATCH V4 3/4] efi/libstub: Use efi_exit_boot_services() in FDT Jeffrey Hugo
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Jeffrey Hugo @ 2016-08-15 19:22 UTC (permalink / raw)
  To: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	matt-mF/unelCI9GS6iBeEJttW/XRex20P6io
  Cc: ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A,
	timur-sgV2jX0FEOL9JmXXK+q4OQ, Jeffrey Hugo

The spec allows ExitBootServices to fail with EFI_INVALID_PARAMETER if a
race condition has occurred where the EFI has updated the memory map after
the stub grabbed a reference to the map.  The spec defines a retry
proceedure with specific requirements to handle this scenario.

This scenario was previously observed on x86 - commit d3768d885c6c ("x86,
efi: retry ExitBootServices() on failure") but the current fix is not spec
compliant and the scenario is now observed on the Qualcomm Technologies
QDF2432 via the FDT stub which does not handle the error and thus causes
boot failures.

Add a helper to the stub library that correctly adhears to the spec in the
case of EFI_INVALID_PARAMETER from ExitBootServices and can be universally
used across all stub implementations.

Signed-off-by: Jeffrey Hugo <jhugo-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
---
 drivers/firmware/efi/libstub/efi-stub-helper.c | 72 ++++++++++++++++++++++++++
 include/linux/efi.h                            | 11 ++++
 2 files changed, 83 insertions(+)

diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
index bbb5166..ca0a4f9 100644
--- a/drivers/firmware/efi/libstub/efi-stub-helper.c
+++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
@@ -740,3 +740,75 @@ char *efi_convert_cmdline(efi_system_table_t *sys_table_arg,
 	*cmd_line_len = options_bytes;
 	return (char *)cmdline_addr;
 }
+
+/*
+ * Handle calling ExitBootServices according to the requirements set out by the
+ * spec.  Obtains the current memory map, and returns that info after calling
+ * ExitBootServices.  The client must specify a function to perform any
+ * processing of the memory map data prior to ExitBootServices.  A client
+ * specific structure may be passed to the function via priv.  The client
+ * function may be called multiple times.
+ */
+efi_status_t efi_exit_boot_services(efi_system_table_t *sys_table_arg,
+				    void *handle,
+				    efi_boottime_memory_map_t *map,
+				    void *priv,
+				    efi_exit_boot_map_processing priv_func)
+{
+	efi_status_t status;
+
+	status = efi_get_memory_map(sys_table_arg, map);
+
+	if (status != EFI_SUCCESS)
+		goto fail;
+
+	status = priv_func(sys_table_arg, map, priv);
+	if (status != EFI_SUCCESS)
+		goto free_map;
+
+	status = efi_call_early(exit_boot_services, handle, *map->key_ptr);
+
+	if (status == EFI_INVALID_PARAMETER) {
+		/*
+		 * The memory map changed between efi_get_memory_map() and
+		 * exit_boot_services().  Per the spec we need to get the
+		 * updated map, and try again.  The spec implies one retry
+		 * should be sufficent, which is confirmed against the EDK2
+		 * implementation.  Per the spec, we can only invoke
+		 * get_memory_map() and exit_boot_services() - we cannot alloc
+		 * so efi_get_memory_map() cannot be used, and we must reuse
+		 * the buffer.  For all practical purposes, the headroom in the
+		 * buffer should account for any changes in the map so the call
+		 * to get_memory_map() is expected to succeed here.
+		 */
+		*map->map_size = *map->buff_size;
+		status = efi_call_early(get_memory_map,
+					map->map_size,
+					*map->map,
+					map->key_ptr,
+					map->desc_size,
+					map->desc_ver);
+		if (status != EFI_SUCCESS)
+			/* exit_boot_services() was called, thus cannot free*/
+			goto fail;
+
+		status = priv_func(sys_table_arg, map, priv);
+		if (status != EFI_SUCCESS)
+			/* exit_boot_services() was called, thus cannot free*/
+			goto fail;
+
+		status = efi_call_early(exit_boot_services, handle,
+					*map->key_ptr);
+	}
+
+	if (status != EFI_SUCCESS)
+		/* exit_boot_services() was called, thus cannot free*/
+		goto fail;
+
+	return EFI_SUCCESS;
+
+free_map:
+	efi_call_early(free_pool, *map->map);
+fail:
+	return status;
+}
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 61400a1..65eca9a 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1470,4 +1470,15 @@ efi_status_t efi_setup_gop(efi_system_table_t *sys_table_arg,
 			   unsigned long size);
 
 bool efi_runtime_disabled(void);
+
+typedef efi_status_t (*efi_exit_boot_map_processing)(
+	efi_system_table_t *sys_table_arg,
+	efi_boottime_memory_map_t *map,
+	void *priv);
+
+efi_status_t efi_exit_boot_services(efi_system_table_t *sys_table,
+				    void *handle,
+				    efi_boottime_memory_map_t *map,
+				    void *priv,
+				    efi_exit_boot_map_processing priv_func);
 #endif /* _LINUX_EFI_H */
-- 
Qualcomm Datacenter Technologies as an affiliate of Qualcomm Technologies, Inc.
Qualcomm Technologies, Inc. is a member of the
Code Aurora Forum, a Linux Foundation Collaborative Project.

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH V4 3/4] efi/libstub: Use efi_exit_boot_services() in FDT
       [not found] ` <1471288933-31810-1-git-send-email-jhugo-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
  2016-08-15 19:22   ` [PATCH V4 1/4] efi/libstub: Allocate headspace in efi_get_memory_map() Jeffrey Hugo
  2016-08-15 19:22   ` [PATCH V4 2/4] efi/libstub: Introduce ExitBootServices helper Jeffrey Hugo
@ 2016-08-15 19:22   ` Jeffrey Hugo
  2016-08-15 19:22   ` [PATCH V4 4/4] x86/efi: Use efi_exit_boot_services() Jeffrey Hugo
  2016-08-19 11:43   ` [PATCH V4 0/4] Handle EFI_INVALID_PARAMETER from ExitBootServices Matt Fleming
  4 siblings, 0 replies; 6+ messages in thread
From: Jeffrey Hugo @ 2016-08-15 19:22 UTC (permalink / raw)
  To: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	matt-mF/unelCI9GS6iBeEJttW/XRex20P6io
  Cc: ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A,
	timur-sgV2jX0FEOL9JmXXK+q4OQ, Jeffrey Hugo

The FDT code directly calls ExitBootServices.  This is inadvisable as the
UEFI spec details a complex set of errors, race conditions, and API
interactions that the caller of ExitBootServices must get correct.  The
FDT code does not handle EFI_INVALID_PARAMETER as required by the spec,
which causes intermittent boot failures on the Qualcomm Technologies
QDF2432.  The efi_exit_boot_services() helper handles the
EFI_INVALID_PARAMETER scenario.

Signed-off-by: Jeffrey Hugo <jhugo-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
---
 drivers/firmware/efi/libstub/fdt.c | 37 +++++++++++++++++++++++++++----------
 1 file changed, 27 insertions(+), 10 deletions(-)

diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c
index e94846d..b66dc40 100644
--- a/drivers/firmware/efi/libstub/fdt.c
+++ b/drivers/firmware/efi/libstub/fdt.c
@@ -152,6 +152,27 @@ fdt_set_fail:
 #define EFI_FDT_ALIGN EFI_PAGE_SIZE
 #endif
 
+struct exit_boot_struct {
+	efi_memory_desc_t *runtime_map;
+	int *runtime_entry_count;
+};
+
+static efi_status_t exit_boot_func(efi_system_table_t *sys_table_arg,
+			    efi_boottime_memory_map_t *map,
+			    void *priv)
+{
+	struct exit_boot_struct *p = priv;
+	/*
+	 * Update the memory map with virtual addresses. The function will also
+	 * populate @runtime_map with copies of just the EFI_MEMORY_RUNTIME
+	 * entries so that we can pass it straight into SetVirtualAddressMap()
+	 */
+	efi_get_virtmap(*map->map, *map->map_size, *map->desc_size,
+			p->runtime_map, p->runtime_entry_count);
+
+	return EFI_SUCCESS;
+}
+
 /*
  * Allocate memory for a new FDT, then add EFI, commandline, and
  * initrd related fields to the FDT.  This routine increases the
@@ -183,6 +204,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 	efi_status_t status;
 	int runtime_entry_count = 0;
 	efi_boottime_memory_map_t map;
+	struct exit_boot_struct priv;
 
 	map.map = &runtime_map;
 	map.map_size = &map_size;
@@ -257,16 +279,11 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
 		}
 	}
 
-	/*
-	 * Update the memory map with virtual addresses. The function will also
-	 * populate @runtime_map with copies of just the EFI_MEMORY_RUNTIME
-	 * entries so that we can pass it straight into SetVirtualAddressMap()
-	 */
-	efi_get_virtmap(memory_map, map_size, desc_size, runtime_map,
-			&runtime_entry_count);
-
-	/* Now we are ready to exit_boot_services.*/
-	status = sys_table->boottime->exit_boot_services(handle, mmap_key);
+	sys_table->boottime->free_pool(memory_map);
+	priv.runtime_map = runtime_map;
+	priv.runtime_entry_count = &runtime_entry_count;
+	status = efi_exit_boot_services(sys_table, handle, &map, &priv,
+					exit_boot_func);
 
 	if (status == EFI_SUCCESS) {
 		efi_set_virtual_address_map_t *svam;
-- 
Qualcomm Datacenter Technologies as an affiliate of Qualcomm Technologies, Inc.
Qualcomm Technologies, Inc. is a member of the
Code Aurora Forum, a Linux Foundation Collaborative Project.

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH V4 4/4] x86/efi: Use efi_exit_boot_services()
       [not found] ` <1471288933-31810-1-git-send-email-jhugo-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
                     ` (2 preceding siblings ...)
  2016-08-15 19:22   ` [PATCH V4 3/4] efi/libstub: Use efi_exit_boot_services() in FDT Jeffrey Hugo
@ 2016-08-15 19:22   ` Jeffrey Hugo
  2016-08-19 11:43   ` [PATCH V4 0/4] Handle EFI_INVALID_PARAMETER from ExitBootServices Matt Fleming
  4 siblings, 0 replies; 6+ messages in thread
From: Jeffrey Hugo @ 2016-08-15 19:22 UTC (permalink / raw)
  To: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	matt-mF/unelCI9GS6iBeEJttW/XRex20P6io
  Cc: ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A,
	timur-sgV2jX0FEOL9JmXXK+q4OQ, Jeffrey Hugo

The eboot code directly calls ExitBootServices.  This is inadvisable as the
UEFI spec details a complex set of errors, race conditions, and API
interactions that the caller of ExitBootServices must get correct.  The
eboot code attempts allocations after calling ExitBootSerives which is
not permitted per the spec.  The efi_exit_boot_services() helper handles
this scenario.

Signed-off-by: Jeffrey Hugo <jhugo-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
---
 arch/x86/boot/compressed/eboot.c | 122 +++++++++++++++++++--------------------
 1 file changed, 60 insertions(+), 62 deletions(-)

diff --git a/arch/x86/boot/compressed/eboot.c b/arch/x86/boot/compressed/eboot.c
index 31d0ca5..29f8b5d 100644
--- a/arch/x86/boot/compressed/eboot.c
+++ b/arch/x86/boot/compressed/eboot.c
@@ -1006,85 +1006,87 @@ static efi_status_t alloc_e820ext(u32 nr_desc, struct setup_data **e820ext,
 	return status;
 }
 
+struct exit_boot_struct {
+	struct boot_params *boot_params;
+	struct efi_info *efi;
+	struct setup_data *e820ext;
+	__u32 e820ext_size;
+	bool is64;
+};
+
+static efi_status_t exit_boot_func(efi_system_table_t *sys_table_arg,
+				   efi_boottime_memory_map_t *map,
+				   void *priv)
+{
+	static bool first = true;
+	const char *signature;
+	__u32 nr_desc;
+	efi_status_t status;
+	struct exit_boot_struct *p = priv;
+
+	if (first) {
+		nr_desc = *map->buff_size / *map->desc_size;
+		if (nr_desc > ARRAY_SIZE(p->boot_params->e820_map)) {
+			u32 nr_e820ext = nr_desc -
+					ARRAY_SIZE(p->boot_params->e820_map);
+
+			status = alloc_e820ext(nr_e820ext, &p->e820ext,
+					       &p->e820ext_size);
+			if (status != EFI_SUCCESS)
+				return status;
+		}
+		first = false;
+	}
+
+	signature = p->is64 ? EFI64_LOADER_SIGNATURE : EFI32_LOADER_SIGNATURE;
+	memcpy(&p->efi->efi_loader_signature, signature, sizeof(__u32));
+
+	p->efi->efi_systab = (unsigned long)sys_table_arg;
+	p->efi->efi_memdesc_size = *map->desc_size;
+	p->efi->efi_memdesc_version = *map->desc_ver;
+	p->efi->efi_memmap = (unsigned long)*map->map;
+	p->efi->efi_memmap_size = *map->map_size;
+
+#ifdef CONFIG_X86_64
+	p->efi->efi_systab_hi = (unsigned long)sys_table_arg >> 32;
+	p->efi->efi_memmap_hi = (unsigned long)*map->map >> 32;
+#endif
+
+	return EFI_SUCCESS;
+}
+
 static efi_status_t exit_boot(struct boot_params *boot_params,
 			      void *handle, bool is64)
 {
-	struct efi_info *efi = &boot_params->efi_info;
 	unsigned long map_sz, key, desc_size, buff_size;
 	efi_memory_desc_t *mem_map;
 	struct setup_data *e820ext;
-	const char *signature;
 	__u32 e820ext_size;
-	__u32 nr_desc, prev_nr_desc;
 	efi_status_t status;
 	__u32 desc_version;
-	bool called_exit = false;
-	u8 nr_entries;
-	int i;
 	efi_boottime_memory_map_t map;
+	struct exit_boot_struct priv;
 
-	nr_desc = 0;
-	e820ext = NULL;
-	e820ext_size = 0;
 	map.map = &mem_map;
 	map.map_size = &map_sz;
 	map.desc_size = &desc_size;
 	map.desc_ver = &desc_version;
 	map.key_ptr = &key;
 	map.buff_size = &buff_size;
+	priv.boot_params = boot_params;
+	priv.efi = &boot_params->efi_info;
+	priv.e820ext = NULL;
+	priv.e820ext_size = 0;
+	priv.is64 = is64;
 
-get_map:
-	status = efi_get_memory_map(sys_table, &map);
-
+	/* Might as well exit boot services now */
+	status = efi_exit_boot_services(sys_table, handle, &map, &priv,
+					exit_boot_func);
 	if (status != EFI_SUCCESS)
 		return status;
 
-	prev_nr_desc = nr_desc;
-	nr_desc = map_sz / desc_size;
-	if (nr_desc > prev_nr_desc &&
-	    nr_desc > ARRAY_SIZE(boot_params->e820_map)) {
-		u32 nr_e820ext = nr_desc - ARRAY_SIZE(boot_params->e820_map);
-
-		status = alloc_e820ext(nr_e820ext, &e820ext, &e820ext_size);
-		if (status != EFI_SUCCESS)
-			goto free_mem_map;
-
-		efi_call_early(free_pool, mem_map);
-		goto get_map; /* Allocated memory, get map again */
-	}
-
-	signature = is64 ? EFI64_LOADER_SIGNATURE : EFI32_LOADER_SIGNATURE;
-	memcpy(&efi->efi_loader_signature, signature, sizeof(__u32));
-
-	efi->efi_systab = (unsigned long)sys_table;
-	efi->efi_memdesc_size = desc_size;
-	efi->efi_memdesc_version = desc_version;
-	efi->efi_memmap = (unsigned long)mem_map;
-	efi->efi_memmap_size = map_sz;
-
-#ifdef CONFIG_X86_64
-	efi->efi_systab_hi = (unsigned long)sys_table >> 32;
-	efi->efi_memmap_hi = (unsigned long)mem_map >> 32;
-#endif
-
-	/* Might as well exit boot services now */
-	status = efi_call_early(exit_boot_services, handle, key);
-	if (status != EFI_SUCCESS) {
-		/*
-		 * ExitBootServices() will fail if any of the event
-		 * handlers change the memory map. In which case, we
-		 * must be prepared to retry, but only once so that
-		 * we're guaranteed to exit on repeated failures instead
-		 * of spinning forever.
-		 */
-		if (called_exit)
-			goto free_mem_map;
-
-		called_exit = true;
-		efi_call_early(free_pool, mem_map);
-		goto get_map;
-	}
-
+	e820ext = priv.e820ext;
+	e820ext_size = priv.e820ext_size;
 	/* Historic? */
 	boot_params->alt_mem_k = 32 * 1024;
 
@@ -1093,10 +1095,6 @@ get_map:
 		return status;
 
 	return EFI_SUCCESS;
-
-free_mem_map:
-	efi_call_early(free_pool, mem_map);
-	return status;
 }
 
 /*
-- 
Qualcomm Datacenter Technologies as an affiliate of Qualcomm Technologies, Inc.
Qualcomm Technologies, Inc. is a member of the
Code Aurora Forum, a Linux Foundation Collaborative Project.

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH V4 0/4] Handle EFI_INVALID_PARAMETER from ExitBootServices
       [not found] ` <1471288933-31810-1-git-send-email-jhugo-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
                     ` (3 preceding siblings ...)
  2016-08-15 19:22   ` [PATCH V4 4/4] x86/efi: Use efi_exit_boot_services() Jeffrey Hugo
@ 2016-08-19 11:43   ` Matt Fleming
  4 siblings, 0 replies; 6+ messages in thread
From: Matt Fleming @ 2016-08-19 11:43 UTC (permalink / raw)
  To: Jeffrey Hugo
  Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA,
	ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	leif.lindholm-QSEj5FYQhm4dnm+yROfE0A,
	timur-sgV2jX0FEOL9JmXXK+q4OQ

On Mon, 15 Aug, at 01:22:09PM, Jeffrey Hugo wrote:
> According to the UEFI spec, the UEFI OS Loader (aka the stub) should
> transition from UEFI to the OS by getting the current memory map from UEFI,
> then calling ExitBootServices.  The spec states that ExitBootServices may
> return EFI_INVALID_PARAMETER if the memory map reference provided by the stub
> is not current, ie UEFI handled some event prior to ExitBootServices which
> modified the map.  The spec states that to handle this scenario, the stub shall
> get the updated map, and invoke ExitBootServices again.  The spec also states
> that once ExitBootServices is invoked, even if it returns error, the only
> APIs the stub is allowed to invoke is GetMemoryMap and ExitBootServices.
> 
> The EFI_INVALID_PARAMETER scenario has been seen in the wild previously in
> x86 but the fix - d3768d885c6c ("x86, efi: retry ExitBootServices() on failure")
> still violates the spec.  The FDT code does not handle this scenario, but
> instances of it are now observed.
> 
> This patch series aims to provide a spec complaint solution that can be reused
> in all stubs, thus preventing each arch or variant from reinventing the wheel
> and likely getting it wrong.

This Jeffrey. This version looks much better and it passes all my
tests.

Queued up in 'urgent'.

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2016-08-19 11:43 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-08-15 19:22 [PATCH V4 0/4] Handle EFI_INVALID_PARAMETER from ExitBootServices Jeffrey Hugo
     [not found] ` <1471288933-31810-1-git-send-email-jhugo-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
2016-08-15 19:22   ` [PATCH V4 1/4] efi/libstub: Allocate headspace in efi_get_memory_map() Jeffrey Hugo
2016-08-15 19:22   ` [PATCH V4 2/4] efi/libstub: Introduce ExitBootServices helper Jeffrey Hugo
2016-08-15 19:22   ` [PATCH V4 3/4] efi/libstub: Use efi_exit_boot_services() in FDT Jeffrey Hugo
2016-08-15 19:22   ` [PATCH V4 4/4] x86/efi: Use efi_exit_boot_services() Jeffrey Hugo
2016-08-19 11:43   ` [PATCH V4 0/4] Handle EFI_INVALID_PARAMETER from ExitBootServices Matt Fleming

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).