Re: [PATCH 1/3] arm64: add EFI stub

Grant Likely
2013-12-06T12:13:07Z

On Fri, 29 Nov 2013 17:05:10 -0500, Mark Salter <msalter@redhat.com> 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@redhat.com>
> Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>

Reviewed-by: Grant Likely <grant.likely@linaro.org>

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@arm.com>
> CC: Will Deacon <will.deacon@arm.com>
> CC: linux-arm-kernel@lists.infradead.org
> CC: matt.fleming@intel.com
> CC: linux-efi@vger.kernel.org
> CC: Leif Lindholm <leif.lindholm@linaro.org>
> CC: roy.franz@linaro.org
> ---
> 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@redhat.com>
> + *
> + * 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@linaro.org>
> + *
> + * This file implements the EFI boot stub for the arm64 kernel.
> + * Adapted from ARM version by Mark Salter <msalter@redhat.com>
> + *
> + * 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@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/