diff options
Diffstat (limited to 'drivers/firmware/efi/libstub/arm-stub.c')
| -rw-r--r-- | drivers/firmware/efi/libstub/arm-stub.c | 88 | 
1 files changed, 73 insertions, 15 deletions
| diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c index e29560e6b40b..950c87f5d279 100644 --- a/drivers/firmware/efi/libstub/arm-stub.c +++ b/drivers/firmware/efi/libstub/arm-stub.c @@ -13,6 +13,7 @@   */  #include <linux/efi.h> +#include <linux/sort.h>  #include <asm/efi.h>  #include "efistub.h" @@ -305,6 +306,44 @@ fail:   */  #define EFI_RT_VIRTUAL_BASE	0x40000000 +static int cmp_mem_desc(const void *l, const void *r) +{ +	const efi_memory_desc_t *left = l, *right = r; + +	return (left->phys_addr > right->phys_addr) ? 1 : -1; +} + +/* + * Returns whether region @left ends exactly where region @right starts, + * or false if either argument is NULL. + */ +static bool regions_are_adjacent(efi_memory_desc_t *left, +				 efi_memory_desc_t *right) +{ +	u64 left_end; + +	if (left == NULL || right == NULL) +		return false; + +	left_end = left->phys_addr + left->num_pages * EFI_PAGE_SIZE; + +	return left_end == right->phys_addr; +} + +/* + * Returns whether region @left and region @right have compatible memory type + * mapping attributes, and are both EFI_MEMORY_RUNTIME regions. + */ +static bool regions_have_compatible_memory_type_attrs(efi_memory_desc_t *left, +						      efi_memory_desc_t *right) +{ +	static const u64 mem_type_mask = EFI_MEMORY_WB | EFI_MEMORY_WT | +					 EFI_MEMORY_WC | EFI_MEMORY_UC | +					 EFI_MEMORY_RUNTIME; + +	return ((left->attribute ^ right->attribute) & mem_type_mask) == 0; +} +  /*   * efi_get_virtmap() - create a virtual mapping for the EFI memory map   * @@ -317,33 +356,52 @@ void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size,  		     int *count)  {  	u64 efi_virt_base = EFI_RT_VIRTUAL_BASE; -	efi_memory_desc_t *out = runtime_map; +	efi_memory_desc_t *in, *prev = NULL, *out = runtime_map;  	int l; -	for (l = 0; l < map_size; l += desc_size) { -		efi_memory_desc_t *in = (void *)memory_map + l; +	/* +	 * To work around potential issues with the Properties Table feature +	 * introduced in UEFI 2.5, which may split PE/COFF executable images +	 * in memory into several RuntimeServicesCode and RuntimeServicesData +	 * regions, we need to preserve the relative offsets between adjacent +	 * EFI_MEMORY_RUNTIME regions with the same memory type attributes. +	 * The easiest way to find adjacent regions is to sort the memory map +	 * before traversing it. +	 */ +	sort(memory_map, map_size / desc_size, desc_size, cmp_mem_desc, NULL); + +	for (l = 0; l < map_size; l += desc_size, prev = in) {  		u64 paddr, size; +		in = (void *)memory_map + l;  		if (!(in->attribute & EFI_MEMORY_RUNTIME))  			continue; +		paddr = in->phys_addr; +		size = in->num_pages * EFI_PAGE_SIZE; +  		/*  		 * 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); +		if (!regions_are_adjacent(prev, in) || +		    !regions_have_compatible_memory_type_attrs(prev, in)) { + +			paddr = round_down(in->phys_addr, SZ_64K); +			size += in->phys_addr - paddr; + +			/* +			 * 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); +			else +				efi_virt_base = round_up(efi_virt_base, SZ_64K); +		}  		in->virt_addr = efi_virt_base + in->phys_addr - paddr;  		efi_virt_base += size; |