Message ID | 1396637113-22790-7-git-send-email-leif.lindholm@linaro.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Hi Leif, On Fri, Apr 04, 2014 at 07:45:09PM +0100, Leif Lindholm wrote: > From: Mark Salter <msalter@redhat.com> > > 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. > Support in the COFF header for signed images was provided by > Ard Biesheuvel. > > Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> > Signed-off-by: Mark Salter <msalter@redhat.com> > Signed-off-by: Leif Lindholm <leif.lindholm@linaro.org> > Cc: Catalin Marinas <catalin.marinas@arm.com> > Cc: Matt Fleming <matt.fleming@intel.com> > --- > arch/arm64/Kconfig | 14 +++ > arch/arm64/kernel/Makefile | 3 + > arch/arm64/kernel/efi-entry.S | 100 ++++++++++++++++ > arch/arm64/kernel/efi-stub.c | 97 +++++++++++++++ > arch/arm64/kernel/head.S | 112 ++++++++++++++++++ > drivers/firmware/efi/arm-stub.c | 248 +++++++++++++++++++++++++++++++++++++++ > 6 files changed, 574 insertions(+) > create mode 100644 arch/arm64/kernel/efi-entry.S > create mode 100644 arch/arm64/kernel/efi-stub.c > create mode 100644 drivers/firmware/efi/arm-stub.c > > diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig > index d9f23ad..791ec61 100644 > --- a/arch/arm64/Kconfig > +++ b/arch/arm64/Kconfig > @@ -280,6 +280,20 @@ 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 > + bool "UEFI firmware support" > + depends on OF I note we're not depending on !CPU_BIG_ENDIAN here, and it looks like the implementation is not endian-clean (I've pointed out a few issues below). We need to fix that up for CPU_BIG_ENDIAN. For the moment we could depend on !CPU_BIG_ENDIAN which would at least to make it clear we don't support EFI && CPU_BIG_ENDIAN yet. The commit message should be updated to mention that. [...] > diff --git a/arch/arm64/kernel/efi-entry.S b/arch/arm64/kernel/efi-entry.S > new file mode 100644 > index 0000000..d99a034e > --- /dev/null > +++ b/arch/arm64/kernel/efi-entry.S > @@ -0,0 +1,100 @@ > +/* > + * EFI entry point. > + * > + * Copyright (C) 2013, 2014 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 I assume we always enter with the CPU in little-endian mode? It would be nice to note that here if so, or otherwise if not > + * > + * 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]! A comment as to what the additional space is for would be nice. It seems to be the relocated kernel address and padding for the required alignment? [...] > +/* > + * 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 > + * 2MiB so we know it won't cross a 2MiB boundary. > + */ Nit: the booting documentation is poor here, but the actual requirement is slightly different than that described. We currently need the DTB to be within the same naturally-aligned 512 MiB region as the kernel, though that restriction could be lifted with some rework of the initial page tables. [...] From here on in, all comments are regarding endianness issues and how we can fix them. If you do not care about BE, stop reading here. ;) > diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S > index 1fe5d8d..2aee658 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 > + /* > + * Magic "MZ" signature for PE/COFF > + * Little Endian: add x13, x18, #0x16 > + */ > +efi_head: > + .long 0x91005a4d As .long will emit the value in native endianness, this will only work for LE kernels. In BE this will be backwards (which looks to be an undefined instruction). > + b stext > +#else > b stext // branch to kernel start, magic > .long 0 // reserved > +#endif > .quad TEXT_OFFSET // Image load offset from start of RAM This also needs to be fixed up endianness wise; I have a series for that which I'll post shortly. > .quad 0 // reserved > .quad 0 // reserved > @@ -119,7 +129,109 @@ > .byte 0x52 > .byte 0x4d > .byte 0x64 > +#ifdef CONFIG_EFI > + .long pe_header - efi_head // Offset to the PE header. Likewise this will be backwards. Unfortunately values derived from calculations involving symbols can't be endian-swapped here due to lack of a suitable relocation -- we'll have to get the linker to do the endianness swap for us. I have a patch enabling the linker to endian-swap such values for us for 64-bit values, but it looks like we'll need other sizes too (for the shorts below). To prevent vast swathes of symbols appearing in the main linker script we could have a header to gather those into a FIXED_ENDIAN_SYMBOLS macro or similar. > +#else > .word 0 // reserved > +#endif > + > +#ifdef CONFIG_EFI > + .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 Everything here but the string will be the wrong way around on BE. We could add .{short,int,long}_le macros to do the endianness swapping of these values for us (which would also help to document the endianness). All other uses of {short,int,long} for non-zero values will need endianness correction. > + .short section_table - optional_header // SizeOfOptionalHeader This might need the linker's help to swap unless the assembler resolves the difference at assemble time. > + .long efi_stub_entry - efi_head // AddressOfEntryPoint > + .long stext - efi_head // BaseOfCode Likewise. > + .long _edata - efi_head // SizeOfImage This will definitely require the help of the linker for endianness swapping. > + // Everything before the kernel image is considered part of the header > + .long stext - efi_head // SizeOfHeaders And this. [...] > + .long _edata - stext // VirtualSize > + .long stext - efi_head // VirtualAddress > + .long _edata - stext // SizeOfRawData > + .long stext - efi_head // PointerToRawData And these. Cheers, Mark.
On Wed, 2014-04-09 at 15:20 +0100, Mark Rutland wrote: > > > > +config EFI > > + bool "UEFI firmware support" > > + depends on OF > > I note we're not depending on !CPU_BIG_ENDIAN here, and it looks like > the implementation is not endian-clean (I've pointed out a few issues > below). > > We need to fix that up for CPU_BIG_ENDIAN. For the moment we could > depend on !CPU_BIG_ENDIAN which would at least to make it clear we don't > support EFI && CPU_BIG_ENDIAN yet. The commit message should be updated > to mention that. > Yes, the !CPU_BIG_ENDIAN was there at one point but got lost along the way. It may be best to put it back for now, but I'll take a stab at getting the stub part sorted out based on your comments. Full runtime services will be trickier and need a followup patch in the future after the initial patch series goes in.
On Fri, Apr 4, 2014 at 1:45 PM, Leif Lindholm <leif.lindholm@linaro.org> wrote: > > +/* > + * EFI entry point for the arm/arm64 EFI stubs. This is the entrypoint > + * that is described in the PE/COFF header. Most of the code is the same > + * for both archictectures, with the arch-specific code provided in the > + * handle_kernel_image() function. > + */ > +unsigned long __init efi_entry(void *handle, efi_system_table_t *sys_table, > + unsigned long *image_addr) > +{ ... > + > + status = handle_cmdline_files(sys_table, image, cmdline_ptr, > + "initrd=", dram_base + SZ_512M, > + (unsigned long *)&initrd_addr, > + (unsigned long *)&initrd_size); So I know this patch is almost three years old, but why is there a 512M limit on the initrd size?
> On 8 Feb 2017, at 16:28, Timur Tabi <timur@codeaurora.org> wrote: > >> On Fri, Apr 4, 2014 at 1:45 PM, Leif Lindholm <leif.lindholm@linaro.org> wrote: >> >> +/* >> + * EFI entry point for the arm/arm64 EFI stubs. This is the entrypoint >> + * that is described in the PE/COFF header. Most of the code is the same >> + * for both archictectures, with the arch-specific code provided in the >> + * handle_kernel_image() function. >> + */ >> +unsigned long __init efi_entry(void *handle, efi_system_table_t *sys_table, >> + unsigned long *image_addr) >> +{ > > ... > >> + >> + status = handle_cmdline_files(sys_table, image, cmdline_ptr, >> + "initrd=", dram_base + SZ_512M, >> + (unsigned long *)&initrd_addr, >> + (unsigned long *)&initrd_size); > > So I know this patch is almost three years old, but why is there a > 512M limit on the initrd size? > How do you reckon this constitutes a limit? > -- > Qualcomm Innovation Center, Inc. > The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, > a Linux Foundation Collaborative Project.
On 02/08/2017 10:29 AM, Ard Biesheuvel wrote: >>> >> + status = handle_cmdline_files(sys_table, image, cmdline_ptr, >>> >> + "initrd=", dram_base + SZ_512M, >>> >> + (unsigned long *)&initrd_addr, >>> >> + (unsigned long *)&initrd_size); >> > >> > So I know this patch is almost three years old, but why is there a >> > 512M limit on the initrd size? >> > > How do you reckon this constitutes a limit? handle_cmdline_files() calls efi_high_alloc() with that limit. I'm still trying to understand all the details myself, but apparently our firmware and initrd need to fit within the first 512MB because of dram_base + SZ_512M. When we change "dram_base + SZ_512M" to "~0", everything works.
On Wed, Feb 08, 2017 at 10:35:02AM -0600, Timur Tabi wrote: > On 02/08/2017 10:29 AM, Ard Biesheuvel wrote: > >>>>> + status = handle_cmdline_files(sys_table, image, cmdline_ptr, > >>>>> + "initrd=", dram_base + SZ_512M, > >>>>> + (unsigned long *)&initrd_addr, > >>>>> + (unsigned long *)&initrd_size); > >>> > >>> So I know this patch is almost three years old, but why is there a > >>> 512M limit on the initrd size? > >>> > >How do you reckon this constitutes a limit? > > handle_cmdline_files() calls efi_high_alloc() with that limit. I'm > still trying to understand all the details myself, but apparently > our firmware and initrd need to fit within the first 512MB because > of dram_base + SZ_512M. When we change "dram_base + SZ_512M" to > "~0", everything works. Just to check, how big is that initrd? I guess it's possible that there simply isn't sufficient contiguous free memory in that range, even if the initrd isn't that large. Can you share the EFI memory map dump from booting with efi=debug? We originally needed to restrict this to ensure that the kernel could map the initrd (and I think the 512M restriction specifically was inherited from the DTB mapping restriction). Since then, we have relaxed things in the kernel, and today Documentation/arm64/booting.txt says: If an initrd/initramfs is passed to the kernel at boot, it must reside entirely within a 1 GB aligned physical memory window of up to 32 GB in size that fully covers the kernel Image as well. ... so I think the EFI stub should be able to take advantage of that relaxation. Ard? Thanks, Mark.
On 2/8/2017 10:03 AM, Mark Rutland wrote: > On Wed, Feb 08, 2017 at 10:35:02AM -0600, Timur Tabi wrote: >> On 02/08/2017 10:29 AM, Ard Biesheuvel wrote: >>>>>>> + status = handle_cmdline_files(sys_table, image, cmdline_ptr, >>>>>>> + "initrd=", dram_base + SZ_512M, >>>>>>> + (unsigned long *)&initrd_addr, >>>>>>> + (unsigned long *)&initrd_size); >>>>> >>>>> So I know this patch is almost three years old, but why is there a >>>>> 512M limit on the initrd size? >>>>> >>> How do you reckon this constitutes a limit? >> >> handle_cmdline_files() calls efi_high_alloc() with that limit. I'm >> still trying to understand all the details myself, but apparently >> our firmware and initrd need to fit within the first 512MB because >> of dram_base + SZ_512M. When we change "dram_base + SZ_512M" to >> "~0", everything works. > > Just to check, how big is that initrd? 120MB right now. Probably could be optimized, but I don't think that's really the issue here. The big problem is we don't have much memory free as the platform requires that all of firmware exists in the first 512MB. On systems with a lot of devices (ie 16+ STAT drives), UEFI ends up doing a lot of allocations, which pushes the platform over the edge due to the limited memory and fragmentation of what is available. > > I guess it's possible that there simply isn't sufficient contiguous free > memory in that range, even if the initrd isn't that large. Can you share > the EFI memory map dump from booting with efi=debug? > > We originally needed to restrict this to ensure that the kernel could > map the initrd (and I think the 512M restriction specifically was > inherited from the DTB mapping restriction). Since then, we have relaxed > things in the kernel, and today Documentation/arm64/booting.txt says: > > If an initrd/initramfs is passed to the kernel at boot, it must > reside entirely within a 1 GB aligned physical memory window of > up to 32 GB in size that fully covers the kernel Image as well. > > ... so I think the EFI stub should be able to take advantage of that > relaxation. > > Ard? > > Thanks, > Mark. > -- > To unsubscribe from this list: send the line "unsubscribe linux-efi" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html >
On 2/8/2017 10:03 AM, Mark Rutland wrote: > On Wed, Feb 08, 2017 at 10:35:02AM -0600, Timur Tabi wrote: >> On 02/08/2017 10:29 AM, Ard Biesheuvel wrote: >>>>>>> + status = handle_cmdline_files(sys_table, image, cmdline_ptr, >>>>>>> + "initrd=", dram_base + SZ_512M, >>>>>>> + (unsigned long *)&initrd_addr, >>>>>>> + (unsigned long *)&initrd_size); >>>>> >>>>> So I know this patch is almost three years old, but why is there a >>>>> 512M limit on the initrd size? >>>>> >>> How do you reckon this constitutes a limit? >> >> handle_cmdline_files() calls efi_high_alloc() with that limit. I'm >> still trying to understand all the details myself, but apparently >> our firmware and initrd need to fit within the first 512MB because >> of dram_base + SZ_512M. When we change "dram_base + SZ_512M" to >> "~0", everything works. > > Just to check, how big is that initrd? > > I guess it's possible that there simply isn't sufficient contiguous free > memory in that range, even if the initrd isn't that large. Can you share > the EFI memory map dump from booting with efi=debug? > > We originally needed to restrict this to ensure that the kernel could > map the initrd (and I think the 512M restriction specifically was > inherited from the DTB mapping restriction). Since then, we have relaxed > things in the kernel, and today Documentation/arm64/booting.txt says: > > If an initrd/initramfs is passed to the kernel at boot, it must > reside entirely within a 1 GB aligned physical memory window of > up to 32 GB in size that fully covers the kernel Image as well. > > ... so I think the EFI stub should be able to take advantage of that > relaxation. I agree. The wrinkle I can see in this is it looks like KASLR can put the kernel anywhere in RAM. How do we ensure initrd is within 32GB of the kernel on a system with 256 GB of RAM? > > Ard? > > Thanks, > Mark. > -- > To unsubscribe from this list: send the line "unsubscribe linux-efi" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html >
On Wed, Feb 08, 2017 at 10:30:37AM -0700, Jeffrey Hugo wrote: > On 2/8/2017 10:03 AM, Mark Rutland wrote: > >On Wed, Feb 08, 2017 at 10:35:02AM -0600, Timur Tabi wrote: > >>On 02/08/2017 10:29 AM, Ard Biesheuvel wrote: > >>>>>>>+ status = handle_cmdline_files(sys_table, image, cmdline_ptr, > >>>>>>>+ "initrd=", dram_base + SZ_512M, > >>>>>>>+ (unsigned long *)&initrd_addr, > >>>>>>>+ (unsigned long *)&initrd_size); > >>>>> > >>>>>So I know this patch is almost three years old, but why is there a > >>>>>512M limit on the initrd size? > >>>>> > >>>How do you reckon this constitutes a limit? > >> > >>handle_cmdline_files() calls efi_high_alloc() with that limit. I'm > >>still trying to understand all the details myself, but apparently > >>our firmware and initrd need to fit within the first 512MB because > >>of dram_base + SZ_512M. When we change "dram_base + SZ_512M" to > >>"~0", everything works. > > > >Just to check, how big is that initrd? > > > >I guess it's possible that there simply isn't sufficient contiguous free > >memory in that range, even if the initrd isn't that large. Can you share > >the EFI memory map dump from booting with efi=debug? > > > >We originally needed to restrict this to ensure that the kernel could > >map the initrd (and I think the 512M restriction specifically was > >inherited from the DTB mapping restriction). Since then, we have relaxed > >things in the kernel, and today Documentation/arm64/booting.txt says: > > > > If an initrd/initramfs is passed to the kernel at boot, it must > > reside entirely within a 1 GB aligned physical memory window of > > up to 32 GB in size that fully covers the kernel Image as well. > > > >... so I think the EFI stub should be able to take advantage of that > >relaxation. > > I agree. The wrinkle I can see in this is it looks like KASLR can > put the kernel anywhere in RAM. How do we ensure initrd is within > 32GB of the kernel on a system with 256 GB of RAM? The EFI stub chose the physical location of the kernel, and should know where it put it. The virtual location of the kernel shouldn't matter. At some point though, this does become best-effort, unless we want a SAT solver in the stub. Thanks, Mark.
> On 8 Feb 2017, at 17:03, Mark Rutland <mark.rutland@arm.com> wrote: > >> On Wed, Feb 08, 2017 at 10:35:02AM -0600, Timur Tabi wrote: >> On 02/08/2017 10:29 AM, Ard Biesheuvel wrote: >>>>>>> + status = handle_cmdline_files(sys_table, image, cmdline_ptr, >>>>>>> + "initrd=", dram_base + SZ_512M, >>>>>>> + (unsigned long *)&initrd_addr, >>>>>>> + (unsigned long *)&initrd_size); >>>>> >>>>> So I know this patch is almost three years old, but why is there a >>>>> 512M limit on the initrd size? >>>>> >>> How do you reckon this constitutes a limit? >> >> handle_cmdline_files() calls efi_high_alloc() with that limit. I'm >> still trying to understand all the details myself, but apparently >> our firmware and initrd need to fit within the first 512MB because >> of dram_base + SZ_512M. When we change "dram_base + SZ_512M" to >> "~0", everything works. > > Just to check, how big is that initrd? > > I guess it's possible that there simply isn't sufficient contiguous free > memory in that range, even if the initrd isn't that large. Can you share > the EFI memory map dump from booting with efi=debug? > > We originally needed to restrict this to ensure that the kernel could > map the initrd (and I think the 512M restriction specifically was > inherited from the DTB mapping restriction). Since then, we have relaxed > things in the kernel, and today Documentation/arm64/booting.txt says: > > If an initrd/initramfs is passed to the kernel at boot, it must > reside entirely within a 1 GB aligned physical memory window of > up to 32 GB in size that fully covers the kernel Image as well. > > ... so I think the EFI stub should be able to take advantage of that > relaxation. > > Ard? > Interestingly enough, this code originates on 32-bit ARM, where the linear mapping is only 800 MB so I suspect that may have something to do with it. I agree the stub should simply follow the rules laid out in booting.txt. I think nobody hit this because it is usually GRUB that loads the initrd not the EFI stub
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index d9f23ad..791ec61 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -280,6 +280,20 @@ 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 + bool "UEFI firmware support" + depends on OF + select LIBFDT + default y + help + This option permits an Image to be loaded directly + by EFI firmware without the use of a bootloader. + See Documentation/efi-stub.txt for more information. + + This is only useful on systems that have UEFI firmware. + However, even with this option, the resultant kernel should + continue to boot on existing non-UEFI platforms. + endmenu menu "Userspace binary formats" diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index 7d811d9..21009b7 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 \ @@ -22,6 +24,7 @@ arm64-obj-$(CONFIG_EARLY_PRINTK) += early_printk.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-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..d99a034e --- /dev/null +++ b/arch/arm64/kernel/efi-entry.S @@ -0,0 +1,100 @@ +/* + * EFI entry point. + * + * Copyright (C) 2013, 2014 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 callee preserved. + */ + mov x20, x0 // DTB address + ldr x0, [sp, #16] // relocated _text address + mov x21, x0 + + adrp x1, _text + add x1, x1, #:lo12:_text + adrp x2, _edata + add x2, x2, #:lo12:_edata + sub x1, x2, x1 + + bl __flush_dcache_area + ic ialluis + + /* Turn off Dcache and MMU */ + mrs x0, CurrentEL + cmp x0, #PSR_MODE_EL2t + ccmp x0, #PSR_MODE_EL2h, #0x4, ne + b.ne 1f + mrs x0, sctlr_el2 + bic x0, x0, #1 << 0 // clear SCTLR.M + bic x0, x0, #1 << 2 // clear SCTLR.C + msr sctlr_el2, x0 + isb + b 2f +1: + 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 +2: + /* 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..c416eff --- /dev/null +++ b/arch/arm64/kernel/efi-stub.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2013, 2014 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 <generated/utsrelease.h> + +/* + * EFI function call wrappers. These are not required for arm/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 + * 2MiB so we know it won't cross a 2MiB boundary. + */ +#define EFI_FDT_ALIGN SZ_2M /* used by allocate_new_fdt_and_exit_boot() */ +#define MAX_FDT_OFFSET SZ_512M + +#define efi_call_early(f, ...) sys_table_arg->boottime->f(__VA_ARGS__) + +static void efi_char16_printk(efi_system_table_t *sys_table_arg, + efi_char16_t *str); + +static efi_status_t efi_open_volume(efi_system_table_t *sys_table, + void *__image, void **__fh); +static efi_status_t efi_file_close(void *__fh, void *handle); + +static efi_status_t +efi_file_read(void *__fh, void *handle, unsigned long *size, void *addr); + +static efi_status_t +efi_file_size(efi_system_table_t *sys_table, void *__fh, + efi_char16_t *filename_16, void **handle, u64 *file_sz); + +/* Include shared EFI stub code */ +#include "../../../drivers/firmware/efi/efi-stub-helper.c" +#include "../../../drivers/firmware/efi/fdt.c" +#include "../../../drivers/firmware/efi/arm-stub.c" + + +static 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) +{ + efi_status_t status; + unsigned long kernel_size, kernel_memsize = 0; + + /* Relocate the image, if required. */ + kernel_size = _edata - _text; + if (*image_addr != (dram_base + TEXT_OFFSET)) { + kernel_memsize = kernel_size + (_end - _edata); + status = efi_relocate_kernel(sys_table, image_addr, + kernel_size, kernel_memsize, + dram_base + TEXT_OFFSET, + PAGE_SIZE); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Failed to relocate kernel\n"); + return status; + } + if (*image_addr != (dram_base + TEXT_OFFSET)) { + pr_efi_err(sys_table, "Failed to alloc kernel memory\n"); + efi_free(sys_table, kernel_memsize, *image_addr); + return EFI_ERROR; + } + *image_size = kernel_memsize; + } + + + return EFI_SUCCESS; +} diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S index 1fe5d8d..2aee658 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 + /* + * 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 + .long pe_header - efi_head // Offset to the PE header. +#else .word 0 // reserved +#endif + +#ifdef CONFIG_EFI + .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 diff --git a/drivers/firmware/efi/arm-stub.c b/drivers/firmware/efi/arm-stub.c new file mode 100644 index 0000000..b9b7c00 --- /dev/null +++ b/drivers/firmware/efi/arm-stub.c @@ -0,0 +1,248 @@ +/* + * EFI stub implementation that is shared by arm and arm64 architectures. + * This should be #included by the EFI stub implementation files. + * + * Copyright (C) 2013,2014 Linaro Limited + * Roy Franz <roy.franz@linaro.org + * Copyright (C) 2013 Red Hat, Inc. + * Mark Salter <msalter@redhat.com> + * + * This file is part of the Linux kernel, and is made available under the + * terms of the GNU General Public License version 2. + * + */ + + +static efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, + void *__image, void **__fh) +{ + efi_file_io_interface_t *io; + efi_loaded_image_t *image = __image; + efi_file_handle_t *fh; + efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID; + efi_status_t status; + void *handle = (void *)(unsigned long)image->device_handle; + + status = sys_table_arg->boottime->handle_protocol(handle, + &fs_proto, (void **)&io); + if (status != EFI_SUCCESS) { + efi_printk(sys_table_arg, "Failed to handle fs_proto\n"); + return status; + } + + status = io->open_volume(io, &fh); + if (status != EFI_SUCCESS) + efi_printk(sys_table_arg, "Failed to open volume\n"); + + *__fh = fh; + return status; +} +static efi_status_t efi_file_close(void *__fh, void *handle) +{ + efi_file_handle_t *fh = __fh; + + return fh->close(handle); +} + +static efi_status_t +efi_file_read(void *__fh, void *handle, unsigned long *size, void *addr) +{ + efi_file_handle_t *fh = __fh; + + return fh->read(handle, size, addr); +} + + +static efi_status_t +efi_file_size(efi_system_table_t *sys_table_arg, void *__fh, + efi_char16_t *filename_16, void **handle, u64 *file_sz) +{ + efi_file_handle_t *h, *fh = __fh; + efi_file_info_t *info; + efi_status_t status; + efi_guid_t info_guid = EFI_FILE_INFO_ID; + unsigned long info_sz; + + status = fh->open(fh, &h, filename_16, EFI_FILE_MODE_READ, (u64)0); + if (status != EFI_SUCCESS) { + efi_printk(sys_table_arg, "Failed to open file: "); + efi_char16_printk(sys_table_arg, filename_16); + efi_printk(sys_table_arg, "\n"); + return status; + } + + *handle = h; + + info_sz = 0; + status = h->get_info(h, &info_guid, &info_sz, NULL); + if (status != EFI_BUFFER_TOO_SMALL) { + efi_printk(sys_table_arg, "Failed to get file info size\n"); + return status; + } + +grow: + status = sys_table_arg->boottime->allocate_pool(EFI_LOADER_DATA, + info_sz, (void **)&info); + if (status != EFI_SUCCESS) { + efi_printk(sys_table_arg, "Failed to alloc mem for file info\n"); + return status; + } + + status = h->get_info(h, &info_guid, &info_sz, + info); + if (status == EFI_BUFFER_TOO_SMALL) { + sys_table_arg->boottime->free_pool(info); + goto grow; + } + + *file_sz = info->file_size; + sys_table_arg->boottime->free_pool(info); + + if (status != EFI_SUCCESS) + efi_printk(sys_table_arg, "Failed to get initrd info\n"); + + return status; +} + + + +static void efi_char16_printk(efi_system_table_t *sys_table_arg, + efi_char16_t *str) +{ + struct efi_simple_text_output_protocol *out; + + out = (struct efi_simple_text_output_protocol *)sys_table_arg->con_out; + efi_call_phys2(out->output_string, out, str); +} + + +/* + * This function handles the architcture specific differences between arm and + * arm64 regarding where the kernel image must be loaded and any memory that + * must be reserved. On failure it is required to free all + * all allocations it has made. + */ +static 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); +/* + * EFI entry point for the arm/arm64 EFI stubs. This is the entrypoint + * that is described in the PE/COFF header. Most of the code is the same + * for both archictectures, with the arch-specific code provided in the + * handle_kernel_image() function. + */ +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 = 0; + unsigned long dram_base; + /* addr/point and size pairs for memory management*/ + unsigned long initrd_addr; + u64 initrd_size = 0; + unsigned long fdt_addr; /* Original DTB */ + u64 fdt_size = 0; /* We don't get size from configuration table */ + char *cmdline_ptr = NULL; + int cmdline_size = 0; + unsigned long new_fdt_addr; + efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID; + unsigned long reserve_addr = 0; + unsigned long reserve_size = 0; + + /* Check if we were booted by the EFI firmware */ + if (sys_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) + goto fail; + + pr_efi(sys_table, "Booting Linux Kernel...\n"); + + /* + * Get a handle to the loaded image protocol. This is used to get + * information about the running image, such as size and the command + * line. + */ + status = efi_call_phys3(sys_table->boottime->handle_protocol, + handle, &loaded_image_proto, (void *)&image); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Failed to get loaded image protocol\n"); + goto fail; + } + + dram_base = get_dram_base(sys_table); + if (dram_base == EFI_ERROR) { + pr_efi_err(sys_table, "Failed to find DRAM base\n"); + goto fail; + } + status = handle_kernel_image(sys_table, image_addr, &image_size, + &reserve_addr, + &reserve_size, + dram_base, image); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Failed to relocate kernel\n"); + goto fail; + } + + /* + * Get the command line from EFI, using the LOADED_IMAGE + * protocol. We are going to copy the command line into the + * device tree, so this can be allocated anywhere. + */ + cmdline_ptr = efi_convert_cmdline(sys_table, image, &cmdline_size); + if (!cmdline_ptr) { + pr_efi_err(sys_table, "getting command line via LOADED_IMAGE_PROTOCOL\n"); + goto fail_free_image; + } + + /* Load a device tree from the configuration table, if present. */ + fdt_addr = (uintptr_t)get_fdt(sys_table); + if (!fdt_addr) { + 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(sys_table, "Failed to load device tree!\n"); + goto fail_free_cmdline; + } + } + + status = handle_cmdline_files(sys_table, image, cmdline_ptr, + "initrd=", dram_base + SZ_512M, + (unsigned long *)&initrd_addr, + (unsigned long *)&initrd_size); + if (status != EFI_SUCCESS) + pr_efi_err(sys_table, "Failed initrd from command line!\n"); + + new_fdt_addr = fdt_addr; + status = allocate_new_fdt_and_exit_boot(sys_table, handle, + &new_fdt_addr, dram_base + MAX_FDT_OFFSET, + initrd_addr, initrd_size, cmdline_ptr, + fdt_addr, fdt_size); + + /* + * If all went well, we need to return the FDT address to the + * calling function so it can be passed to kernel as part of + * the kernel boot protocol. + */ + if (status == EFI_SUCCESS) + return new_fdt_addr; + + pr_efi_err(sys_table, "Failed to update FDT and exit boot services\n"); + + efi_free(sys_table, initrd_size, initrd_addr); + efi_free(sys_table, fdt_size, fdt_addr); + +fail_free_cmdline: + efi_free(sys_table, cmdline_size, (unsigned long)cmdline_ptr); + +fail_free_image: + efi_free(sys_table, image_size, *image_addr); + efi_free(sys_table, reserve_size, reserve_addr); +fail: + return EFI_ERROR; +}