This patch adds EFI support for the ARM Linux kernel.
The EFI stub operates similarly to the x86 and arm64 stubs: 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 optionally loading the initrd and device tree from the system partition based on the kernel command line.
Signed-off-by: Roy Franz roy.franz@linaro.org Signed-off-by: Ard Biesheuvel ard.biesheuvel@linaro.org --- arch/arm/Kconfig | 19 +++++ arch/arm/boot/compressed/.gitignore | 2 + arch/arm/boot/compressed/Makefile | 18 +++-- arch/arm/boot/compressed/efi-header.S | 141 ++++++++++++++++++++++++++++++++++ arch/arm/boot/compressed/efi-stub.c | 89 +++++++++++++++++++++ arch/arm/boot/compressed/head.S | 66 +++++++++++++++- arch/arm/include/asm/efi.h | 135 ++++++++++++++++++++++++++++++++ arch/arm/include/asm/fixmap.h | 4 + arch/arm/kernel/setup.c | 3 + 9 files changed, 471 insertions(+), 6 deletions(-) create mode 100644 arch/arm/boot/compressed/efi-header.S create mode 100644 arch/arm/boot/compressed/efi-stub.c create mode 100644 arch/arm/include/asm/efi.h
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 45df48ba0b12..44ed539d2799 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1990,6 +1990,25 @@ config AUTO_ZRELADDR 0xf8000000. This assumes the zImage being placed in the first 128MB from start of memory.
+config EFI_STUB + bool + +config EFI + bool "UEFI runtime service support" + depends on OF && !CPU_BIG_ENDIAN && HIGHMEM + select UCS2_STRING + select EFI_PARAMS_FROM_FDT + select EFI_STUB + select EFI_ARMSTUB + select EFI_RUNTIME_WRAPPERS + ---help--- + This option provides support for runtime services provided + by UEFI firmware (such as non-volatile variables, realtime + clock, and platform reset). A UEFI stub is also provided to + allow the kernel to be booted as an EFI application. This + is only useful for kernels that may run on systems that have + UEFI firmware. + endmenu
menu "CPU Power Management" diff --git a/arch/arm/boot/compressed/.gitignore b/arch/arm/boot/compressed/.gitignore index 0714e0334e33..cc3487ec5b4d 100644 --- a/arch/arm/boot/compressed/.gitignore +++ b/arch/arm/boot/compressed/.gitignore @@ -14,8 +14,10 @@ vmlinux.lds # borrowed libfdt files fdt.c fdt.h +fdt_empty_tree.c fdt_ro.c fdt_rw.c +fdt_sw.c fdt_wip.c libfdt.h libfdt_internal.h diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile index 6e1fb2b2ecc7..48cd1fb8bdb2 100644 --- a/arch/arm/boot/compressed/Makefile +++ b/arch/arm/boot/compressed/Makefile @@ -76,9 +76,9 @@ suffix_$(CONFIG_KERNEL_LZMA) = lzma suffix_$(CONFIG_KERNEL_XZ) = xzkern suffix_$(CONFIG_KERNEL_LZ4) = lz4
-# Borrowed libfdt files for the ATAG compatibility mode +# Borrowed libfdt files for the ATAG compatibility mode and EFI stub support
-libfdt := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c +libfdt := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c fdt_empty_tree.c fdt_sw.c libfdt_hdrs := fdt.h libfdt.h libfdt_internal.h
libfdt_objs := $(addsuffix .o, $(basename $(libfdt))) @@ -86,11 +86,19 @@ 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 +fdt-obj-$(CONFIG_ARM_ATAG_DTB_COMPAT) += atags_to_fdt.o +fdt-obj-$(CONFIG_EFI) += efi-stub.o efi-banner.o \ + ../../../../drivers/firmware/efi/libstub/lib.a + +$(obj)/efi-banner.o: OBJCOPYFLAGS=-j .rodata +$(obj)/efi-banner.o: $(objtree)/init/version.o FORCE + $(call if_changed,objcopy) + +ifneq ($(fdt-obj-y),) +OBJS += $(libfdt_objs) $(fdt-obj-y) endif
targets := vmlinux vmlinux.lds \ diff --git a/arch/arm/boot/compressed/efi-header.S b/arch/arm/boot/compressed/efi-header.S new file mode 100644 index 000000000000..da25dbd4d77e --- /dev/null +++ b/arch/arm/boot/compressed/efi-header.S @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2013-2015 Linaro Ltd + * Authors: Roy Franz roy.franz@linaro.org + * Ard Biesheuvel ard.biesheuvel@linaro.org + * + * 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. + */ + +#ifdef CONFIG_EFI +#define NOP __efi_nop +#endif + + .macro __efi_nop + @ This is almost but not quite a NOP, since it does clobber the + @ condition flags. But is the best we can do for EFI, since PE/COFF + @ expects the magic string "MZ" at offset 0, while the ARM/Linux boot + @ protocol expects an executable instruction there. + .inst 0xe1005a4d @ tst r0, sp, asr #20 + .endm + + .macro __EFI_HEADER +#ifdef CONFIG_EFI + .set start_offset, __efi_start - start + .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 - start @ 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 _end - __efi_start @ SizeOfCode + + .long 0 @ SizeOfInitializedData + .long 0 @ SizeOfUninitializedData + + .long efi_stub_entry - start @ AddressOfEntryPoint + .long start_offset @ BaseOfCode + .long 0 @ data + +extra_header_fields: + .long 0 @ ImageBase + .long 0x200 @ SectionAlignment + .long 0x200 @ FileAlignment + .short 0 @ MajorOperatingSystemVersion + .short 0 @ MinorOperatingSystemVersion + .short 0 @ MajorImageVersion + .short 0 @ MinorImageVersion + .short 0 @ MajorSubsystemVersion + .short 0 @ MinorSubsystemVersion + .long 0 @ Win32VersionValue + + .long _end - start @ SizeOfImage + + @ Everything before the entry point is considered part of the header + .long start_offset @ 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 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 _end - __efi_start @ VirtualSize + .long __efi_start @ VirtualAddress + .long _edata - __efi_start @ SizeOfRawData + .long __efi_start @ 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 9 +__efi_start: +#endif + .endm diff --git a/arch/arm/boot/compressed/efi-stub.c b/arch/arm/boot/compressed/efi-stub.c new file mode 100644 index 000000000000..34b76f7742f9 --- /dev/null +++ b/arch/arm/boot/compressed/efi-stub.c @@ -0,0 +1,89 @@ +/* + * 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 <asm/efi.h> + +efi_status_t handle_kernel_image(efi_system_table_t *sys_table, + unsigned long *image_addr, + unsigned long *image_size, + unsigned long *reserve_addr, + unsigned long *reserve_size, + unsigned long dram_base, + efi_loaded_image_t *image) +{ + unsigned long nr_pages; + efi_status_t status; + /* Use alloc_addr to tranlsate between types */ + efi_physical_addr_t alloc_addr; + + /* + * Verify that the DRAM base address is compatible the the ARM + * boot protocol, which determines the base of DRAM by masking + * off the low 24 bits of the address at which the zImage is + * loaded. These assumptions are made by the decompressor, + * before any memory map is available. + */ + dram_base = round_up(dram_base, SZ_128M); + + /* + * Reserve memory for the uncompressed kernel image. This is + * all that prevents any future allocations from conflicting + * with the kernel. Since we can't tell from the compressed + * image how much DRAM the kernel actually uses (due to BSS + * size uncertainty) we allocate the maximum possible size. + * Do this very early, as prints can cause memory allocations + * that may conflict with this. + */ + alloc_addr = dram_base; + *reserve_size = MAX_UNCOMP_KERNEL_SIZE; + nr_pages = round_up(*reserve_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE; + status = sys_table->boottime->allocate_pages(EFI_ALLOCATE_ADDRESS, + EFI_LOADER_DATA, + nr_pages, &alloc_addr); + if (status != EFI_SUCCESS) { + *reserve_size = 0; + pr_efi_err(sys_table, "Unable to allocate memory for uncompressed kernel.\n"); + return status; + } + *reserve_addr = alloc_addr; + + /* + * Relocate the zImage, if required. ARM doesn't have a + * preferred address, so we set it to 0, as we want to allocate + * as low in memory as possible. + */ + *image_size = image->image_size; + status = efi_relocate_kernel(sys_table, image_addr, *image_size, + *image_size, 0, 0); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Failed to relocate kernel.\n"); + efi_free(sys_table, *reserve_size, *reserve_addr); + *reserve_size = 0; + return status; + } + + /* + * Check to see if we were able to allocate memory low enough + * in memory. The kernel determines the base of DRAM from the + * address at which the zImage is loaded. + */ + if (*image_addr + *image_size > dram_base + ZIMAGE_OFFSET_LIMIT) { + pr_efi_err(sys_table, "Failed to relocate kernel, no low memory available.\n"); + efi_free(sys_table, *reserve_size, *reserve_addr); + *reserve_size = 0; + efi_free(sys_table, *image_size, *image_addr); + *image_size = 0; + return EFI_LOAD_ERROR; + } + return EFI_SUCCESS; +} diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S index 2c45b5709fa4..3e8315b703d2 100644 --- a/arch/arm/boot/compressed/head.S +++ b/arch/arm/boot/compressed/head.S @@ -12,6 +12,16 @@ #include <asm/assembler.h> #include <asm/v7m.h>
+#include "efi-header.S" + +#ifndef NOP +#define NOP __nop +#endif + + .macro __nop + mov r0, r0 + .endm + AR_CLASS( .arch armv7-a ) M_CLASS( .arch armv7-m )
@@ -126,7 +136,7 @@ start: .type start,#function .rept 7 - mov r0, r0 + NOP .endr ARM( mov r0, r0 ) ARM( b 1f ) @@ -138,6 +148,8 @@ start: .word _magic_end @ zImage end address .word 0x04030201 @ endianness flag
+ __EFI_HEADER + THUMB( .thumb ) 1: ARM_BE8( setend be ) @ go BE8 if compiled for BE8 @@ -1353,6 +1365,58 @@ __enter_kernel:
reloc_code_end:
+#ifdef CONFIG_EFI + AR_CLASS( .arm ) + .align 2 +ENTRY(efi_stub_entry) + @ 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 + ldr r2, [r3] + add r3, r2, r3 + 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 error code. + cmn r0, #1 + beq efi_load_fail + + @ Save return values of efi_entry + stmfd sp!, {r0, r3} + bl cache_clean_flush + bl cache_off + ldmfd sp!, {r2, lr} + + @ Set parameters for booting zImage according to boot protocol + @ put FDT address in r2, it was returned by efi_entry() + @ r1 is the machine type, and r0 needs to be 0 + mov r0, #0 + mov r1, #0xFFFFFFFF + + @ Branch to (possibly) relocated zImage that is in lr + ldr ip, =start_offset + add lr, lr, ip + ret lr + +efi_load_fail: + @ Return EFI_LOAD_ERROR to EFI firmware on error. + ldr r0, =0x80000001 + ldmfd sp!, {fp, pc} +ENDPROC(efi_stub_entry) + +_start: .long start - . +#endif + .align .section ".stack", "aw", %nobits .L_user_stack: .space 4096 diff --git a/arch/arm/include/asm/efi.h b/arch/arm/include/asm/efi.h new file mode 100644 index 000000000000..feeea7836d62 --- /dev/null +++ b/arch/arm/include/asm/efi.h @@ -0,0 +1,135 @@ +#ifndef _ASM_ARM_EFI_H +#define _ASM_ARM_EFI_H + +#include <asm/cacheflush.h> +#include <asm/cachetype.h> +#include <asm/fixmap.h> +#include <asm/highmem.h> +#include <asm/mach/map.h> +#include <asm/mmu_context.h> +#include <asm/pgtable.h> + +#ifdef CONFIG_EFI +extern void efi_init(void); + +typedef enum { + __EFI_DEVICE = MT_DEVICE, + __EFI_MEMORY_RW = MT_MEMORY_RW, + __EFI_MEMORY_RWX = MT_MEMORY_RWX, +} __arm_efi_prot_t; + +static inline void *__arm_efi_early_memremap(phys_addr_t pa, int size) +{ + u32 footprint = (pa & ~PAGE_MASK) + size; + pgprot_t prot = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY); + + /* + * Use two adjacent fixmap entries to create a mapping for + * [pa, pa + size>. This gives us between 4 KB and 8 KB of + * virtual space, depending on the alignment of pa. + */ + if (footprint > 2 * PAGE_SIZE) + return NULL; + + __set_fixmap(FIX_EFI_EARLY0, pa, prot); + + if (footprint > PAGE_SIZE) + __set_fixmap(FIX_EFI_EARLY1, pa + PAGE_SIZE, prot); + + return (void *)fix_to_virt(FIX_EFI_EARLY0) + (pa & ~PAGE_MASK); +} + +static inline void __arm_efi_early_memunmap(void *va, int size) +{ + __set_fixmap(FIX_EFI_EARLY0, 0, 0); + __set_fixmap(FIX_EFI_EARLY1, 0, 0); +} + +static inline void *__arm_efi_memremap(phys_addr_t phys_addr, size_t size) +{ + return kmap_atomic(phys_to_page(phys_addr)) + (phys_addr & ~PAGE_MASK); +} + +static inline void __arm_efi_memunmap(void *vaddr) +{ + __kunmap_atomic(vaddr); +} + +static inline void create_efi_mapping(struct mm_struct *mm, phys_addr_t phys, + unsigned long virt, phys_addr_t size, + __arm_efi_prot_t type) +{ + struct map_desc md = { + .virtual = virt, + .pfn = __phys_to_pfn(phys), + .length = size, + .type = (unsigned int)type, + }; + __create_mapping(mm, &md); +} + +#else +#define efi_init() +#endif /* CONFIG_EFI */ + +#define efi_call_virt(f, ...) \ +({ \ + efi_##f##_t *__f; \ + efi_status_t __s; \ + \ + efi_virtmap_load(); \ + __f = efi.systab->runtime->f; \ + __s = __f(__VA_ARGS__); \ + efi_virtmap_unload(); \ + __s; \ +}) + +#define __efi_call_virt(f, ...) \ +({ \ + efi_##f##_t *__f; \ + \ + efi_virtmap_load(); \ + __f = efi.systab->runtime->f; \ + __f(__VA_ARGS__); \ + efi_virtmap_unload(); \ +}) + +static inline void efi_set_pgd(struct mm_struct *mm) +{ + if (unlikely(mm->context.vmalloc_seq != init_mm.context.vmalloc_seq)) + __check_vmalloc_seq(mm); + + cpu_switch_mm(mm->pgd, mm); + + flush_tlb_all(); + if (icache_is_vivt_asid_tagged()) + __flush_icache_all(); +} + +void efi_virtmap_load(void); +void efi_virtmap_unload(void); + +/* arch specific definitions used by the stub code */ + +#define efi_call_early(f, ...) sys_table_arg->boottime->f(__VA_ARGS__) + +/* + * A reasonable upper bound for the uncompressed kernel size is 32 MBytes, + * so we will reserve that amount of memory. We have no easy way to tell what + * the actuall size of code + data the uncompressed kernel will use. + * If this is insufficient, the decompressor will relocate itself out of the + * way before performing the decompression. + */ +#define MAX_UNCOMP_KERNEL_SIZE SZ_32M + +/* + * The kernel zImage should preferably be located between 32 MB and 128 MB + * 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 SZ_128M +#define MIN_ZIMAGE_OFFSET MAX_UNCOMP_KERNEL_SIZE +#define MAX_FDT_OFFSET ZIMAGE_OFFSET_LIMIT + +#endif /* _ASM_ARM_EFI_H */ diff --git a/arch/arm/include/asm/fixmap.h b/arch/arm/include/asm/fixmap.h index 0415eae1df27..6580a225b91c 100644 --- a/arch/arm/include/asm/fixmap.h +++ b/arch/arm/include/asm/fixmap.h @@ -15,6 +15,10 @@ enum fixed_addresses { FIX_TEXT_POKE0, FIX_TEXT_POKE1,
+ /* reuse the poke0/poke1 fixmap slots for the early EFI init code */ + FIX_EFI_EARLY0 = FIX_TEXT_POKE0, + FIX_EFI_EARLY1 = FIX_TEXT_POKE1, + __end_of_fixed_addresses };
diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index a88d377848f2..d10ddff8269f 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -7,6 +7,7 @@ * 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/export.h> #include <linux/kernel.h> #include <linux/stddef.h> @@ -36,6 +37,7 @@ #include <asm/cp15.h> #include <asm/cpu.h> #include <asm/cputype.h> +#include <asm/efi.h> #include <asm/elf.h> #include <asm/procinfo.h> #include <asm/psci.h> @@ -940,6 +942,7 @@ void __init setup_arch(char **cmdline_p) early_paging_init(mdesc, lookup_processor_type(read_cpuid_id())); setup_dma_zone(mdesc); early_fixmap_init(); + efi_init(); sanity_check_meminfo(); arm_memblock_init(mdesc);