Skip to content

Commit fc07716

Browse files
Jeffrey Hugomfleming
Jeffrey Hugo
authored andcommitted
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 d3768d8 ("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: Jeffrey Hugo <[email protected]> Cc: Ard Biesheuvel <[email protected]> Cc: Mark Rutland <[email protected]> Cc: Leif Lindholm <[email protected]> Cc: Ingo Molnar <[email protected]> Cc: <[email protected]> Signed-off-by: Matt Fleming <[email protected]>
1 parent dadb57a commit fc07716

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

drivers/firmware/efi/libstub/efi-stub-helper.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,3 +740,76 @@ char *efi_convert_cmdline(efi_system_table_t *sys_table_arg,
740740
*cmd_line_len = options_bytes;
741741
return (char *)cmdline_addr;
742742
}
743+
744+
/*
745+
* Handle calling ExitBootServices according to the requirements set out by the
746+
* spec. Obtains the current memory map, and returns that info after calling
747+
* ExitBootServices. The client must specify a function to perform any
748+
* processing of the memory map data prior to ExitBootServices. A client
749+
* specific structure may be passed to the function via priv. The client
750+
* function may be called multiple times.
751+
*/
752+
efi_status_t efi_exit_boot_services(efi_system_table_t *sys_table_arg,
753+
void *handle,
754+
struct efi_boot_memmap *map,
755+
void *priv,
756+
efi_exit_boot_map_processing priv_func)
757+
{
758+
efi_status_t status;
759+
760+
status = efi_get_memory_map(sys_table_arg, map);
761+
762+
if (status != EFI_SUCCESS)
763+
goto fail;
764+
765+
status = priv_func(sys_table_arg, map, priv);
766+
if (status != EFI_SUCCESS)
767+
goto free_map;
768+
769+
status = efi_call_early(exit_boot_services, handle, *map->key_ptr);
770+
771+
if (status == EFI_INVALID_PARAMETER) {
772+
/*
773+
* The memory map changed between efi_get_memory_map() and
774+
* exit_boot_services(). Per the UEFI Spec v2.6, Section 6.4:
775+
* EFI_BOOT_SERVICES.ExitBootServices we need to get the
776+
* updated map, and try again. The spec implies one retry
777+
* should be sufficent, which is confirmed against the EDK2
778+
* implementation. Per the spec, we can only invoke
779+
* get_memory_map() and exit_boot_services() - we cannot alloc
780+
* so efi_get_memory_map() cannot be used, and we must reuse
781+
* the buffer. For all practical purposes, the headroom in the
782+
* buffer should account for any changes in the map so the call
783+
* to get_memory_map() is expected to succeed here.
784+
*/
785+
*map->map_size = *map->buff_size;
786+
status = efi_call_early(get_memory_map,
787+
map->map_size,
788+
*map->map,
789+
map->key_ptr,
790+
map->desc_size,
791+
map->desc_ver);
792+
793+
/* exit_boot_services() was called, thus cannot free */
794+
if (status != EFI_SUCCESS)
795+
goto fail;
796+
797+
status = priv_func(sys_table_arg, map, priv);
798+
/* exit_boot_services() was called, thus cannot free */
799+
if (status != EFI_SUCCESS)
800+
goto fail;
801+
802+
status = efi_call_early(exit_boot_services, handle, *map->key_ptr);
803+
}
804+
805+
/* exit_boot_services() was called, thus cannot free */
806+
if (status != EFI_SUCCESS)
807+
goto fail;
808+
809+
return EFI_SUCCESS;
810+
811+
free_map:
812+
efi_call_early(free_pool, *map->map);
813+
fail:
814+
return status;
815+
}

include/linux/efi.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,4 +1462,14 @@ extern void efi_call_virt_check_flags(unsigned long flags, const char *call);
14621462
arch_efi_call_virt_teardown(); \
14631463
})
14641464

1465+
typedef efi_status_t (*efi_exit_boot_map_processing)(
1466+
efi_system_table_t *sys_table_arg,
1467+
struct efi_boot_memmap *map,
1468+
void *priv);
1469+
1470+
efi_status_t efi_exit_boot_services(efi_system_table_t *sys_table,
1471+
void *handle,
1472+
struct efi_boot_memmap *map,
1473+
void *priv,
1474+
efi_exit_boot_map_processing priv_func);
14651475
#endif /* _LINUX_EFI_H */

0 commit comments

Comments
 (0)