This adds support to boot a big endian kernel from UEFI firmware, which is always little endian. The EFI stub itself is built as little endian, and embedded into a big endian kernel image. To enable this, we need to build all the stub's dependencies as little endian, including FDT parsing code and string functions. This is accomplished by building little endian versions of those support files and link them into a static library which is used by the inner stub build. Signed-off-by: Ard Biesheuvel <ard.biesheuvel@xxxxxxxxxx> --- arch/arm64/kernel/Makefile | 7 +++- arch/arm64/kernel/efi-entry.S | 42 +++++++++++++++++------ arch/arm64/kernel/efistub-le/Makefile | 52 +++++++++++++++++++++++++++++ arch/arm64/kernel/efistub-le/efi-le-entry.S | 13 ++++++++ arch/arm64/kernel/efistub-le/efistub-le.lds | 35 +++++++++++++++++++ arch/arm64/kernel/efistub-le/le.h | 12 +++++++ arch/arm64/kernel/efistub-le/strstr.c | 20 +++++++++++ drivers/firmware/efi/libstub/fdt.c | 4 +++ 8 files changed, 173 insertions(+), 12 deletions(-) create mode 100644 arch/arm64/kernel/efistub-le/Makefile create mode 100644 arch/arm64/kernel/efistub-le/efi-le-entry.S create mode 100644 arch/arm64/kernel/efistub-le/efistub-le.lds create mode 100644 arch/arm64/kernel/efistub-le/le.h create mode 100644 arch/arm64/kernel/efistub-le/strstr.c diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index afaeb734295a..942cd042e93e 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -27,7 +27,12 @@ arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o arm64-obj-$(CONFIG_ARM64_CPU_SUSPEND) += sleep.o suspend.o arm64-obj-$(CONFIG_JUMP_LABEL) += jump_label.o arm64-obj-$(CONFIG_KGDB) += kgdb.o -arm64-obj-$(CONFIG_EFI) += efi.o efi-stub.o efi-entry.o + +arm64-efi-obj-y := efi.o +arm64-efi-obj-$(CONFIG_EFI_STUB) += efi-stub.o efi-entry.o +arm64-efi-obj-$(CONFIG_EFI_LE_STUB) += efistub-le/ +arm64-efi-obj-$(CONFIG_CPU_BIG_ENDIAN) += efi-be-runtime.o efi-be-call.o +arm64-obj-$(CONFIG_EFI) += $(arm64-efi-obj-y) 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 index a0016d3a17da..89f34bb86cfd 100644 --- a/arch/arm64/kernel/efi-entry.S +++ b/arch/arm64/kernel/efi-entry.S @@ -34,8 +34,34 @@ ENTRY(efi_stub_entry) * Create a stack frame to save FP/LR with extra space * for image_addr variable passed to efi_entry(). */ - stp x29, x30, [sp, #-32]! + stp x29, x30, [sp, #-48]! + stp x22, x23, [sp, #32] +#ifdef CONFIG_EFI_LE_STUB + adr x4, efi_stub_entry + ldp w8, w9, [x4, #-32] +STUB_BE(rev w8, w8 ) +STUB_BE(rev w9, w9 ) + add x8, x4, w8, sxtw // x8: base of Image + add x9, x4, w9, sxtw // x9: offset of linux_banner + + ldp x22, x23, [x4, #-24] // x22: size of Image +STUB_BE(rev x23, x23 ) // x23: stext offset + + /* + * Get a pointer to linux_banner in the outer image and store it + * in this image. + */ + adrp x4, le_linux_banner + str x9, [x4, #:lo12:le_linux_banner] +#else + adrp x8, _text + add x8, x8, #:lo12:_text // x8: base of Image + adrp x9, _edata + add x9, x9, #:lo12:_edata + sub x22, x9, x8 // x22: size of Image + ldr x23, =stext_offset // x23: stext offset +#endif /* * Call efi_entry to do the real work. * x0 and x1 are already set up by firmware. Current runtime @@ -45,8 +71,6 @@ ENTRY(efi_stub_entry) * 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 @@ -61,18 +85,13 @@ ENTRY(efi_stub_entry) */ mov x20, x0 // DTB address ldr x0, [sp, #16] // relocated _text address - ldr x21, =stext_offset - add x21, x0, x21 + add x21, x0, x23 /* * Flush dcache covering current runtime addresses * of kernel text/data. Then flush all of icache. */ - adrp x1, _text - add x1, x1, #:lo12:_text - adrp x2, _edata - add x2, x2, #:lo12:_edata - sub x1, x2, x1 + mov x1, x22 bl __flush_dcache_area ic ialluis @@ -103,7 +122,8 @@ ENTRY(efi_stub_entry) efi_load_fail: mov x0, #EFI_LOAD_ERROR - ldp x29, x30, [sp], #32 + ldp x22, x23, [sp, #32] + ldp x29, x30, [sp], #48 ret ENDPROC(efi_stub_entry) diff --git a/arch/arm64/kernel/efistub-le/Makefile b/arch/arm64/kernel/efistub-le/Makefile new file mode 100644 index 000000000000..38347b0633c8 --- /dev/null +++ b/arch/arm64/kernel/efistub-le/Makefile @@ -0,0 +1,52 @@ + +# +# Build a little endian EFI stub and wrap it into a single .o +# + +# the LE objects making up the LE efi stub +le-objs := efi-entry.o efi-stub.o strstr.o cache.o \ + lib-memchr.o lib-memcmp.o lib-memcpy.o lib-memmove.o \ + lib-memset.o lib-strchr.o lib-strlen.o lib-strncmp.o \ + fdt-fdt.o fdt-fdt_ro.o fdt-fdt_rw.o fdt-fdt_sw.o \ + fdt-fdt_wip.o fdt-fdt_empty_tree.o \ + libstub-fdt.o libstub-arm-stub.o libstub-efi-stub-helper.o + +extra-y := efi-le-stub.bin efi-le-stub.elf $(le-objs) + +KBUILD_CFLAGS := $(subst -pg,,$(KBUILD_CFLAGS)) -fno-stack-protector \ + -mlittle-endian -I$(srctree)/scripts/dtc/libfdt + +le-targets := $(addprefix $(obj)/, $(le-objs)) +$(le-targets): KBUILD_AFLAGS += -mlittle-endian -include $(srctree)/$(src)/le.h + +$(obj)/efi-entry.o: $(obj)/../efi-entry.S FORCE + $(call if_changed_dep,as_o_S) + +CFLAGS_efi-stub.o += -DTEXT_OFFSET=$(TEXT_OFFSET) +$(obj)/efi-stub.o: $(obj)/../efi-stub.c FORCE + $(call if_changed_dep,cc_o_c) + +$(obj)/cache.o: $(src)/../../mm/cache.S FORCE + $(call if_changed_dep,as_o_S) + +$(obj)/lib-%.o: $(src)/../../lib/%.S FORCE + $(call if_changed_dep,as_o_S) + +$(obj)/fdt-%.o: $(srctree)/lib/%.c FORCE + $(call if_changed_dep,cc_o_c) + +$(obj)/libstub-%.o: $(srctree)/drivers/firmware/efi/libstub/%.c FORCE + $(call if_changed_dep,cc_o_c) + +$(obj)/efi-le-stub.elf: LDFLAGS=-EL -Map $@.map -T +$(obj)/efi-le-stub.elf: $(src)/efistub-le.lds $(le-targets) FORCE + $(call if_changed,ld) + +$(obj)/efi-le-stub.bin: OBJCOPYFLAGS=-O binary +$(obj)/efi-le-stub.bin: $(obj)/efi-le-stub.elf FORCE + $(call if_changed,objcopy) + +# the BE object containing the entire LE stub +obj-y := efi-le-entry.o + +$(obj)/efi-le-entry.o: $(obj)/efi-le-stub.bin diff --git a/arch/arm64/kernel/efistub-le/efi-le-entry.S b/arch/arm64/kernel/efistub-le/efi-le-entry.S new file mode 100644 index 000000000000..f615430209e5 --- /dev/null +++ b/arch/arm64/kernel/efistub-le/efi-le-entry.S @@ -0,0 +1,13 @@ + +#include <linux/linkage.h> + + .text + .align 12 + .long _text - efi_stub_entry + .long linux_banner - efi_stub_entry + .quad _kernel_size_le + .quad stext_offset + .quad 0 +ENTRY(efi_stub_entry) + .incbin "arch/arm64/kernel/efistub-le/efi-le-stub.bin" +ENDPROC(efi_stub_entry) diff --git a/arch/arm64/kernel/efistub-le/efistub-le.lds b/arch/arm64/kernel/efistub-le/efistub-le.lds new file mode 100644 index 000000000000..20361c43aa2e --- /dev/null +++ b/arch/arm64/kernel/efistub-le/efistub-le.lds @@ -0,0 +1,35 @@ + +ENTRY(efi_stub_entry) + +SECTIONS { + /* + * The inner and outer alignment of this chunk of code need to be the + * same so that PC relative references using adrp/add or adrp/ldr pairs + * will work correctly. + * Skip 32 bytes here, so we can put the binary blob at an offset of + * 4k + 0x20 in the outer image, and use the gap to share constants + * emitted by the outer linker but required in the stub. + */ + .text 0x20 : { + arch/arm64/kernel/efistub-le/efi-entry.o(.init.text) + *(.init.text) + *(.text) + *(.text*) + } + .rodata : { + . = ALIGN(16); + *(.rodata) + *(.rodata*) + *(.init.rodata) + } + .data : { + . = ALIGN(16); + *(.data) + *(.data*) + le_linux_banner = .; + . += 8; + } + /DISCARD/ : { + *(__ex_table) + } +} diff --git a/arch/arm64/kernel/efistub-le/le.h b/arch/arm64/kernel/efistub-le/le.h new file mode 100644 index 000000000000..f4a28a5f6815 --- /dev/null +++ b/arch/arm64/kernel/efistub-le/le.h @@ -0,0 +1,12 @@ + +/* + * This is a bit of a hack, but it is necessary to correctly compile .S files + * that contain CPU_LE()/CPU_BE() statements, as these are defined to depend on + * CONFIG_ symbols and not on the endianness of the compiler. + */ +#ifdef CONFIG_CPU_BIG_ENDIAN +#define STUB_BE(code...) code +#else +#define STUB_BE(code...) +#endif +#undef CONFIG_CPU_BIG_ENDIAN diff --git a/arch/arm64/kernel/efistub-le/strstr.c b/arch/arm64/kernel/efistub-le/strstr.c new file mode 100644 index 000000000000..daed0bbcc0c6 --- /dev/null +++ b/arch/arm64/kernel/efistub-le/strstr.c @@ -0,0 +1,20 @@ + +#include <linux/types.h> +#include <linux/string.h> + +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; +} diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c index a56bb3528755..651c639a8a18 100644 --- a/drivers/firmware/efi/libstub/fdt.c +++ b/drivers/firmware/efi/libstub/fdt.c @@ -22,6 +22,10 @@ efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt, unsigned long map_size, unsigned long desc_size, u32 desc_ver) { +#ifdef CONFIG_EFI_LE_STUB + extern char const *le_linux_banner; + char const *linux_banner = le_linux_banner; +#endif int node, prev; int status; u32 fdt_val32; -- 1.8.3.2 -- 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