Message ID | 1375478948-22562-7-git-send-email-roy.franz@linaro.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Fri, Aug 02, 2013 at 02:29:07PM -0700, Roy Franz wrote: > 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. > > Signed-off-by: Roy Franz <roy.franz@linaro.org> > --- > arch/arm/boot/compressed/Makefile | 18 +- > arch/arm/boot/compressed/efi-header.S | 114 ++++++++ > arch/arm/boot/compressed/efi-stub.c | 514 +++++++++++++++++++++++++++++++++ > arch/arm/boot/compressed/head.S | 90 +++++- > 4 files changed, 732 insertions(+), 4 deletions(-) > create mode 100644 arch/arm/boot/compressed/efi-header.S > create mode 100644 arch/arm/boot/compressed/efi-stub.c > > diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile > index 7ac1610..c62826a 100644 > --- a/arch/arm/boot/compressed/Makefile > +++ b/arch/arm/boot/compressed/Makefile > @@ -106,8 +106,22 @@ $(addprefix $(obj)/,$(libfdt) $(libfdt_hdrs)): $(obj)/%: $(srctree)/scripts/dtc/ > $(addprefix $(obj)/,$(libfdt_objs) atags_to_fdt.o): \ > $(addprefix $(obj)/,$(libfdt_hdrs)) > > +$(addprefix $(obj)/,$(libfdt_objs) efi-stub.o): \ > + $(addprefix $(obj)/,$(libfdt_hdrs)) > + > ifeq ($(CONFIG_ARM_ATAG_DTB_COMPAT),y) > -OBJS += $(libfdt_objs) atags_to_fdt.o > +OBJS += atags_to_fdt.o > +USE_LIBFDT = y > +endif > + > +ifeq ($(CONFIG_EFI_STUB),y) > +CFLAGS_efi-stub.o += -DTEXT_OFFSET=$(TEXT_OFFSET) > +OBJS += efi-stub.o > +USE_LIBFDT = y > +endif > + > +ifeq ($(USE_LIBFDT),y) > +OBJS += $(libfdt_objs) > endif > > targets := vmlinux vmlinux.lds \ > @@ -125,7 +139,7 @@ ORIG_CFLAGS := $(KBUILD_CFLAGS) > KBUILD_CFLAGS = $(subst -pg, , $(ORIG_CFLAGS)) > endif > > -ccflags-y := -fpic -mno-single-pic-base -fno-builtin -I$(obj) > +ccflags-y := -fpic -mno-single-pic-base -fno-builtin -I$(obj) -fno-stack-protector > asflags-y := -DZIMAGE > > # Supply kernel BSS size to the decompressor via a linker symbol. > diff --git a/arch/arm/boot/compressed/efi-header.S b/arch/arm/boot/compressed/efi-header.S > new file mode 100644 > index 0000000..6ff32cc > --- /dev/null > +++ b/arch/arm/boot/compressed/efi-header.S > @@ -0,0 +1,114 @@ > +@ Copyright (C) 2013 Linaro Ltd; <roy.franz@linaro.org> > +@ > +@ This file contains the PE/COFF header that is part of the > +@ EFI stub. > +@ > + > + .org 0x3c > + @ > + @ The PE header can be anywhere in the file, but for > + @ simplicity we keep it together with the MSDOS header > + @ The offset to the PE/COFF header needs to be at offset > + @ 0x3C in the MSDOS header. > + @ The only 2 fields of the MSDOS header that are used are this > + @ PE/COFF offset, and the "MZ" bytes at offset 0x0. > + @ > + .long pe_header @ Offset to the PE header. > + > + .align 3 > +pe_header: > + > + > +pe_header: > + .ascii "PE" > + .short 0 > + > +coff_header: > + .short 0x01c2 @ ARM or Thumb > + .short 2 @ nr_sections > + .long 0 @ TimeDateStamp > + .long 0 @ PointerToSymbolTable > + .long 1 @ NumberOfSymbols > + .short section_table - optional_header @ SizeOfOptionalHeader > + .short 0x306 @ Characteristics. > + @ IMAGE_FILE_32BIT_MACHINE | > + @ IMAGE_FILE_DEBUG_STRIPPED | > + @ IMAGE_FILE_EXECUTABLE_IMAGE | > + @ IMAGE_FILE_LINE_NUMS_STRIPPED > + > +optional_header: > + .short 0x10b @ PE32 format > + .byte 0x02 @ MajorLinkerVersion > + .byte 0x14 @ MinorLinkerVersion > + > + .long 0 @ SizeOfCode > + > + .long 0 @ SizeOfInitializedData > + .long 0 @ SizeOfUninitializedData > + > + .long efi_stub_entry @ AddressOfEntryPoint > + .long efi_stub_entry @ BaseOfCode > + .long 0 @ data > + > +extra_header_fields: > + .long 0 @ ImageBase > + .long 0x20 @ SectionAlignment > + .long 0x20 @ FileAlignment > + .short 0 @ MajorOperatingSystemVersion > + .short 0 @ MinorOperatingSystemVersion > + .short 0 @ MajorImageVersion > + .short 0 @ MinorImageVersion > + .short 0 @ MajorSubsystemVersion > + .short 0 @ MinorSubsystemVersion > + .long 0 @ Win32VersionValue > + > + .long _edata @ SizeOfImage > + > + @ Everything before the entry point is considered part of the header > + .long efi_stub_entry @ SizeOfHeaders > + .long 0 @ CheckSum > + .short 0xa @ Subsystem (EFI application) > + .short 0 @ DllCharacteristics > + .long 0 @ SizeOfStackReserve > + .long 0 @ SizeOfStackCommit > + .long 0 @ SizeOfHeapReserve > + .long 0 @ SizeOfHeapCommit > + .long 0 @ LoaderFlags > + .long 0x0 @ NumberOfRvaAndSizes > + > + # Section table > +section_table: > + > + # > + # The EFI application loader requires a relocation section > + # because EFI applications must be relocatable. This is a > + # dummy section as far as we are concerned. > + # > + .ascii ".reloc" > + .byte 0 > + .byte 0 @ end of 0 padding of section name > + .long 0 > + .long 0 > + .long 0 @ SizeOfRawData > + .long 0 @ PointerToRawData > + .long 0 @ PointerToRelocations > + .long 0 @ PointerToLineNumbers > + .short 0 @ NumberOfRelocations > + .short 0 @ NumberOfLineNumbers > + .long 0x42100040 @ Characteristics (section flags) > + > + > + .ascii ".text" > + .byte 0 > + .byte 0 > + .byte 0 @ end of 0 padding of section name > + .long _edata - efi_stub_entry @ VirtualSize > + .long efi_stub_entry @ VirtualAddress > + .long _edata - efi_stub_entry @ SizeOfRawData > + .long efi_stub_entry @ PointerToRawData > + > + .long 0 @ PointerToRelocations (0 for executables) > + .long 0 @ PointerToLineNumbers (0 for executables) > + .short 0 @ NumberOfRelocations (0 for executables) > + .short 0 @ NumberOfLineNumbers (0 for executables) > + .long 0xe0500020 @ Characteristics (section flags) > diff --git a/arch/arm/boot/compressed/efi-stub.c b/arch/arm/boot/compressed/efi-stub.c > new file mode 100644 > index 0000000..41fa1e2 > --- /dev/null > +++ b/arch/arm/boot/compressed/efi-stub.c > @@ -0,0 +1,514 @@ > +/* > + * linux/arch/arm/boot/compressed/efi-stub.c > + * > + * Copyright (C) 2013 Linaro Ltd; <roy.franz@linaro.org> > + * > + * This file implements the EFI boot stub for the ARM kernel > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + */ > +#include <linux/efi.h> > +#include <libfdt.h> > + > + > +/* Error code returned to ASM code instead of valid FDT address. */ > +#define EFI_STUB_ERROR (~0) > + > +/* EFI function call wrappers. These are not required for > + * ARM, but wrappers are required for X86 to convert between > + * ABIs. These wrappers are provided to allow code sharing > + * between X86 and ARM. Since these wrappers directly invoke the > + * EFI function pointer, the function pointer type must be properly > + * defined, which is not the case for X86 One advantage of this is > + * it allows for type checking of arguments, which is not > + * possible with the X86 wrappers. > + */ > +#define efi_call_phys0(f) f() > +#define efi_call_phys1(f, a1) f(a1) > +#define efi_call_phys2(f, a1, a2) f(a1, a2) > +#define efi_call_phys3(f, a1, a2, a3) f(a1, a2, a3) > +#define efi_call_phys4(f, a1, a2, a3, a4) f(a1, a2, a3, a4) > +#define efi_call_phys5(f, a1, a2, a3, a4, a5) f(a1, a2, a3, a4, a5) > + > +/* The maximum uncompressed kernel size is 32 MBytes, so we will reserve > + * that for the decompressed kernel. We have no easy way to tell what > + * the actuall size of code + data the uncompressed kernel will use. > + */ > +#define MAX_UNCOMP_KERNEL_SIZE 0x02000000 > + > +/* The kernel zImage should be located between 32 Mbytes > + * and 128 MBytes from the base of DRAM. The min > + * address leaves space for a maximal size uncompressed image, > + * and the max address is due to how the zImage decompressor > + * picks a destination address. > + */ > +#define MAX_ZIMAGE_OFFSET 0x08000000 > +#define MIN_ZIMAGE_OFFSET MAX_UNCOMP_KERNEL_SIZE > + > +#define MAX_CMDLINE_LEN 500 > + > +struct fdt_region { > + u64 base; > + u64 size; > +}; > + > +/* > + * Additional size that could be used for FDT entries added by > + * the UEFI OS Loader Estimation based on: > + * EDID (300bytes) + bootargs + initrd region (20bytes) > + * + system memory region (20bytes) + mp_core entries (200 > + * bytes) > + */ > +#define FDT_ADDITIONAL_ENTRIES_SIZE (0x300 + MAX_CMDLINE_LEN) > + > +/* Include shared EFI stub code */ > +#include "../../../../drivers/firmware/efi/efi-stub-helper.c" > + > + > +static int is_linux_reserved_region(int memory_type) > +{ > + switch (memory_type) { > + case EfiRuntimeServicesCode: > + case EfiRuntimeServicesData: > + case EfiUnusableMemory: > + case EfiACPIReclaimMemory: > + case EfiACPIMemoryNVS: > + return 1; > + default: > + return 0; > + } > +} > + > + > +static int relocate_kernel(efi_system_table_t *sys_table, > + unsigned long *load_addr, unsigned long *load_size, > + unsigned long min_addr, unsigned long max_addr) > +{ > + /* Get current address of kernel. */ > + unsigned long cur_zimage_addr = *load_addr; > + unsigned long zimage_size = *load_size; > + unsigned long new_addr = 0; > + unsigned long nr_pages; > + > + efi_status_t status; > + > + if (!load_addr || !load_size) > + return EFI_INVALID_PARAMETER; > + > + *load_size = 0; > + if (cur_zimage_addr > min_addr > + && (cur_zimage_addr + zimage_size) < max_addr) { > + /* We don't need to do anything, as kernel at an acceptable > + * address already. > + */ > + return EFI_SUCCESS; > + } > + /* > + * The EFI firmware loader could have placed the kernel image > + * anywhere in memory, but the kernel has restrictions on the > + * min and max physical address it can run at. > + */ > + nr_pages = round_up(zimage_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE; > + > + status = efi_low_alloc(sys_table, zimage_size, 0, > + &new_addr, min_addr); > + if (status != EFI_SUCCESS) { > + efi_printk(sys_table, "Failed to alloc memory for kernel.\n"); > + return status; > + } > + > + if (new_addr > (max_addr - zimage_size)) { > + efi_free(sys_table, zimage_size, new_addr); > + efi_printk(sys_table, "Failed to alloc usable memory for kernel.\n"); > + return EFI_INVALID_PARAMETER; > + } > + > + /* We know source/dest won't overlap since both memory ranges > + * have been allocated by UEFI, so we can safely use memcpy. > + */ > + memcpy((void *)new_addr, (void *)(unsigned long)cur_zimage_addr, > + zimage_size); > + > + /* Return the load address and size */ > + *load_addr = new_addr; > + *load_size = zimage_size; > + > + > + return status; > +} > + > + > +/* Convert the unicode UEFI command line to ASCII to pass to kernel. > + * Size of memory allocated return in *cmd_line_len. > + * Returns NULL on error. > + */ > +static char *convert_cmdline_to_ascii(efi_system_table_t *sys_table, > + efi_loaded_image_t *image, > + unsigned long *cmd_line_len, > + u32 max_addr) > +{ > + u16 *s2; > + u8 *s1 = NULL; > + unsigned long cmdline_addr = 0; > + int load_options_size = image->load_options_size / 2; /* ASCII */ > + void *options = (u16 *)image->load_options; > + int options_size = 0; > + int status; > + int i; > + u16 zero = 0; > + > + if (options) { > + s2 = options; > + while (*s2 && *s2 != '\n' && options_size < load_options_size) { > + s2++; > + options_size++; > + } > + } > + > + if (options_size == 0) { > + /* No command line options, so return empty string*/ > + options_size = 1; > + options = &zero; > + } > + > + if (options_size > MAX_CMDLINE_LEN) > + options_size = MAX_CMDLINE_LEN; > + > + options_size++; /* NUL termination */ > + > + status = efi_high_alloc(sys_table, options_size, 0, > + &cmdline_addr, max_addr); > + if (status != EFI_SUCCESS) > + return NULL; > + > + s1 = (u8 *)(unsigned long)cmdline_addr; > + s2 = (u16 *)options; > + > + for (i = 0; i < options_size - 1; i++) > + *s1++ = *s2++; > + > + *s1 = '\0'; > + > + *cmd_line_len = options_size; > + return (char *)(unsigned long)cmdline_addr; > +} > + > +static u32 update_fdt_and_exit_boot(efi_system_table_t *sys_table, > + void *handle, unsigned long dram_base, > + void *orig_fdt, u64 *orig_fdt_size, > + char *cmdline_ptr, > + unsigned long *cmdline_size, > + u64 initrd_addr, u64 initrd_size) > +{ > + unsigned long new_fdt_size; > + unsigned long new_fdt_addr; > + void *fdt; > + int node; > + int status; > + int i; > + unsigned long map_size, desc_size; > + unsigned long mmap_key; > + efi_memory_desc_t *memory_map; > + unsigned long fdt_val; > + > + new_fdt_size = *orig_fdt_size + FDT_ADDITIONAL_ENTRIES_SIZE; > + status = efi_high_alloc(sys_table, new_fdt_size, 0, &new_fdt_addr, > + dram_base + MAX_ZIMAGE_OFFSET); > + if (status != EFI_SUCCESS) { > + efi_printk(sys_table, "ERROR: Unable to allocate memory for new device tree.\n"); > + goto fail; > + } > + > + > + fdt = (void *)new_fdt_addr; > + status = fdt_open_into(orig_fdt, fdt, new_fdt_size); > + if (status != 0) { > + efi_printk(sys_table, "ERROR: Device Tree open_int failed.\n"); > + goto fail_free_new_fdt; > + } > + /* We are done with the original DTB, so free it. */ > + efi_free(sys_table, *orig_fdt_size, (u32)orig_fdt); > + *orig_fdt_size = 0; > + > + node = fdt_subnode_offset(fdt, 0, "chosen"); > + if (node < 0) { > + node = fdt_add_subnode(fdt, 0, "chosen"); > + if (node < 0) { > + efi_printk(sys_table, "Error on finding 'chosen' node\n"); > + goto fail_free_new_fdt; > + } > + } > + > + if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) { > + status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr, > + strlen(cmdline_ptr) + 1); > + if (status) { > + efi_printk(sys_table, "Failed to set new bootarg\n"); > + goto fail_free_new_fdt; > + } > + } > + /* We are done with original command line, so free it. */ > + efi_free(sys_table, *cmdline_size, (u32)cmdline_ptr); > + *cmdline_size = 0; > + > + /* Set intird address/end in device tree, if present */ > + if (initrd_size != 0) { > + u64 initrd_image_end; > + u64 initrd_image_start = cpu_to_fdt64(initrd_addr); > + status = fdt_setprop(fdt, node, "linux,initrd-start", > + &initrd_image_start, sizeof(u64)); > + if (status) { > + efi_printk(sys_table, "Failed to set new 'linux,initrd-start'\n"); > + goto fail_free_new_fdt; > + } > + initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size); > + status = fdt_setprop(fdt, node, "linux,initrd-end", > + &initrd_image_end, sizeof(u64)); > + if (status) { > + efi_printk(sys_table, "Failed to set new 'linux,initrd-end'\n"); > + goto fail_free_new_fdt; > + } > + } > + > + /* Update memory map in the device tree. The memory node must > + * be present in the tree.*/ > + node = fdt_subnode_offset(fdt, 0, "memory"); > + if (node < 0) { > + efi_printk(sys_table, "ERROR: FDT memory node does not exist in DTB.\n"); > + goto fail_free_new_fdt; > + } > + > + status = efi_get_memory_map(sys_table, &memory_map, &map_size, > + &desc_size, &mmap_key); > + if (status != EFI_SUCCESS) > + goto fail_free_new_fdt; > + > + for (i = 0; i < (map_size / sizeof(efi_memory_desc_t)); i++) { > + efi_memory_desc_t *desc; > + unsigned long m = (unsigned long)memory_map; > + desc = (efi_memory_desc_t *)(m + (i * desc_size)); > + > + if (is_linux_reserved_region(desc->type)) { > + status = fdt_add_mem_rsv(fdt, desc->phys_addr, > + desc->num_pages * EFI_PAGE_SIZE); > + if (status != 0) { > + efi_printk(sys_table, "ERROR: Failed to add 'memreserve' to fdt.\n"); > + goto fail_free_mmap; > + } > + } > + } > + > + > + /* Add FDT entries for EFI runtime services in chosen node. > + * We need to add the final memory map, so this is done at > + * the very end. > + */ > + node = fdt_subnode_offset(fdt, 0, "chosen"); > + fdt_val = cpu_to_fdt32((unsigned long)sys_table); > + status = fdt_setprop(fdt, node, "efi-system-table", > + &fdt_val, sizeof(fdt_val)); > + if (status) { > + efi_printk(sys_table, "Failed to set new 'efi-system-table'\n"); > + goto fail_free_new_fdt; > + } > + fdt_val = cpu_to_fdt32(desc_size); > + status = fdt_setprop(fdt, node, "efi-mmap-desc-size", > + &fdt_val, sizeof(fdt_val)); > + if (status) { > + efi_printk(sys_table, "Failed to set new 'efi-mmap-desc-size'\n"); > + goto fail_free_new_fdt; > + } > + fdt_val = cpu_to_fdt32(map_size); > + status = fdt_setprop(fdt, node, "efi-runtime-mmap-size", > + &fdt_val, sizeof(fdt_val)); > + if (status) { > + efi_printk(sys_table, "Failed to set new 'efi-runtime-mmap-size'\n"); > + goto fail_free_new_fdt; > + } > + fdt_val = cpu_to_fdt32((unsigned long)memory_map); > + status = fdt_setprop(fdt, node, "efi-runtime-mmap", > + &fdt_val, sizeof(fdt_val)); > + if (status) { > + efi_printk(sys_table, "Failed to set new 'efi-runtime-mmap'\n"); > + goto fail_free_new_fdt; > + } > + > + /* Now we need to exit boot services. We need the key from > + * the most recent read of the memory map to do this. We can't > + * free this buffer in the normal case, but do free it when > + * exit_boot_services() fails or adding the memory map to the FDT > + * fails. > + */ > + status = efi_call_phys2(sys_table->boottime->exit_boot_services, > + handle, mmap_key); > + > + if (status != EFI_SUCCESS) { > + efi_printk(sys_table, "exit boot services failed.\n"); > + goto fail_free_mmap; > + } > + > + 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: > + return 0; > +} > + > + > +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; /* Original DTB */ > + u64 fdt_size = 0; > + u64 kernel_reserve_addr; > + u64 kernel_reserve_size = 0; > + char *cmdline_ptr; > + unsigned long cmdline_size = 0; > + 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, "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, "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, "ERROR converting command line to ascii.\n"); > + 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, "Error loading dtb blob\n"); > + goto fail_free_cmdline; > + } > + > + err = fdt_check_header((void *)(unsigned long)fdt_addr); > + if (err != 0) { > + efi_printk(sys_table, "ERROR: Device Tree header not valid\n"); > + goto fail_free_dtb; > + } > + if (fdt_totalsize((void *)(unsigned long)fdt_addr) > fdt_size) { > + efi_printk(sys_table, "ERROR: Incomplete device tree.\n"); > + goto fail_free_dtb; > + > + } > + > + > + /* 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, "Error: no 'memory' node in device tree.\n"); > + goto fail_free_dtb; > + } > + > + /* 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, "ERROR allocating memory for uncompressed kernel.\n"); > + goto fail_free_dtb; > + } > + > + /* 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 + MAX_ZIMAGE_OFFSET); > + if (status != EFI_SUCCESS) { > + efi_printk(sys_table, "Failed to relocate kernel\n"); > + goto fail_free_kernel_reserve; > + } > + > + status = handle_cmdline_files(sys_table, image, cmdline_ptr, "initrd=", > + dram_base + MAX_ZIMAGE_OFFSET, > + &initrd_addr, &initrd_size); > + if (status != EFI_SUCCESS) { > + efi_printk(sys_table, "Error loading initrd\n"); > + goto fail_free_zimage; > + } > + > + new_fdt_addr = update_fdt_and_exit_boot(sys_table, handle, > + dram_base, fdt, &fdt_size, > + cmdline_ptr, &cmdline_size, > + initrd_addr, initrd_size); > + > + if (new_fdt_addr == 0) { > + efi_printk(sys_table, "Error updating device tree and exiting boot services.\n"); > + goto fail_free_initrd; > + } > + > + > + /* 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_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_dtb: > + 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/head.S b/arch/arm/boot/compressed/head.S > index 75189f1..4c70b9e 100644 > --- a/arch/arm/boot/compressed/head.S > +++ b/arch/arm/boot/compressed/head.S > @@ -122,19 +122,106 @@ > .arm @ Always enter in ARM state > start: > .type start,#function > - .rept 7 > +#ifdef CONFIG_EFI_STUB > + @ Magic MSDOS signature for PE/COFF + ADD opcode > + .word 0x62805a4d What about BE32? In that case, the instruction is a coprocessor load, that loads from a random address to a coprocessor that almost certainly doesn't exist. This will probably fault. Since BE32 is only for older platforms (<v6) and this is not easily solvable, it might be sensible to make the EFI stub support depend on !CPU_ENDIAN_BE32. > +#else > + mov r0, r0 > +#endif > + .rept 5 You reduced the .rept count by 2, but only inserted one extra word, perhaps because of the extra, but buggy, insertion below. > mov r0, r0 > .endr > ARM( mov r0, r0 ) > ARM( b 1f ) > THUMB( adr r12, BSYM(1f) ) > THUMB( bx r12 ) Can't you just replace 1f with zimage_continue directly in the above lines, instead of the subsequent extra branch: > + THUMB( .thumb ) > +1: > + b zimage_continue This also avoids having two labels both called '1'. I believe the magic word is expected to be in a predictable offset, but the size of the preceding branch is unpredictable in Thumb (you could use b.w, or possibly remove the branch altogether, as explained above). > .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: > + .text > + @ 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. > + .arm > + ARM( mov r0, r0 ) > + ARM( b 1f ) Those above two instructions are effectively just no-op padding. Do you need them at all? > + 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} > + > + @ Save args to EFI app across got fixup call > + stmfd sp!, {r0, r1} > + ldmfd sp!, {r0, r1} > + > + @ 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, #8 @ we only need 4 bytes, > + @ but keep stack 8 byte aligned. > + mov r2, sp > + @ 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 time > + > + sub r3, r11, r12 @ calculate the delta offset > + str r3, [r2, #0] > + bl efi_entry > + > + @ get new zImage entry address from stack, put into r3 > + ldr r3, [sp, #0] > + add sp, #8 @ restore stack > + > + @ Check for error return from EFI stub (0xFFFFFFFF) > + ldr r1, =0xffffffff > + cmp r0, r1 > + 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} > + > + @ put DTB address in r2, it was returned by EFI entry > + mov r2, r0 > + ldr r1, =0xffffffff @ DTB machine type > + mov r0, #0 @ r0 is 0 > + > + @ Branch to (possibly) relocated zImage entry that is in r3 > + bx r3 > + > +efi_load_fail: > + @ We need to exit THUMB mode here, to return to EFI firmware. If you lose these 4 lines: > + THUMB( adr r12, BSYM(1f) ) > + THUMB( bx r12 ) > +1: > + .arm ... > + @ Return EFI_LOAD_ERROR to EFI firmware on error. > + ldr r0, =0x80000001 and replace these > + ldmfd sp!, {fp, lr} > + mov pc, lr with: ldmfd sp!, {fp, pc} then the Thumb-ness on return will be determined by whatever is loaded into pc. There's no special need to switch back to ARM before the final return, provided that "mov pc,<Rm>" is not used (that will never switch from Thumb to ARM). Cheers ---Dave > + THUMB( .thumb ) Then you can also remove the above line. > +#endif > + > +zimage_continue: > mrs r9, cpsr > #ifdef CONFIG_ARM_VIRT_EXT > bl __hyp_stub_install @ get into SVC mode, reversibly > @@ -167,7 +254,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 > -- > 1.7.10.4 > > > _______________________________________________ > linux-arm-kernel mailing list > linux-arm-kernel@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
On Mon, Aug 05, 2013 at 03:11:49PM +0100, Dave Martin wrote: > > diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S > > index 75189f1..4c70b9e 100644 > > --- a/arch/arm/boot/compressed/head.S > > +++ b/arch/arm/boot/compressed/head.S > > @@ -122,19 +122,106 @@ > > .arm @ Always enter in ARM state > > start: > > .type start,#function > > - .rept 7 > > +#ifdef CONFIG_EFI_STUB > > + @ Magic MSDOS signature for PE/COFF + ADD opcode > > + .word 0x62805a4d > > What about BE32? The ARM bindings for UEFI specify that the processor must be in little-endian mode. > In that case, the instruction is a coprocessor load, that loads from a > random address to a coprocessor that almost certainly doesn't exist. > This will probably fault. > > Since BE32 is only for older platforms (<v6) and this is not easily > solvable, it might be sensible to make the EFI stub support depend on > !CPU_ENDIAN_BE32. Well, it would make more sense to make EFI_STUB depend on EFI and EFI depend on !CPU_ENDIAN_BE32. Which is something I can add to my next set of general ARM UEFI patches. Thanks. / Leif
On Mon, Aug 5, 2013 at 8:33 AM, Leif Lindholm <leif.lindholm@linaro.org> wrote: > On Mon, Aug 05, 2013 at 03:11:49PM +0100, Dave Martin wrote: >> > diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S >> > index 75189f1..4c70b9e 100644 >> > --- a/arch/arm/boot/compressed/head.S >> > +++ b/arch/arm/boot/compressed/head.S >> > @@ -122,19 +122,106 @@ >> > .arm @ Always enter in ARM state >> > start: >> > .type start,#function >> > - .rept 7 >> > +#ifdef CONFIG_EFI_STUB >> > + @ Magic MSDOS signature for PE/COFF + ADD opcode >> > + .word 0x62805a4d >> >> What about BE32? > > The ARM bindings for UEFI specify that the processor must be in > little-endian mode. > >> In that case, the instruction is a coprocessor load, that loads from a >> random address to a coprocessor that almost certainly doesn't exist. >> This will probably fault. >> >> Since BE32 is only for older platforms (<v6) and this is not easily >> solvable, it might be sensible to make the EFI stub support depend on >> !CPU_ENDIAN_BE32. > > Well, it would make more sense to make EFI_STUB depend on EFI and > EFI depend on !CPU_ENDIAN_BE32. Which is something I can add to > my next set of general ARM UEFI patches. Thanks. > / > Leif I had EFI_STUB depend on EFI at one point during my development, but took it out because there was no actual dependency (the stub will work fine without other EFI features.) The features will most likely be used together, but I wasn't sure if we would want to enforce this with a config dependency. I don't care one way or the other, I'd just like the dependencies to be correct and follow best practices. Thanks, Roy
On Mon, Aug 05, 2013 at 04:33:19PM +0100, Leif Lindholm wrote: > On Mon, Aug 05, 2013 at 03:11:49PM +0100, Dave Martin wrote: > > > diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S > > > index 75189f1..4c70b9e 100644 > > > --- a/arch/arm/boot/compressed/head.S > > > +++ b/arch/arm/boot/compressed/head.S > > > @@ -122,19 +122,106 @@ > > > .arm @ Always enter in ARM state > > > start: > > > .type start,#function > > > - .rept 7 > > > +#ifdef CONFIG_EFI_STUB > > > + @ Magic MSDOS signature for PE/COFF + ADD opcode > > > + .word 0x62805a4d > > > > What about BE32? > > The ARM bindings for UEFI specify that the processor must be in > little-endian mode. > > > In that case, the instruction is a coprocessor load, that loads from a > > random address to a coprocessor that almost certainly doesn't exist. > > This will probably fault. > > > > Since BE32 is only for older platforms (<v6) and this is not easily > > solvable, it might be sensible to make the EFI stub support depend on > > !CPU_ENDIAN_BE32. > > Well, it would make more sense to make EFI_STUB depend on EFI and > EFI depend on !CPU_ENDIAN_BE32. Which is something I can add to > my next set of general ARM UEFI patches. Thanks. That sounds fair enough. Cheers ---Dave
On Tue, Aug 06, 2013 at 01:06:17AM +0100, Roy Franz wrote: > On Mon, Aug 5, 2013 at 8:33 AM, Leif Lindholm <leif.lindholm@linaro.org> wrote: > > On Mon, Aug 05, 2013 at 03:11:49PM +0100, Dave Martin wrote: > >> > diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S > >> > index 75189f1..4c70b9e 100644 > >> > --- a/arch/arm/boot/compressed/head.S > >> > +++ b/arch/arm/boot/compressed/head.S > >> > @@ -122,19 +122,106 @@ > >> > .arm @ Always enter in ARM state > >> > start: > >> > .type start,#function > >> > - .rept 7 > >> > +#ifdef CONFIG_EFI_STUB > >> > + @ Magic MSDOS signature for PE/COFF + ADD opcode > >> > + .word 0x62805a4d Is BE8 supported? If so, this would put the bytes 62 80 5A 4D in the binary, which is not the right magic. For this magic, you could use .byte instead. To help future maintainers, I suggest noting in a comment that executing through this magic relies on little-endian instruction byte order (so, LE or BE8), and the ARM instruction set. What about the endianness of the other PE/COFF header fields? Are they always little-endian, or are some fields native-endian (and if so, how is the endianness of the header determined by the loader)? > >> > >> What about BE32? > > > > The ARM bindings for UEFI specify that the processor must be in > > little-endian mode. > > > >> In that case, the instruction is a coprocessor load, that loads from a > >> random address to a coprocessor that almost certainly doesn't exist. > >> This will probably fault. > >> > >> Since BE32 is only for older platforms (<v6) and this is not easily > >> solvable, it might be sensible to make the EFI stub support depend on > >> !CPU_ENDIAN_BE32. > > > > Well, it would make more sense to make EFI_STUB depend on EFI and > > EFI depend on !CPU_ENDIAN_BE32. Which is something I can add to > > my next set of general ARM UEFI patches. Thanks. > > / > > Leif > > I had EFI_STUB depend on EFI at one point during my development, but took > it out because there was no actual dependency (the stub will work fine without > other EFI features.) The features will most likely be used together, > but I wasn't > sure if we would want to enforce this with a config dependency. I > don't care one > way or the other, I'd just like the dependencies to be correct and > follow best practices. I guess it's up to you, so long as the constraint is expressed in Kconfig somehow. Cheers ---Dave
diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile index 7ac1610..c62826a 100644 --- a/arch/arm/boot/compressed/Makefile +++ b/arch/arm/boot/compressed/Makefile @@ -106,8 +106,22 @@ $(addprefix $(obj)/,$(libfdt) $(libfdt_hdrs)): $(obj)/%: $(srctree)/scripts/dtc/ $(addprefix $(obj)/,$(libfdt_objs) atags_to_fdt.o): \ $(addprefix $(obj)/,$(libfdt_hdrs)) +$(addprefix $(obj)/,$(libfdt_objs) efi-stub.o): \ + $(addprefix $(obj)/,$(libfdt_hdrs)) + ifeq ($(CONFIG_ARM_ATAG_DTB_COMPAT),y) -OBJS += $(libfdt_objs) atags_to_fdt.o +OBJS += atags_to_fdt.o +USE_LIBFDT = y +endif + +ifeq ($(CONFIG_EFI_STUB),y) +CFLAGS_efi-stub.o += -DTEXT_OFFSET=$(TEXT_OFFSET) +OBJS += efi-stub.o +USE_LIBFDT = y +endif + +ifeq ($(USE_LIBFDT),y) +OBJS += $(libfdt_objs) endif targets := vmlinux vmlinux.lds \ @@ -125,7 +139,7 @@ ORIG_CFLAGS := $(KBUILD_CFLAGS) KBUILD_CFLAGS = $(subst -pg, , $(ORIG_CFLAGS)) endif -ccflags-y := -fpic -mno-single-pic-base -fno-builtin -I$(obj) +ccflags-y := -fpic -mno-single-pic-base -fno-builtin -I$(obj) -fno-stack-protector asflags-y := -DZIMAGE # Supply kernel BSS size to the decompressor via a linker symbol. diff --git a/arch/arm/boot/compressed/efi-header.S b/arch/arm/boot/compressed/efi-header.S new file mode 100644 index 0000000..6ff32cc --- /dev/null +++ b/arch/arm/boot/compressed/efi-header.S @@ -0,0 +1,114 @@ +@ Copyright (C) 2013 Linaro Ltd; <roy.franz@linaro.org> +@ +@ This file contains the PE/COFF header that is part of the +@ EFI stub. +@ + + .org 0x3c + @ + @ The PE header can be anywhere in the file, but for + @ simplicity we keep it together with the MSDOS header + @ The offset to the PE/COFF header needs to be at offset + @ 0x3C in the MSDOS header. + @ The only 2 fields of the MSDOS header that are used are this + @ PE/COFF offset, and the "MZ" bytes at offset 0x0. + @ + .long pe_header @ Offset to the PE header. + + .align 3 +pe_header: + + +pe_header: + .ascii "PE" + .short 0 + +coff_header: + .short 0x01c2 @ ARM or Thumb + .short 2 @ nr_sections + .long 0 @ TimeDateStamp + .long 0 @ PointerToSymbolTable + .long 1 @ NumberOfSymbols + .short section_table - optional_header @ SizeOfOptionalHeader + .short 0x306 @ Characteristics. + @ IMAGE_FILE_32BIT_MACHINE | + @ IMAGE_FILE_DEBUG_STRIPPED | + @ IMAGE_FILE_EXECUTABLE_IMAGE | + @ IMAGE_FILE_LINE_NUMS_STRIPPED + +optional_header: + .short 0x10b @ PE32 format + .byte 0x02 @ MajorLinkerVersion + .byte 0x14 @ MinorLinkerVersion + + .long 0 @ SizeOfCode + + .long 0 @ SizeOfInitializedData + .long 0 @ SizeOfUninitializedData + + .long efi_stub_entry @ AddressOfEntryPoint + .long efi_stub_entry @ BaseOfCode + .long 0 @ data + +extra_header_fields: + .long 0 @ ImageBase + .long 0x20 @ SectionAlignment + .long 0x20 @ FileAlignment + .short 0 @ MajorOperatingSystemVersion + .short 0 @ MinorOperatingSystemVersion + .short 0 @ MajorImageVersion + .short 0 @ MinorImageVersion + .short 0 @ MajorSubsystemVersion + .short 0 @ MinorSubsystemVersion + .long 0 @ Win32VersionValue + + .long _edata @ SizeOfImage + + @ Everything before the entry point is considered part of the header + .long efi_stub_entry @ SizeOfHeaders + .long 0 @ CheckSum + .short 0xa @ Subsystem (EFI application) + .short 0 @ DllCharacteristics + .long 0 @ SizeOfStackReserve + .long 0 @ SizeOfStackCommit + .long 0 @ SizeOfHeapReserve + .long 0 @ SizeOfHeapCommit + .long 0 @ LoaderFlags + .long 0x0 @ NumberOfRvaAndSizes + + # Section table +section_table: + + # + # The EFI application loader requires a relocation section + # because EFI applications must be relocatable. This is a + # dummy section as far as we are concerned. + # + .ascii ".reloc" + .byte 0 + .byte 0 @ end of 0 padding of section name + .long 0 + .long 0 + .long 0 @ SizeOfRawData + .long 0 @ PointerToRawData + .long 0 @ PointerToRelocations + .long 0 @ PointerToLineNumbers + .short 0 @ NumberOfRelocations + .short 0 @ NumberOfLineNumbers + .long 0x42100040 @ Characteristics (section flags) + + + .ascii ".text" + .byte 0 + .byte 0 + .byte 0 @ end of 0 padding of section name + .long _edata - efi_stub_entry @ VirtualSize + .long efi_stub_entry @ VirtualAddress + .long _edata - efi_stub_entry @ SizeOfRawData + .long efi_stub_entry @ PointerToRawData + + .long 0 @ PointerToRelocations (0 for executables) + .long 0 @ PointerToLineNumbers (0 for executables) + .short 0 @ NumberOfRelocations (0 for executables) + .short 0 @ NumberOfLineNumbers (0 for executables) + .long 0xe0500020 @ Characteristics (section flags) diff --git a/arch/arm/boot/compressed/efi-stub.c b/arch/arm/boot/compressed/efi-stub.c new file mode 100644 index 0000000..41fa1e2 --- /dev/null +++ b/arch/arm/boot/compressed/efi-stub.c @@ -0,0 +1,514 @@ +/* + * linux/arch/arm/boot/compressed/efi-stub.c + * + * Copyright (C) 2013 Linaro Ltd; <roy.franz@linaro.org> + * + * This file implements the EFI boot stub for the ARM kernel + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/efi.h> +#include <libfdt.h> + + +/* Error code returned to ASM code instead of valid FDT address. */ +#define EFI_STUB_ERROR (~0) + +/* EFI function call wrappers. These are not required for + * ARM, but wrappers are required for X86 to convert between + * ABIs. These wrappers are provided to allow code sharing + * between X86 and ARM. Since these wrappers directly invoke the + * EFI function pointer, the function pointer type must be properly + * defined, which is not the case for X86 One advantage of this is + * it allows for type checking of arguments, which is not + * possible with the X86 wrappers. + */ +#define efi_call_phys0(f) f() +#define efi_call_phys1(f, a1) f(a1) +#define efi_call_phys2(f, a1, a2) f(a1, a2) +#define efi_call_phys3(f, a1, a2, a3) f(a1, a2, a3) +#define efi_call_phys4(f, a1, a2, a3, a4) f(a1, a2, a3, a4) +#define efi_call_phys5(f, a1, a2, a3, a4, a5) f(a1, a2, a3, a4, a5) + +/* The maximum uncompressed kernel size is 32 MBytes, so we will reserve + * that for the decompressed kernel. We have no easy way to tell what + * the actuall size of code + data the uncompressed kernel will use. + */ +#define MAX_UNCOMP_KERNEL_SIZE 0x02000000 + +/* The kernel zImage should be located between 32 Mbytes + * and 128 MBytes from the base of DRAM. The min + * address leaves space for a maximal size uncompressed image, + * and the max address is due to how the zImage decompressor + * picks a destination address. + */ +#define MAX_ZIMAGE_OFFSET 0x08000000 +#define MIN_ZIMAGE_OFFSET MAX_UNCOMP_KERNEL_SIZE + +#define MAX_CMDLINE_LEN 500 + +struct fdt_region { + u64 base; + u64 size; +}; + +/* + * Additional size that could be used for FDT entries added by + * the UEFI OS Loader Estimation based on: + * EDID (300bytes) + bootargs + initrd region (20bytes) + * + system memory region (20bytes) + mp_core entries (200 + * bytes) + */ +#define FDT_ADDITIONAL_ENTRIES_SIZE (0x300 + MAX_CMDLINE_LEN) + +/* Include shared EFI stub code */ +#include "../../../../drivers/firmware/efi/efi-stub-helper.c" + + +static int is_linux_reserved_region(int memory_type) +{ + switch (memory_type) { + case EfiRuntimeServicesCode: + case EfiRuntimeServicesData: + case EfiUnusableMemory: + case EfiACPIReclaimMemory: + case EfiACPIMemoryNVS: + return 1; + default: + return 0; + } +} + + +static int relocate_kernel(efi_system_table_t *sys_table, + unsigned long *load_addr, unsigned long *load_size, + unsigned long min_addr, unsigned long max_addr) +{ + /* Get current address of kernel. */ + unsigned long cur_zimage_addr = *load_addr; + unsigned long zimage_size = *load_size; + unsigned long new_addr = 0; + unsigned long nr_pages; + + efi_status_t status; + + if (!load_addr || !load_size) + return EFI_INVALID_PARAMETER; + + *load_size = 0; + if (cur_zimage_addr > min_addr + && (cur_zimage_addr + zimage_size) < max_addr) { + /* We don't need to do anything, as kernel at an acceptable + * address already. + */ + return EFI_SUCCESS; + } + /* + * The EFI firmware loader could have placed the kernel image + * anywhere in memory, but the kernel has restrictions on the + * min and max physical address it can run at. + */ + nr_pages = round_up(zimage_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE; + + status = efi_low_alloc(sys_table, zimage_size, 0, + &new_addr, min_addr); + if (status != EFI_SUCCESS) { + efi_printk(sys_table, "Failed to alloc memory for kernel.\n"); + return status; + } + + if (new_addr > (max_addr - zimage_size)) { + efi_free(sys_table, zimage_size, new_addr); + efi_printk(sys_table, "Failed to alloc usable memory for kernel.\n"); + return EFI_INVALID_PARAMETER; + } + + /* We know source/dest won't overlap since both memory ranges + * have been allocated by UEFI, so we can safely use memcpy. + */ + memcpy((void *)new_addr, (void *)(unsigned long)cur_zimage_addr, + zimage_size); + + /* Return the load address and size */ + *load_addr = new_addr; + *load_size = zimage_size; + + + return status; +} + + +/* Convert the unicode UEFI command line to ASCII to pass to kernel. + * Size of memory allocated return in *cmd_line_len. + * Returns NULL on error. + */ +static char *convert_cmdline_to_ascii(efi_system_table_t *sys_table, + efi_loaded_image_t *image, + unsigned long *cmd_line_len, + u32 max_addr) +{ + u16 *s2; + u8 *s1 = NULL; + unsigned long cmdline_addr = 0; + int load_options_size = image->load_options_size / 2; /* ASCII */ + void *options = (u16 *)image->load_options; + int options_size = 0; + int status; + int i; + u16 zero = 0; + + if (options) { + s2 = options; + while (*s2 && *s2 != '\n' && options_size < load_options_size) { + s2++; + options_size++; + } + } + + if (options_size == 0) { + /* No command line options, so return empty string*/ + options_size = 1; + options = &zero; + } + + if (options_size > MAX_CMDLINE_LEN) + options_size = MAX_CMDLINE_LEN; + + options_size++; /* NUL termination */ + + status = efi_high_alloc(sys_table, options_size, 0, + &cmdline_addr, max_addr); + if (status != EFI_SUCCESS) + return NULL; + + s1 = (u8 *)(unsigned long)cmdline_addr; + s2 = (u16 *)options; + + for (i = 0; i < options_size - 1; i++) + *s1++ = *s2++; + + *s1 = '\0'; + + *cmd_line_len = options_size; + return (char *)(unsigned long)cmdline_addr; +} + +static u32 update_fdt_and_exit_boot(efi_system_table_t *sys_table, + void *handle, unsigned long dram_base, + void *orig_fdt, u64 *orig_fdt_size, + char *cmdline_ptr, + unsigned long *cmdline_size, + u64 initrd_addr, u64 initrd_size) +{ + unsigned long new_fdt_size; + unsigned long new_fdt_addr; + void *fdt; + int node; + int status; + int i; + unsigned long map_size, desc_size; + unsigned long mmap_key; + efi_memory_desc_t *memory_map; + unsigned long fdt_val; + + new_fdt_size = *orig_fdt_size + FDT_ADDITIONAL_ENTRIES_SIZE; + status = efi_high_alloc(sys_table, new_fdt_size, 0, &new_fdt_addr, + dram_base + MAX_ZIMAGE_OFFSET); + if (status != EFI_SUCCESS) { + efi_printk(sys_table, "ERROR: Unable to allocate memory for new device tree.\n"); + goto fail; + } + + + fdt = (void *)new_fdt_addr; + status = fdt_open_into(orig_fdt, fdt, new_fdt_size); + if (status != 0) { + efi_printk(sys_table, "ERROR: Device Tree open_int failed.\n"); + goto fail_free_new_fdt; + } + /* We are done with the original DTB, so free it. */ + efi_free(sys_table, *orig_fdt_size, (u32)orig_fdt); + *orig_fdt_size = 0; + + node = fdt_subnode_offset(fdt, 0, "chosen"); + if (node < 0) { + node = fdt_add_subnode(fdt, 0, "chosen"); + if (node < 0) { + efi_printk(sys_table, "Error on finding 'chosen' node\n"); + goto fail_free_new_fdt; + } + } + + if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) { + status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr, + strlen(cmdline_ptr) + 1); + if (status) { + efi_printk(sys_table, "Failed to set new bootarg\n"); + goto fail_free_new_fdt; + } + } + /* We are done with original command line, so free it. */ + efi_free(sys_table, *cmdline_size, (u32)cmdline_ptr); + *cmdline_size = 0; + + /* Set intird address/end in device tree, if present */ + if (initrd_size != 0) { + u64 initrd_image_end; + u64 initrd_image_start = cpu_to_fdt64(initrd_addr); + status = fdt_setprop(fdt, node, "linux,initrd-start", + &initrd_image_start, sizeof(u64)); + if (status) { + efi_printk(sys_table, "Failed to set new 'linux,initrd-start'\n"); + goto fail_free_new_fdt; + } + initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size); + status = fdt_setprop(fdt, node, "linux,initrd-end", + &initrd_image_end, sizeof(u64)); + if (status) { + efi_printk(sys_table, "Failed to set new 'linux,initrd-end'\n"); + goto fail_free_new_fdt; + } + } + + /* Update memory map in the device tree. The memory node must + * be present in the tree.*/ + node = fdt_subnode_offset(fdt, 0, "memory"); + if (node < 0) { + efi_printk(sys_table, "ERROR: FDT memory node does not exist in DTB.\n"); + goto fail_free_new_fdt; + } + + status = efi_get_memory_map(sys_table, &memory_map, &map_size, + &desc_size, &mmap_key); + if (status != EFI_SUCCESS) + goto fail_free_new_fdt; + + for (i = 0; i < (map_size / sizeof(efi_memory_desc_t)); i++) { + efi_memory_desc_t *desc; + unsigned long m = (unsigned long)memory_map; + desc = (efi_memory_desc_t *)(m + (i * desc_size)); + + if (is_linux_reserved_region(desc->type)) { + status = fdt_add_mem_rsv(fdt, desc->phys_addr, + desc->num_pages * EFI_PAGE_SIZE); + if (status != 0) { + efi_printk(sys_table, "ERROR: Failed to add 'memreserve' to fdt.\n"); + goto fail_free_mmap; + } + } + } + + + /* Add FDT entries for EFI runtime services in chosen node. + * We need to add the final memory map, so this is done at + * the very end. + */ + node = fdt_subnode_offset(fdt, 0, "chosen"); + fdt_val = cpu_to_fdt32((unsigned long)sys_table); + status = fdt_setprop(fdt, node, "efi-system-table", + &fdt_val, sizeof(fdt_val)); + if (status) { + efi_printk(sys_table, "Failed to set new 'efi-system-table'\n"); + goto fail_free_new_fdt; + } + fdt_val = cpu_to_fdt32(desc_size); + status = fdt_setprop(fdt, node, "efi-mmap-desc-size", + &fdt_val, sizeof(fdt_val)); + if (status) { + efi_printk(sys_table, "Failed to set new 'efi-mmap-desc-size'\n"); + goto fail_free_new_fdt; + } + fdt_val = cpu_to_fdt32(map_size); + status = fdt_setprop(fdt, node, "efi-runtime-mmap-size", + &fdt_val, sizeof(fdt_val)); + if (status) { + efi_printk(sys_table, "Failed to set new 'efi-runtime-mmap-size'\n"); + goto fail_free_new_fdt; + } + fdt_val = cpu_to_fdt32((unsigned long)memory_map); + status = fdt_setprop(fdt, node, "efi-runtime-mmap", + &fdt_val, sizeof(fdt_val)); + if (status) { + efi_printk(sys_table, "Failed to set new 'efi-runtime-mmap'\n"); + goto fail_free_new_fdt; + } + + /* Now we need to exit boot services. We need the key from + * the most recent read of the memory map to do this. We can't + * free this buffer in the normal case, but do free it when + * exit_boot_services() fails or adding the memory map to the FDT + * fails. + */ + status = efi_call_phys2(sys_table->boottime->exit_boot_services, + handle, mmap_key); + + if (status != EFI_SUCCESS) { + efi_printk(sys_table, "exit boot services failed.\n"); + goto fail_free_mmap; + } + + 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: + return 0; +} + + +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; /* Original DTB */ + u64 fdt_size = 0; + u64 kernel_reserve_addr; + u64 kernel_reserve_size = 0; + char *cmdline_ptr; + unsigned long cmdline_size = 0; + 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, "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, "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, "ERROR converting command line to ascii.\n"); + 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, "Error loading dtb blob\n"); + goto fail_free_cmdline; + } + + err = fdt_check_header((void *)(unsigned long)fdt_addr); + if (err != 0) { + efi_printk(sys_table, "ERROR: Device Tree header not valid\n"); + goto fail_free_dtb; + } + if (fdt_totalsize((void *)(unsigned long)fdt_addr) > fdt_size) { + efi_printk(sys_table, "ERROR: Incomplete device tree.\n"); + goto fail_free_dtb; + + } + + + /* 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, "Error: no 'memory' node in device tree.\n"); + goto fail_free_dtb; + } + + /* 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, "ERROR allocating memory for uncompressed kernel.\n"); + goto fail_free_dtb; + } + + /* 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 + MAX_ZIMAGE_OFFSET); + if (status != EFI_SUCCESS) { + efi_printk(sys_table, "Failed to relocate kernel\n"); + goto fail_free_kernel_reserve; + } + + status = handle_cmdline_files(sys_table, image, cmdline_ptr, "initrd=", + dram_base + MAX_ZIMAGE_OFFSET, + &initrd_addr, &initrd_size); + if (status != EFI_SUCCESS) { + efi_printk(sys_table, "Error loading initrd\n"); + goto fail_free_zimage; + } + + new_fdt_addr = update_fdt_and_exit_boot(sys_table, handle, + dram_base, fdt, &fdt_size, + cmdline_ptr, &cmdline_size, + initrd_addr, initrd_size); + + if (new_fdt_addr == 0) { + efi_printk(sys_table, "Error updating device tree and exiting boot services.\n"); + goto fail_free_initrd; + } + + + /* 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_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_dtb: + 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/head.S b/arch/arm/boot/compressed/head.S index 75189f1..4c70b9e 100644 --- a/arch/arm/boot/compressed/head.S +++ b/arch/arm/boot/compressed/head.S @@ -122,19 +122,106 @@ .arm @ Always enter in ARM state 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 ) + THUMB( .thumb ) +1: + b zimage_continue .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: + .text + @ 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. + .arm + ARM( mov r0, r0 ) + ARM( b 1f ) + 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} + + @ Save args to EFI app across got fixup call + stmfd sp!, {r0, r1} + ldmfd sp!, {r0, r1} + + @ 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, #8 @ we only need 4 bytes, + @ but keep stack 8 byte aligned. + mov r2, sp + @ 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 time + + sub r3, r11, r12 @ calculate the delta offset + str r3, [r2, #0] + bl efi_entry + + @ get new zImage entry address from stack, put into r3 + ldr r3, [sp, #0] + add sp, #8 @ restore stack + + @ Check for error return from EFI stub (0xFFFFFFFF) + ldr r1, =0xffffffff + cmp r0, r1 + 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} + + @ put DTB address in r2, it was returned by EFI entry + mov r2, r0 + ldr r1, =0xffffffff @ DTB machine type + mov r0, #0 @ r0 is 0 + + @ Branch to (possibly) relocated zImage entry that is in r3 + bx r3 + +efi_load_fail: + @ We need to exit THUMB mode here, to return to EFI firmware. + THUMB( adr r12, BSYM(1f) ) + THUMB( bx r12 ) +1: + .arm + @ Return EFI_LOAD_ERROR to EFI firmware on error. + ldr r0, =0x80000001 + ldmfd sp!, {fp, lr} + mov pc, lr + THUMB( .thumb ) +#endif + +zimage_continue: mrs r9, cpsr #ifdef CONFIG_ARM_VIRT_EXT bl __hyp_stub_install @ get into SVC mode, reversibly @@ -167,7 +254,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
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. Signed-off-by: Roy Franz <roy.franz@linaro.org> --- arch/arm/boot/compressed/Makefile | 18 +- arch/arm/boot/compressed/efi-header.S | 114 ++++++++ arch/arm/boot/compressed/efi-stub.c | 514 +++++++++++++++++++++++++++++++++ arch/arm/boot/compressed/head.S | 90 +++++- 4 files changed, 732 insertions(+), 4 deletions(-) create mode 100644 arch/arm/boot/compressed/efi-header.S create mode 100644 arch/arm/boot/compressed/efi-stub.c