Commit fc07716b authored by Jeffrey Hugo's avatar Jeffrey Hugo Committed by Matt Fleming

efi/libstub: Introduce ExitBootServices helper

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 d3768d88 ("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.  The user will notice the boot failure as the kernel is not
executed and the system may drop back to a UEFI shell, but will be
unresponsive to input and the system will require a power cycle to recover.

Add a helper to the stub library that correctly adheres to the spec in the
case of EFI_INVALID_PARAMETER from ExitBootServices and can be universally
used across all stub implementations.
Signed-off-by: default avatarJeffrey Hugo <jhugo@codeaurora.org>
Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Leif Lindholm <leif.lindholm@linaro.org>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarMatt Fleming <matt@codeblueprint.co.uk>
parent dadb57ab
...@@ -740,3 +740,76 @@ char *efi_convert_cmdline(efi_system_table_t *sys_table_arg, ...@@ -740,3 +740,76 @@ char *efi_convert_cmdline(efi_system_table_t *sys_table_arg,
*cmd_line_len = options_bytes; *cmd_line_len = options_bytes;
return (char *)cmdline_addr; 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,
struct efi_boot_memmap *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 UEFI Spec v2.6, Section 6.4:
* EFI_BOOT_SERVICES.ExitBootServices 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);
/* exit_boot_services() was called, thus cannot free */
if (status != EFI_SUCCESS)
goto fail;
status = priv_func(sys_table_arg, map, priv);
/* exit_boot_services() was called, thus cannot free */
if (status != EFI_SUCCESS)
goto fail;
status = efi_call_early(exit_boot_services, handle, *map->key_ptr);
}
/* exit_boot_services() was called, thus cannot free */
if (status != EFI_SUCCESS)
goto fail;
return EFI_SUCCESS;
free_map:
efi_call_early(free_pool, *map->map);
fail:
return status;
}
...@@ -1462,4 +1462,14 @@ extern void efi_call_virt_check_flags(unsigned long flags, const char *call); ...@@ -1462,4 +1462,14 @@ extern void efi_call_virt_check_flags(unsigned long flags, const char *call);
arch_efi_call_virt_teardown(); \ arch_efi_call_virt_teardown(); \
}) })
typedef efi_status_t (*efi_exit_boot_map_processing)(
efi_system_table_t *sys_table_arg,
struct efi_boot_memmap *map,
void *priv);
efi_status_t efi_exit_boot_services(efi_system_table_t *sys_table,
void *handle,
struct efi_boot_memmap *map,
void *priv,
efi_exit_boot_map_processing priv_func);
#endif /* _LINUX_EFI_H */ #endif /* _LINUX_EFI_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment