diff options
Diffstat (limited to 'drivers/firmware/efi/libstub/efi-stub-helper.c')
| -rw-r--r-- | drivers/firmware/efi/libstub/efi-stub-helper.c | 169 | 
1 files changed, 139 insertions, 30 deletions
diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c index 3bd127f95315..aded10662020 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) +				struct efi_boot_memmap *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; +	struct efi_boot_memmap 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; +	struct efi_boot_memmap boot_map; + +	boot_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; +	struct efi_boot_memmap boot_map; -	status = efi_get_memory_map(sys_table_arg, &map, &map_size, &desc_size, -				    NULL, NULL); +	boot_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, &boot_map);  	if (status != EFI_SUCCESS)  		goto fail; @@ -704,3 +740,76 @@ 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, +				    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; +}  | 
