On Fri, 29 Nov 2013 17:05:10 -0500, Mark Salter <msalter@xxxxxxxxxx> wrote: > This patch adds PE/COFF header fields to the start of the Image > so that it appears as an EFI application to EFI firmware. An EFI > stub is included to allow direct booting of the kernel Image. Due > to EFI firmware limitations, only little endian kernels with 4K > page sizes are supported at this time. Support in the COFF header > for signed images was provided by Ard Biesheuvel. > > Signed-off-by: Mark Salter <msalter@xxxxxxxxxx> > Signed-off-by: Ard Biesheuvel <ard.biesheuvel@xxxxxxxxxx> Reviewed-by: Grant Likely <grant.likely@xxxxxxxxxx> I've already made comments on Roy's arm32 version of this code. I don't like the duplication and it needs to be consolidated, but I would be fine with consolidation being done as follow-on patches if that expedites getting the code in. g. > CC: Catalin Marinas <catalin.marinas@xxxxxxx> > CC: Will Deacon <will.deacon@xxxxxxx> > CC: linux-arm-kernel@xxxxxxxxxxxxxxxxxxx > CC: matt.fleming@xxxxxxxxx > CC: linux-efi@xxxxxxxxxxxxxxx > CC: Leif Lindholm <leif.lindholm@xxxxxxxxxx> > CC: roy.franz@xxxxxxxxxx > --- > arch/arm64/Kconfig | 10 ++ > arch/arm64/kernel/Makefile | 3 + > arch/arm64/kernel/efi-entry.S | 81 ++++++++++++ > arch/arm64/kernel/efi-stub.c | 280 ++++++++++++++++++++++++++++++++++++++++++ > arch/arm64/kernel/head.S | 112 +++++++++++++++++ > 5 files changed, 486 insertions(+) > create mode 100644 arch/arm64/kernel/efi-entry.S > create mode 100644 arch/arm64/kernel/efi-stub.c > > diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig > index 809c1b8..10b0e93 100644 > --- a/arch/arm64/Kconfig > +++ b/arch/arm64/Kconfig > @@ -250,6 +250,16 @@ config CMDLINE_FORCE > This is useful if you cannot or don't want to change the > command-line options your boot loader passes to the kernel. > > +config EFI_STUB > + bool "EFI stub support" > + depends on !CPU_BIG_ENDIAN && !ARM64_64K_PAGES && OF > + select LIBFDT > + default y > + help > + This kernel feature allows an Image to be loaded directly > + by EFI firmware without the use of a bootloader. > + See Documentation/efi-stub.txt for more information. > + > endmenu > > menu "Userspace binary formats" > diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile > index 5ba2fd4..1c52b84 100644 > --- a/arch/arm64/kernel/Makefile > +++ b/arch/arm64/kernel/Makefile > @@ -4,6 +4,8 @@ > > CPPFLAGS_vmlinux.lds := -DTEXT_OFFSET=$(TEXT_OFFSET) > AFLAGS_head.o := -DTEXT_OFFSET=$(TEXT_OFFSET) > +CFLAGS_efi-stub.o := -DTEXT_OFFSET=$(TEXT_OFFSET) \ > + -I$(src)/../../../scripts/dtc/libfdt > > # Object file lists. > arm64-obj-y := cputable.o debug-monitors.o entry.o irq.o fpsimd.o \ > @@ -18,6 +20,7 @@ arm64-obj-$(CONFIG_SMP) += smp.o smp_spin_table.o > arm64-obj-$(CONFIG_HW_PERF_EVENTS) += perf_event.o > arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT)+= hw_breakpoint.o > arm64-obj-$(CONFIG_EARLY_PRINTK) += early_printk.o > +arm64-obj-$(CONFIG_EFI_STUB) += efi-stub.o efi-entry.o > > obj-y += $(arm64-obj-y) vdso/ > obj-m += $(arm64-obj-m) > diff --git a/arch/arm64/kernel/efi-entry.S b/arch/arm64/kernel/efi-entry.S > new file mode 100644 > index 0000000..5f6d179 > --- /dev/null > +++ b/arch/arm64/kernel/efi-entry.S > @@ -0,0 +1,81 @@ > +/* > + * EFI entry point. > + * > + * Copyright (C) 2013 Red Hat, Inc. > + * Author: Mark Salter <msalter@xxxxxxxxxx> > + * > + * 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/linkage.h> > +#include <linux/init.h> > + > +#include <asm/assembler.h> > + > +#define EFI_LOAD_ERROR 0x8000000000000001 > + > + __INIT > + > + /* > + * We arrive here from the EFI boot manager with: > + * > + * * MMU on with identity-mapped RAM. > + * * Icache and Dcache on > + * > + * We will most likely be running from some place other than where > + * we want to be. The kernel image wants to be placed at TEXT_OFFSET > + * from start of RAM. > + */ > +ENTRY(efi_stub_entry) > + stp x29, x30, [sp, #-32]! > + > + /* > + * Call efi_entry to do the real work. > + * x0 and x1 are already set up by firmware. Current runtime > + * address of image is calculated and passed via *image_addr. > + * > + * unsigned long efi_entry(void *handle, > + * efi_system_table_t *sys_table, > + * unsigned long *image_addr) ; > + */ > + adrp x8, _text > + add x8, x8, #:lo12:_text > + add x2, sp, 16 > + str x8, [x2] > + bl efi_entry > + cmn x0, #1 > + b.eq efi_load_fail > + > + /* > + * efi_entry() will have relocated the kernel image if necessary > + * and we return here with device tree address in x0 and the kernel > + * entry point stored at *image_addr. Save those values in registers > + * which are preserved by __flush_dcache_all. > + */ > + ldr x1, [sp, #16] > + mov x20, x0 > + mov x21, x1 > + > + bl __flush_dcache_all > + /* Turn off Dcache and MMU */ > + mrs x0, sctlr_el1 > + bic x0, x0, #1 << 0 // clear SCTLR.M > + bic x0, x0, #1 << 2 // clear SCTLR.C > + msr sctlr_el1, x0 > + isb > + > + /* Jump to real entry point */ > + mov x0, x20 > + mov x1, xzr > + mov x2, xzr > + mov x3, xzr > + br x21 > + > +efi_load_fail: > + mov x0, EFI_LOAD_ERROR > + ldp x29, x30, [sp], #32 > + ret > + > +ENDPROC(efi_stub_entry) > diff --git a/arch/arm64/kernel/efi-stub.c b/arch/arm64/kernel/efi-stub.c > new file mode 100644 > index 0000000..f000b04 > --- /dev/null > +++ b/arch/arm64/kernel/efi-stub.c > @@ -0,0 +1,280 @@ > +/* > + * linux/arch/arm/boot/compressed/efi-stub.c > + * > + * Copyright (C) 2013 Linaro Ltd; <roy.franz@xxxxxxxxxx> > + * > + * This file implements the EFI boot stub for the arm64 kernel. > + * Adapted from ARM version by Mark Salter <msalter@xxxxxxxxxx> > + * > + * 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 <linux/libfdt.h> > +#include <asm/sections.h> > +#include <generated/compile.h> > +#include <linux/uts.h> > +#include <linux/utsname.h> > +#include <generated/utsrelease.h> > +#include <linux/version.h> > + > +/* error code which can't be mistaken for valid address */ > +#define EFI_ERROR (~0UL) > + > +/* > + * EFI function call wrappers. These are not required for arm64, but wrappers > + * are required for X86 to convert between ABIs. These wrappers are provided > + * to allow code sharing between X86 and other architectures. 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) > + > +/* > + * AArch64 requires the DTB to be 8-byte aligned in the first 512MiB from > + * start of kernel and may not cross a 2MiB boundary. We set alignment to > + * equal max size so we know it won't cross a 2MiB boudary. > + */ > +#define MAX_DTB_SIZE 0x40000 > +#define DTB_ALIGN MAX_DTB_SIZE > +#define MAX_DTB_OFFSET 0x20000000 > + > +#define pr_efi(msg) efi_printk(sys_table, "EFI stub: "msg) > +#define pr_efi_err(msg) efi_printk(sys_table, "EFI stub: ERROR: "msg) > + > +struct fdt_region { > + u64 base; > + u64 size; > +}; > + > +/* Include shared EFI stub code */ > +#include "../../../drivers/firmware/efi/efi-stub-helper.c" > +#include "../../../drivers/firmware/efi/fdt.c" > + > +static unsigned long __init get_dram_base(efi_system_table_t *sys_table) > +{ > + efi_status_t status; > + unsigned long map_size, desc_size; > + unsigned long membase = EFI_ERROR; > + efi_memory_desc_t *memory_map; > + int i; > + > + status = efi_get_memory_map(sys_table, &memory_map, &map_size, > + &desc_size, NULL, NULL); > + if (status == EFI_SUCCESS) { > + 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 (desc->num_pages == 0) > + break; > + > + if (desc->type == EFI_CONVENTIONAL_MEMORY) { > + unsigned long base = desc->phys_addr; > + > + base &= ~((unsigned long)(TEXT_OFFSET - 1)); > + > + if (membase > base) > + membase = base; > + } > + } > + } > + return membase; > +} > + > +unsigned long __init efi_entry(void *handle, efi_system_table_t *sys_table, > + unsigned long *image_addr) > +{ > + efi_loaded_image_t *image; > + efi_status_t status; > + unsigned long image_size, image_memsize = 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; > + unsigned long new_fdt_size; > + char *cmdline_ptr; > + int cmdline_size = 0; > + unsigned long new_fdt_addr; > + unsigned long map_size, desc_size; > + unsigned long mmap_key; > + efi_memory_desc_t *memory_map; > + u32 desc_ver; > + 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; > + > + pr_efi("Booting Linux Kernel...\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) { > + pr_efi_err("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 = efi_convert_cmdline_to_ascii(sys_table, image, > + &cmdline_size); > + if (!cmdline_ptr) { > + pr_efi_err("Failed to convert command line to ascii\n"); > + goto fail; > + } > + > + status = handle_cmdline_files(sys_table, image, cmdline_ptr, "dtb=", > + ~0UL, (unsigned long *)&fdt_addr, > + (unsigned long *)&fdt_size); > + if (status != EFI_SUCCESS) { > + pr_efi_err("Failed to load device tree blob\n"); > + goto fail_free_cmdline; > + } > + > + if (fdt_check_header((void *)fdt_addr)) { > + pr_efi_err("Device Tree header not valid\n"); > + goto fail_free_dtb; > + } > + if (fdt_totalsize((void *)fdt_addr) > fdt_size) { > + pr_efi_err("Incomplete device tree\n"); > + goto fail_free_dtb; > + } > + > + dram_base = get_dram_base(sys_table); > + if (dram_base == EFI_ERROR) { > + pr_efi_err("Failed to get DRAM base\n"); > + goto fail_free_dtb; > + } > + > + /* Relocate the image, if required. */ > + image_size = image->image_size; > + if (*image_addr != (dram_base + TEXT_OFFSET)) { > + image_memsize = image_size + (_end - _edata); > + status = efi_relocate_kernel(sys_table, image_addr, > + image_size, image_memsize, > + dram_base + TEXT_OFFSET, > + PAGE_SIZE); > + if (status != EFI_SUCCESS) { > + pr_efi_err("Failed to relocate kernel\n"); > + goto fail_free_dtb; > + } > + if (*image_addr != (dram_base + TEXT_OFFSET)) { > + pr_efi_err("Failed to alloc kernel memory\n"); > + goto fail_free_image; > + } > + } > + > + status = handle_cmdline_files(sys_table, image, cmdline_ptr, "initrd=", > + dram_base + 0x20000000, > + (unsigned long *)&initrd_addr, > + (unsigned long *)&initrd_size); > + if (status != EFI_SUCCESS) > + pr_efi("No initrd found\n"); > + > + /* > + * 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. We increment the size > + * by PAGE_SIZE since the firmware allocates by pages anyway. > + */ > + new_fdt_size = fdt_size + EFI_PAGE_SIZE; > + while (1) { > + status = efi_high_alloc(sys_table, new_fdt_size, DTB_ALIGN, > + &new_fdt_addr, > + dram_base + MAX_DTB_OFFSET); > + if (status != EFI_SUCCESS) { > + pr_efi_err("No memory for new device tree\n"); > + goto fail_free_initrd; > + } > + > + /* > + * Now that we have done our final memory allocation, we can > + * get the memory map key needed for exit_boot_services(). > + */ > + status = efi_get_memory_map(sys_table, &memory_map, &map_size, > + &desc_size, &desc_ver, &mmap_key); > + if (status != EFI_SUCCESS) > + goto fail_free_new_fdt; > + > + status = update_fdt(sys_table, > + (void *)fdt_addr, (void *)new_fdt_addr, > + new_fdt_size, cmdline_ptr, > + initrd_addr, initrd_size, > + memory_map, map_size, desc_size, desc_ver); > + > + /* 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 += EFI_PAGE_SIZE; > + } else { > + pr_efi_err("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) { > + pr_efi_err("Exit boot services failed\n"); > + goto fail_free_mmap; > + } > + > + /* > + * Now we need to return the FDT address to the calling > + * function so it 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_image: > + efi_free(sys_table, image_memsize, *image_addr); > + > +fail_free_dtb: > + if (fdt_addr) > + efi_free(sys_table, fdt_size, fdt_addr); > + > +fail_free_cmdline: > + efi_free(sys_table, cmdline_size, (u64)cmdline_ptr); > + > +fail: > + return EFI_ERROR; > +} > diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S > index 03adf8f..720429e 100644 > --- a/arch/arm64/kernel/head.S > +++ b/arch/arm64/kernel/head.S > @@ -107,8 +107,18 @@ > /* > * DO NOT MODIFY. Image header expected by Linux boot-loaders. > */ > +#ifdef CONFIG_EFI_STUB > + /* > + * Magic "MZ" signature for PE/COFF > + * Little Endian: add x13, x18, #0x16 > + */ > +efi_head: > + .long 0x91005a4d > + b stext > +#else > b stext // branch to kernel start, magic > .long 0 // reserved > +#endif > .quad TEXT_OFFSET // Image load offset from start of RAM > .quad 0 // reserved > .quad 0 // reserved > @@ -119,7 +129,109 @@ > .byte 0x52 > .byte 0x4d > .byte 0x64 > +#ifdef CONFIG_EFI_STUB > + .long pe_header - efi_head // Offset to the PE header. > +#else > .word 0 // reserved > +#endif > + > +#ifdef CONFIG_EFI_STUB > + .align 3 > +pe_header: > + .ascii "PE" > + .short 0 > +coff_header: > + .short 0xaa64 // AArch64 > + .short 2 // nr_sections > + .long 0 // TimeDateStamp > + .long 0 // PointerToSymbolTable > + .long 1 // NumberOfSymbols > + .short section_table - optional_header // SizeOfOptionalHeader > + .short 0x206 // Characteristics. > + // IMAGE_FILE_DEBUG_STRIPPED | > + // IMAGE_FILE_EXECUTABLE_IMAGE | > + // IMAGE_FILE_LINE_NUMS_STRIPPED > +optional_header: > + .short 0x20b // PE32+ format > + .byte 0x02 // MajorLinkerVersion > + .byte 0x14 // MinorLinkerVersion > + .long _edata - stext // SizeOfCode > + .long 0 // SizeOfInitializedData > + .long 0 // SizeOfUninitializedData > + .long efi_stub_entry - efi_head // AddressOfEntryPoint > + .long stext - efi_head // BaseOfCode > + > +extra_header_fields: > + .quad 0 // ImageBase > + .long 0x20 // SectionAlignment > + .long 0x8 // FileAlignment > + .short 0 // MajorOperatingSystemVersion > + .short 0 // MinorOperatingSystemVersion > + .short 0 // MajorImageVersion > + .short 0 // MinorImageVersion > + .short 0 // MajorSubsystemVersion > + .short 0 // MinorSubsystemVersion > + .long 0 // Win32VersionValue > + > + .long _edata - efi_head // SizeOfImage > + > + // Everything before the kernel image is considered part of the header > + .long stext - efi_head // SizeOfHeaders > + .long 0 // CheckSum > + .short 0xa // Subsystem (EFI application) > + .short 0 // DllCharacteristics > + .quad 0 // SizeOfStackReserve > + .quad 0 // SizeOfStackCommit > + .quad 0 // SizeOfHeapReserve > + .quad 0 // SizeOfHeapCommit > + .long 0 // LoaderFlags > + .long 0x6 // NumberOfRvaAndSizes > + > + .quad 0 // ExportTable > + .quad 0 // ImportTable > + .quad 0 // ResourceTable > + .quad 0 // ExceptionTable > + .quad 0 // CertificationTable > + .quad 0 // BaseRelocationTable > + > + // 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 - stext // VirtualSize > + .long stext - efi_head // VirtualAddress > + .long _edata - stext // SizeOfRawData > + .long stext - efi_head // 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) > + .align 5 > +#endif > > ENTRY(stext) > mov x21, x0 // x21=FDT > -- > 1.8.3.1 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-kernel" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html > Please read the FAQ at http://www.tux.org/lkml/ -- To unsubscribe from this list: send the line "unsubscribe linux-efi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html