diff options
Diffstat (limited to 'drivers/firmware/efi/libstub/arm32-stub.c')
-rw-r--r-- | drivers/firmware/efi/libstub/arm32-stub.c | 150 |
1 files changed, 130 insertions, 20 deletions
diff --git a/drivers/firmware/efi/libstub/arm32-stub.c b/drivers/firmware/efi/libstub/arm32-stub.c index e1f0b28e1dcb..becbda445913 100644 --- a/drivers/firmware/efi/libstub/arm32-stub.c +++ b/drivers/firmware/efi/libstub/arm32-stub.c @@ -9,6 +9,8 @@ #include <linux/efi.h> #include <asm/efi.h> +#include "efistub.h" + efi_status_t check_platform_features(efi_system_table_t *sys_table_arg) { int block; @@ -63,6 +65,132 @@ void free_screen_info(efi_system_table_t *sys_table_arg, struct screen_info *si) efi_call_early(free_pool, si); } +static efi_status_t reserve_kernel_base(efi_system_table_t *sys_table_arg, + unsigned long dram_base, + unsigned long *reserve_addr, + unsigned long *reserve_size) +{ + efi_physical_addr_t alloc_addr; + efi_memory_desc_t *memory_map; + unsigned long nr_pages, map_size, desc_size, buff_size; + efi_status_t status; + unsigned long l; + + struct efi_boot_memmap map = { + .map = &memory_map, + .map_size = &map_size, + .desc_size = &desc_size, + .desc_ver = NULL, + .key_ptr = NULL, + .buff_size = &buff_size, + }; + + /* + * Reserve memory for the uncompressed kernel image. This is + * all that prevents any future allocations from conflicting + * with the kernel. Since we can't tell from the compressed + * image how much DRAM the kernel actually uses (due to BSS + * size uncertainty) we allocate the maximum possible size. + * Do this very early, as prints can cause memory allocations + * that may conflict with this. + */ + alloc_addr = dram_base + MAX_UNCOMP_KERNEL_SIZE; + nr_pages = MAX_UNCOMP_KERNEL_SIZE / EFI_PAGE_SIZE; + status = efi_call_early(allocate_pages, EFI_ALLOCATE_MAX_ADDRESS, + EFI_BOOT_SERVICES_DATA, nr_pages, &alloc_addr); + if (status == EFI_SUCCESS) { + if (alloc_addr == dram_base) { + *reserve_addr = alloc_addr; + *reserve_size = MAX_UNCOMP_KERNEL_SIZE; + return EFI_SUCCESS; + } + /* + * If we end up here, the allocation succeeded but starts below + * dram_base. This can only occur if the real base of DRAM is + * not a multiple of 128 MB, in which case dram_base will have + * been rounded up. Since this implies that a part of the region + * was already occupied, we need to fall through to the code + * below to ensure that the existing allocations don't conflict. + * For this reason, we use EFI_BOOT_SERVICES_DATA above and not + * EFI_LOADER_DATA, which we wouldn't able to distinguish from + * allocations that we want to disallow. + */ + } + + /* + * If the allocation above failed, we may still be able to proceed: + * if the only allocations in the region are of types that will be + * released to the OS after ExitBootServices(), the decompressor can + * safely overwrite them. + */ + status = efi_get_memory_map(sys_table_arg, &map); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, + "reserve_kernel_base(): Unable to retrieve memory map.\n"); + return status; + } + + for (l = 0; l < map_size; l += desc_size) { + efi_memory_desc_t *desc; + u64 start, end; + + desc = (void *)memory_map + l; + start = desc->phys_addr; + end = start + desc->num_pages * EFI_PAGE_SIZE; + + /* Skip if entry does not intersect with region */ + if (start >= dram_base + MAX_UNCOMP_KERNEL_SIZE || + end <= dram_base) + continue; + + switch (desc->type) { + case EFI_BOOT_SERVICES_CODE: + case EFI_BOOT_SERVICES_DATA: + /* Ignore types that are released to the OS anyway */ + continue; + + case EFI_CONVENTIONAL_MEMORY: + /* + * Reserve the intersection between this entry and the + * region. + */ + start = max(start, (u64)dram_base); + end = min(end, (u64)dram_base + MAX_UNCOMP_KERNEL_SIZE); + + status = efi_call_early(allocate_pages, + EFI_ALLOCATE_ADDRESS, + EFI_LOADER_DATA, + (end - start) / EFI_PAGE_SIZE, + &start); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, + "reserve_kernel_base(): alloc failed.\n"); + goto out; + } + break; + + case EFI_LOADER_CODE: + case EFI_LOADER_DATA: + /* + * These regions may be released and reallocated for + * another purpose (including EFI_RUNTIME_SERVICE_DATA) + * at any time during the execution of the OS loader, + * so we cannot consider them as safe. + */ + default: + /* + * Treat any other allocation in the region as unsafe */ + status = EFI_OUT_OF_RESOURCES; + goto out; + } + } + + status = EFI_SUCCESS; +out: + efi_call_early(free_pool, memory_map); + return status; +} + efi_status_t handle_kernel_image(efi_system_table_t *sys_table, unsigned long *image_addr, unsigned long *image_size, @@ -71,10 +199,7 @@ efi_status_t handle_kernel_image(efi_system_table_t *sys_table, unsigned long dram_base, efi_loaded_image_t *image) { - unsigned long nr_pages; efi_status_t status; - /* Use alloc_addr to tranlsate between types */ - efi_physical_addr_t alloc_addr; /* * Verify that the DRAM base address is compatible with the ARM @@ -85,27 +210,12 @@ efi_status_t handle_kernel_image(efi_system_table_t *sys_table, */ dram_base = round_up(dram_base, SZ_128M); - /* - * Reserve memory for the uncompressed kernel image. This is - * all that prevents any future allocations from conflicting - * with the kernel. Since we can't tell from the compressed - * image how much DRAM the kernel actually uses (due to BSS - * size uncertainty) we allocate the maximum possible size. - * Do this very early, as prints can cause memory allocations - * that may conflict with this. - */ - alloc_addr = dram_base; - *reserve_size = MAX_UNCOMP_KERNEL_SIZE; - nr_pages = round_up(*reserve_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE; - status = sys_table->boottime->allocate_pages(EFI_ALLOCATE_ADDRESS, - EFI_LOADER_DATA, - nr_pages, &alloc_addr); + status = reserve_kernel_base(sys_table, dram_base, reserve_addr, + reserve_size); if (status != EFI_SUCCESS) { - *reserve_size = 0; pr_efi_err(sys_table, "Unable to allocate memory for uncompressed kernel.\n"); return status; } - *reserve_addr = alloc_addr; /* * Relocate the zImage, so that it appears in the lowest 128 MB |