Merge branch 'efi-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull EFI fixes from Ingo Molnar:
 "This contains a Xen fix, an arm64 fix and a race condition /
  robustization set of fixes related to ExitBootServices() usage and
  boundary conditions"

* 'efi-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  x86/efi: Use efi_exit_boot_services()
  efi/libstub: Use efi_exit_boot_services() in FDT
  efi/libstub: Introduce ExitBootServices helper
  efi/libstub: Allocate headspace in efi_get_memory_map()
  efi: Fix handling error value in fdt_find_uefi_params
  efi: Make for_each_efi_memory_desc_in_map() cope with running on Xen
This commit is contained in:
Linus Torvalds 2016-09-13 12:02:00 -07:00
commit 7c2c114416
6 changed files with 282 additions and 119 deletions

View File

@ -1004,79 +1004,87 @@ static efi_status_t alloc_e820ext(u32 nr_desc, struct setup_data **e820ext,
return status; 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,
struct efi_boot_memmap *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, static efi_status_t exit_boot(struct boot_params *boot_params,
void *handle, bool is64) void *handle, bool is64)
{ {
struct efi_info *efi = &boot_params->efi_info; unsigned long map_sz, key, desc_size, buff_size;
unsigned long map_sz, key, desc_size;
efi_memory_desc_t *mem_map; efi_memory_desc_t *mem_map;
struct setup_data *e820ext; struct setup_data *e820ext;
const char *signature;
__u32 e820ext_size; __u32 e820ext_size;
__u32 nr_desc, prev_nr_desc;
efi_status_t status; efi_status_t status;
__u32 desc_version; __u32 desc_version;
bool called_exit = false; struct efi_boot_memmap map;
u8 nr_entries; struct exit_boot_struct priv;
int i;
nr_desc = 0; map.map = &mem_map;
e820ext = NULL; map.map_size = &map_sz;
e820ext_size = 0; map.desc_size = &desc_size;
map.desc_ver = &desc_version;
get_map: map.key_ptr = &key;
status = efi_get_memory_map(sys_table, &mem_map, &map_sz, &desc_size, map.buff_size = &buff_size;
&desc_version, &key); priv.boot_params = boot_params;
priv.efi = &boot_params->efi_info;
priv.e820ext = NULL;
priv.e820ext_size = 0;
priv.is64 = is64;
/* Might as well exit boot services now */
status = efi_exit_boot_services(sys_table, handle, &map, &priv,
exit_boot_func);
if (status != EFI_SUCCESS) if (status != EFI_SUCCESS)
return status; return status;
prev_nr_desc = nr_desc; e820ext = priv.e820ext;
nr_desc = map_sz / desc_size; e820ext_size = priv.e820ext_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;
}
/* Historic? */ /* Historic? */
boot_params->alt_mem_k = 32 * 1024; boot_params->alt_mem_k = 32 * 1024;
@ -1085,10 +1093,6 @@ get_map:
return status; return status;
return EFI_SUCCESS; return EFI_SUCCESS;
free_mem_map:
efi_call_early(free_pool, mem_map);
return status;
} }
/* /*

View File

@ -657,9 +657,12 @@ static int __init fdt_find_uefi_params(unsigned long node, const char *uname,
} }
if (subnode) { if (subnode) {
node = of_get_flat_dt_subnode_by_name(node, subnode); int err = of_get_flat_dt_subnode_by_name(node, subnode);
if (node < 0)
if (err < 0)
return 0; return 0;
node = err;
} }
return __find_uefi_params(node, info, dt_params[i].params); return __find_uefi_params(node, info, dt_params[i].params);

View File

@ -41,6 +41,8 @@ static unsigned long __chunk_size = EFI_READ_CHUNK_SIZE;
#define EFI_ALLOC_ALIGN EFI_PAGE_SIZE #define EFI_ALLOC_ALIGN EFI_PAGE_SIZE
#endif #endif
#define EFI_MMAP_NR_SLACK_SLOTS 8
struct file_info { struct file_info {
efi_file_handle_t *handle; efi_file_handle_t *handle;
u64 size; 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_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg,
efi_memory_desc_t **map, struct efi_boot_memmap *map)
unsigned long *map_size,
unsigned long *desc_size,
u32 *desc_ver,
unsigned long *key_ptr)
{ {
efi_memory_desc_t *m = NULL; efi_memory_desc_t *m = NULL;
efi_status_t status; efi_status_t status;
unsigned long key; unsigned long key;
u32 desc_version; 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: 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, status = efi_call_early(allocate_pool, EFI_LOADER_DATA,
*map_size, (void **)&m); *map->map_size, (void **)&m);
if (status != EFI_SUCCESS) if (status != EFI_SUCCESS)
goto fail; goto fail;
*desc_size = 0; *map->desc_size = 0;
key = 0; key = 0;
status = efi_call_early(get_memory_map, map_size, m, status = efi_call_early(get_memory_map, map->map_size, m,
&key, desc_size, &desc_version); &key, map->desc_size, &desc_version);
if (status == EFI_BUFFER_TOO_SMALL) { if (status == EFI_BUFFER_TOO_SMALL ||
!mmap_has_headroom(*map->buff_size, *map->map_size,
*map->desc_size)) {
efi_call_early(free_pool, m); 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; goto again;
} }
if (status != EFI_SUCCESS) if (status != EFI_SUCCESS)
efi_call_early(free_pool, m); efi_call_early(free_pool, m);
if (key_ptr && status == EFI_SUCCESS) if (map->key_ptr && status == EFI_SUCCESS)
*key_ptr = key; *map->key_ptr = key;
if (desc_ver && status == EFI_SUCCESS) if (map->desc_ver && status == EFI_SUCCESS)
*desc_ver = desc_version; *map->desc_ver = desc_version;
fail: fail:
*map = m; *map->map = m;
return status; return status;
} }
@ -113,13 +128,20 @@ fail:
unsigned long get_dram_base(efi_system_table_t *sys_table_arg) unsigned long get_dram_base(efi_system_table_t *sys_table_arg)
{ {
efi_status_t status; efi_status_t status;
unsigned long map_size; unsigned long map_size, buff_size;
unsigned long membase = EFI_ERROR; unsigned long membase = EFI_ERROR;
struct efi_memory_map map; struct efi_memory_map map;
efi_memory_desc_t *md; efi_memory_desc_t *md;
struct efi_boot_memmap boot_map;
status = efi_get_memory_map(sys_table_arg, (efi_memory_desc_t **)&map.map, boot_map.map = (efi_memory_desc_t **)&map.map;
&map_size, &map.desc_size, NULL, NULL); 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) if (status != EFI_SUCCESS)
return membase; 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 size, unsigned long align,
unsigned long *addr, unsigned long max) 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_memory_desc_t *map;
efi_status_t status; efi_status_t status;
unsigned long nr_pages; unsigned long nr_pages;
u64 max_addr = 0; u64 max_addr = 0;
int i; int i;
struct efi_boot_memmap boot_map;
status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size, boot_map.map = &map;
NULL, NULL); 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, &boot_map);
if (status != EFI_SUCCESS) if (status != EFI_SUCCESS)
goto fail; 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 size, unsigned long align,
unsigned long *addr) unsigned long *addr)
{ {
unsigned long map_size, desc_size; unsigned long map_size, desc_size, buff_size;
efi_memory_desc_t *map; efi_memory_desc_t *map;
efi_status_t status; efi_status_t status;
unsigned long nr_pages; unsigned long nr_pages;
int i; int i;
struct efi_boot_memmap boot_map;
status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size, boot_map.map = &map;
NULL, NULL); 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, &boot_map);
if (status != EFI_SUCCESS) if (status != EFI_SUCCESS)
goto fail; goto fail;
@ -704,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;
}

View File

@ -152,6 +152,27 @@ fdt_set_fail:
#define EFI_FDT_ALIGN EFI_PAGE_SIZE #define EFI_FDT_ALIGN EFI_PAGE_SIZE
#endif #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,
struct efi_boot_memmap *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 to 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 * Allocate memory for a new FDT, then add EFI, commandline, and
* initrd related fields to the FDT. This routine increases the * initrd related fields to the FDT. This routine increases the
@ -175,13 +196,22 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
unsigned long fdt_addr, unsigned long fdt_addr,
unsigned long fdt_size) unsigned long fdt_size)
{ {
unsigned long map_size, desc_size; unsigned long map_size, desc_size, buff_size;
u32 desc_ver; u32 desc_ver;
unsigned long mmap_key; unsigned long mmap_key;
efi_memory_desc_t *memory_map, *runtime_map; efi_memory_desc_t *memory_map, *runtime_map;
unsigned long new_fdt_size; unsigned long new_fdt_size;
efi_status_t status; efi_status_t status;
int runtime_entry_count = 0; int runtime_entry_count = 0;
struct efi_boot_memmap map;
struct exit_boot_struct priv;
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 * Get a copy of the current memory map that we will use to prepare
@ -189,8 +219,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 * subsequent allocations adding entries, since they could not affect
* the number of EFI_MEMORY_RUNTIME regions. * the number of EFI_MEMORY_RUNTIME regions.
*/ */
status = efi_get_memory_map(sys_table, &runtime_map, &map_size, status = efi_get_memory_map(sys_table, &map);
&desc_size, &desc_ver, &mmap_key);
if (status != EFI_SUCCESS) { if (status != EFI_SUCCESS) {
pr_efi_err(sys_table, "Unable to retrieve UEFI memory map.\n"); pr_efi_err(sys_table, "Unable to retrieve UEFI memory map.\n");
return status; return status;
@ -199,6 +228,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
pr_efi(sys_table, pr_efi(sys_table,
"Exiting boot services and installing virtual address map...\n"); "Exiting boot services and installing virtual address map...\n");
map.map = &memory_map;
/* /*
* Estimate size of new FDT, and allocate memory for it. We * Estimate size of new FDT, and allocate memory for it. We
* will allocate a bigger buffer if this ends up being too * will allocate a bigger buffer if this ends up being too
@ -218,8 +248,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 * we can get the memory map key needed for
* exit_boot_services(). * exit_boot_services().
*/ */
status = efi_get_memory_map(sys_table, &memory_map, &map_size, status = efi_get_memory_map(sys_table, &map);
&desc_size, &desc_ver, &mmap_key);
if (status != EFI_SUCCESS) if (status != EFI_SUCCESS)
goto fail_free_new_fdt; goto fail_free_new_fdt;
@ -250,16 +279,11 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
} }
} }
/* sys_table->boottime->free_pool(memory_map);
* Update the memory map with virtual addresses. The function will also priv.runtime_map = runtime_map;
* populate @runtime_map with copies of just the EFI_MEMORY_RUNTIME priv.runtime_entry_count = &runtime_entry_count;
* entries so that we can pass it straight into SetVirtualAddressMap() status = efi_exit_boot_services(sys_table, handle, &map, &priv,
*/ exit_boot_func);
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);
if (status == EFI_SUCCESS) { if (status == EFI_SUCCESS) {
efi_set_virtual_address_map_t *svam; efi_set_virtual_address_map_t *svam;

View File

@ -73,12 +73,20 @@ efi_status_t efi_random_alloc(efi_system_table_t *sys_table_arg,
unsigned long random_seed) unsigned long random_seed)
{ {
unsigned long map_size, desc_size, total_slots = 0, target_slot; unsigned long map_size, desc_size, total_slots = 0, target_slot;
unsigned long buff_size;
efi_status_t status; efi_status_t status;
efi_memory_desc_t *memory_map; efi_memory_desc_t *memory_map;
int map_offset; int map_offset;
struct efi_boot_memmap map;
status = efi_get_memory_map(sys_table_arg, &memory_map, &map_size, map.map = &memory_map;
&desc_size, NULL, NULL); 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) if (status != EFI_SUCCESS)
return status; return status;

View File

@ -118,6 +118,15 @@ typedef struct {
u32 imagesize; u32 imagesize;
} efi_capsule_header_t; } efi_capsule_header_t;
struct efi_boot_memmap {
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 capsule flags * EFI capsule flags
*/ */
@ -946,7 +955,7 @@ extern int efi_memattr_apply_permissions(struct mm_struct *mm,
/* Iterate through an efi_memory_map */ /* Iterate through an efi_memory_map */
#define for_each_efi_memory_desc_in_map(m, md) \ #define for_each_efi_memory_desc_in_map(m, md) \
for ((md) = (m)->map; \ for ((md) = (m)->map; \
((void *)(md) + (m)->desc_size) <= (m)->map_end; \ (md) && ((void *)(md) + (m)->desc_size) <= (m)->map_end; \
(md) = (void *)(md) + (m)->desc_size) (md) = (void *)(md) + (m)->desc_size)
/** /**
@ -1371,11 +1380,7 @@ char *efi_convert_cmdline(efi_system_table_t *sys_table_arg,
efi_loaded_image_t *image, int *cmd_line_len); efi_loaded_image_t *image, int *cmd_line_len);
efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg, efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg,
efi_memory_desc_t **map, struct efi_boot_memmap *map);
unsigned long *map_size,
unsigned long *desc_size,
u32 *desc_ver,
unsigned long *key_ptr);
efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg, efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg,
unsigned long size, unsigned long align, unsigned long size, unsigned long align,
@ -1457,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 */