diff options
Diffstat (limited to 'arch/arm')
-rw-r--r-- | arch/arm/Kconfig | 11 | ||||
-rw-r--r-- | arch/arm/boot/Makefile | 10 | ||||
-rw-r--r-- | arch/arm/boot/compressed/Makefile | 17 | ||||
-rw-r--r-- | arch/arm/boot/compressed/efi-header.S | 111 | ||||
-rw-r--r-- | arch/arm/boot/compressed/efi-stub.c | 475 | ||||
-rw-r--r-- | arch/arm/boot/compressed/efi-stub.h | 5 | ||||
-rw-r--r-- | arch/arm/boot/compressed/head.S | 83 | ||||
-rw-r--r-- | arch/arm/boot/compressed/string.c | 21 |
8 files changed, 719 insertions, 14 deletions
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index e6cf288b6704..5913f21a3b63 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1894,6 +1894,17 @@ bool "UEFI runtime service support" However, even with this option, the resultant kernel should continue to boot on existing non-UEFI platforms. +config EFI_STUB + bool "EFI stub support" + depends on !CPU_BIG_ENDIAN + ---help--- + This kernel feature allows a zImage to be loaded directly + by EFI firmware without the use of a bootloader. A PE/COFF + header is added to the zImage in a way that makes the binary + both a Linux zImage and an PE/COFF executable that can be + executed directly by EFI firmware. + See Documentation/efi-stub.txt for more information. + config SECCOMP bool prompt "Enable seccomp to safely compute untrusted bytecode" diff --git a/arch/arm/boot/Makefile b/arch/arm/boot/Makefile index 84aa2caf07ed..f1192f5ddbae 100644 --- a/arch/arm/boot/Makefile +++ b/arch/arm/boot/Makefile @@ -31,7 +31,7 @@ ifeq ($(CONFIG_XIP_KERNEL),y) $(obj)/xipImage: vmlinux FORCE $(call if_changed,objcopy) - @$(kecho) ' Kernel: $@ is ready (physical address: $(CONFIG_XIP_PHYS_ADDR))' + @$(kecho) ' Kernel: $@ is ready (physical address: $(CONFIG_XIP_PHYS_ADDR))' ' (#'`cat .version`')' $(obj)/Image $(obj)/zImage: FORCE @echo 'Kernel configured for XIP (CONFIG_XIP_KERNEL=y)' @@ -46,14 +46,14 @@ $(obj)/xipImage: FORCE $(obj)/Image: vmlinux FORCE $(call if_changed,objcopy) - @$(kecho) ' Kernel: $@ is ready' + @$(kecho) ' Kernel: $@ is ready' ' (#'`cat .version`')' $(obj)/compressed/vmlinux: $(obj)/Image FORCE $(Q)$(MAKE) $(build)=$(obj)/compressed $@ $(obj)/zImage: $(obj)/compressed/vmlinux FORCE $(call if_changed,objcopy) - @$(kecho) ' Kernel: $@ is ready' + @$(kecho) ' Kernel: $@ is ready' ' (#'`cat .version`')' endif @@ -78,7 +78,7 @@ fi $(obj)/uImage: $(obj)/zImage FORCE @$(check_for_multiple_loadaddr) $(call if_changed,uimage) - @$(kecho) ' Image $@ is ready' + @$(kecho) ' Image $@ is ready' ' (#'`cat .version`')' $(obj)/bootp/bootp: $(obj)/zImage initrd FORCE $(Q)$(MAKE) $(build)=$(obj)/bootp $@ @@ -86,7 +86,7 @@ $(obj)/bootp/bootp: $(obj)/zImage initrd FORCE $(obj)/bootpImage: $(obj)/bootp/bootp FORCE $(call if_changed,objcopy) - @$(kecho) ' Kernel: $@ is ready' + @$(kecho) ' Kernel: $@ is ready' ' (#'`cat .version`')' PHONY += initrd FORCE initrd: diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile index 7ac1610252ba..551dbf9c78ba 100644 --- a/arch/arm/boot/compressed/Makefile +++ b/arch/arm/boot/compressed/Makefile @@ -103,11 +103,22 @@ libfdt_objs := $(addsuffix .o, $(basename $(libfdt))) $(addprefix $(obj)/,$(libfdt) $(libfdt_hdrs)): $(obj)/%: $(srctree)/scripts/dtc/libfdt/% $(call cmd,shipped) -$(addprefix $(obj)/,$(libfdt_objs) atags_to_fdt.o): \ +$(addprefix $(obj)/,$(libfdt_objs) atags_to_fdt.o 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 +136,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 -fno-stack-protector -I$(obj) 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 000000000000..6965e0fd9d7d --- /dev/null +++ b/arch/arm/boot/compressed/efi-header.S @@ -0,0 +1,111 @@ +@ 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: + .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 _edata - efi_stub_entry @ 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 000000000000..87584e4262ad --- /dev/null +++ b/arch/arm/boot/compressed/efi-stub.c @@ -0,0 +1,475 @@ +/* + * 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> +#include "efi-stub.h" + +/* 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 ZIMAGE_OFFSET_LIMIT 0x08000000 +#define MIN_ZIMAGE_OFFSET MAX_UNCOMP_KERNEL_SIZE + +#define PRINTK_PREFIX "EFIstub: " + +struct fdt_region { + u64 base; + u64 size; +}; + + +/* Include shared EFI stub code */ +#include "../../../../drivers/firmware/efi/efi-stub-helper.c" + +static int relocate_kernel(efi_system_table_t *sys_table, + unsigned long *zimage_addr, + unsigned long zimage_size, + unsigned long min_addr, unsigned long max_addr) +{ + unsigned long cur_zimage_addr; + unsigned long new_addr = 0; + + efi_status_t status; + + if (!zimage_addr || !zimage_size) + return EFI_INVALID_PARAMETER; + + cur_zimage_addr = *zimage_addr; + + if (cur_zimage_addr > min_addr + && (cur_zimage_addr + zimage_size) < max_addr) { + /* We don't need to do anything, as kernel is 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. + */ + status = efi_low_alloc(sys_table, zimage_size, 0, + &new_addr, min_addr); + if (status != EFI_SUCCESS) { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to allocate usable 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, PRINTK_PREFIX"ERROR: Failed to allocate 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 */ + *zimage_addr = new_addr; + + 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 = 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; + } + + 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 *)cmdline_addr; + s2 = (u16 *)options; + + for (i = 0; i < options_size - 1; i++) + *s1++ = *s2++; + + *s1 = '\0'; + + *cmd_line_len = options_size; + return (char *)cmdline_addr; +} + + +static int update_fdt(efi_system_table_t *sys_table, void *orig_fdt, void *fdt, + int new_fdt_size, char *cmdline_ptr, u64 initrd_addr, + u64 initrd_size, efi_memory_desc_t *memory_map, + int map_size, int desc_size) +{ + int node; + int status; + unsigned long fdt_val; + + status = fdt_open_into(orig_fdt, fdt, new_fdt_size); + if (status != 0) + goto fdt_set_fail; + + node = fdt_subnode_offset(fdt, 0, "chosen"); + if (node < 0) { + node = fdt_add_subnode(fdt, 0, "chosen"); + if (node < 0) { + status = node; /* node is error code when negative */ + goto fdt_set_fail; + } + } + + if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) { + status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr, + strlen(cmdline_ptr) + 1); + if (status) + goto fdt_set_fail; + } + + /* 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) + goto fdt_set_fail; + 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) + goto fdt_set_fail; + } + + /* Add FDT entries for EFI runtime services in chosen node. */ + 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) + goto fdt_set_fail; + + fdt_val = cpu_to_fdt32(desc_size); + status = fdt_setprop(fdt, node, "efi-mmap-desc-size", + &fdt_val, sizeof(fdt_val)); + if (status) + goto fdt_set_fail; + + fdt_val = cpu_to_fdt32(map_size); + status = fdt_setprop(fdt, node, "efi-runtime-mmap-size", + &fdt_val, sizeof(fdt_val)); + if (status) + goto fdt_set_fail; + + fdt_val = cpu_to_fdt32((unsigned long)memory_map); + status = fdt_setprop(fdt, node, "efi-runtime-mmap", + &fdt_val, sizeof(fdt_val)); + + /* Stuff the whole memory map into FDT */ + status = fdt_setprop(fdt, node, "efi-runtime-mmap-blob", + memory_map, sizeof(map_size)); + + if (status) + goto fdt_set_fail; + + return EFI_SUCCESS; + +fdt_set_fail: + if (status == -FDT_ERR_NOSPACE) + return EFI_BUFFER_TOO_SMALL; + + return EFI_LOAD_ERROR; +} + + + +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: Unable to allocate memory for command line.\n"); + goto fail; + } + +#if 1 + /* HACK, so we can boot from boot manager. If we are passed a real + * command line, use it. + * + * TODO_RFRANZ - for debug only, not for real patch series + */ + if (!cmdline_ptr[0]) { + cmdline_ptr = "initrd=initrd dtb=dtb console=ttyAMA0 earlyprintk=keep"; + efi_printk(sys_table, PRINTK_PREFIX"Booted from boot manager, forcing command line to: "); + efi_printk(sys_table, cmdline_ptr); + efi_printk(sys_table, "\n"); + } +#endif + + /* 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_addr is + * a 64 bit value (efi_phys_addr_t), but EFI guarantees that + * all addresses are 32 bit for ARM32. */ + 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; + while (1) { + 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); + + /* Succeeding the first time is the expected case. */ + if (status == EFI_SUCCESS) + break; + + if (status == EFI_BUFFER_TOO_SMALL) { + /* 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; + } + else + { + efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to constuct new device tree.\n"); + goto fail_free_mmap; + } + } + + /* 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_size, kernel_reserve_addr); + +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.h b/arch/arm/boot/compressed/efi-stub.h new file mode 100644 index 000000000000..0fe937679f5a --- /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) +#endif diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S index 75189f13cf54..aae3c75c2ec0 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]! + 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 diff --git a/arch/arm/boot/compressed/string.c b/arch/arm/boot/compressed/string.c index 36e53ef9200f..5397792942db 100644 --- a/arch/arm/boot/compressed/string.c +++ b/arch/arm/boot/compressed/string.c @@ -111,6 +111,27 @@ char *strchr(const char *s, int c) return (char *)s; } +/** + * strstr - Find the first substring in a %NUL terminated string + * @s1: The string to be searched + * @s2: The string to search for + */ +char *strstr(const char *s1, const char *s2) +{ + size_t l1, l2; + + l2 = strlen(s2); + if (!l2) + return (char *)s1; + l1 = strlen(s1); + while (l1 >= l2) { + l1--; + if (!memcmp(s1, s2, l2)) + return (char *)s1; + s1++; + } + return NULL; +} #undef memset void *memset(void *s, int c, size_t count) |