[PATCH] efi/arm: fix allocation failure when reserving the kernel base

Chester Lin
2019-08-02T05:40:31Z

In some cases the arm32 efistub could fail to allocate memory for
uncompressed kernel. For example, we got the following error message when
verifying EFI stub on Raspberry Pi-2 [kernel-5.2.1 + grub-2.04] :

EFI stub: Booting Linux Kernel...
EFI stub: ERROR: Unable to allocate memory for uncompressed kernel.
EFI stub: ERROR: Failed to relocate kernel

After checking the EFI memory map we found that the first page [0 - 0xfff]
had been reserved by Raspberry Pi-2's firmware, and the efistub tried to
set the dram base at 0, which was actually in a reserved region.

grub> lsefimmap
Type Physical start - end #Pages Size Attributes
reserved 0000000000000000-0000000000000fff 00000001 4KiB WB
conv-mem 0000000000001000-0000000007ef5fff 00007ef5 130004KiB WB
RT-data 0000000007ef6000-0000000007f09fff 00000014 80KiB RT WB
conv-mem 0000000007f0a000-000000002d871fff 00025968 615840KiB WB
.....

To avoid a reserved address, we have to ignore the memory regions which are
marked as EFI_RESERVED_TYPE, and only conventional memory regions can be
chosen. If the region before the kernel base is unaligned, it will be
marked as EFI_RESERVED_TYPE and let kernel ignore it so that memblock_limit
will not be sticked with a very low address such as 0x1000.

Signed-off-by: Chester Lin <clin@suse.com>
---
arch/arm/mm/mmu.c | 3 ++
drivers/firmware/efi/libstub/arm32-stub.c | 43 ++++++++++++++++++-----
2 files changed, 37 insertions(+), 9 deletions(-)

diff --git a/arch/arm/mm/mmu.c b/arch/arm/mm/mmu.c
index f3ce34113f89..909b11ba48d8 100644
--- a/arch/arm/mm/mmu.c
+++ b/arch/arm/mm/mmu.c
@@ -1184,6 +1184,9 @@ void __init adjust_lowmem_bounds(void)
phys_addr_t block_start = reg->base;
phys_addr_t block_end = reg->base + reg->size;

+ if (memblock_is_nomap(reg))
+ continue;
+
if (reg->base < vmalloc_limit) {
if (block_end > lowmem_limit)
/*
diff --git a/drivers/firmware/efi/libstub/arm32-stub.c b/drivers/firmware/efi/libstub/arm32-stub.c
index e8f7aefb6813..10d33d36df00 100644
--- a/drivers/firmware/efi/libstub/arm32-stub.c
+++ b/drivers/firmware/efi/libstub/arm32-stub.c
@@ -128,7 +128,7 @@ static efi_status_t reserve_kernel_base(efi_system_table_t *sys_table_arg,

for (l = 0; l < map_size; l += desc_size) {
efi_memory_desc_t *desc;
- u64 start, end;
+ u64 start, end, spare, kernel_base;

desc = (void *)memory_map + l;
start = desc->phys_addr;
@@ -144,27 +144,52 @@ static efi_status_t reserve_kernel_base(efi_system_table_t *sys_table_arg,
case EFI_BOOT_SERVICES_DATA:
/* Ignore types that are released to the OS anyway */
continue;
-
+ case EFI_RESERVED_TYPE:
+ /* Ignore reserved regions */
+ 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);
+ kernel_base = round_up(start, PMD_SIZE);
+ spare = kernel_base - start;
+ end = min(end, kernel_base + MAX_UNCOMP_KERNEL_SIZE);
+
+ status = efi_call_early(allocate_pages,
+ EFI_ALLOCATE_ADDRESS,
+ EFI_LOADER_DATA,
+ MAX_UNCOMP_KERNEL_SIZE / EFI_PAGE_SIZE,
+ &kernel_base);
+ if (status != EFI_SUCCESS) {
+ pr_efi_err(sys_table_arg,
+ "reserve_kernel_base: alloc failed.\n");
+ goto out;
+ }
+ *reserve_addr = kernel_base;

+ if (!spare)
+ break;
+ /*
+ * If there's a gap between start and kernel_base,
+ * it needs be reserved so that the memblock_limit
+ * will not fall on a very low address when running
+ * adjust_lowmem_bounds(), wchich could eventually
+ * cause CMA reservation issue.
+ */
status = efi_call_early(allocate_pages,
EFI_ALLOCATE_ADDRESS,
- EFI_LOADER_DATA,
- (end - start) / EFI_PAGE_SIZE,
+ EFI_RESERVED_TYPE,
+ spare / EFI_PAGE_SIZE,
&start);
if (status != EFI_SUCCESS) {
pr_efi_err(sys_table_arg,
- "reserve_kernel_base(): alloc failed.\n");
+ "reserve spare-region failed\n");
goto out;
}
- break;

+ break;
case EFI_LOADER_CODE:
case EFI_LOADER_DATA:
/*
@@ -220,7 +245,7 @@ efi_status_t handle_kernel_image(efi_system_table_t *sys_table,
*image_size = image->image_size;
status = efi_relocate_kernel(sys_table, image_addr, *image_size,
*image_size,
- dram_base + MAX_UNCOMP_KERNEL_SIZE, 0);
+ *reserve_addr + MAX_UNCOMP_KERNEL_SIZE, 0);
if (status != EFI_SUCCESS) {
pr_efi_err(sys_table, "Failed to relocate kernel.\n");
efi_free(sys_table, *reserve_size, *reserve_addr);
@@ -233,7 +258,7 @@ efi_status_t handle_kernel_image(efi_system_table_t *sys_table,
* in memory. The kernel determines the base of DRAM from the
* address at which the zImage is loaded.
*/
- if (*image_addr + *image_size > dram_base + ZIMAGE_OFFSET_LIMIT) {
+ if (*image_addr + *image_size > *reserve_addr + ZIMAGE_OFFSET_LIMIT) {
pr_efi_err(sys_table, "Failed to relocate kernel, no low memory available.\n");
efi_free(sys_table, *reserve_size, *reserve_addr);
*reserve_size = 0;
--
2.22.0