diff options
Diffstat (limited to 'drivers/firmware/efi/libstub')
| -rw-r--r-- | drivers/firmware/efi/libstub/Makefile | 15 | ||||
| -rw-r--r-- | drivers/firmware/efi/libstub/arm-stub.c | 67 | ||||
| -rw-r--r-- | drivers/firmware/efi/libstub/efi-stub-helper.c | 43 | ||||
| -rw-r--r-- | drivers/firmware/efi/libstub/efistub.h | 8 | ||||
| -rw-r--r-- | drivers/firmware/efi/libstub/fdt.c | 62 | 
5 files changed, 173 insertions, 22 deletions
| diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile index b14bc2b9fb4d..280bc0a63365 100644 --- a/drivers/firmware/efi/libstub/Makefile +++ b/drivers/firmware/efi/libstub/Makefile @@ -19,8 +19,23 @@ KBUILD_CFLAGS			:= $(cflags-y) \  				   $(call cc-option,-fno-stack-protector)  GCOV_PROFILE			:= n +KASAN_SANITIZE			:= n  lib-y				:= efi-stub-helper.o  lib-$(CONFIG_EFI_ARMSTUB)	+= arm-stub.o fdt.o  CFLAGS_fdt.o			+= -I$(srctree)/scripts/dtc/libfdt/ + +# +# arm64 puts the stub in the kernel proper, which will unnecessarily retain all +# code indefinitely unless it is annotated as __init/__initdata/__initconst etc. +# So let's apply the __init annotations at the section level, by prefixing +# the section names directly. This will ensure that even all the inline string +# literals are covered. +# +extra-$(CONFIG_ARM64)		:= $(lib-y) +lib-$(CONFIG_ARM64)		:= $(patsubst %.o,%.init.o,$(lib-y)) + +OBJCOPYFLAGS := --prefix-alloc-sections=.init +$(obj)/%.init.o: $(obj)/%.o FORCE +	$(call if_changed,objcopy) diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c index eb48a1a1a576..dcae482a9a17 100644 --- a/drivers/firmware/efi/libstub/arm-stub.c +++ b/drivers/firmware/efi/libstub/arm-stub.c @@ -17,10 +17,10 @@  #include "efistub.h" -static int __init efi_secureboot_enabled(efi_system_table_t *sys_table_arg) +static int efi_secureboot_enabled(efi_system_table_t *sys_table_arg)  { -	static efi_guid_t const var_guid __initconst = EFI_GLOBAL_VARIABLE_GUID; -	static efi_char16_t const var_name[] __initconst = { +	static efi_guid_t const var_guid = EFI_GLOBAL_VARIABLE_GUID; +	static efi_char16_t const var_name[] = {  		'S', 'e', 'c', 'u', 'r', 'e', 'B', 'o', 'o', 't', 0 };  	efi_get_variable_t *f_getvar = sys_table_arg->runtime->get_variable; @@ -164,7 +164,7 @@ efi_status_t handle_kernel_image(efi_system_table_t *sys_table,   * for both archictectures, with the arch-specific code provided in the   * handle_kernel_image() function.   */ -unsigned long __init efi_entry(void *handle, efi_system_table_t *sys_table, +unsigned long efi_entry(void *handle, efi_system_table_t *sys_table,  			       unsigned long *image_addr)  {  	efi_loaded_image_t *image; @@ -295,3 +295,62 @@ fail_free_image:  fail:  	return EFI_ERROR;  } + +/* + * This is the base address at which to start allocating virtual memory ranges + * for UEFI Runtime Services. This is in the low TTBR0 range so that we can use + * any allocation we choose, and eliminate the risk of a conflict after kexec. + * The value chosen is the largest non-zero power of 2 suitable for this purpose + * both on 32-bit and 64-bit ARM CPUs, to maximize the likelihood that it can + * be mapped efficiently. + */ +#define EFI_RT_VIRTUAL_BASE	0x40000000 + +/* + * efi_get_virtmap() - create a virtual mapping for the EFI memory map + * + * This function populates the virt_addr fields of all memory region descriptors + * in @memory_map whose EFI_MEMORY_RUNTIME attribute is set. Those descriptors + * are also copied to @runtime_map, and their total count is returned in @count. + */ +void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size, +		     unsigned long desc_size, efi_memory_desc_t *runtime_map, +		     int *count) +{ +	u64 efi_virt_base = EFI_RT_VIRTUAL_BASE; +	efi_memory_desc_t *out = runtime_map; +	int l; + +	for (l = 0; l < map_size; l += desc_size) { +		efi_memory_desc_t *in = (void *)memory_map + l; +		u64 paddr, size; + +		if (!(in->attribute & EFI_MEMORY_RUNTIME)) +			continue; + +		/* +		 * Make the mapping compatible with 64k pages: this allows +		 * a 4k page size kernel to kexec a 64k page size kernel and +		 * vice versa. +		 */ +		paddr = round_down(in->phys_addr, SZ_64K); +		size = round_up(in->num_pages * EFI_PAGE_SIZE + +				in->phys_addr - paddr, SZ_64K); + +		/* +		 * Avoid wasting memory on PTEs by choosing a virtual base that +		 * is compatible with section mappings if this region has the +		 * appropriate size and physical alignment. (Sections are 2 MB +		 * on 4k granule kernels) +		 */ +		if (IS_ALIGNED(in->phys_addr, SZ_2M) && size >= SZ_2M) +			efi_virt_base = round_up(efi_virt_base, SZ_2M); + +		in->virt_addr = efi_virt_base + in->phys_addr - paddr; +		efi_virt_base += size; + +		memcpy(out, in, desc_size); +		out = (void *)out + desc_size; +		++*count; +	} +} diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c index a920fec8fe88..af5d63c7cc53 100644 --- a/drivers/firmware/efi/libstub/efi-stub-helper.c +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c @@ -32,6 +32,15 @@  static unsigned long __chunk_size = EFI_READ_CHUNK_SIZE; +/* + * Allow the platform to override the allocation granularity: this allows + * systems that have the capability to run with a larger page size to deal + * with the allocations for initrd and fdt more efficiently. + */ +#ifndef EFI_ALLOC_ALIGN +#define EFI_ALLOC_ALIGN		EFI_PAGE_SIZE +#endif +  struct file_info {  	efi_file_handle_t *handle;  	u64 size; @@ -66,25 +75,29 @@ efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg,  	unsigned long key;  	u32 desc_version; -	*map_size = sizeof(*m) * 32; -again: +	*map_size = 0; +	*desc_size = 0; +	key = 0; +	status = efi_call_early(get_memory_map, map_size, NULL, +				&key, desc_size, &desc_version); +	if (status != EFI_BUFFER_TOO_SMALL) +		return EFI_LOAD_ERROR; +  	/*  	 * 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); +	*map_size += *desc_size;  	status = efi_call_early(allocate_pool, EFI_LOADER_DATA,  				*map_size, (void **)&m);  	if (status != EFI_SUCCESS)  		goto fail; -	*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) {  		efi_call_early(free_pool, m); -		goto again; +		return EFI_LOAD_ERROR;  	}  	if (status != EFI_SUCCESS) @@ -101,7 +114,7 @@ fail:  } -unsigned long __init 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;  	unsigned long map_size; @@ -150,10 +163,10 @@ efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg,  	 * a specific address.  We are doing page-based allocations,  	 * so we must be aligned to a page.  	 */ -	if (align < EFI_PAGE_SIZE) -		align = EFI_PAGE_SIZE; +	if (align < EFI_ALLOC_ALIGN) +		align = EFI_ALLOC_ALIGN; -	nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE; +	nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;  again:  	for (i = 0; i < map_size / desc_size; i++) {  		efi_memory_desc_t *desc; @@ -235,10 +248,10 @@ efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg,  	 * a specific address.  We are doing page-based allocations,  	 * so we must be aligned to a page.  	 */ -	if (align < EFI_PAGE_SIZE) -		align = EFI_PAGE_SIZE; +	if (align < EFI_ALLOC_ALIGN) +		align = EFI_ALLOC_ALIGN; -	nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE; +	nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;  	for (i = 0; i < map_size / desc_size; i++) {  		efi_memory_desc_t *desc;  		unsigned long m = (unsigned long)map; @@ -292,7 +305,7 @@ void efi_free(efi_system_table_t *sys_table_arg, unsigned long size,  	if (!size)  		return; -	nr_pages = round_up(size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE; +	nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;  	efi_call_early(free_pages, addr, nr_pages);  } @@ -561,7 +574,7 @@ efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg,  	 * to the preferred address.  If that fails, allocate as low  	 * as possible while respecting the required alignment.  	 */ -	nr_pages = round_up(alloc_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE; +	nr_pages = round_up(alloc_size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;  	status = efi_call_early(allocate_pages,  				EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,  				nr_pages, &efi_addr); diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h index 304ab295ca1a..47437b16b186 100644 --- a/drivers/firmware/efi/libstub/efistub.h +++ b/drivers/firmware/efi/libstub/efistub.h @@ -5,6 +5,10 @@  /* error code which can't be mistaken for valid address */  #define EFI_ERROR	(~0UL) +#undef memcpy +#undef memset +#undef memmove +  void efi_char16_printk(efi_system_table_t *, efi_char16_t *);  efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, void *__image, @@ -39,4 +43,8 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,  void *get_fdt(efi_system_table_t *sys_table); +void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size, +		     unsigned long desc_size, efi_memory_desc_t *runtime_map, +		     int *count); +  #endif diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c index c846a9608cbd..91da56c4fd54 100644 --- a/drivers/firmware/efi/libstub/fdt.c +++ b/drivers/firmware/efi/libstub/fdt.c @@ -14,6 +14,8 @@  #include <linux/libfdt.h>  #include <asm/efi.h> +#include "efistub.h" +  efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,  			unsigned long orig_fdt_size,  			void *fdt, int new_fdt_size, char *cmdline_ptr, @@ -193,9 +195,26 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,  	unsigned long map_size, desc_size;  	u32 desc_ver;  	unsigned long mmap_key; -	efi_memory_desc_t *memory_map; +	efi_memory_desc_t *memory_map, *runtime_map;  	unsigned long new_fdt_size;  	efi_status_t status; +	int runtime_entry_count = 0; + +	/* +	 * Get a copy of the current memory map that we will use to prepare +	 * the input for SetVirtualAddressMap(). We don't have to worry about +	 * 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); +	if (status != EFI_SUCCESS) { +		pr_efi_err(sys_table, "Unable to retrieve UEFI memory map.\n"); +		return status; +	} + +	pr_efi(sys_table, +	       "Exiting boot services and installing virtual address map...\n");  	/*  	 * Estimate size of new FDT, and allocate memory for it. We @@ -248,12 +267,48 @@ 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); +	if (status == EFI_SUCCESS) { +		efi_set_virtual_address_map_t *svam; -	if (status == EFI_SUCCESS) -		return status; +		/* Install the new virtual address map */ +		svam = sys_table->runtime->set_virtual_address_map; +		status = svam(runtime_entry_count * desc_size, desc_size, +			      desc_ver, runtime_map); + +		/* +		 * We are beyond the point of no return here, so if the call to +		 * SetVirtualAddressMap() failed, we need to signal that to the +		 * incoming kernel but proceed normally otherwise. +		 */ +		if (status != EFI_SUCCESS) { +			int l; + +			/* +			 * Set the virtual address field of all +			 * EFI_MEMORY_RUNTIME entries to 0. This will signal +			 * the incoming kernel that no virtual translation has +			 * been installed. +			 */ +			for (l = 0; l < map_size; l += desc_size) { +				efi_memory_desc_t *p = (void *)memory_map + l; + +				if (p->attribute & EFI_MEMORY_RUNTIME) +					p->virt_addr = 0; +			} +		} +		return EFI_SUCCESS; +	}  	pr_efi_err(sys_table, "Exit boot services failed.\n"); @@ -264,6 +319,7 @@ fail_free_new_fdt:  	efi_free(sys_table, new_fdt_size, *new_fdt_addr);  fail: +	sys_table->boottime->free_pool(runtime_map);  	return EFI_LOAD_ERROR;  } |