]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ARM: add UEFI stub support
authorRoy Franz <roy.franz@linaro.org>
Thu, 24 Sep 2015 03:17:54 +0000 (20:17 -0700)
committerard <ard.biesheuvel@linaro.org>
Mon, 14 Dec 2015 09:38:21 +0000 (10:38 +0100)
This patch adds EFI stub 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>
Tested-by: Ryan Harkin <ryan.harkin@linaro.org>
Reviewed-by: Matt Fleming <matt@codeblueprint.co.uk>
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
arch/arm/Kconfig
arch/arm/boot/compressed/Makefile
arch/arm/boot/compressed/efi-header.S [new file with mode: 0644]
arch/arm/boot/compressed/head.S
arch/arm/boot/compressed/vmlinux.lds.S
arch/arm/include/asm/efi.h
drivers/firmware/efi/libstub/Makefile
drivers/firmware/efi/libstub/arm-stub.c
drivers/firmware/efi/libstub/arm32-stub.c [new file with mode: 0644]

index 446e49b56e6ae26a958e7c4214676df4c1664200..67f8303d513dba8865b8ea99b4eb2a6de3fe2978 100644 (file)
@@ -2041,6 +2041,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 support"
+       depends on OF && !CPU_BIG_ENDIAN && MMU && AUTO_ZRELADDR && !XIP_KERNEL
+       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"
index 3f9a9ebc77c389bbf794ee5a6840ceb0294b63f3..4c23a68a0917049e4c7e9e864dcebca38d6b75b2 100644 (file)
@@ -167,9 +167,11 @@ if [ $(words $(ZRELADDR)) -gt 1 -a "$(CONFIG_AUTO_ZRELADDR)" = "" ]; then \
        false; \
 fi
 
+efi-obj-$(CONFIG_EFI_STUB) := $(objtree)/drivers/firmware/efi/libstub/lib.a
+
 $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o \
                $(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) \
-               $(bswapsdi2) FORCE
+               $(bswapsdi2) $(efi-obj-y) FORCE
        @$(check_for_multiple_zreladdr)
        $(call if_changed,ld)
        @$(check_for_bad_syms)
diff --git a/arch/arm/boot/compressed/efi-header.S b/arch/arm/boot/compressed/efi-header.S
new file mode 100644 (file)
index 0000000..9d5dc4f
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+               .macro  __nop
+#ifdef CONFIG_EFI_STUB
+               @ This is almost but not quite a NOP, since it does clobber the
+               @ condition flags. But it 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   'M' | ('Z' << 8) | (0x1310 << 16)   @ tstne r0, #0x4d000
+#else
+               mov     r0, r0
+#endif
+               .endm
+
+               .macro  __EFI_HEADER
+#ifdef CONFIG_EFI_STUB
+               b       __efi_start
+
+               .set    start_offset, __efi_start - start
+               .org    start + 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.
+
+pe_header:
+               .ascii  "PE\0\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
+               .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:
+               @
+               @ 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\0\0"
+               .long   0                       @ VirtualSize
+               .long   0                       @ VirtualAddress
+               .long   0                       @ SizeOfRawData
+               .long   0                       @ PointerToRawData
+               .long   0                       @ PointerToRelocations
+               .long   0                       @ PointerToLineNumbers
+               .short  0                       @ NumberOfRelocations
+               .short  0                       @ NumberOfLineNumbers
+               .long   0x42100040              @ Characteristics
+
+               .ascii  ".text\0\0\0"
+               .long   _end - __efi_start      @ VirtualSize
+               .long   __efi_start             @ VirtualAddress
+               .long   _edata - __efi_start    @ SizeOfRawData
+               .long   __efi_start             @ PointerToRawData
+               .long   0                       @ PointerToRelocations
+               .long   0                       @ PointerToLineNumbers
+               .short  0                       @ NumberOfRelocations
+               .short  0                       @ NumberOfLineNumbers
+               .long   0xe0500020              @ Characteristics
+
+               .align  9
+__efi_start:
+#endif
+               .endm
index 06e983f59980ffc4c0955891d894bbbbaa5cb5e2..af11c2f8f3b7a63c350db162511c779617d70f61 100644 (file)
@@ -12,6 +12,8 @@
 #include <asm/assembler.h>
 #include <asm/v7m.h>
 
+#include "efi-header.S"
+
  AR_CLASS(     .arch   armv7-a )
  M_CLASS(      .arch   armv7-m )
 
 start:
                .type   start,#function
                .rept   7
-               mov     r0, r0
+               __nop
                .endr
    ARM(                mov     r0, r0          )
    ARM(                b       1f              )
@@ -139,7 +141,8 @@ start:
                .word   0x04030201      @ endianness flag
 
  THUMB(                .thumb                  )
-1:
+1:             __EFI_HEADER
+
  ARM_BE8(      setend  be              )       @ go BE8 if compiled for BE8
  AR_CLASS(     mrs     r9, cpsr        )
 #ifdef CONFIG_ARM_VIRT_EXT
@@ -1353,6 +1356,53 @@ __enter_kernel:
 
 reloc_code_end:
 
