[PATCH 15/16] Add EFI stub for ARM
From: Roy Franz <hidden>
Date: 2013-08-14 02:08:01
Also in:
linux-efi, lkml
On Tue, Aug 13, 2013 at 6:44 PM, Roy Franz [off-list ref] wrote:
quoted hunk
Thanks Dave - comments inline, and I have an updated head.S diff at the end. Roy On Tue, Aug 13, 2013 at 7:19 AM, Dave Martin [off-list ref] wrote:quoted
On Fri, Aug 09, 2013 at 04:26:16PM -0700, Roy Franz wrote:quoted
This patch adds EFI stub support for the ARM Linux kernel. The EFI stub operations similarly to the x86 stub: it is a shim between the EFI firmware and the normal zImage entry point, and sets up the environment that the zImage is expecting. This includes loading the initrd (optionaly) and device tree from the system partition based on the kernel command line. The stub updates the device tree as necessary, including adding reserved memory regions and adding entries for EFI runtime services. The PE/COFF "MZ" header at offset 0 results in the first instruction being an add that corrupts r5, which is not used by the zImage interface.Thanks for the update -- a few more comments, nothing major.quoted
Signed-off-by: Roy Franz <redacted> --- arch/arm/boot/compressed/Makefile | 15 +- arch/arm/boot/compressed/efi-header.S | 111 ++++++++...quoted
quoted
+ goto fdt_set_fail; + + return EFI_SUCCESS;This looks better.quoted
+ +fdt_set_fail: + if (status == -FDT_ERR_NOSPACE) + return EFI_BUFFER_TOO_SMALL; + + return EFI_LOAD_ERROR; +} + + +Maybe add a comment to indicate that this returns the address of the relocated fdt, or EFI_LOAD_ERROR. By default "int" feels more likely to return a status code. It is not common to return pointers using the "int" type: it may be preferable to use unsigned long of void * instead. This won't change the functionality. Casts to (int) which could overflow the signed range can cause GCC to generate bizarre code in some situations, because C doesn't have to guarantee wrapping when casting to signed types. Since we just pass that value through without doing any arithmetic I think we're unlikely to hit that here, but it's best avoided anyhow.The function now returns only status, not the FDT address, so I have changed it to an int. When I changed the function to no longer do the memory allocation for the new FDT this changed, but I missed changing the return type to int.quoted
quoted
+int efi_entry(void *handle, efi_system_table_t *sys_table, + unsigned long *zimage_addr) +{ + efi_loaded_image_t *image; + int status; + unsigned long nr_pages; + const struct fdt_region *region; + + void *fdt; + int err; + int node; + unsigned long zimage_size = 0; + unsigned long dram_base; + /* addr/point and size pairs for memory management*/ + u64 initrd_addr; + u64 initrd_size = 0; + u64 fdt_addr; + u64 fdt_size = 0; + u64 kernel_reserve_addr; + u64 kernel_reserve_size = 0; + char *cmdline_ptr; + unsigned long cmdline_size = 0; + + unsigned long map_size, desc_size; + unsigned long mmap_key; + efi_memory_desc_t *memory_map; + + unsigned long new_fdt_size; + unsigned long new_fdt_addr; + + efi_guid_t proto = LOADED_IMAGE_PROTOCOL_GUID; + + /* Check if we were booted by the EFI firmware */ + if (sys_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) + goto fail; + + efi_printk(sys_table, PRINTK_PREFIX"Booting Linux using EFI stub.\n"); + + + /* get the command line from EFI, using the LOADED_IMAGE protocol */ + status = efi_call_phys3(sys_table->boottime->handle_protocol, + handle, &proto, (void *)&image); + if (status != EFI_SUCCESS) { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to get handle for LOADED_IMAGE_PROTOCOL\n"); + goto fail; + } + + /* We are going to copy this into device tree, so we don't care where in + * memory it is. + */ + cmdline_ptr = convert_cmdline_to_ascii(sys_table, image, + &cmdline_size, 0xFFFFFFFF); + if (!cmdline_ptr) { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: converting command line to ascii failed.\n");The real reason for this failure is failure to allocate memory: there's no other way it can fail. So, the error message could be "Unable to allocate memory for command line"done.quoted
quoted
+ goto fail; + } + + /* We first load the device tree, as we need to get the base address of + * DRAM from the device tree. The zImage, device tree, and initrd + * have address restrictions that are relative to the base of DRAM. + */ + status = handle_cmdline_files(sys_table, image, cmdline_ptr, "dtb=", + 0xffffffff, &fdt_addr, &fdt_size); + if (status != EFI_SUCCESS) { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load device tree blob.\n"); + goto fail_free_cmdline; + } + + err = fdt_check_header((void *)(unsigned long)fdt_addr); + if (err != 0) { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: device tree header not valid\n"); + goto fail_free_fdt; + } + if (fdt_totalsize((void *)(unsigned long)fdt_addr) > fdt_size) { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: Incomplete device tree.\n"); + goto fail_free_fdt; + + } + + + /* Look up the base of DRAM from the device tree.*/ + fdt = (void *)(u32)fdt_addr; + node = fdt_subnode_offset(fdt, 0, "memory"); + region = fdt_getprop(fdt, node, "reg", NULL); + if (region) { + dram_base = fdt64_to_cpu(region->base); + } else { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: no 'memory' node in device tree.\n"); + goto fail_free_fdt; + } + + /* Reserve memory for the uncompressed kernel image. */ + kernel_reserve_addr = dram_base; + kernel_reserve_size = MAX_UNCOMP_KERNEL_SIZE; + nr_pages = round_up(kernel_reserve_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE; + status = efi_call_phys4(sys_table->boottime->allocate_pages, + EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA, + nr_pages, &kernel_reserve_addr); + if (status != EFI_SUCCESS) { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: unable to allocate memory for uncompressed kernel.\n"); + goto fail_free_fdt; + } + + /* Relocate the zImage, if required. */ + zimage_size = image->image_size; + status = relocate_kernel(sys_table, zimage_addr, zimage_size, + dram_base + MIN_ZIMAGE_OFFSET, + dram_base + ZIMAGE_OFFSET_LIMIT); + if (status != EFI_SUCCESS) { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to relocate kernel\n"); + goto fail_free_kernel_reserve; + } + + status = handle_cmdline_files(sys_table, image, cmdline_ptr, "initrd=", + dram_base + ZIMAGE_OFFSET_LIMIT, + &initrd_addr, &initrd_size); + if (status != EFI_SUCCESS) { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load initrd\n"); + goto fail_free_zimage; + } + + /* Estimate size of new FDT, and allocate memory for it. We + * will allocate a bigger buffer if this ends up being too + * small, so a rough guess is OK here.*/ + new_fdt_size = fdt_size + cmdline_size + 0x200; + +fdt_alloc_retry:Minor stylistic thing, but looping using goto is not too readable. The following while loop might be a bit clearer, but I leave it up to you. while (1) {quoted
+ status = efi_high_alloc(sys_table, new_fdt_size, 0, &new_fdt_addr, + dram_base + ZIMAGE_OFFSET_LIMIT); + if (status != EFI_SUCCESS) { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to allocate memory for new device tree.\n"); + goto fail_free_initrd; + } + + /* Now that we have done our final memory allocation (and free) + * we can get the memory map key needed + * forexit_boot_services.*/ + status = efi_get_memory_map(sys_table, &memory_map, &map_size, + &desc_size, &mmap_key); + if (status != EFI_SUCCESS) + goto fail_free_new_fdt; + + status = update_fdt(sys_table, + fdt, (void *)new_fdt_addr, new_fdt_size, + cmdline_ptr, + initrd_addr, initrd_size, + memory_map, map_size, desc_size);if (status == EFI_SUCCESS) break; instead ofquoted
+ + if (status != EFI_SUCCESS) {then if (status != EFI_BUFFER_TOO_SMALL)quoted
+ efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to constuct new device tree.\n"); + goto fail_free_initrd;instead ofquoted
+ if (status == EFI_BUFFER_TOO_SMALL) {thenquoted
+ /* We need to allocate more space for the new + * device tree, so free existing buffer that is + * too small. Also free memory map, as we will need + * to get new one that reflects the free/alloc we do + * on the device tree buffer. */ + efi_free(sys_table, new_fdt_size, new_fdt_addr); + efi_call_phys1(sys_table->boottime->free_pool, + memory_map); + new_fdt_size += new_fdt_size/4;And then just loop } instead of the rest:I have replaced the allocation retry with a while loop as suggested.quoted
quoted
+ goto fdt_alloc_retry; + } + efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to constuct new device tree.\n"); + goto fail_free_initrd; + } + + /* Now we are ready to exit_boot_services.*/ + status = efi_call_phys2(sys_table->boottime->exit_boot_services, + handle, mmap_key); + + if (status != EFI_SUCCESS) { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: exit boot services failed.\n"); + goto fail_free_mmap; + } + + + /* Now we need to return the FDT address to the calling + * assembly to this can be used as part of normal boot. + */ + return new_fdt_addr; + +fail_free_mmap: + efi_call_phys1(sys_table->boottime->free_pool, memory_map); + +fail_free_new_fdt: + efi_free(sys_table, new_fdt_size, new_fdt_addr); + +fail_free_initrd: + efi_free(sys_table, initrd_size, initrd_addr); + +fail_free_zimage: + efi_free(sys_table, zimage_size, *zimage_addr); + +fail_free_kernel_reserve: + efi_free(sys_table, kernel_reserve_addr, kernel_reserve_size); + +fail_free_fdt: + efi_free(sys_table, fdt_size, fdt_addr); + +fail_free_cmdline: + efi_free(sys_table, cmdline_size, (u32)cmdline_ptr); + +fail: + return EFI_STUB_ERROR; +}diff --git a/arch/arm/boot/compressed/efi-stub.hb/arch/arm/boot/compressed/efi-stub.h new file mode 100644 index 0000000..0fe9376--- /dev/null +++ b/arch/arm/boot/compressed/efi-stub.h@@ -0,0 +1,5 @@ +#ifndef _ARM_EFI_STUB_H +#define _ARM_EFI_STUB_H +/* Error code returned to ASM code instead of valid FDT address. */ +#define EFI_STUB_ERROR (~0) +#endifdiff --git a/arch/arm/boot/compressed/head.Sb/arch/arm/boot/compressed/head.S index 75189f1..5401a3a 100644--- a/arch/arm/boot/compressed/head.S +++ b/arch/arm/boot/compressed/head.S@@ -10,6 +10,7 @@ */ #include <linux/linkage.h> #include <asm/assembler.h> +#include "efi-stub.h" .arch armv7-a /*@@ -120,21 +121,99 @@ */ .align .arm @ Always enter in ARMstate + .text start: .type start,#function - .rept 7 +#ifdef CONFIG_EFI_STUB + @ Magic MSDOS signature for PE/COFF + ADD opcode + .word 0x62805a4d +#else + mov r0, r0 +#endif + .rept 5 mov r0, r0 .endr - ARM( mov r0, r0 ) - ARM( b 1f ) - THUMB( adr r12, BSYM(1f) ) - THUMB( bx r12 ) + + adrl r12, BSYM(zimage_continue) + ARM( mov pc, r12 ) + THUMB( bx r12 ) + @ zimage_continue will be in ARM or thumb mode as configured .word 0x016f2818 @ Magic numbers to help the loader .word start @ absolute load/run zImage address .word _edata @ zImage end address + +#ifdef CONFIG_EFI_STUB + @ Portions of the MSDOS file header must be at offset + @ 0x3c from the start of the file. All PE/COFF headers + @ are kept contiguous for simplicity. +#include "efi-header.S" + +efi_stub_entry: + @ The EFI stub entry point is not at a fixed address, however + @ this address must be set in the PE/COFF header. + @ EFI entry point is in A32 mode, switch to T32 if configured. + THUMB( adr r12, BSYM(1f) ) + THUMB( bx r12 ) THUMB( .thumb ) 1: + @ Save lr on stack for possible return to EFI firmware. + @ Don't care about fp, but need 64 bit alignment.... + stmfd sp!, {fp, lr} + + @ allocate space on stack for return of new entry point of + @ zImage, as EFI stub may copy the kernel. Pass address + @ of space in r2 - EFI stub will fill in the pointer. + + sub sp, sp, #8 @ we only need 4 bytes, + @ but keep stack 8 byte aligned. + mov r2, spYou can save an instruction here: skip these two instructions, and see below just before the call to efi_entry [1]quoted
+ @ Pass our actual runtime start address in pointer data + adr r11, LC0 @ address of LC0 at run time + ldr r12, [r11, #0] @ address of LC0 at link timeCan we just move this delta calculation after efi_entry? You don't need to specifiy an explicit offset if it's zero, btw: ldr r12, [r11] works just as well. Same for the other instances.So after looking at this it was pretty messed up. The LC0 business was to compute the linktime/runtime offset, which I had used to lookup the uncompressed kernel size. That ended up not being useful, but this code stayed around. I've reworked this section of code to remove that cruft and incorporate your suggestions. I've put an updated diff of just head.S at the end of the email.quoted
quoted
+ + sub r3, r11, r12 @ calculate the delta offset + str r3, [r2, #0]I think r2 is still equal to sp, so str r3, [sp] should be OK. [1]Or, combine this with the modification of sp: str r3, [sp, #-8]! mov r2, spquoted
+ bl efi_entry + + @ get new zImage entry address from stack, put into r3 + ldr r3, [sp, #0] + add sp, sp, #8 @ restore stackldr r3, [sp], #8quoted
+ + @ Check for error return from EFI stub + mov r1, #EFI_STUB_ERROR + cmp r0, r1cmp r0, #EFI_STUB_ERROR probably works. The assembler will turn this into a cmn as necessary.quoted
+ beq efi_load_fail + + + @ Save return values of efi_entry + stmfd sp!, {r0, r3} + bl cache_clean_flush + bl cache_off + ldmfd sp!, {r0, r3} + + @ Set parameters for booting zImage according to boot protocol + @ put FDT address in r2, it was returned by efi_entry() + @ r1 is FDT machine type, and r0 needs to be 0 + mov r2, r0 + mov r1, #0xFFFFFFFF + mov r0, #0 + + @ Branch to (possibly) relocated zImage that is in r3 + @ Make sure we are in A32 mode, as zImage requires + THUMB( bx r3 ) + ARM( mov pc, r3 ) + +efi_load_fail: + @ Return EFI_LOAD_ERROR to EFI firmware on error. + @ Switch back to ARM mode for EFI is done based on + @ return address on stack + ldr r0, =0x80000001 + ldmfd sp!, {fp, pc} +#endif + + THUMB( .thumb ) +zimage_continue: mrs r9, cpsr #ifdef CONFIG_ARM_VIRT_EXT bl __hyp_stub_install @ get into SVC mode, reversibly@@ -167,7 +246,6 @@ not_angel: * by the linker here, but it should preserve r7, r8, andr9. */ - .text #ifdef CONFIG_AUTO_ZRELADDR @ determine final kernel image address -- 1.7.10.4 _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel at lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kerneldiff --git a/arch/arm/boot/compressed/head.Sb/arch/arm/boot/compressed/head.S index 75189f1..820a238 100644--- a/arch/arm/boot/compressed/head.S +++ b/arch/arm/boot/compressed/head.S@@ -10,6 +10,7 @@ */ #include <linux/linkage.h> #include <asm/assembler.h> +#include "efi-stub.h" .arch armv7-a /*@@ -120,21 +121,92 @@ */ .align .arm @ Always enter in ARM state + .text start: .type start,#function - .rept 7 +#ifdef CONFIG_EFI_STUB + @ Magic MSDOS signature for PE/COFF + ADD opcode + @ the EFI stub only supports little endian, as the EFI functions + @ it invokes are little endian. + .word 0x62805a4d +#else + mov r0, r0 +#endif + .rept 5 mov r0, r0 .endr - ARM( mov r0, r0 ) - ARM( b 1f ) - THUMB( adr r12, BSYM(1f) ) - THUMB( bx r12 ) + + adrl r12, BSYM(zimage_continue) + ARM( mov pc, r12 ) + THUMB( bx r12 ) + @ zimage_continue will be in ARM or thumb mode as configured .word 0x016f2818 @ Magic numbers to help the loader .word start @ absolute load/run zImage address .word _edata @ zImage end address + +#ifdef CONFIG_EFI_STUB + @ Portions of the MSDOS file header must be at offset + @ 0x3c from the start of the file. All PE/COFF headers + @ are kept contiguous for simplicity. +#include "efi-header.S" + +efi_stub_entry: + @ The EFI stub entry point is not at a fixed address, however + @ this address must be set in the PE/COFF header. + @ EFI entry point is in A32 mode, switch to T32 if configured. + THUMB( adr r12, BSYM(1f) ) + THUMB( bx r12 ) THUMB( .thumb ) 1: + @ Save lr on stack for possible return to EFI firmware. + @ Don't care about fp, but need 64 bit alignment.... + stmfd sp!, {fp, lr} + + @ allocate space on stack for passing current zImage address + @ and for the EFI stub to return of new entry point of + @ zImage, as EFI stub may copy the kernel. Pointer address + @ is passed in r2. r0 and r1 are passed through from the + @ EFI firmware to efi_entry + adr r3, start + str r3, [sp, #8]!
above line should be: str r3, [sp, #-8]! that - sign is really hard to see in gmail :)
quoted hunk
+ mov r2, sp @ pass pointer in r2 + bl efi_entry + ldr r3, [sp], #8 @ get new zImage address from stack + + @ Check for error return from EFI stub. r0 has FDT address + @ or EFI_STUB_ERROR error code. + cmp r0, #EFI_STUB_ERROR + beq efi_load_fail + + @ Save return values of efi_entry + stmfd sp!, {r0, r3} + bl cache_clean_flush + bl cache_off + ldmfd sp!, {r0, r3} + + @ Set parameters for booting zImage according to boot protocol + @ put FDT address in r2, it was returned by efi_entry() + @ r1 is FDT machine type, and r0 needs to be 0 + mov r2, r0 + mov r1, #0xFFFFFFFF + mov r0, #0 + + @ Branch to (possibly) relocated zImage that is in r3 + @ Make sure we are in A32 mode, as zImage requires + THUMB( bx r3 ) + ARM( mov pc, r3 ) + +efi_load_fail: + @ Return EFI_LOAD_ERROR to EFI firmware on error. + @ Switch back to ARM mode for EFI is done based on + @ return address on stack in case we are in THUMB mode + ldr r0, =0x80000001 + ldmfd sp!, {fp, pc} @ put lr from stack into pc +#endif + + THUMB( .thumb ) +zimage_continue: mrs r9, cpsr #ifdef CONFIG_ARM_VIRT_EXT bl __hyp_stub_install @ get into SVC mode, reversibly@@ -167,7 +239,6 @@ not_angel: * by the linker here, but it should preserve r7, r8, and r9. */ - .text #ifdef CONFIG_AUTO_ZRELADDR @ determine final kernel image address