mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 13:41:51 +00:00
Merge branch 'core-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull EFI fixes from Ingo Molnar: "Two EFI fixes: one for x86, one for ARM, fixing a boot crash bug that can trigger under newer EFI firmware" * 'core-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: arm64/efi: Fix boot crash by not padding between EFI_MEMORY_RUNTIME regions x86/efi: Fix boot crash by mapping EFI memmap entries bottom-up at runtime, instead of top-down
This commit is contained in:
commit
a758379b03
@ -258,7 +258,8 @@ static bool __init efi_virtmap_init(void)
|
||||
*/
|
||||
if (!is_normal_ram(md))
|
||||
prot = __pgprot(PROT_DEVICE_nGnRE);
|
||||
else if (md->type == EFI_RUNTIME_SERVICES_CODE)
|
||||
else if (md->type == EFI_RUNTIME_SERVICES_CODE ||
|
||||
!PAGE_ALIGNED(md->phys_addr))
|
||||
prot = PAGE_KERNEL_EXEC;
|
||||
else
|
||||
prot = PAGE_KERNEL;
|
||||
|
@ -704,6 +704,70 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate the EFI memory map in reverse order because the regions
|
||||
* will be mapped top-down. The end result is the same as if we had
|
||||
* mapped things forward, but doesn't require us to change the
|
||||
* existing implementation of efi_map_region().
|
||||
*/
|
||||
static inline void *efi_map_next_entry_reverse(void *entry)
|
||||
{
|
||||
/* Initial call */
|
||||
if (!entry)
|
||||
return memmap.map_end - memmap.desc_size;
|
||||
|
||||
entry -= memmap.desc_size;
|
||||
if (entry < memmap.map)
|
||||
return NULL;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/*
|
||||
* efi_map_next_entry - Return the next EFI memory map descriptor
|
||||
* @entry: Previous EFI memory map descriptor
|
||||
*
|
||||
* This is a helper function to iterate over the EFI memory map, which
|
||||
* we do in different orders depending on the current configuration.
|
||||
*
|
||||
* To begin traversing the memory map @entry must be %NULL.
|
||||
*
|
||||
* Returns %NULL when we reach the end of the memory map.
|
||||
*/
|
||||
static void *efi_map_next_entry(void *entry)
|
||||
{
|
||||
if (!efi_enabled(EFI_OLD_MEMMAP) && efi_enabled(EFI_64BIT)) {
|
||||
/*
|
||||
* Starting in UEFI v2.5 the EFI_PROPERTIES_TABLE
|
||||
* config table feature requires us to map all entries
|
||||
* in the same order as they appear in the EFI memory
|
||||
* map. That is to say, entry N must have a lower
|
||||
* virtual address than entry N+1. This is because the
|
||||
* firmware toolchain leaves relative references in
|
||||
* the code/data sections, which are split and become
|
||||
* separate EFI memory regions. Mapping things
|
||||
* out-of-order leads to the firmware accessing
|
||||
* unmapped addresses.
|
||||
*
|
||||
* Since we need to map things this way whether or not
|
||||
* the kernel actually makes use of
|
||||
* EFI_PROPERTIES_TABLE, let's just switch to this
|
||||
* scheme by default for 64-bit.
|
||||
*/
|
||||
return efi_map_next_entry_reverse(entry);
|
||||
}
|
||||
|
||||
/* Initial call */
|
||||
if (!entry)
|
||||
return memmap.map;
|
||||
|
||||
entry += memmap.desc_size;
|
||||
if (entry >= memmap.map_end)
|
||||
return NULL;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/*
|
||||
* Map the efi memory ranges of the runtime services and update new_mmap with
|
||||
* virtual addresses.
|
||||
@ -714,7 +778,8 @@ static void * __init efi_map_regions(int *count, int *pg_shift)
|
||||
unsigned long left = 0;
|
||||
efi_memory_desc_t *md;
|
||||
|
||||
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
|
||||
p = NULL;
|
||||
while ((p = efi_map_next_entry(p))) {
|
||||
md = p;
|
||||
if (!(md->attribute & EFI_MEMORY_RUNTIME)) {
|
||||
#ifdef CONFIG_X86_64
|
||||
|
@ -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);
|
||||
if (!regions_are_adjacent(prev, in) ||
|
||||
!regions_have_compatible_memory_type_attrs(prev, in)) {
|
||||
|
||||
/*
|
||||
* 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);
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user