+#ifdef CONFIG_EFI_STUB
+               .align  2
+_start:                .long   start - .
+
+ENTRY(efi_stub_entry)
+               @ 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     ip, _start
+               ldr     r3, [ip]
+               add     r3, r3, ip
+               stmfd   sp!, {r3, lr}
+               mov     r2, sp                  @ pass zImage address in r2
+               bl      efi_entry
+
+               @ Check for error return from EFI stub. r0 has FDT address
+               @ or error code.
+               cmn     r0, #1
+               beq     efi_load_fail
+
+               @ Preserve return value of efi_entry() in r4
+               mov     r4, r0
+               bl      cache_clean_flush
+               bl      cache_off
+
+               @ 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
+               mov     r2, r4
+
+               @ Branch to (possibly) relocated zImage that is in [sp]
+               ldr     lr, [sp]
+               ldr     ip, =start_offset
+               add     lr, lr, ip
+               mov     pc, lr                          @ no mode switch
+
+efi_load_fail:
+               @ Return EFI_LOAD_ERROR to EFI firmware on error.
+               ldr     r0, =0x80000001
+               ldmfd   sp!, {ip, pc}
+ENDPROC(efi_stub_entry)
+#endif
+
                .align
                .section ".stack", "aw", %nobits
 .L_user_stack: .space  4096
index 2b60b843ac5e9517f06e3e20190afa983fa7d21f..81c493156ce87e12a4719268076c861da18e7a53 100644 (file)
@@ -48,6 +48,13 @@ SECTIONS
     *(.rodata)
     *(.rodata.*)
   }
+  .data : {
+    /*
+     * The EFI stub always executes from RAM, and runs strictly before the
+     * decompressor, so we can make an exception for its r/w data, and keep it
+     */
+    *(.data.efistub)
+  }
   .piggydata : {
     *(.piggydata)
   }
index c91e330616adca57f0dc65c7833af39e69f7d312..e0eea72deb87eb1ba3967b464a8848256565ee6c 100644 (file)
@@ -57,4 +57,27 @@ void efi_virtmap_unload(void);
 #define efi_init()
 #endif /* CONFIG_EFI */
 
+/* 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 */
index 3c0467d3688cff14df877fea66d61c3fbb3279bd..8cf9ccbf42d5829f366f48aaa967f98086b134d5 100644 (file)
@@ -34,6 +34,7 @@ $(obj)/lib-%.o: $(srctree)/lib/%.c FORCE
 lib-$(CONFIG_EFI_ARMSTUB)      += arm-stub.o fdt.o string.o \
                                   $(patsubst %.c,lib-%.o,$(arm-deps))
 
+lib-$(CONFIG_ARM)              += arm32-stub.o
 lib-$(CONFIG_ARM64)            += arm64-stub.o
 CFLAGS_arm64-stub.o            := -DTEXT_OFFSET=$(TEXT_OFFSET)
 
@@ -67,3 +68,11 @@ quiet_cmd_stubcopy = STUBCPY $@
                     $(OBJDUMP) -r $@ | grep $(STUBCOPY_RELOC-y)        \
                     && (echo >&2 "$@: absolute symbol references not allowed in the EFI stub"; \
                         rm -f $@; /bin/false); else /bin/false; fi
+
+#
+# ARM discards the .data section because it disallows r/w data in the
+# decompressor. So move our .data to .data.efistub, which is preserved
+# explicitly by the decompressor linker script.
+#
+STUBCOPY_FLAGS-$(CONFIG_ARM)   += --rename-section .data=.data.efistub
+STUBCOPY_RELOC-$(CONFIG_ARM)   := R_ARM_ABS
index 950c87f5d279335210088e4154eda135b24304d5..3397902e4040a30f566ff4b84555f17d7150d8f8 100644 (file)
@@ -303,8 +303,10 @@ fail:
  * The value chosen is the largest non-zero power of 2 suitable for this purpose
  * both on 32-bit and 64-bit ARM CPUs, to maximize the likelihood that it can
  * be mapped efficiently.
+ * Since 32-bit ARM could potentially execute with a 1G/3G user/kernel split,
+ * map everything below 1 GB.
  */
-#define EFI_RT_VIRTUAL_BASE    0x40000000
+#define EFI_RT_VIRTUAL_BASE    SZ_512M
 
 static int cmp_mem_desc(const void *l, const void *r)
 {
diff --git a/drivers/firmware/efi/libstub/arm32-stub.c b/drivers/firmware/efi/libstub/arm32-stub.c
new file mode 100644 (file)
index 0000000..495ebd6
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013 Linaro Ltd;  <roy.franz@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.
+ *
+ */
+#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 with the ARM
+        * boot protocol, which determines the base of DRAM by masking
+        * off the low 27 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, so that it appears in the lowest 128 MB
+        * memory window.
+        */
+       *image_size = image->image_size;
+       status = efi_relocate_kernel(sys_table, image_addr, *image_size,
+                                    *image_size,
+                                    dram_base + MAX_UNCOMP_KERNEL_SIZE, 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;
+}