diff mbox

[15/16] Add EFI stub for ARM

Message ID 1376090777-20090-16-git-send-email-roy.franz@linaro.org (mailing list archive)
State New, archived
Headers show

Commit Message

Roy Franz Aug. 9, 2013, 11:26 p.m. UTC
This patch adds EFI stub support for the ARM Linux kernel.  The EFI stub
operations similarly to the x86 stub: 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 loading the initrd (optionaly) and
device tree from the system partition based on the kernel command line.
The stub updates the device tree as necessary, including adding reserved
memory regions and adding entries for EFI runtime services. The PE/COFF
"MZ" header at offset 0 results in the first instruction being an add
that corrupts r5, which is not used by the zImage interface.

Signed-off-by: Roy Franz <roy.franz@linaro.org>
---
 arch/arm/boot/compressed/Makefile     |   15 +-
 arch/arm/boot/compressed/efi-header.S |  111 ++++++++
 arch/arm/boot/compressed/efi-stub.c   |  448 +++++++++++++++++++++++++++++++++
 arch/arm/boot/compressed/efi-stub.h   |    5 +
 arch/arm/boot/compressed/head.S       |   90 ++++++-
 5 files changed, 661 insertions(+), 8 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/boot/compressed/efi-stub.h

Comments

Mark Salter Aug. 12, 2013, 2:51 p.m. UTC | #1
On Fri, 2013-08-09 at 16:26 -0700, Roy Franz wrote:
> +static int relocate_kernel(efi_system_table_t *sys_table,
> +                          unsigned long *zimage_addr,
> +                          unsigned long zimage_size,
> +                          unsigned long min_addr, unsigned long max_addr)
> +{
> +       /* Get current address of kernel. */
> +       unsigned long cur_zimage_addr = *zimage_addr;
> +       unsigned long new_addr = 0;
> +
> +       efi_status_t status;
> +
> +       if (!zimage_addr || !zimage_size)
> +               return EFI_INVALID_PARAMETER;
> +

This parameter check is too late. You already used zimage_addr to
initialize cur_zimage_addr.

--Mark
Roy Franz Aug. 13, 2013, 12:01 a.m. UTC | #2
On Mon, Aug 12, 2013 at 7:51 AM, Mark Salter <msalter@redhat.com> wrote:
> On Fri, 2013-08-09 at 16:26 -0700, Roy Franz wrote:
>> +static int relocate_kernel(efi_system_table_t *sys_table,
>> +                          unsigned long *zimage_addr,
>> +                          unsigned long zimage_size,
>> +                          unsigned long min_addr, unsigned long max_addr)
>> +{
>> +       /* Get current address of kernel. */
>> +       unsigned long cur_zimage_addr = *zimage_addr;
>> +       unsigned long new_addr = 0;
>> +
>> +       efi_status_t status;
>> +
>> +       if (!zimage_addr || !zimage_size)
>> +               return EFI_INVALID_PARAMETER;
>> +
>
> This parameter check is too late. You already used zimage_addr to
> initialize cur_zimage_addr.
>
> --Mark
>

Thanks - I'll fix this in the next version.

Roy
Dave Martin Aug. 13, 2013, 2:19 p.m. UTC | #3
On Fri, Aug 09, 2013 at 04:26:16PM -0700, Roy Franz wrote:
> This patch adds EFI stub support for the ARM Linux kernel.  The EFI stub
> operations similarly to the x86 stub: 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 loading the initrd (optionaly) and
> device tree from the system partition based on the kernel command line.
> The stub updates the device tree as necessary, including adding reserved
> memory regions and adding entries for EFI runtime services. The PE/COFF
> "MZ" header at offset 0 results in the first instruction being an add
> that corrupts r5, which is not used by the zImage interface.

Thanks for the update -- a few more comments, nothing major.

> Signed-off-by: Roy Franz <roy.franz@linaro.org>
> ---
>  arch/arm/boot/compressed/Makefile     |   15 +-
>  arch/arm/boot/compressed/efi-header.S |  111 ++++++++
>  arch/arm/boot/compressed/efi-stub.c   |  448 +++++++++++++++++++++++++++++++++
>  arch/arm/boot/compressed/efi-stub.h   |    5 +
>  arch/arm/boot/compressed/head.S       |   90 ++++++-
>  5 files changed, 661 insertions(+), 8 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/boot/compressed/efi-stub.h
> 
> diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile
> index 7ac1610..5fad8bd 100644
> --- a/arch/arm/boot/compressed/Makefile
> +++ b/arch/arm/boot/compressed/Makefile
> @@ -103,11 +103,22 @@ 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
> +OBJS	+= atags_to_fdt.o
> +USE_LIBFDT = y
> +endif
> +
> +ifeq ($(CONFIG_EFI_STUB),y)
> +CFLAGS_efi-stub.o += -DTEXT_OFFSET=$(TEXT_OFFSET)
> +OBJS	+= efi-stub.o
> +USE_LIBFDT = y
> +endif
> +
> +ifeq ($(USE_LIBFDT),y)
> +OBJS	+= $(libfdt_objs)
>  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 0000000..6965e0f
> --- /dev/null
> +++ b/arch/arm/boot/compressed/efi-header.S
> @@ -0,0 +1,111 @@
> +@ Copyright (C) 2013 Linaro Ltd;  <roy.franz@linaro.org>
> +@
> +@ This file contains the PE/COFF header that is part of the
> +@ EFI stub.
> +@
> +
> +	.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			@ 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	_edata - efi_stub_entry		@ SizeOfCode
> +
> +	.long	0				@ SizeOfInitializedData
> +	.long	0				@ SizeOfUninitializedData
> +
> +	.long	efi_stub_entry			@ AddressOfEntryPoint
> +	.long	efi_stub_entry			@ BaseOfCode
> +	.long	0				@ data
> +
> +extra_header_fields:
> +	.long	0				@ ImageBase
> +	.long	0x20				@ SectionAlignment
> +	.long	0x20				@ FileAlignment
> +	.short	0				@ MajorOperatingSystemVersion
> +	.short	0				@ MinorOperatingSystemVersion
> +	.short	0				@ MajorImageVersion
> +	.short	0				@ MinorImageVersion
> +	.short	0				@ MajorSubsystemVersion
> +	.short	0				@ MinorSubsystemVersion
> +	.long	0				@ Win32VersionValue
> +
> +	.long	_edata				@ SizeOfImage
> +
> +	@ Everything before the entry point is considered part of the header
> +	.long	efi_stub_entry			@ 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	0x0				@ NumberOfRvaAndSizes
> +
> +	# 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 - efi_stub_entry		@ VirtualSize
> +	.long	efi_stub_entry			@ VirtualAddress
> +	.long	_edata - efi_stub_entry		@ SizeOfRawData
> +	.long	efi_stub_entry			@ 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)
> diff --git a/arch/arm/boot/compressed/efi-stub.c b/arch/arm/boot/compressed/efi-stub.c
> new file mode 100644
> index 0000000..4fce68b
> --- /dev/null
> +++ b/arch/arm/boot/compressed/efi-stub.c
> @@ -0,0 +1,448 @@
> +/*
> + * 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 <libfdt.h>
> +#include "efi-stub.h"
> +
> +/* EFI function call wrappers.  These are not required for
> + * ARM, but wrappers are required for X86 to convert between
> + * ABIs.  These wrappers are provided to allow code sharing
> + * between X86 and ARM.  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)
> +
> +/* The maximum uncompressed kernel size is 32 MBytes, so we will reserve
> + * that for the decompressed kernel.  We have no easy way to tell what
> + * the actuall size of code + data the uncompressed kernel will use.
> + */
> +#define MAX_UNCOMP_KERNEL_SIZE	0x02000000
> +
> +/* The kernel zImage should be located between 32 Mbytes
> + * and 128 MBytes 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	0x08000000
> +#define MIN_ZIMAGE_OFFSET	MAX_UNCOMP_KERNEL_SIZE
> +
> +#define PRINTK_PREFIX		"EFIstub: "
> +
> +struct fdt_region {
> +	u64 base;
> +	u64 size;
> +};
> +
> +
> +/* Include shared EFI stub code */
> +#include "../../../../drivers/firmware/efi/efi-stub-helper.c"
> +
> +static int relocate_kernel(efi_system_table_t *sys_table,
> +			   unsigned long *zimage_addr,
> +			   unsigned long zimage_size,
> +			   unsigned long min_addr, unsigned long max_addr)
> +{
> +	/* Get current address of kernel. */
> +	unsigned long cur_zimage_addr = *zimage_addr;
> +	unsigned long new_addr = 0;
> +
> +	efi_status_t status;
> +
> +	if (!zimage_addr || !zimage_size)
> +		return EFI_INVALID_PARAMETER;
> +
> +	if (cur_zimage_addr > min_addr
> +	    && (cur_zimage_addr + zimage_size) < max_addr) {
> +		/* We don't need to do anything, as kernel is at an
> +		 * acceptable address already.
> +		 */
> +		return EFI_SUCCESS;
> +	}
> +	/*
> +	 * The EFI firmware loader could have placed the kernel image
> +	 * anywhere in memory, but the kernel has restrictions on the
> +	 * min and max physical address it can run at.
> +	 */
> +	status = efi_low_alloc(sys_table, zimage_size, 0,
> +			   &new_addr, min_addr);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to allocate usable memory for kernel.\n");
> +		return status;
> +	}
> +
> +	if (new_addr > (max_addr - zimage_size)) {
> +		efi_free(sys_table, zimage_size, new_addr);
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to allocate usable memory for kernel.\n");
> +		return EFI_INVALID_PARAMETER;
> +	}
> +
> +	/* We know source/dest won't overlap since both memory ranges
> +	 * have been allocated by UEFI, so we can safely use memcpy.
> +	 */
> +	memcpy((void *)new_addr, (void *)(unsigned long)cur_zimage_addr,
> +	       zimage_size);
> +
> +	/* Return the load address  */
> +	*zimage_addr = new_addr;
> +
> +	return status;
> +}
> +
> +
> +/* Convert the unicode UEFI command line to ASCII to pass to kernel.
> + * Size of memory allocated return in *cmd_line_len.
> + * Returns NULL on error.
> + */
> +static char *convert_cmdline_to_ascii(efi_system_table_t *sys_table,
> +				      efi_loaded_image_t *image,
> +				      unsigned long *cmd_line_len,
> +				      u32 max_addr)
> +{
> +	u16 *s2;
> +	u8 *s1 = NULL;
> +	unsigned long cmdline_addr = 0;
> +	int load_options_size = image->load_options_size / 2; /* ASCII */
> +	void *options = (u16 *)image->load_options;
> +	int options_size = 0;
> +	int status;
> +	int i;
> +	u16 zero = 0;
> +
> +	if (options) {
> +		s2 = options;
> +		while (*s2 && *s2 != '\n' && options_size < load_options_size) {
> +			s2++;
> +			options_size++;
> +		}
> +	}
> +
> +	if (options_size == 0) {
> +		/* No command line options, so return empty string*/
> +		options_size = 1;
> +		options = &zero;
> +	}
> +
> +	options_size++;  /* NUL termination */
> +
> +	status = efi_high_alloc(sys_table, options_size, 0,
> +			    &cmdline_addr, max_addr);
> +	if (status != EFI_SUCCESS)
> +		return NULL;
> +
> +	s1 = (u8 *)(unsigned long)cmdline_addr;
> +	s2 = (u16 *)options;
> +
> +	for (i = 0; i < options_size - 1; i++)
> +		*s1++ = *s2++;
> +
> +	*s1 = '\0';
> +
> +	*cmd_line_len = options_size;
> +	return (char *)(unsigned long)cmdline_addr;
> +}
> +
> +
> +static u32 update_fdt(efi_system_table_t *sys_table, void *orig_fdt, void *fdt,
> +		      int new_fdt_size, char *cmdline_ptr, u64 initrd_addr,
> +		      u64 initrd_size, efi_memory_desc_t *memory_map,
> +		      int map_size, int desc_size)
> +{
> +	int node;
> +	int status;
> +	unsigned long fdt_val;
> +
> +	status = fdt_open_into(orig_fdt, fdt, new_fdt_size);
> +	if (status != 0)
> +		goto fdt_set_fail;
> +
> +	node = fdt_subnode_offset(fdt, 0, "chosen");
> +	if (node < 0) {
> +		node = fdt_add_subnode(fdt, 0, "chosen");
> +		if (node < 0) {
> +			status = node; /* node is error code when negative */
> +			goto fdt_set_fail;
> +		}
> +	}
> +
> +	if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) {
> +		status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
> +				     strlen(cmdline_ptr) + 1);
> +		if (status)
> +			goto fdt_set_fail;
> +	}
> +
> +	/* Set intird address/end in device tree, if present */
> +	if (initrd_size != 0) {
> +		u64 initrd_image_end;
> +		u64 initrd_image_start = cpu_to_fdt64(initrd_addr);
> +		status = fdt_setprop(fdt, node, "linux,initrd-start",
> +				     &initrd_image_start, sizeof(u64));
> +		if (status)
> +			goto fdt_set_fail;
> +		initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size);
> +		status = fdt_setprop(fdt, node, "linux,initrd-end",
> +				     &initrd_image_end, sizeof(u64));
> +		if (status)
> +			goto fdt_set_fail;
> +	}
> +
> +	/* Add FDT entries for EFI runtime services in chosen node. */
> +	node = fdt_subnode_offset(fdt, 0, "chosen");
> +	fdt_val = cpu_to_fdt32((unsigned long)sys_table);
> +	status = fdt_setprop(fdt, node, "efi-system-table",
> +			     &fdt_val, sizeof(fdt_val));
> +	if (status)
> +		goto fdt_set_fail;
> +
> +	fdt_val = cpu_to_fdt32(desc_size);
> +	status = fdt_setprop(fdt, node, "efi-mmap-desc-size",
> +			     &fdt_val, sizeof(fdt_val));
> +	if (status)
> +		goto fdt_set_fail;
> +
> +	fdt_val = cpu_to_fdt32(map_size);
> +	status = fdt_setprop(fdt, node, "efi-runtime-mmap-size",
> +			     &fdt_val, sizeof(fdt_val));
> +	if (status)
> +		goto fdt_set_fail;
> +
> +	fdt_val = cpu_to_fdt32((unsigned long)memory_map);
> +	status = fdt_setprop(fdt, node, "efi-runtime-mmap",
> +			     &fdt_val, sizeof(fdt_val));
> +	if (status)
> +		goto fdt_set_fail;
> +
> +	return EFI_SUCCESS;

This looks better.

> +
> +fdt_set_fail:
> +	if (status == -FDT_ERR_NOSPACE)
> +		return EFI_BUFFER_TOO_SMALL;
> +
> +	return EFI_LOAD_ERROR;
> +}
> +
> +
> +

Maybe add a comment to indicate that this returns the address of the
relocated fdt, or EFI_LOAD_ERROR.

By default "int" feels more likely to return a status code.

It is not common to return pointers using the "int" type: it may be
preferable to use unsigned long of void * instead.  This won't
change the functionality.

Casts to (int) which could overflow the signed range can cause GCC
to generate bizarre code in some situations, because C doesn't
have to guarantee wrapping when casting to signed types.  Since we
just pass that value through without doing any arithmetic I think we're
unlikely to hit that here, but it's best avoided anyhow.

> +int efi_entry(void *handle, efi_system_table_t *sys_table,
> +	      unsigned long *zimage_addr)
> +{
> +	efi_loaded_image_t *image;
> +	int status;
> +	unsigned long nr_pages;
> +	const struct fdt_region *region;
> +
> +	void *fdt;
> +	int err;
> +	int node;
> +	unsigned long zimage_size = 0;
> +	unsigned long dram_base;
> +	/* addr/point and size pairs for memory management*/
> +	u64 initrd_addr;
> +	u64 initrd_size = 0;
> +	u64 fdt_addr;
> +	u64 fdt_size = 0;
> +	u64 kernel_reserve_addr;
> +	u64 kernel_reserve_size = 0;
> +	char *cmdline_ptr;
> +	unsigned long cmdline_size = 0;
> +
> +	unsigned long map_size, desc_size;
> +	unsigned long mmap_key;
> +	efi_memory_desc_t *memory_map;
> +
> +	unsigned long new_fdt_size;
> +	unsigned long new_fdt_addr;
> +
> +	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;
> +
> +	efi_printk(sys_table, PRINTK_PREFIX"Booting Linux using EFI stub.\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) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: 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 = convert_cmdline_to_ascii(sys_table, image,
> +					       &cmdline_size, 0xFFFFFFFF);
> +	if (!cmdline_ptr) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: converting command line to ascii failed.\n");

The real reason for this failure is failure to allocate memory: there's
no other way it can fail.

So, the error message could be "Unable to allocate memory for command
line"

> +		goto fail;
> +	}
> +
> +	/* We first load the device tree, as we need to get the base address of
> +	 * DRAM from the device tree.  The zImage, device tree, and initrd
> +	 * have address restrictions that are relative to the base of DRAM.
> +	 */
> +	status = handle_cmdline_files(sys_table, image, cmdline_ptr, "dtb=",
> +				      0xffffffff, &fdt_addr, &fdt_size);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load device tree blob.\n");
> +		goto fail_free_cmdline;
> +	}
> +
> +	err = fdt_check_header((void *)(unsigned long)fdt_addr);
> +	if (err != 0) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: device tree header not valid\n");
> +		goto fail_free_fdt;
> +	}
> +	if (fdt_totalsize((void *)(unsigned long)fdt_addr) > fdt_size) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Incomplete device tree.\n");
> +		goto fail_free_fdt;
> +
> +	}
> +
> +
> +	/* Look up the base of DRAM from the device tree.*/
> +	fdt = (void *)(u32)fdt_addr;
> +	node = fdt_subnode_offset(fdt, 0, "memory");
> +	region = fdt_getprop(fdt, node, "reg", NULL);
> +	if (region) {
> +		dram_base = fdt64_to_cpu(region->base);
> +	} else {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: no 'memory' node in device tree.\n");
> +		goto fail_free_fdt;
> +	}
> +
> +	/* Reserve memory for the uncompressed kernel image. */
> +	kernel_reserve_addr = dram_base;
> +	kernel_reserve_size = MAX_UNCOMP_KERNEL_SIZE;
> +	nr_pages = round_up(kernel_reserve_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
> +	status = efi_call_phys4(sys_table->boottime->allocate_pages,
> +				EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
> +				nr_pages, &kernel_reserve_addr);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: unable to allocate memory for uncompressed kernel.\n");
> +		goto fail_free_fdt;
> +	}
> +
> +	/* Relocate the zImage, if required. */
> +	zimage_size = image->image_size;
> +	status = relocate_kernel(sys_table, zimage_addr, zimage_size,
> +				 dram_base + MIN_ZIMAGE_OFFSET,
> +				 dram_base + ZIMAGE_OFFSET_LIMIT);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to relocate kernel\n");
> +		goto fail_free_kernel_reserve;
> +	}
> +
> +	status = handle_cmdline_files(sys_table, image, cmdline_ptr, "initrd=",
> +				      dram_base + ZIMAGE_OFFSET_LIMIT,
> +				      &initrd_addr, &initrd_size);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load initrd\n");
> +		goto fail_free_zimage;
> +	}
> +
> +	/* 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.*/
> +	new_fdt_size = fdt_size + cmdline_size + 0x200;
> +
> +fdt_alloc_retry:

Minor stylistic thing, but looping using goto is not too readable.

The following while loop might be a bit clearer, but I leave it
up to you.

while (1) {

> +	status = efi_high_alloc(sys_table, new_fdt_size, 0, &new_fdt_addr,
> +			    dram_base + ZIMAGE_OFFSET_LIMIT);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to allocate memory for new device tree.\n");
> +		goto fail_free_initrd;
> +	}
> +
> +	/* Now that we have done our final memory allocation (and free)
> +	 * we can get the memory map key needed
> +	 * forexit_boot_services.*/
> +	status = efi_get_memory_map(sys_table, &memory_map, &map_size,
> +				    &desc_size, &mmap_key);
> +	if (status != EFI_SUCCESS)
> +		goto fail_free_new_fdt;
> +
> +	status = update_fdt(sys_table,
> +			    fdt, (void *)new_fdt_addr, new_fdt_size,
> +			    cmdline_ptr,
> +			    initrd_addr, initrd_size,
> +			    memory_map, map_size, desc_size);

if (status == EFI_SUCCESS)
	break;

instead of

> +
> +	if (status != EFI_SUCCESS) {

then

	if (status != EFI_BUFFER_TOO_SMALL)
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to constuct new device tree.\n");
> +		goto fail_free_initrd;

instead of

> +		if (status == EFI_BUFFER_TOO_SMALL) {

then

> +			/* 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 += new_fdt_size/4;

And then just loop
	}

instead of the rest:

> +			goto fdt_alloc_retry;
> +		}
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to constuct new device tree.\n");
> +		goto fail_free_initrd;
> +	}
> +
> +	/* 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) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: exit boot services failed.\n");
> +		goto fail_free_mmap;
> +	}
> +
> +
> +	/* Now we need to return the FDT address to the calling
> +	 * assembly to this 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_zimage:
> +	efi_free(sys_table, zimage_size, *zimage_addr);
> +
> +fail_free_kernel_reserve:
> +	efi_free(sys_table, kernel_reserve_addr, kernel_reserve_size);
> +
> +fail_free_fdt:
> +	efi_free(sys_table, fdt_size, fdt_addr);
> +
> +fail_free_cmdline:
> +	efi_free(sys_table, cmdline_size, (u32)cmdline_ptr);
> +
> +fail:
> +	return EFI_STUB_ERROR;
> +}
> diff --git a/arch/arm/boot/compressed/efi-stub.h b/arch/arm/boot/compressed/efi-stub.h
> new file mode 100644
> index 0000000..0fe9376
> --- /dev/null
> +++ b/arch/arm/boot/compressed/efi-stub.h
> @@ -0,0 +1,5 @@
> +#ifndef _ARM_EFI_STUB_H
> +#define _ARM_EFI_STUB_H
> +/* Error code returned to ASM code instead of valid FDT address. */
> +#define EFI_STUB_ERROR		(~0)
> +#endif
> diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S
> index 75189f1..5401a3a 100644
> --- a/arch/arm/boot/compressed/head.S
> +++ b/arch/arm/boot/compressed/head.S
> @@ -10,6 +10,7 @@
>   */
>  #include <linux/linkage.h>
>  #include <asm/assembler.h>
> +#include "efi-stub.h"
>  
>  	.arch	armv7-a
>  /*
> @@ -120,21 +121,99 @@
>   */
>  		.align
>  		.arm				@ Always enter in ARM state
> +		.text
>  start:
>  		.type	start,#function
> -		.rept	7
> +#ifdef CONFIG_EFI_STUB
> +		@ Magic MSDOS signature for PE/COFF + ADD opcode
> +		.word	0x62805a4d
> +#else
> +		mov	r0, r0
> +#endif
> +		.rept	5
>  		mov	r0, r0
>  		.endr
> -   ARM(		mov	r0, r0		)
> -   ARM(		b	1f		)
> - THUMB(		adr	r12, BSYM(1f)	)
> - THUMB(		bx	r12		)
> +
> +		adrl	r12, BSYM(zimage_continue)
> + ARM(		mov     pc, r12 )
> + THUMB(		bx	r12     )
> +		@ zimage_continue will be in ARM or thumb mode as configured
>  
>  		.word	0x016f2818		@ Magic numbers to help the loader
>  		.word	start			@ absolute load/run zImage address
>  		.word	_edata			@ zImage end address
> +
> +#ifdef CONFIG_EFI_STUB
> +		@ Portions of the MSDOS file header must be at offset
> +		@ 0x3c from the start of the file.  All PE/COFF headers
> +		@ are kept contiguous for simplicity.
> +#include "efi-header.S"
> +
> +efi_stub_entry:
> +		@ The EFI stub entry point is not at a fixed address, however
> +		@ this address must be set in the PE/COFF header.
> +		@ EFI entry point is in A32 mode, switch to T32 if configured.
> + THUMB(		adr	r12, BSYM(1f)	)
> + THUMB(		bx	r12		)
>   THUMB(		.thumb			)
>  1:
> +		@ 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 return of new entry point of
> +		@ zImage, as EFI stub may copy the kernel.  Pass address
> +		@ of space in r2 - EFI stub will fill in the pointer.
> +
> +		sub	sp, sp, #8		@ we only need 4 bytes,
> +						@ but keep stack 8 byte aligned.
> +		mov	r2, sp

You can save an instruction here: skip these two instructions, and see
below just before the call to efi_entry [1]

> +		@ Pass our actual runtime start address in pointer data
> +		adr	r11, LC0		@ address of LC0 at run time
> +		ldr	r12, [r11, #0]		@ address of LC0 at link time

Can we just move this delta calculation after efi_entry?

You don't need to specifiy an explicit offset if it's zero, btw:

	ldr	r12, [r11]

works just as well.  Same for the other instances.

> +
> +		sub	r3, r11, r12		@ calculate the delta offset
> +		str	r3, [r2, #0]

I think r2 is still equal to sp, so

	str	r3, [sp]

should be OK.

[1]Or, combine this with the modification of sp:

	str	r3, [sp, #-8]!
	mov	r2, sp

> +		bl	efi_entry
> +
> +		@ get new zImage entry address from stack, put into r3
> +		ldr	r3, [sp, #0]
> +		add	sp, sp, #8  		@ restore stack

	ldr	r3, [sp], #8

> +
> +		@ Check for error return from EFI stub
> +		mov	r1, #EFI_STUB_ERROR
> +		cmp	r0, r1

	cmp r0, #EFI_STUB_ERROR

probably works.  The assembler will turn this into a cmn as necessary.

> +		beq	efi_load_fail
> +
> +
> +		@ Save return values of efi_entry
> +		stmfd	sp!, {r0, r3}
> +		bl	cache_clean_flush
> +		bl	cache_off
> +		ldmfd   sp!, {r0, r3}
> +
> +		@ Set parameters for booting zImage according to boot protocol
> +		@ put FDT address in r2, it was returned by efi_entry()
> +		@ r1 is FDT machine type, and r0 needs to be 0
> +		mov	r2, r0
> +		mov	r1, #0xFFFFFFFF
> +		mov	r0, #0
> +
> +		@ Branch to (possibly) relocated zImage that is in r3
> +		@ Make sure we are in A32 mode, as zImage requires
> + THUMB(		bx	r3		)
> + ARM(		mov	pc, r3		)
> +
> +efi_load_fail:
> +		@ Return EFI_LOAD_ERROR to EFI firmware on error.
> +		@ Switch back to ARM mode for EFI is done based on
> +		@ return address on stack
> +		ldr	r0, =0x80000001
> +		ldmfd	sp!, {fp, pc}
> +#endif
> +
> + THUMB(		.thumb			)
> +zimage_continue:
>  		mrs	r9, cpsr
>  #ifdef CONFIG_ARM_VIRT_EXT
>  		bl	__hyp_stub_install	@ get into SVC mode, reversibly
> @@ -167,7 +246,6 @@ not_angel:
>  		 * by the linker here, but it should preserve r7, r8, and r9.
>  		 */
>  
> -		.text
>  
>  #ifdef CONFIG_AUTO_ZRELADDR
>  		@ determine final kernel image address
> -- 
> 1.7.10.4
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Roy Franz Aug. 14, 2013, 2:07 a.m. UTC | #4
On Tue, Aug 13, 2013 at 6:44 PM, Roy Franz <roy.franz@linaro.org> wrote:
>
> Thanks Dave - comments inline, and I have an updated head.S diff at the end.
>
> Roy
>
>
> On Tue, Aug 13, 2013 at 7:19 AM, Dave Martin <Dave.Martin@arm.com> wrote:
>>
>> On Fri, Aug 09, 2013 at 04:26:16PM -0700, Roy Franz wrote:
>> > This patch adds EFI stub support for the ARM Linux kernel.  The EFI stub
>> > operations similarly to the x86 stub: 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 loading the initrd (optionaly) and
>> > device tree from the system partition based on the kernel command line.
>> > The stub updates the device tree as necessary, including adding reserved
>> > memory regions and adding entries for EFI runtime services. The PE/COFF
>> > "MZ" header at offset 0 results in the first instruction being an add
>> > that corrupts r5, which is not used by the zImage interface.
>>
>> Thanks for the update -- a few more comments, nothing major.
>>
>> > Signed-off-by: Roy Franz <roy.franz@linaro.org>
>> > ---
>> >  arch/arm/boot/compressed/Makefile     |   15 +-
>> >  arch/arm/boot/compressed/efi-header.S |  111 ++++++++
>
> ...
>>
>>
>> > +             goto fdt_set_fail;
>> > +
>> > +     return EFI_SUCCESS;
>>
>> This looks better.
>>
>> > +
>> > +fdt_set_fail:
>> > +     if (status == -FDT_ERR_NOSPACE)
>> > +             return EFI_BUFFER_TOO_SMALL;
>> > +
>> > +     return EFI_LOAD_ERROR;
>> > +}
>> > +
>> > +
>> > +
>>
>> Maybe add a comment to indicate that this returns the address of the
>> relocated fdt, or EFI_LOAD_ERROR.
>>
>> By default "int" feels more likely to return a status code.
>>
>> It is not common to return pointers using the "int" type: it may be
>> preferable to use unsigned long of void * instead.  This won't
>> change the functionality.
>>
>> Casts to (int) which could overflow the signed range can cause GCC
>> to generate bizarre code in some situations, because C doesn't
>> have to guarantee wrapping when casting to signed types.  Since we
>> just pass that value through without doing any arithmetic I think we're
>> unlikely to hit that here, but it's best avoided anyhow.
>
>
> The function now returns only status, not the FDT address, so I have changed
> it to an int.
> When I changed the function to no longer do the memory allocation for the
> new FDT this changed,
> but I missed changing the return type to int.
>
>
>>
>>
>> > +int efi_entry(void *handle, efi_system_table_t *sys_table,
>> > +           unsigned long *zimage_addr)
>> > +{
>> > +     efi_loaded_image_t *image;
>> > +     int status;
>> > +     unsigned long nr_pages;
>> > +     const struct fdt_region *region;
>> > +
>> > +     void *fdt;
>> > +     int err;
>> > +     int node;
>> > +     unsigned long zimage_size = 0;
>> > +     unsigned long dram_base;
>> > +     /* addr/point and size pairs for memory management*/
>> > +     u64 initrd_addr;
>> > +     u64 initrd_size = 0;
>> > +     u64 fdt_addr;
>> > +     u64 fdt_size = 0;
>> > +     u64 kernel_reserve_addr;
>> > +     u64 kernel_reserve_size = 0;
>> > +     char *cmdline_ptr;
>> > +     unsigned long cmdline_size = 0;
>> > +
>> > +     unsigned long map_size, desc_size;
>> > +     unsigned long mmap_key;
>> > +     efi_memory_desc_t *memory_map;
>> > +
>> > +     unsigned long new_fdt_size;
>> > +     unsigned long new_fdt_addr;
>> > +
>> > +     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;
>> > +
>> > +     efi_printk(sys_table, PRINTK_PREFIX"Booting Linux using EFI
>> > stub.\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) {
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: 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 = convert_cmdline_to_ascii(sys_table, image,
>> > +                                            &cmdline_size, 0xFFFFFFFF);
>> > +     if (!cmdline_ptr) {
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: converting
>> > command line to ascii failed.\n");
>>
>> The real reason for this failure is failure to allocate memory: there's
>> no other way it can fail.
>>
>> So, the error message could be "Unable to allocate memory for command
>> line"
>
>
> done.
>>
>>
>> > +             goto fail;
>> > +     }
>> > +
>> > +     /* We first load the device tree, as we need to get the base
>> > address of
>> > +      * DRAM from the device tree.  The zImage, device tree, and initrd
>> > +      * have address restrictions that are relative to the base of
>> > DRAM.
>> > +      */
>> > +     status = handle_cmdline_files(sys_table, image, cmdline_ptr,
>> > "dtb=",
>> > +                                   0xffffffff, &fdt_addr, &fdt_size);
>> > +     if (status != EFI_SUCCESS) {
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load
>> > device tree blob.\n");
>> > +             goto fail_free_cmdline;
>> > +     }
>> > +
>> > +     err = fdt_check_header((void *)(unsigned long)fdt_addr);
>> > +     if (err != 0) {
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: device tree
>> > header not valid\n");
>> > +             goto fail_free_fdt;
>> > +     }
>> > +     if (fdt_totalsize((void *)(unsigned long)fdt_addr) > fdt_size) {
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Incomplete
>> > device tree.\n");
>> > +             goto fail_free_fdt;
>> > +
>> > +     }
>> > +
>> > +
>> > +     /* Look up the base of DRAM from the device tree.*/
>> > +     fdt = (void *)(u32)fdt_addr;
>> > +     node = fdt_subnode_offset(fdt, 0, "memory");
>> > +     region = fdt_getprop(fdt, node, "reg", NULL);
>> > +     if (region) {
>> > +             dram_base = fdt64_to_cpu(region->base);
>> > +     } else {
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: no 'memory'
>> > node in device tree.\n");
>> > +             goto fail_free_fdt;
>> > +     }
>> > +
>> > +     /* Reserve memory for the uncompressed kernel image. */
>> > +     kernel_reserve_addr = dram_base;
>> > +     kernel_reserve_size = MAX_UNCOMP_KERNEL_SIZE;
>> > +     nr_pages = round_up(kernel_reserve_size, EFI_PAGE_SIZE) /
>> > EFI_PAGE_SIZE;
>> > +     status = efi_call_phys4(sys_table->boottime->allocate_pages,
>> > +                             EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
>> > +                             nr_pages, &kernel_reserve_addr);
>> > +     if (status != EFI_SUCCESS) {
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: unable to
>> > allocate memory for uncompressed kernel.\n");
>> > +             goto fail_free_fdt;
>> > +     }
>> > +
>> > +     /* Relocate the zImage, if required. */
>> > +     zimage_size = image->image_size;
>> > +     status = relocate_kernel(sys_table, zimage_addr, zimage_size,
>> > +                              dram_base + MIN_ZIMAGE_OFFSET,
>> > +                              dram_base + ZIMAGE_OFFSET_LIMIT);
>> > +     if (status != EFI_SUCCESS) {
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to
>> > relocate kernel\n");
>> > +             goto fail_free_kernel_reserve;
>> > +     }
>> > +
>> > +     status = handle_cmdline_files(sys_table, image, cmdline_ptr,
>> > "initrd=",
>> > +                                   dram_base + ZIMAGE_OFFSET_LIMIT,
>> > +                                   &initrd_addr, &initrd_size);
>> > +     if (status != EFI_SUCCESS) {
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load
>> > initrd\n");
>> > +             goto fail_free_zimage;
>> > +     }
>> > +
>> > +     /* 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.*/
>> > +     new_fdt_size = fdt_size + cmdline_size + 0x200;
>> > +
>> > +fdt_alloc_retry:
>>
>> Minor stylistic thing, but looping using goto is not too readable.
>>
>> The following while loop might be a bit clearer, but I leave it
>> up to you.
>>
>> while (1) {
>>
>> > +     status = efi_high_alloc(sys_table, new_fdt_size, 0, &new_fdt_addr,
>> > +                         dram_base + ZIMAGE_OFFSET_LIMIT);
>> > +     if (status != EFI_SUCCESS) {
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to
>> > allocate memory for new device tree.\n");
>> > +             goto fail_free_initrd;
>> > +     }
>> > +
>> > +     /* Now that we have done our final memory allocation (and free)
>> > +      * we can get the memory map key needed
>> > +      * forexit_boot_services.*/
>> > +     status = efi_get_memory_map(sys_table, &memory_map, &map_size,
>> > +                                 &desc_size, &mmap_key);
>> > +     if (status != EFI_SUCCESS)
>> > +             goto fail_free_new_fdt;
>> > +
>> > +     status = update_fdt(sys_table,
>> > +                         fdt, (void *)new_fdt_addr, new_fdt_size,
>> > +                         cmdline_ptr,
>> > +                         initrd_addr, initrd_size,
>> > +                         memory_map, map_size, desc_size);
>>
>> if (status == EFI_SUCCESS)
>>         break;
>>
>> instead of
>>
>> > +
>> > +     if (status != EFI_SUCCESS) {
>>
>> then
>>
>>         if (status != EFI_BUFFER_TOO_SMALL)
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to
>> > constuct new device tree.\n");
>> > +             goto fail_free_initrd;
>>
>> instead of
>>
>> > +             if (status == EFI_BUFFER_TOO_SMALL) {
>>
>> then
>>
>> > +                     /* 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 += new_fdt_size/4;
>>
>> And then just loop
>>         }
>>
>> instead of the rest:
>>
> I have replaced the allocation retry with a while loop as suggested.
>
>> > +                     goto fdt_alloc_retry;
>> > +             }
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to
>> > constuct new device tree.\n");
>> > +             goto fail_free_initrd;
>> > +     }
>> > +
>> > +     /* 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) {
>> > +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: exit boot
>> > services failed.\n");
>> > +             goto fail_free_mmap;
>> > +     }
>> > +
>> > +
>> > +     /* Now we need to return the FDT address to the calling
>> > +      * assembly to this 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_zimage:
>> > +     efi_free(sys_table, zimage_size, *zimage_addr);
>> > +
>> > +fail_free_kernel_reserve:
>> > +     efi_free(sys_table, kernel_reserve_addr, kernel_reserve_size);
>> > +
>> > +fail_free_fdt:
>> > +     efi_free(sys_table, fdt_size, fdt_addr);
>> > +
>> > +fail_free_cmdline:
>> > +     efi_free(sys_table, cmdline_size, (u32)cmdline_ptr);
>> > +
>> > +fail:
>> > +     return EFI_STUB_ERROR;
>> > +}
>> > diff --git a/arch/arm/boot/compressed/efi-stub.h
>> > b/arch/arm/boot/compressed/efi-stub.h
>> > new file mode 100644
>> > index 0000000..0fe9376
>> > --- /dev/null
>> > +++ b/arch/arm/boot/compressed/efi-stub.h
>> > @@ -0,0 +1,5 @@
>> > +#ifndef _ARM_EFI_STUB_H
>> > +#define _ARM_EFI_STUB_H
>> > +/* Error code returned to ASM code instead of valid FDT address. */
>> > +#define EFI_STUB_ERROR               (~0)
>> > +#endif
>> > diff --git a/arch/arm/boot/compressed/head.S
>> > b/arch/arm/boot/compressed/head.S
>> > index 75189f1..5401a3a 100644
>> > --- a/arch/arm/boot/compressed/head.S
>> > +++ b/arch/arm/boot/compressed/head.S
>> > @@ -10,6 +10,7 @@
>> >   */
>> >  #include <linux/linkage.h>
>> >  #include <asm/assembler.h>
>> > +#include "efi-stub.h"
>> >
>> >       .arch   armv7-a
>> >  /*
>> > @@ -120,21 +121,99 @@
>> >   */
>> >               .align
>> >               .arm                            @ Always enter in ARM
>> > state
>> > +             .text
>> >  start:
>> >               .type   start,#function
>> > -             .rept   7
>> > +#ifdef CONFIG_EFI_STUB
>> > +             @ Magic MSDOS signature for PE/COFF + ADD opcode
>> > +             .word   0x62805a4d
>> > +#else
>> > +             mov     r0, r0
>> > +#endif
>> > +             .rept   5
>> >               mov     r0, r0
>> >               .endr
>> > -   ARM(              mov     r0, r0          )
>> > -   ARM(              b       1f              )
>> > - THUMB(              adr     r12, BSYM(1f)   )
>> > - THUMB(              bx      r12             )
>> > +
>> > +             adrl    r12, BSYM(zimage_continue)
>> > + ARM(                mov     pc, r12 )
>> > + THUMB(              bx      r12     )
>> > +             @ zimage_continue will be in ARM or thumb mode as
>> > configured
>> >
>> >               .word   0x016f2818              @ Magic numbers to help
>> > the loader
>> >               .word   start                   @ absolute load/run zImage
>> > address
>> >               .word   _edata                  @ zImage end address
>> > +
>> > +#ifdef CONFIG_EFI_STUB
>> > +             @ Portions of the MSDOS file header must be at offset
>> > +             @ 0x3c from the start of the file.  All PE/COFF headers
>> > +             @ are kept contiguous for simplicity.
>> > +#include "efi-header.S"
>> > +
>> > +efi_stub_entry:
>> > +             @ The EFI stub entry point is not at a fixed address,
>> > however
>> > +             @ this address must be set in the PE/COFF header.
>> > +             @ EFI entry point is in A32 mode, switch to T32 if
>> > configured.
>> > + THUMB(              adr     r12, BSYM(1f)   )
>> > + THUMB(              bx      r12             )
>> >   THUMB(              .thumb                  )
>> >  1:
>> > +             @ 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 return of new entry point of
>> > +             @ zImage, as EFI stub may copy the kernel.  Pass address
>> > +             @ of space in r2 - EFI stub will fill in the pointer.
>> > +
>> > +             sub     sp, sp, #8              @ we only need 4 bytes,
>> > +                                             @ but keep stack 8 byte
>> > aligned.
>> > +             mov     r2, sp
>>
>> You can save an instruction here: skip these two instructions, and see
>> below just before the call to efi_entry [1]
>>
>> > +             @ Pass our actual runtime start address in pointer data
>> > +             adr     r11, LC0                @ address of LC0 at run
>> > time
>> > +             ldr     r12, [r11, #0]          @ address of LC0 at link
>> > time
>>
>> Can we just move this delta calculation after efi_entry?
>>
>> You don't need to specifiy an explicit offset if it's zero, btw:
>>
>>         ldr     r12, [r11]
>>
>> works just as well.  Same for the other instances.
>
>
> So after looking at this it was pretty messed up.  The LC0 business was to
> compute the
> linktime/runtime offset, which I had used to lookup the uncompressed kernel
> size.
> That ended up not being useful, but this code stayed around.  I've reworked
> this
> section of code to remove that cruft and incorporate your suggestions.
>
> I've put an updated diff of just head.S at the end of the email.
>
>>
>> > +
>> > +             sub     r3, r11, r12            @ calculate the delta
>> > offset
>> > +             str     r3, [r2, #0]
>>
>> I think r2 is still equal to sp, so
>>
>>         str     r3, [sp]
>>
>> should be OK.
>>
>> [1]Or, combine this with the modification of sp:
>>
>>         str     r3, [sp, #-8]!
>>         mov     r2, sp
>>
>> > +             bl      efi_entry
>> > +
>> > +             @ get new zImage entry address from stack, put into r3
>> > +             ldr     r3, [sp, #0]
>> > +             add     sp, sp, #8              @ restore stack
>>
>>         ldr     r3, [sp], #8
>>
>> > +
>> > +             @ Check for error return from EFI stub
>> > +             mov     r1, #EFI_STUB_ERROR
>> > +             cmp     r0, r1
>>
>>         cmp r0, #EFI_STUB_ERROR
>>
>> probably works.  The assembler will turn this into a cmn as necessary.
>>
>> > +             beq     efi_load_fail
>> > +
>> > +
>> > +             @ Save return values of efi_entry
>> > +             stmfd   sp!, {r0, r3}
>> > +             bl      cache_clean_flush
>> > +             bl      cache_off
>> > +             ldmfd   sp!, {r0, r3}
>> > +
>> > +             @ Set parameters for booting zImage according to boot
>> > protocol
>> > +             @ put FDT address in r2, it was returned by efi_entry()
>> > +             @ r1 is FDT machine type, and r0 needs to be 0
>> > +             mov     r2, r0
>> > +             mov     r1, #0xFFFFFFFF
>> > +             mov     r0, #0
>> > +
>> > +             @ Branch to (possibly) relocated zImage that is in r3
>> > +             @ Make sure we are in A32 mode, as zImage requires
>> > + THUMB(              bx      r3              )
>> > + ARM(                mov     pc, r3          )
>> > +
>> > +efi_load_fail:
>> > +             @ Return EFI_LOAD_ERROR to EFI firmware on error.
>> > +             @ Switch back to ARM mode for EFI is done based on
>> > +             @ return address on stack
>> > +             ldr     r0, =0x80000001
>> > +             ldmfd   sp!, {fp, pc}
>> > +#endif
>> > +
>> > + THUMB(              .thumb                  )
>> > +zimage_continue:
>> >               mrs     r9, cpsr
>> >  #ifdef CONFIG_ARM_VIRT_EXT
>> >               bl      __hyp_stub_install      @ get into SVC mode,
>> > reversibly
>> > @@ -167,7 +246,6 @@ not_angel:
>> >                * by the linker here, but it should preserve r7, r8, and
>> > r9.
>> >                */
>> >
>> > -             .text
>> >
>> >  #ifdef CONFIG_AUTO_ZRELADDR
>> >               @ determine final kernel image address
>> > --
>> > 1.7.10.4
>> >
>> >
>> > _______________________________________________
>> > linux-arm-kernel mailing list
>> > linux-arm-kernel@lists.infradead.org
>> > http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
>
> diff --git a/arch/arm/boot/compressed/head.S
> b/arch/arm/boot/compressed/head.S
> index 75189f1..820a238 100644
> --- a/arch/arm/boot/compressed/head.S
> +++ b/arch/arm/boot/compressed/head.S
> @@ -10,6 +10,7 @@
>   */
>  #include <linux/linkage.h>
>  #include <asm/assembler.h>
> +#include "efi-stub.h"
>
>   .arch armv7-a
>  /*
> @@ -120,21 +121,92 @@
>   */
>   .align
>   .arm @ Always enter in ARM state
> + .text
>  start:
>   .type start,#function
> - .rept 7
> +#ifdef CONFIG_EFI_STUB
> + @ Magic MSDOS signature for PE/COFF + ADD opcode
> + @ the EFI stub only supports little endian, as the EFI functions
> + @ it invokes are little endian.
> + .word 0x62805a4d
> +#else
> + mov r0, r0
> +#endif
> + .rept 5
>   mov r0, r0
>   .endr
> -   ARM( mov r0, r0 )
> -   ARM( b 1f )
> - THUMB( adr r12, BSYM(1f) )
> - THUMB( bx r12 )
> +
> + adrl r12, BSYM(zimage_continue)
> + ARM( mov     pc, r12 )
> + THUMB( bx r12     )
> + @ zimage_continue will be in ARM or thumb mode as configured
>
>   .word 0x016f2818 @ Magic numbers to help the loader
>   .word start @ absolute load/run zImage address
>   .word _edata @ zImage end address
> +
> +#ifdef CONFIG_EFI_STUB
> + @ Portions of the MSDOS file header must be at offset
> + @ 0x3c from the start of the file.  All PE/COFF headers
> + @ are kept contiguous for simplicity.
> +#include "efi-header.S"
> +
> +efi_stub_entry:
> + @ The EFI stub entry point is not at a fixed address, however
> + @ this address must be set in the PE/COFF header.
> + @ EFI entry point is in A32 mode, switch to T32 if configured.
> + THUMB( adr r12, BSYM(1f) )
> + THUMB( bx r12 )
>   THUMB( .thumb )
>  1:
> + @ 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
> + str r3, [sp, #8]!

above line should be:
str r3, [sp, #-8]!

that - sign is really hard to see in gmail :)

> + 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 EFI_STUB_ERROR error code.
> + cmp r0, #EFI_STUB_ERROR
> + beq efi_load_fail
> +
> + @ Save return values of efi_entry
> + stmfd sp!, {r0, r3}
> + bl cache_clean_flush
> + bl cache_off
> + ldmfd   sp!, {r0, r3}
> +
> + @ Set parameters for booting zImage according to boot protocol
> + @ put FDT address in r2, it was returned by efi_entry()
> + @ r1 is FDT machine type, and r0 needs to be 0
> + mov r2, r0
> + mov r1, #0xFFFFFFFF
> + mov r0, #0
> +
> + @ Branch to (possibly) relocated zImage that is in r3
> + @ Make sure we are in A32 mode, as zImage requires
> + THUMB( bx r3 )
> + ARM( mov pc, r3 )
> +
> +efi_load_fail:
> + @ Return EFI_LOAD_ERROR to EFI firmware on error.
> + @ Switch back to ARM mode for EFI is done based on
> + @ return address on stack in case we are in THUMB mode
> + ldr r0, =0x80000001
> + ldmfd sp!, {fp, pc} @ put lr from stack into pc
> +#endif
> +
> + THUMB( .thumb )
> +zimage_continue:
>   mrs r9, cpsr
>  #ifdef CONFIG_ARM_VIRT_EXT
>   bl __hyp_stub_install @ get into SVC mode, reversibly
> @@ -167,7 +239,6 @@ not_angel:
>   * by the linker here, but it should preserve r7, r8, and r9.
>   */
>
> - .text
>
>  #ifdef CONFIG_AUTO_ZRELADDR
>   @ determine final kernel image address
>
Grant Likely Aug. 30, 2013, 2:36 p.m. UTC | #5
On Fri,  9 Aug 2013 16:26:16 -0700, Roy Franz <roy.franz@linaro.org> wrote:
> This patch adds EFI stub support for the ARM Linux kernel.  The EFI stub
> operations similarly to the x86 stub: 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 loading the initrd (optionaly) and
> device tree from the system partition based on the kernel command line.
> The stub updates the device tree as necessary, including adding reserved
> memory regions and adding entries for EFI runtime services. The PE/COFF
> "MZ" header at offset 0 results in the first instruction being an add
> that corrupts r5, which is not used by the zImage interface.
> 
> Signed-off-by: Roy Franz <roy.franz@linaro.org>

Hi Roy,

Looks like nice tight code. I've got some comments below, but in general
I'm pretty happy with it.

g.

> ---
>  arch/arm/boot/compressed/Makefile     |   15 +-
>  arch/arm/boot/compressed/efi-header.S |  111 ++++++++
>  arch/arm/boot/compressed/efi-stub.c   |  448 +++++++++++++++++++++++++++++++++
>  arch/arm/boot/compressed/efi-stub.h   |    5 +
>  arch/arm/boot/compressed/head.S       |   90 ++++++-
>  5 files changed, 661 insertions(+), 8 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/boot/compressed/efi-stub.h
> 
> diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile
> index 7ac1610..5fad8bd 100644
> --- a/arch/arm/boot/compressed/Makefile
> +++ b/arch/arm/boot/compressed/Makefile
> @@ -103,11 +103,22 @@ 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
> +OBJS	+= atags_to_fdt.o
> +USE_LIBFDT = y
> +endif
> +
> +ifeq ($(CONFIG_EFI_STUB),y)
> +CFLAGS_efi-stub.o += -DTEXT_OFFSET=$(TEXT_OFFSET)
> +OBJS	+= efi-stub.o
> +USE_LIBFDT = y
> +endif
> +
> +ifeq ($(USE_LIBFDT),y)
> +OBJS	+= $(libfdt_objs)
>  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 0000000..6965e0f
> --- /dev/null
> +++ b/arch/arm/boot/compressed/efi-header.S
> @@ -0,0 +1,111 @@
> +@ Copyright (C) 2013 Linaro Ltd;  <roy.franz@linaro.org>
> +@
> +@ This file contains the PE/COFF header that is part of the
> +@ EFI stub.
> +@
> +
> +	.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			@ 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	_edata - efi_stub_entry		@ SizeOfCode
> +
> +	.long	0				@ SizeOfInitializedData
> +	.long	0				@ SizeOfUninitializedData
> +
> +	.long	efi_stub_entry			@ AddressOfEntryPoint
> +	.long	efi_stub_entry			@ BaseOfCode
> +	.long	0				@ data
> +
> +extra_header_fields:
> +	.long	0				@ ImageBase
> +	.long	0x20				@ SectionAlignment
> +	.long	0x20				@ FileAlignment
> +	.short	0				@ MajorOperatingSystemVersion
> +	.short	0				@ MinorOperatingSystemVersion
> +	.short	0				@ MajorImageVersion
> +	.short	0				@ MinorImageVersion
> +	.short	0				@ MajorSubsystemVersion
> +	.short	0				@ MinorSubsystemVersion
> +	.long	0				@ Win32VersionValue
> +
> +	.long	_edata				@ SizeOfImage
> +
> +	@ Everything before the entry point is considered part of the header
> +	.long	efi_stub_entry			@ 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	0x0				@ NumberOfRvaAndSizes
> +
> +	# 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 - efi_stub_entry		@ VirtualSize
> +	.long	efi_stub_entry			@ VirtualAddress
> +	.long	_edata - efi_stub_entry		@ SizeOfRawData
> +	.long	efi_stub_entry			@ 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)
> diff --git a/arch/arm/boot/compressed/efi-stub.c b/arch/arm/boot/compressed/efi-stub.c
> new file mode 100644
> index 0000000..4fce68b
> --- /dev/null
> +++ b/arch/arm/boot/compressed/efi-stub.c
> @@ -0,0 +1,448 @@
> +/*
> + * 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 <libfdt.h>
> +#include "efi-stub.h"
> +
> +/* EFI function call wrappers.  These are not required for
> + * ARM, but wrappers are required for X86 to convert between
> + * ABIs.  These wrappers are provided to allow code sharing
> + * between X86 and ARM.  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)
> +
> +/* The maximum uncompressed kernel size is 32 MBytes, so we will reserve
> + * that for the decompressed kernel.  We have no easy way to tell what
> + * the actuall size of code + data the uncompressed kernel will use.
> + */
> +#define MAX_UNCOMP_KERNEL_SIZE	0x02000000
> +
> +/* The kernel zImage should be located between 32 Mbytes
> + * and 128 MBytes 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	0x08000000
> +#define MIN_ZIMAGE_OFFSET	MAX_UNCOMP_KERNEL_SIZE
> +
> +#define PRINTK_PREFIX		"EFIstub: "
> +
> +struct fdt_region {
> +	u64 base;
> +	u64 size;
> +};
> +
> +
> +/* Include shared EFI stub code */
> +#include "../../../../drivers/firmware/efi/efi-stub-helper.c"
> +
> +static int relocate_kernel(efi_system_table_t *sys_table,
> +			   unsigned long *zimage_addr,
> +			   unsigned long zimage_size,
> +			   unsigned long min_addr, unsigned long max_addr)
> +{
> +	/* Get current address of kernel. */
> +	unsigned long cur_zimage_addr = *zimage_addr;
> +	unsigned long new_addr = 0;
> +
> +	efi_status_t status;
> +
> +	if (!zimage_addr || !zimage_size)
> +		return EFI_INVALID_PARAMETER;
> +
> +	if (cur_zimage_addr > min_addr
> +	    && (cur_zimage_addr + zimage_size) < max_addr) {
> +		/* We don't need to do anything, as kernel is at an
> +		 * acceptable address already.
> +		 */
> +		return EFI_SUCCESS;
> +	}
> +	/*
> +	 * The EFI firmware loader could have placed the kernel image
> +	 * anywhere in memory, but the kernel has restrictions on the
> +	 * min and max physical address it can run at.
> +	 */
> +	status = efi_low_alloc(sys_table, zimage_size, 0,
> +			   &new_addr, min_addr);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to allocate usable memory for kernel.\n");
> +		return status;
> +	}
> +
> +	if (new_addr > (max_addr - zimage_size)) {
> +		efi_free(sys_table, zimage_size, new_addr);
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to allocate usable memory for kernel.\n");
> +		return EFI_INVALID_PARAMETER;
> +	}
> +
> +	/* We know source/dest won't overlap since both memory ranges
> +	 * have been allocated by UEFI, so we can safely use memcpy.
> +	 */
> +	memcpy((void *)new_addr, (void *)(unsigned long)cur_zimage_addr,
> +	       zimage_size);
> +
> +	/* Return the load address  */
> +	*zimage_addr = new_addr;
> +
> +	return status;
> +}

This function should be sharable with x86. ARM has more restrictions
that x86, but I don't see any reason for them to be separate. ARM64 is
certainly going to want to use some form of this too.

> +
> +
> +/* Convert the unicode UEFI command line to ASCII to pass to kernel.
> + * Size of memory allocated return in *cmd_line_len.
> + * Returns NULL on error.
> + */
> +static char *convert_cmdline_to_ascii(efi_system_table_t *sys_table,
> +				      efi_loaded_image_t *image,
> +				      unsigned long *cmd_line_len,
> +				      u32 max_addr)

x86 has equivalent code. This function should be factored out and used
by both.

> +{
> +	u16 *s2;
> +	u8 *s1 = NULL;
> +	unsigned long cmdline_addr = 0;
> +	int load_options_size = image->load_options_size / 2; /* ASCII */
> +	void *options = (u16 *)image->load_options;

load_options is already a void*. What is the cast here for?

> +	int options_size = 0;
> +	int status;
> +	int i;
> +	u16 zero = 0;
> +
> +	if (options) {
> +		s2 = options;
> +		while (*s2 && *s2 != '\n' && options_size < load_options_size) {
> +			s2++;
> +			options_size++;
> +		}
> +	}
> +
> +	if (options_size == 0) {
> +		/* No command line options, so return empty string*/
> +		options_size = 1;
> +		options = &zero;
> +	}
> +
> +	options_size++;  /* NUL termination */
> +
> +	status = efi_high_alloc(sys_table, options_size, 0,
> +			    &cmdline_addr, max_addr);
> +	if (status != EFI_SUCCESS)
> +		return NULL;
> +
> +	s1 = (u8 *)(unsigned long)cmdline_addr;
> +	s2 = (u16 *)options;
> +
> +	for (i = 0; i < options_size - 1; i++)
> +		*s1++ = *s2++;
> +
> +	*s1 = '\0';
> +
> +	*cmd_line_len = options_size;
> +	return (char *)(unsigned long)cmdline_addr;

Double casting? That looks wrong. Why does the (unsigned long) bit need
to be there?

> +}
> +
> +
> +static u32 update_fdt(efi_system_table_t *sys_table, void *orig_fdt, void *fdt,
> +		      int new_fdt_size, char *cmdline_ptr, u64 initrd_addr,
> +		      u64 initrd_size, efi_memory_desc_t *memory_map,
> +		      int map_size, int desc_size)

ARM64 will want access to this function.

> +{
> +	int node;
> +	int status;
> +	unsigned long fdt_val;
> +
> +	status = fdt_open_into(orig_fdt, fdt, new_fdt_size);
> +	if (status != 0)
> +		goto fdt_set_fail;
> +
> +	node = fdt_subnode_offset(fdt, 0, "chosen");
> +	if (node < 0) {
> +		node = fdt_add_subnode(fdt, 0, "chosen");
> +		if (node < 0) {
> +			status = node; /* node is error code when negative */
> +			goto fdt_set_fail;
> +		}
> +	}
> +
> +	if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) {
> +		status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
> +				     strlen(cmdline_ptr) + 1);
> +		if (status)
> +			goto fdt_set_fail;
> +	}

Some comments in this function would help poor readers like me.

> +
> +	/* Set intird address/end in device tree, if present */
> +	if (initrd_size != 0) {
> +		u64 initrd_image_end;
> +		u64 initrd_image_start = cpu_to_fdt64(initrd_addr);
> +		status = fdt_setprop(fdt, node, "linux,initrd-start",
> +				     &initrd_image_start, sizeof(u64));
> +		if (status)
> +			goto fdt_set_fail;
> +		initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size);
> +		status = fdt_setprop(fdt, node, "linux,initrd-end",
> +				     &initrd_image_end, sizeof(u64));
> +		if (status)
> +			goto fdt_set_fail;
> +	}
> +
> +	/* Add FDT entries for EFI runtime services in chosen node. */
> +	node = fdt_subnode_offset(fdt, 0, "chosen");
> +	fdt_val = cpu_to_fdt32((unsigned long)sys_table);
> +	status = fdt_setprop(fdt, node, "efi-system-table",
> +			     &fdt_val, sizeof(fdt_val));
> +	if (status)
> +		goto fdt_set_fail;
> +
> +	fdt_val = cpu_to_fdt32(desc_size);
> +	status = fdt_setprop(fdt, node, "efi-mmap-desc-size",
> +			     &fdt_val, sizeof(fdt_val));
> +	if (status)
> +		goto fdt_set_fail;
> +
> +	fdt_val = cpu_to_fdt32(map_size);
> +	status = fdt_setprop(fdt, node, "efi-runtime-mmap-size",
> +			     &fdt_val, sizeof(fdt_val));
> +	if (status)
> +		goto fdt_set_fail;
> +
> +	fdt_val = cpu_to_fdt32((unsigned long)memory_map);
> +	status = fdt_setprop(fdt, node, "efi-runtime-mmap",
> +			     &fdt_val, sizeof(fdt_val));
> +	if (status)
> +		goto fdt_set_fail;
> +
> +	return EFI_SUCCESS;
> +
> +fdt_set_fail:
> +	if (status == -FDT_ERR_NOSPACE)
> +		return EFI_BUFFER_TOO_SMALL;
> +
> +	return EFI_LOAD_ERROR;
> +}
> +
> +
> +
> +int efi_entry(void *handle, efi_system_table_t *sys_table,
> +	      unsigned long *zimage_addr)
> +{
> +	efi_loaded_image_t *image;
> +	int status;
> +	unsigned long nr_pages;
> +	const struct fdt_region *region;
> +
> +	void *fdt;
> +	int err;
> +	int node;
> +	unsigned long zimage_size = 0;
> +	unsigned long dram_base;
> +	/* addr/point and size pairs for memory management*/
> +	u64 initrd_addr;
> +	u64 initrd_size = 0;
> +	u64 fdt_addr;
> +	u64 fdt_size = 0;
> +	u64 kernel_reserve_addr;
> +	u64 kernel_reserve_size = 0;
> +	char *cmdline_ptr;
> +	unsigned long cmdline_size = 0;
> +
> +	unsigned long map_size, desc_size;
> +	unsigned long mmap_key;
> +	efi_memory_desc_t *memory_map;
> +
> +	unsigned long new_fdt_size;
> +	unsigned long new_fdt_addr;
> +
> +	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;
> +
> +	efi_printk(sys_table, PRINTK_PREFIX"Booting Linux using EFI stub.\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) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: 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 = convert_cmdline_to_ascii(sys_table, image,
> +					       &cmdline_size, 0xFFFFFFFF);
> +	if (!cmdline_ptr) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: converting command line to ascii failed.\n");
> +		goto fail;
> +	}
> +
> +	/* We first load the device tree, as we need to get the base address of
> +	 * DRAM from the device tree.  The zImage, device tree, and initrd
> +	 * have address restrictions that are relative to the base of DRAM.
> +	 */
> +	status = handle_cmdline_files(sys_table, image, cmdline_ptr, "dtb=",
> +				      0xffffffff, &fdt_addr, &fdt_size);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load device tree blob.\n");
> +		goto fail_free_cmdline;
> +	}
> +
> +	err = fdt_check_header((void *)(unsigned long)fdt_addr);
> +	if (err != 0) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: device tree header not valid\n");
> +		goto fail_free_fdt;
> +	}
> +	if (fdt_totalsize((void *)(unsigned long)fdt_addr) > fdt_size) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Incomplete device tree.\n");
> +		goto fail_free_fdt;
> +
> +	}
> +
> +
> +	/* Look up the base of DRAM from the device tree.*/
> +	fdt = (void *)(u32)fdt_addr;

More double casting. This one might be legitimate, but you need to
describe why. Is there any possibility that the FDT would appear above
4G?

> +	node = fdt_subnode_offset(fdt, 0, "memory");
> +	region = fdt_getprop(fdt, node, "reg", NULL);
> +	if (region) {
> +		dram_base = fdt64_to_cpu(region->base);
> +	} else {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: no 'memory' node in device tree.\n");
> +		goto fail_free_fdt;

Shouldn't fail here. If there is no memory node then create one.

> +	}
> +
> +	/* Reserve memory for the uncompressed kernel image. */
> +	kernel_reserve_addr = dram_base;
> +	kernel_reserve_size = MAX_UNCOMP_KERNEL_SIZE;
> +	nr_pages = round_up(kernel_reserve_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
> +	status = efi_call_phys4(sys_table->boottime->allocate_pages,
> +				EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
> +				nr_pages, &kernel_reserve_addr);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: unable to allocate memory for uncompressed kernel.\n");
> +		goto fail_free_fdt;
> +	}
> +
> +	/* Relocate the zImage, if required. */
> +	zimage_size = image->image_size;
> +	status = relocate_kernel(sys_table, zimage_addr, zimage_size,
> +				 dram_base + MIN_ZIMAGE_OFFSET,
> +				 dram_base + ZIMAGE_OFFSET_LIMIT);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to relocate kernel\n");
> +		goto fail_free_kernel_reserve;
> +	}
> +
> +	status = handle_cmdline_files(sys_table, image, cmdline_ptr, "initrd=",
> +				      dram_base + ZIMAGE_OFFSET_LIMIT,
> +				      &initrd_addr, &initrd_size);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load initrd\n");
> +		goto fail_free_zimage;
> +	}
> +
> +	/* 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.*/
> +	new_fdt_size = fdt_size + cmdline_size + 0x200;
> +
> +fdt_alloc_retry:
> +	status = efi_high_alloc(sys_table, new_fdt_size, 0, &new_fdt_addr,
> +			    dram_base + ZIMAGE_OFFSET_LIMIT);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to allocate memory for new device tree.\n");
> +		goto fail_free_initrd;
> +	}
> +
> +	/* Now that we have done our final memory allocation (and free)
> +	 * we can get the memory map key needed
> +	 * forexit_boot_services.*/
> +	status = efi_get_memory_map(sys_table, &memory_map, &map_size,
> +				    &desc_size, &mmap_key);
> +	if (status != EFI_SUCCESS)
> +		goto fail_free_new_fdt;
> +
> +	status = update_fdt(sys_table,
> +			    fdt, (void *)new_fdt_addr, new_fdt_size,
> +			    cmdline_ptr,
> +			    initrd_addr, initrd_size,
> +			    memory_map, map_size, desc_size);
> +
> +	if (status != EFI_SUCCESS) {
> +		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 += new_fdt_size/4;
> +			goto fdt_alloc_retry;
> +		}
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to constuct new device tree.\n");
> +		goto fail_free_initrd;
> +	}
> +
> +	/* 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) {
> +		efi_printk(sys_table, PRINTK_PREFIX"ERROR: exit boot services failed.\n");
> +		goto fail_free_mmap;
> +	}
> +
> +
> +	/* Now we need to return the FDT address to the calling
> +	 * assembly to this 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_zimage:
> +	efi_free(sys_table, zimage_size, *zimage_addr);
> +
> +fail_free_kernel_reserve:
> +	efi_free(sys_table, kernel_reserve_addr, kernel_reserve_size);
> +
> +fail_free_fdt:
> +	efi_free(sys_table, fdt_size, fdt_addr);
> +
> +fail_free_cmdline:
> +	efi_free(sys_table, cmdline_size, (u32)cmdline_ptr);
> +
> +fail:
> +	return EFI_STUB_ERROR;
> +}
> diff --git a/arch/arm/boot/compressed/efi-stub.h b/arch/arm/boot/compressed/efi-stub.h
> new file mode 100644
> index 0000000..0fe9376
> --- /dev/null
> +++ b/arch/arm/boot/compressed/efi-stub.h
> @@ -0,0 +1,5 @@
> +#ifndef _ARM_EFI_STUB_H
> +#define _ARM_EFI_STUB_H
> +/* Error code returned to ASM code instead of valid FDT address. */
> +#define EFI_STUB_ERROR		(~0)
> +#endif
> diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S
> index 75189f1..5401a3a 100644
> --- a/arch/arm/boot/compressed/head.S
> +++ b/arch/arm/boot/compressed/head.S
> @@ -10,6 +10,7 @@
>   */
>  #include <linux/linkage.h>
>  #include <asm/assembler.h>
> +#include "efi-stub.h"
>  
>  	.arch	armv7-a
>  /*
> @@ -120,21 +121,99 @@
>   */
>  		.align
>  		.arm				@ Always enter in ARM state
> +		.text
>  start:
>  		.type	start,#function
> -		.rept	7
> +#ifdef CONFIG_EFI_STUB
> +		@ Magic MSDOS signature for PE/COFF + ADD opcode
> +		.word	0x62805a4d
> +#else
> +		mov	r0, r0
> +#endif
> +		.rept	5
>  		mov	r0, r0
>  		.endr
> -   ARM(		mov	r0, r0		)
> -   ARM(		b	1f		)
> - THUMB(		adr	r12, BSYM(1f)	)
> - THUMB(		bx	r12		)
> +
> +		adrl	r12, BSYM(zimage_continue)
> + ARM(		mov     pc, r12 )
> + THUMB(		bx	r12     )
> +		@ zimage_continue will be in ARM or thumb mode as configured
>  
>  		.word	0x016f2818		@ Magic numbers to help the loader
>  		.word	start			@ absolute load/run zImage address
>  		.word	_edata			@ zImage end address
> +
> +#ifdef CONFIG_EFI_STUB
> +		@ Portions of the MSDOS file header must be at offset
> +		@ 0x3c from the start of the file.  All PE/COFF headers
> +		@ are kept contiguous for simplicity.
> +#include "efi-header.S"
> +
> +efi_stub_entry:
> +		@ The EFI stub entry point is not at a fixed address, however
> +		@ this address must be set in the PE/COFF header.
> +		@ EFI entry point is in A32 mode, switch to T32 if configured.
> + THUMB(		adr	r12, BSYM(1f)	)
> + THUMB(		bx	r12		)
>   THUMB(		.thumb			)
>  1:
> +		@ 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 return of new entry point of
> +		@ zImage, as EFI stub may copy the kernel.  Pass address
> +		@ of space in r2 - EFI stub will fill in the pointer.
> +
> +		sub	sp, sp, #8		@ we only need 4 bytes,
> +						@ but keep stack 8 byte aligned.
> +		mov	r2, sp
> +		@ Pass our actual runtime start address in pointer data
> +		adr	r11, LC0		@ address of LC0 at run time
> +		ldr	r12, [r11, #0]		@ address of LC0 at link time
> +
> +		sub	r3, r11, r12		@ calculate the delta offset
> +		str	r3, [r2, #0]
> +		bl	efi_entry
> +
> +		@ get new zImage entry address from stack, put into r3
> +		ldr	r3, [sp, #0]
> +		add	sp, sp, #8  		@ restore stack
> +
> +		@ Check for error return from EFI stub
> +		mov	r1, #EFI_STUB_ERROR
> +		cmp	r0, r1
> +		beq	efi_load_fail
> +
> +
> +		@ Save return values of efi_entry
> +		stmfd	sp!, {r0, r3}
> +		bl	cache_clean_flush
> +		bl	cache_off
> +		ldmfd   sp!, {r0, r3}
> +
> +		@ Set parameters for booting zImage according to boot protocol
> +		@ put FDT address in r2, it was returned by efi_entry()
> +		@ r1 is FDT machine type, and r0 needs to be 0
> +		mov	r2, r0
> +		mov	r1, #0xFFFFFFFF
> +		mov	r0, #0
> +
> +		@ Branch to (possibly) relocated zImage that is in r3
> +		@ Make sure we are in A32 mode, as zImage requires
> + THUMB(		bx	r3		)
> + ARM(		mov	pc, r3		)
> +
> +efi_load_fail:
> +		@ Return EFI_LOAD_ERROR to EFI firmware on error.
> +		@ Switch back to ARM mode for EFI is done based on
> +		@ return address on stack
> +		ldr	r0, =0x80000001
> +		ldmfd	sp!, {fp, pc}
> +#endif
> +
> + THUMB(		.thumb			)
> +zimage_continue:
>  		mrs	r9, cpsr
>  #ifdef CONFIG_ARM_VIRT_EXT
>  		bl	__hyp_stub_install	@ get into SVC mode, reversibly
> @@ -167,7 +246,6 @@ not_angel:
>  		 * by the linker here, but it should preserve r7, r8, and r9.
>  		 */
>  
> -		.text
>  
>  #ifdef CONFIG_AUTO_ZRELADDR
>  		@ determine final kernel image address
> -- 
> 1.7.10.4
> 
> --
> 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/
Roy Franz Aug. 31, 2013, 12:35 a.m. UTC | #6
On Fri, Aug 30, 2013 at 7:36 AM, Grant Likely <grant.likely@secretlab.ca> wrote:
> On Fri,  9 Aug 2013 16:26:16 -0700, Roy Franz <roy.franz@linaro.org> wrote:
>> This patch adds EFI stub support for the ARM Linux kernel.  The EFI stub
>> operations similarly to the x86 stub: 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 loading the initrd (optionaly) and
>> device tree from the system partition based on the kernel command line.
>> The stub updates the device tree as necessary, including adding reserved
>> memory regions and adding entries for EFI runtime services. The PE/COFF
>> "MZ" header at offset 0 results in the first instruction being an add
>> that corrupts r5, which is not used by the zImage interface.
>>
>> Signed-off-by: Roy Franz <roy.franz@linaro.org>
>
> Hi Roy,
>
> Looks like nice tight code. I've got some comments below, but in general
> I'm pretty happy with it.
>
> g.
>
>> ---
>>  arch/arm/boot/compressed/Makefile     |   15 +-
>>  arch/arm/boot/compressed/efi-header.S |  111 ++++++++
>>  arch/arm/boot/compressed/efi-stub.c   |  448 +++++++++++++++++++++++++++++++++
>>  arch/arm/boot/compressed/efi-stub.h   |    5 +
>>  arch/arm/boot/compressed/head.S       |   90 ++++++-
>>  5 files changed, 661 insertions(+), 8 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/boot/compressed/efi-stub.h
>>
>> diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile
>> index 7ac1610..5fad8bd 100644
>> --- a/arch/arm/boot/compressed/Makefile
>> +++ b/arch/arm/boot/compressed/Makefile
>> @@ -103,11 +103,22 @@ 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
>> +OBJS += atags_to_fdt.o
>> +USE_LIBFDT = y
>> +endif
>> +
>> +ifeq ($(CONFIG_EFI_STUB),y)
>> +CFLAGS_efi-stub.o += -DTEXT_OFFSET=$(TEXT_OFFSET)
>> +OBJS += efi-stub.o
>> +USE_LIBFDT = y
>> +endif
>> +
>> +ifeq ($(USE_LIBFDT),y)
>> +OBJS += $(libfdt_objs)
>>  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 0000000..6965e0f
>> --- /dev/null
>> +++ b/arch/arm/boot/compressed/efi-header.S
>> @@ -0,0 +1,111 @@
>> +@ Copyright (C) 2013 Linaro Ltd;  <roy.franz@linaro.org>
>> +@
>> +@ This file contains the PE/COFF header that is part of the
>> +@ EFI stub.
>> +@
>> +
>> +     .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                       @ 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   _edata - efi_stub_entry         @ SizeOfCode
>> +
>> +     .long   0                               @ SizeOfInitializedData
>> +     .long   0                               @ SizeOfUninitializedData
>> +
>> +     .long   efi_stub_entry                  @ AddressOfEntryPoint
>> +     .long   efi_stub_entry                  @ BaseOfCode
>> +     .long   0                               @ data
>> +
>> +extra_header_fields:
>> +     .long   0                               @ ImageBase
>> +     .long   0x20                            @ SectionAlignment
>> +     .long   0x20                            @ FileAlignment
>> +     .short  0                               @ MajorOperatingSystemVersion
>> +     .short  0                               @ MinorOperatingSystemVersion
>> +     .short  0                               @ MajorImageVersion
>> +     .short  0                               @ MinorImageVersion
>> +     .short  0                               @ MajorSubsystemVersion
>> +     .short  0                               @ MinorSubsystemVersion
>> +     .long   0                               @ Win32VersionValue
>> +
>> +     .long   _edata                          @ SizeOfImage
>> +
>> +     @ Everything before the entry point is considered part of the header
>> +     .long   efi_stub_entry                  @ 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   0x0                             @ NumberOfRvaAndSizes
>> +
>> +     # 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 - efi_stub_entry         @ VirtualSize
>> +     .long   efi_stub_entry                  @ VirtualAddress
>> +     .long   _edata - efi_stub_entry         @ SizeOfRawData
>> +     .long   efi_stub_entry                  @ 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)
>> diff --git a/arch/arm/boot/compressed/efi-stub.c b/arch/arm/boot/compressed/efi-stub.c
>> new file mode 100644
>> index 0000000..4fce68b
>> --- /dev/null
>> +++ b/arch/arm/boot/compressed/efi-stub.c
>> @@ -0,0 +1,448 @@
>> +/*
>> + * 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 <libfdt.h>
>> +#include "efi-stub.h"
>> +
>> +/* EFI function call wrappers.  These are not required for
>> + * ARM, but wrappers are required for X86 to convert between
>> + * ABIs.  These wrappers are provided to allow code sharing
>> + * between X86 and ARM.  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)
>> +
>> +/* The maximum uncompressed kernel size is 32 MBytes, so we will reserve
>> + * that for the decompressed kernel.  We have no easy way to tell what
>> + * the actuall size of code + data the uncompressed kernel will use.
>> + */
>> +#define MAX_UNCOMP_KERNEL_SIZE       0x02000000
>> +
>> +/* The kernel zImage should be located between 32 Mbytes
>> + * and 128 MBytes 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  0x08000000
>> +#define MIN_ZIMAGE_OFFSET    MAX_UNCOMP_KERNEL_SIZE
>> +
>> +#define PRINTK_PREFIX                "EFIstub: "
>> +
>> +struct fdt_region {
>> +     u64 base;
>> +     u64 size;
>> +};
>> +
>> +
>> +/* Include shared EFI stub code */
>> +#include "../../../../drivers/firmware/efi/efi-stub-helper.c"
>> +
>> +static int relocate_kernel(efi_system_table_t *sys_table,
>> +                        unsigned long *zimage_addr,
>> +                        unsigned long zimage_size,
>> +                        unsigned long min_addr, unsigned long max_addr)
>> +{
>> +     /* Get current address of kernel. */
>> +     unsigned long cur_zimage_addr = *zimage_addr;
>> +     unsigned long new_addr = 0;
>> +
>> +     efi_status_t status;
>> +
>> +     if (!zimage_addr || !zimage_size)
>> +             return EFI_INVALID_PARAMETER;
>> +
>> +     if (cur_zimage_addr > min_addr
>> +         && (cur_zimage_addr + zimage_size) < max_addr) {
>> +             /* We don't need to do anything, as kernel is at an
>> +              * acceptable address already.
>> +              */
>> +             return EFI_SUCCESS;
>> +     }
>> +     /*
>> +      * The EFI firmware loader could have placed the kernel image
>> +      * anywhere in memory, but the kernel has restrictions on the
>> +      * min and max physical address it can run at.
>> +      */
>> +     status = efi_low_alloc(sys_table, zimage_size, 0,
>> +                        &new_addr, min_addr);
>> +     if (status != EFI_SUCCESS) {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to allocate usable memory for kernel.\n");
>> +             return status;
>> +     }
>> +
>> +     if (new_addr > (max_addr - zimage_size)) {
>> +             efi_free(sys_table, zimage_size, new_addr);
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to allocate usable memory for kernel.\n");
>> +             return EFI_INVALID_PARAMETER;
>> +     }
>> +
>> +     /* We know source/dest won't overlap since both memory ranges
>> +      * have been allocated by UEFI, so we can safely use memcpy.
>> +      */
>> +     memcpy((void *)new_addr, (void *)(unsigned long)cur_zimage_addr,
>> +            zimage_size);
>> +
>> +     /* Return the load address  */
>> +     *zimage_addr = new_addr;
>> +
>> +     return status;
>> +}
>
> This function should be sharable with x86. ARM has more restrictions
> that x86, but I don't see any reason for them to be separate. ARM64 is
> certainly going to want to use some form of this too.

I'll look at sharing this with x86.  x86 has the additional concept of
a preferred address
but other than that looks similar.

>
>> +
>> +
>> +/* Convert the unicode UEFI command line to ASCII to pass to kernel.
>> + * Size of memory allocated return in *cmd_line_len.
>> + * Returns NULL on error.
>> + */
>> +static char *convert_cmdline_to_ascii(efi_system_table_t *sys_table,
>> +                                   efi_loaded_image_t *image,
>> +                                   unsigned long *cmd_line_len,
>> +                                   u32 max_addr)
>
> x86 has equivalent code. This function should be factored out and used
> by both.

I'll update x86 to use this.

>
>> +{
>> +     u16 *s2;
>> +     u8 *s1 = NULL;
>> +     unsigned long cmdline_addr = 0;
>> +     int load_options_size = image->load_options_size / 2; /* ASCII */
>> +     void *options = (u16 *)image->load_options;
>
> load_options is already a void*. What is the cast here for?

Removed.

>
>> +     int options_size = 0;
>> +     int status;
>> +     int i;
>> +     u16 zero = 0;
>> +
>> +     if (options) {
>> +             s2 = options;
>> +             while (*s2 && *s2 != '\n' && options_size < load_options_size) {
>> +                     s2++;
>> +                     options_size++;
>> +             }
>> +     }
>> +
>> +     if (options_size == 0) {
>> +             /* No command line options, so return empty string*/
>> +             options_size = 1;
>> +             options = &zero;
>> +     }
>> +
>> +     options_size++;  /* NUL termination */
>> +
>> +     status = efi_high_alloc(sys_table, options_size, 0,
>> +                         &cmdline_addr, max_addr);
>> +     if (status != EFI_SUCCESS)
>> +             return NULL;
>> +
>> +     s1 = (u8 *)(unsigned long)cmdline_addr;
>> +     s2 = (u16 *)options;
>> +
>> +     for (i = 0; i < options_size - 1; i++)
>> +             *s1++ = *s2++;
>> +
>> +     *s1 = '\0';
>> +
>> +     *cmd_line_len = options_size;
>> +     return (char *)(unsigned long)cmdline_addr;
>
> Double casting? That looks wrong. Why does the (unsigned long) bit need
> to be there?
Removed.  likely a holdover from a version where that was a 64 bit type.
(see more on this below...)

>
>> +}
>> +
>> +
>> +static u32 update_fdt(efi_system_table_t *sys_table, void *orig_fdt, void *fdt,
>> +                   int new_fdt_size, char *cmdline_ptr, u64 initrd_addr,
>> +                   u64 initrd_size, efi_memory_desc_t *memory_map,
>> +                   int map_size, int desc_size)
>
> ARM64 will want access to this function.

I will move this to be shared.  I have discussed with Leif making the
EFI FDT entries used by EFI runtime services 64 bit for
both ARM32/ARM64, so this function should be identical in both cases.
>
>> +{
>> +     int node;
>> +     int status;
>> +     unsigned long fdt_val;
>> +
>> +     status = fdt_open_into(orig_fdt, fdt, new_fdt_size);
>> +     if (status != 0)
>> +             goto fdt_set_fail;
>> +
>> +     node = fdt_subnode_offset(fdt, 0, "chosen");
>> +     if (node < 0) {
>> +             node = fdt_add_subnode(fdt, 0, "chosen");
>> +             if (node < 0) {
>> +                     status = node; /* node is error code when negative */
>> +                     goto fdt_set_fail;
>> +             }
>> +     }
>> +
>> +     if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) {
>> +             status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
>> +                                  strlen(cmdline_ptr) + 1);
>> +             if (status)
>> +                     goto fdt_set_fail;
>> +     }
>
> Some comments in this function would help poor readers like me.

I'll review and improve the commenting for this this function
>
>> +
>> +     /* Set intird address/end in device tree, if present */
>> +     if (initrd_size != 0) {
>> +             u64 initrd_image_end;
>> +             u64 initrd_image_start = cpu_to_fdt64(initrd_addr);
>> +             status = fdt_setprop(fdt, node, "linux,initrd-start",
>> +                                  &initrd_image_start, sizeof(u64));
>> +             if (status)
>> +                     goto fdt_set_fail;
>> +             initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size);
>> +             status = fdt_setprop(fdt, node, "linux,initrd-end",
>> +                                  &initrd_image_end, sizeof(u64));
>> +             if (status)
>> +                     goto fdt_set_fail;
>> +     }
>> +
>> +     /* Add FDT entries for EFI runtime services in chosen node. */
>> +     node = fdt_subnode_offset(fdt, 0, "chosen");
>> +     fdt_val = cpu_to_fdt32((unsigned long)sys_table);
>> +     status = fdt_setprop(fdt, node, "efi-system-table",
>> +                          &fdt_val, sizeof(fdt_val));
>> +     if (status)
>> +             goto fdt_set_fail;
>> +
>> +     fdt_val = cpu_to_fdt32(desc_size);
>> +     status = fdt_setprop(fdt, node, "efi-mmap-desc-size",
>> +                          &fdt_val, sizeof(fdt_val));
>> +     if (status)
>> +             goto fdt_set_fail;
>> +
>> +     fdt_val = cpu_to_fdt32(map_size);
>> +     status = fdt_setprop(fdt, node, "efi-runtime-mmap-size",
>> +                          &fdt_val, sizeof(fdt_val));
>> +     if (status)
>> +             goto fdt_set_fail;
>> +
>> +     fdt_val = cpu_to_fdt32((unsigned long)memory_map);
>> +     status = fdt_setprop(fdt, node, "efi-runtime-mmap",
>> +                          &fdt_val, sizeof(fdt_val));
>> +     if (status)
>> +             goto fdt_set_fail;
>> +
>> +     return EFI_SUCCESS;
>> +
>> +fdt_set_fail:
>> +     if (status == -FDT_ERR_NOSPACE)
>> +             return EFI_BUFFER_TOO_SMALL;
>> +
>> +     return EFI_LOAD_ERROR;
>> +}
>> +
>> +
>> +
>> +int efi_entry(void *handle, efi_system_table_t *sys_table,
>> +           unsigned long *zimage_addr)
>> +{
>> +     efi_loaded_image_t *image;
>> +     int status;
>> +     unsigned long nr_pages;
>> +     const struct fdt_region *region;
>> +
>> +     void *fdt;
>> +     int err;
>> +     int node;
>> +     unsigned long zimage_size = 0;
>> +     unsigned long dram_base;
>> +     /* addr/point and size pairs for memory management*/
>> +     u64 initrd_addr;
>> +     u64 initrd_size = 0;
>> +     u64 fdt_addr;
>> +     u64 fdt_size = 0;
>> +     u64 kernel_reserve_addr;
>> +     u64 kernel_reserve_size = 0;
>> +     char *cmdline_ptr;
>> +     unsigned long cmdline_size = 0;
>> +
>> +     unsigned long map_size, desc_size;
>> +     unsigned long mmap_key;
>> +     efi_memory_desc_t *memory_map;
>> +
>> +     unsigned long new_fdt_size;
>> +     unsigned long new_fdt_addr;
>> +
>> +     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;
>> +
>> +     efi_printk(sys_table, PRINTK_PREFIX"Booting Linux using EFI stub.\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) {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: 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 = convert_cmdline_to_ascii(sys_table, image,
>> +                                            &cmdline_size, 0xFFFFFFFF);
>> +     if (!cmdline_ptr) {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: converting command line to ascii failed.\n");
>> +             goto fail;
>> +     }
>> +
>> +     /* We first load the device tree, as we need to get the base address of
>> +      * DRAM from the device tree.  The zImage, device tree, and initrd
>> +      * have address restrictions that are relative to the base of DRAM.
>> +      */
>> +     status = handle_cmdline_files(sys_table, image, cmdline_ptr, "dtb=",
>> +                                   0xffffffff, &fdt_addr, &fdt_size);
>> +     if (status != EFI_SUCCESS) {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load device tree blob.\n");
>> +             goto fail_free_cmdline;
>> +     }
>> +
>> +     err = fdt_check_header((void *)(unsigned long)fdt_addr);
>> +     if (err != 0) {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: device tree header not valid\n");
>> +             goto fail_free_fdt;
>> +     }
>> +     if (fdt_totalsize((void *)(unsigned long)fdt_addr) > fdt_size) {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Incomplete device tree.\n");
>> +             goto fail_free_fdt;
>> +
>> +     }
>> +
>> +
>> +     /* Look up the base of DRAM from the device tree.*/
>> +     fdt = (void *)(u32)fdt_addr;
>
> More double casting. This one might be legitimate, but you need to
> describe why. Is there any possibility that the FDT would appear above
> 4G?

It can't be above 4GiB.  This highlights some inconsistencies in the
shared APIs that I need to clean up - some that
I added use u64 for passing addresses, which lead to casts like this.
I will change the new functions
to match the existing alloc/free to use native sizes, as this works
for the arm32 and x86 32 bit architectures
as well as the 64 bit ones.  (The alternative is to move all the APIs
to u64/efi_phys_addr_t but this would be more intrusive
to the existing x86 code.)

>
>> +     node = fdt_subnode_offset(fdt, 0, "memory");
>> +     region = fdt_getprop(fdt, node, "reg", NULL);
>> +     if (region) {
>> +             dram_base = fdt64_to_cpu(region->base);
>> +     } else {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: no 'memory' node in device tree.\n");
>> +             goto fail_free_fdt;
>
> Shouldn't fail here. If there is no memory node then create one.

It would be good to do that, however I don't know of a way to get this
information using boot services.
This is available from within the UEFI firmware, but not exposed to
EFI applications as far as I can tell.
I think that the way to fix this is to have the UEFI firmware 'own'
the device tree and make it available to
the stub, after filling in platform specific stuff like this.  I think
this goes along the lines of you have suggested
regarding adding a FDT protocol to UEFI.

>
>> +     }
>> +
>> +     /* Reserve memory for the uncompressed kernel image. */
>> +     kernel_reserve_addr = dram_base;
>> +     kernel_reserve_size = MAX_UNCOMP_KERNEL_SIZE;
>> +     nr_pages = round_up(kernel_reserve_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
>> +     status = efi_call_phys4(sys_table->boottime->allocate_pages,
>> +                             EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
>> +                             nr_pages, &kernel_reserve_addr);
>> +     if (status != EFI_SUCCESS) {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: unable to allocate memory for uncompressed kernel.\n");
>> +             goto fail_free_fdt;
>> +     }
>> +
>> +     /* Relocate the zImage, if required. */
>> +     zimage_size = image->image_size;
>> +     status = relocate_kernel(sys_table, zimage_addr, zimage_size,
>> +                              dram_base + MIN_ZIMAGE_OFFSET,
>> +                              dram_base + ZIMAGE_OFFSET_LIMIT);
>> +     if (status != EFI_SUCCESS) {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to relocate kernel\n");
>> +             goto fail_free_kernel_reserve;
>> +     }
>> +
>> +     status = handle_cmdline_files(sys_table, image, cmdline_ptr, "initrd=",
>> +                                   dram_base + ZIMAGE_OFFSET_LIMIT,
>> +                                   &initrd_addr, &initrd_size);
>> +     if (status != EFI_SUCCESS) {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load initrd\n");
>> +             goto fail_free_zimage;
>> +     }
>> +
>> +     /* 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.*/
>> +     new_fdt_size = fdt_size + cmdline_size + 0x200;
>> +
>> +fdt_alloc_retry:
>> +     status = efi_high_alloc(sys_table, new_fdt_size, 0, &new_fdt_addr,
>> +                         dram_base + ZIMAGE_OFFSET_LIMIT);
>> +     if (status != EFI_SUCCESS) {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to allocate memory for new device tree.\n");
>> +             goto fail_free_initrd;
>> +     }
>> +
>> +     /* Now that we have done our final memory allocation (and free)
>> +      * we can get the memory map key needed
>> +      * forexit_boot_services.*/
>> +     status = efi_get_memory_map(sys_table, &memory_map, &map_size,
>> +                                 &desc_size, &mmap_key);
>> +     if (status != EFI_SUCCESS)
>> +             goto fail_free_new_fdt;
>> +
>> +     status = update_fdt(sys_table,
>> +                         fdt, (void *)new_fdt_addr, new_fdt_size,
>> +                         cmdline_ptr,
>> +                         initrd_addr, initrd_size,
>> +                         memory_map, map_size, desc_size);
>> +
>> +     if (status != EFI_SUCCESS) {
>> +             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 += new_fdt_size/4;
>> +                     goto fdt_alloc_retry;
>> +             }
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to constuct new device tree.\n");
>> +             goto fail_free_initrd;
>> +     }
>> +
>> +     /* 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) {
>> +             efi_printk(sys_table, PRINTK_PREFIX"ERROR: exit boot services failed.\n");
>> +             goto fail_free_mmap;
>> +     }
>> +
>> +
>> +     /* Now we need to return the FDT address to the calling
>> +      * assembly to this 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_zimage:
>> +     efi_free(sys_table, zimage_size, *zimage_addr);
>> +
>> +fail_free_kernel_reserve:
>> +     efi_free(sys_table, kernel_reserve_addr, kernel_reserve_size);
>> +
>> +fail_free_fdt:
>> +     efi_free(sys_table, fdt_size, fdt_addr);
>> +
>> +fail_free_cmdline:
>> +     efi_free(sys_table, cmdline_size, (u32)cmdline_ptr);
>> +
>> +fail:
>> +     return EFI_STUB_ERROR;
>> +}
>> diff --git a/arch/arm/boot/compressed/efi-stub.h b/arch/arm/boot/compressed/efi-stub.h
>> new file mode 100644
>> index 0000000..0fe9376
>> --- /dev/null
>> +++ b/arch/arm/boot/compressed/efi-stub.h
>> @@ -0,0 +1,5 @@
>> +#ifndef _ARM_EFI_STUB_H
>> +#define _ARM_EFI_STUB_H
>> +/* Error code returned to ASM code instead of valid FDT address. */
>> +#define EFI_STUB_ERROR               (~0)
>> +#endif
>> diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S
>> index 75189f1..5401a3a 100644
>> --- a/arch/arm/boot/compressed/head.S
>> +++ b/arch/arm/boot/compressed/head.S
>> @@ -10,6 +10,7 @@
>>   */
>>  #include <linux/linkage.h>
>>  #include <asm/assembler.h>
>> +#include "efi-stub.h"
>>
>>       .arch   armv7-a
>>  /*
>> @@ -120,21 +121,99 @@
>>   */
>>               .align
>>               .arm                            @ Always enter in ARM state
>> +             .text
>>  start:
>>               .type   start,#function
>> -             .rept   7
>> +#ifdef CONFIG_EFI_STUB
>> +             @ Magic MSDOS signature for PE/COFF + ADD opcode
>> +             .word   0x62805a4d
>> +#else
>> +             mov     r0, r0
>> +#endif
>> +             .rept   5
>>               mov     r0, r0
>>               .endr
>> -   ARM(              mov     r0, r0          )
>> -   ARM(              b       1f              )
>> - THUMB(              adr     r12, BSYM(1f)   )
>> - THUMB(              bx      r12             )
>> +
>> +             adrl    r12, BSYM(zimage_continue)
>> + ARM(                mov     pc, r12 )
>> + THUMB(              bx      r12     )
>> +             @ zimage_continue will be in ARM or thumb mode as configured
>>
>>               .word   0x016f2818              @ Magic numbers to help the loader
>>               .word   start                   @ absolute load/run zImage address
>>               .word   _edata                  @ zImage end address
>> +
>> +#ifdef CONFIG_EFI_STUB
>> +             @ Portions of the MSDOS file header must be at offset
>> +             @ 0x3c from the start of the file.  All PE/COFF headers
>> +             @ are kept contiguous for simplicity.
>> +#include "efi-header.S"
>> +
>> +efi_stub_entry:
>> +             @ The EFI stub entry point is not at a fixed address, however
>> +             @ this address must be set in the PE/COFF header.
>> +             @ EFI entry point is in A32 mode, switch to T32 if configured.
>> + THUMB(              adr     r12, BSYM(1f)   )
>> + THUMB(              bx      r12             )
>>   THUMB(              .thumb                  )
>>  1:
>> +             @ 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 return of new entry point of
>> +             @ zImage, as EFI stub may copy the kernel.  Pass address
>> +             @ of space in r2 - EFI stub will fill in the pointer.
>> +
>> +             sub     sp, sp, #8              @ we only need 4 bytes,
>> +                                             @ but keep stack 8 byte aligned.
>> +             mov     r2, sp
>> +             @ Pass our actual runtime start address in pointer data
>> +             adr     r11, LC0                @ address of LC0 at run time
>> +             ldr     r12, [r11, #0]          @ address of LC0 at link time
>> +
>> +             sub     r3, r11, r12            @ calculate the delta offset
>> +             str     r3, [r2, #0]
>> +             bl      efi_entry
>> +
>> +             @ get new zImage entry address from stack, put into r3
>> +             ldr     r3, [sp, #0]
>> +             add     sp, sp, #8              @ restore stack
>> +
>> +             @ Check for error return from EFI stub
>> +             mov     r1, #EFI_STUB_ERROR
>> +             cmp     r0, r1
>> +             beq     efi_load_fail
>> +
>> +
>> +             @ Save return values of efi_entry
>> +             stmfd   sp!, {r0, r3}
>> +             bl      cache_clean_flush
>> +             bl      cache_off
>> +             ldmfd   sp!, {r0, r3}
>> +
>> +             @ Set parameters for booting zImage according to boot protocol
>> +             @ put FDT address in r2, it was returned by efi_entry()
>> +             @ r1 is FDT machine type, and r0 needs to be 0
>> +             mov     r2, r0
>> +             mov     r1, #0xFFFFFFFF
>> +             mov     r0, #0
>> +
>> +             @ Branch to (possibly) relocated zImage that is in r3
>> +             @ Make sure we are in A32 mode, as zImage requires
>> + THUMB(              bx      r3              )
>> + ARM(                mov     pc, r3          )
>> +
>> +efi_load_fail:
>> +             @ Return EFI_LOAD_ERROR to EFI firmware on error.
>> +             @ Switch back to ARM mode for EFI is done based on
>> +             @ return address on stack
>> +             ldr     r0, =0x80000001
>> +             ldmfd   sp!, {fp, pc}
>> +#endif
>> +
>> + THUMB(              .thumb                  )
>> +zimage_continue:
>>               mrs     r9, cpsr
>>  #ifdef CONFIG_ARM_VIRT_EXT
>>               bl      __hyp_stub_install      @ get into SVC mode, reversibly
>> @@ -167,7 +246,6 @@ not_angel:
>>                * by the linker here, but it should preserve r7, r8, and r9.
>>                */
>>
>> -             .text
>>
>>  #ifdef CONFIG_AUTO_ZRELADDR
>>               @ determine final kernel image address
>> --
>> 1.7.10.4
>>
>> --
>> 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/
>
diff mbox

Patch

diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile
index 7ac1610..5fad8bd 100644
--- a/arch/arm/boot/compressed/Makefile
+++ b/arch/arm/boot/compressed/Makefile
@@ -103,11 +103,22 @@  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
+OBJS	+= atags_to_fdt.o
+USE_LIBFDT = y
+endif
+
+ifeq ($(CONFIG_EFI_STUB),y)
+CFLAGS_efi-stub.o += -DTEXT_OFFSET=$(TEXT_OFFSET)
+OBJS	+= efi-stub.o
+USE_LIBFDT = y
+endif
+
+ifeq ($(USE_LIBFDT),y)
+OBJS	+= $(libfdt_objs)
 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 0000000..6965e0f
--- /dev/null
+++ b/arch/arm/boot/compressed/efi-header.S
@@ -0,0 +1,111 @@ 
+@ Copyright (C) 2013 Linaro Ltd;  <roy.franz@linaro.org>
+@
+@ This file contains the PE/COFF header that is part of the
+@ EFI stub.
+@
+
+	.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			@ 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	_edata - efi_stub_entry		@ SizeOfCode
+
+	.long	0				@ SizeOfInitializedData
+	.long	0				@ SizeOfUninitializedData
+
+	.long	efi_stub_entry			@ AddressOfEntryPoint
+	.long	efi_stub_entry			@ BaseOfCode
+	.long	0				@ data
+
+extra_header_fields:
+	.long	0				@ ImageBase
+	.long	0x20				@ SectionAlignment
+	.long	0x20				@ FileAlignment
+	.short	0				@ MajorOperatingSystemVersion
+	.short	0				@ MinorOperatingSystemVersion
+	.short	0				@ MajorImageVersion
+	.short	0				@ MinorImageVersion
+	.short	0				@ MajorSubsystemVersion
+	.short	0				@ MinorSubsystemVersion
+	.long	0				@ Win32VersionValue
+
+	.long	_edata				@ SizeOfImage
+
+	@ Everything before the entry point is considered part of the header
+	.long	efi_stub_entry			@ 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	0x0				@ NumberOfRvaAndSizes
+
+	# 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 - efi_stub_entry		@ VirtualSize
+	.long	efi_stub_entry			@ VirtualAddress
+	.long	_edata - efi_stub_entry		@ SizeOfRawData
+	.long	efi_stub_entry			@ 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)
diff --git a/arch/arm/boot/compressed/efi-stub.c b/arch/arm/boot/compressed/efi-stub.c
new file mode 100644
index 0000000..4fce68b
--- /dev/null
+++ b/arch/arm/boot/compressed/efi-stub.c
@@ -0,0 +1,448 @@ 
+/*
+ * 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 <libfdt.h>
+#include "efi-stub.h"
+
+/* EFI function call wrappers.  These are not required for
+ * ARM, but wrappers are required for X86 to convert between
+ * ABIs.  These wrappers are provided to allow code sharing
+ * between X86 and ARM.  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)
+
+/* The maximum uncompressed kernel size is 32 MBytes, so we will reserve
+ * that for the decompressed kernel.  We have no easy way to tell what
+ * the actuall size of code + data the uncompressed kernel will use.
+ */
+#define MAX_UNCOMP_KERNEL_SIZE	0x02000000
+
+/* The kernel zImage should be located between 32 Mbytes
+ * and 128 MBytes 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	0x08000000
+#define MIN_ZIMAGE_OFFSET	MAX_UNCOMP_KERNEL_SIZE
+
+#define PRINTK_PREFIX		"EFIstub: "
+
+struct fdt_region {
+	u64 base;
+	u64 size;
+};
+
+
+/* Include shared EFI stub code */
+#include "../../../../drivers/firmware/efi/efi-stub-helper.c"
+
+static int relocate_kernel(efi_system_table_t *sys_table,
+			   unsigned long *zimage_addr,
+			   unsigned long zimage_size,
+			   unsigned long min_addr, unsigned long max_addr)
+{
+	/* Get current address of kernel. */
+	unsigned long cur_zimage_addr = *zimage_addr;
+	unsigned long new_addr = 0;
+
+	efi_status_t status;
+
+	if (!zimage_addr || !zimage_size)
+		return EFI_INVALID_PARAMETER;
+
+	if (cur_zimage_addr > min_addr
+	    && (cur_zimage_addr + zimage_size) < max_addr) {
+		/* We don't need to do anything, as kernel is at an
+		 * acceptable address already.
+		 */
+		return EFI_SUCCESS;
+	}
+	/*
+	 * The EFI firmware loader could have placed the kernel image
+	 * anywhere in memory, but the kernel has restrictions on the
+	 * min and max physical address it can run at.
+	 */
+	status = efi_low_alloc(sys_table, zimage_size, 0,
+			   &new_addr, min_addr);
+	if (status != EFI_SUCCESS) {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to allocate usable memory for kernel.\n");
+		return status;
+	}
+
+	if (new_addr > (max_addr - zimage_size)) {
+		efi_free(sys_table, zimage_size, new_addr);
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to allocate usable memory for kernel.\n");
+		return EFI_INVALID_PARAMETER;
+	}
+
+	/* We know source/dest won't overlap since both memory ranges
+	 * have been allocated by UEFI, so we can safely use memcpy.
+	 */
+	memcpy((void *)new_addr, (void *)(unsigned long)cur_zimage_addr,
+	       zimage_size);
+
+	/* Return the load address  */
+	*zimage_addr = new_addr;
+
+	return status;
+}
+
+
+/* Convert the unicode UEFI command line to ASCII to pass to kernel.
+ * Size of memory allocated return in *cmd_line_len.
+ * Returns NULL on error.
+ */
+static char *convert_cmdline_to_ascii(efi_system_table_t *sys_table,
+				      efi_loaded_image_t *image,
+				      unsigned long *cmd_line_len,
+				      u32 max_addr)
+{
+	u16 *s2;
+	u8 *s1 = NULL;
+	unsigned long cmdline_addr = 0;
+	int load_options_size = image->load_options_size / 2; /* ASCII */
+	void *options = (u16 *)image->load_options;
+	int options_size = 0;
+	int status;
+	int i;
+	u16 zero = 0;
+
+	if (options) {
+		s2 = options;
+		while (*s2 && *s2 != '\n' && options_size < load_options_size) {
+			s2++;
+			options_size++;
+		}
+	}
+
+	if (options_size == 0) {
+		/* No command line options, so return empty string*/
+		options_size = 1;
+		options = &zero;
+	}
+
+	options_size++;  /* NUL termination */
+
+	status = efi_high_alloc(sys_table, options_size, 0,
+			    &cmdline_addr, max_addr);
+	if (status != EFI_SUCCESS)
+		return NULL;
+
+	s1 = (u8 *)(unsigned long)cmdline_addr;
+	s2 = (u16 *)options;
+
+	for (i = 0; i < options_size - 1; i++)
+		*s1++ = *s2++;
+
+	*s1 = '\0';
+
+	*cmd_line_len = options_size;
+	return (char *)(unsigned long)cmdline_addr;
+}
+
+
+static u32 update_fdt(efi_system_table_t *sys_table, void *orig_fdt, void *fdt,
+		      int new_fdt_size, char *cmdline_ptr, u64 initrd_addr,
+		      u64 initrd_size, efi_memory_desc_t *memory_map,
+		      int map_size, int desc_size)
+{
+	int node;
+	int status;
+	unsigned long fdt_val;
+
+	status = fdt_open_into(orig_fdt, fdt, new_fdt_size);
+	if (status != 0)
+		goto fdt_set_fail;
+
+	node = fdt_subnode_offset(fdt, 0, "chosen");
+	if (node < 0) {
+		node = fdt_add_subnode(fdt, 0, "chosen");
+		if (node < 0) {
+			status = node; /* node is error code when negative */
+			goto fdt_set_fail;
+		}
+	}
+
+	if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) {
+		status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
+				     strlen(cmdline_ptr) + 1);
+		if (status)
+			goto fdt_set_fail;
+	}
+
+	/* Set intird address/end in device tree, if present */
+	if (initrd_size != 0) {
+		u64 initrd_image_end;
+		u64 initrd_image_start = cpu_to_fdt64(initrd_addr);
+		status = fdt_setprop(fdt, node, "linux,initrd-start",
+				     &initrd_image_start, sizeof(u64));
+		if (status)
+			goto fdt_set_fail;
+		initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size);
+		status = fdt_setprop(fdt, node, "linux,initrd-end",
+				     &initrd_image_end, sizeof(u64));
+		if (status)
+			goto fdt_set_fail;
+	}
+
+	/* Add FDT entries for EFI runtime services in chosen node. */
+	node = fdt_subnode_offset(fdt, 0, "chosen");
+	fdt_val = cpu_to_fdt32((unsigned long)sys_table);
+	status = fdt_setprop(fdt, node, "efi-system-table",
+			     &fdt_val, sizeof(fdt_val));
+	if (status)
+		goto fdt_set_fail;
+
+	fdt_val = cpu_to_fdt32(desc_size);
+	status = fdt_setprop(fdt, node, "efi-mmap-desc-size",
+			     &fdt_val, sizeof(fdt_val));
+	if (status)
+		goto fdt_set_fail;
+
+	fdt_val = cpu_to_fdt32(map_size);
+	status = fdt_setprop(fdt, node, "efi-runtime-mmap-size",
+			     &fdt_val, sizeof(fdt_val));
+	if (status)
+		goto fdt_set_fail;
+
+	fdt_val = cpu_to_fdt32((unsigned long)memory_map);
+	status = fdt_setprop(fdt, node, "efi-runtime-mmap",
+			     &fdt_val, sizeof(fdt_val));
+	if (status)
+		goto fdt_set_fail;
+
+	return EFI_SUCCESS;
+
+fdt_set_fail:
+	if (status == -FDT_ERR_NOSPACE)
+		return EFI_BUFFER_TOO_SMALL;
+
+	return EFI_LOAD_ERROR;
+}
+
+
+
+int efi_entry(void *handle, efi_system_table_t *sys_table,
+	      unsigned long *zimage_addr)
+{
+	efi_loaded_image_t *image;
+	int status;
+	unsigned long nr_pages;
+	const struct fdt_region *region;
+
+	void *fdt;
+	int err;
+	int node;
+	unsigned long zimage_size = 0;
+	unsigned long dram_base;
+	/* addr/point and size pairs for memory management*/
+	u64 initrd_addr;
+	u64 initrd_size = 0;
+	u64 fdt_addr;
+	u64 fdt_size = 0;
+	u64 kernel_reserve_addr;
+	u64 kernel_reserve_size = 0;
+	char *cmdline_ptr;
+	unsigned long cmdline_size = 0;
+
+	unsigned long map_size, desc_size;
+	unsigned long mmap_key;
+	efi_memory_desc_t *memory_map;
+
+	unsigned long new_fdt_size;
+	unsigned long new_fdt_addr;
+
+	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;
+
+	efi_printk(sys_table, PRINTK_PREFIX"Booting Linux using EFI stub.\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) {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: 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 = convert_cmdline_to_ascii(sys_table, image,
+					       &cmdline_size, 0xFFFFFFFF);
+	if (!cmdline_ptr) {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: converting command line to ascii failed.\n");
+		goto fail;
+	}
+
+	/* We first load the device tree, as we need to get the base address of
+	 * DRAM from the device tree.  The zImage, device tree, and initrd
+	 * have address restrictions that are relative to the base of DRAM.
+	 */
+	status = handle_cmdline_files(sys_table, image, cmdline_ptr, "dtb=",
+				      0xffffffff, &fdt_addr, &fdt_size);
+	if (status != EFI_SUCCESS) {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load device tree blob.\n");
+		goto fail_free_cmdline;
+	}
+
+	err = fdt_check_header((void *)(unsigned long)fdt_addr);
+	if (err != 0) {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: device tree header not valid\n");
+		goto fail_free_fdt;
+	}
+	if (fdt_totalsize((void *)(unsigned long)fdt_addr) > fdt_size) {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Incomplete device tree.\n");
+		goto fail_free_fdt;
+
+	}
+
+
+	/* Look up the base of DRAM from the device tree.*/
+	fdt = (void *)(u32)fdt_addr;
+	node = fdt_subnode_offset(fdt, 0, "memory");
+	region = fdt_getprop(fdt, node, "reg", NULL);
+	if (region) {
+		dram_base = fdt64_to_cpu(region->base);
+	} else {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: no 'memory' node in device tree.\n");
+		goto fail_free_fdt;
+	}
+
+	/* Reserve memory for the uncompressed kernel image. */
+	kernel_reserve_addr = dram_base;
+	kernel_reserve_size = MAX_UNCOMP_KERNEL_SIZE;
+	nr_pages = round_up(kernel_reserve_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
+	status = efi_call_phys4(sys_table->boottime->allocate_pages,
+				EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
+				nr_pages, &kernel_reserve_addr);
+	if (status != EFI_SUCCESS) {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: unable to allocate memory for uncompressed kernel.\n");
+		goto fail_free_fdt;
+	}
+
+	/* Relocate the zImage, if required. */
+	zimage_size = image->image_size;
+	status = relocate_kernel(sys_table, zimage_addr, zimage_size,
+				 dram_base + MIN_ZIMAGE_OFFSET,
+				 dram_base + ZIMAGE_OFFSET_LIMIT);
+	if (status != EFI_SUCCESS) {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to relocate kernel\n");
+		goto fail_free_kernel_reserve;
+	}
+
+	status = handle_cmdline_files(sys_table, image, cmdline_ptr, "initrd=",
+				      dram_base + ZIMAGE_OFFSET_LIMIT,
+				      &initrd_addr, &initrd_size);
+	if (status != EFI_SUCCESS) {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load initrd\n");
+		goto fail_free_zimage;
+	}
+
+	/* 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.*/
+	new_fdt_size = fdt_size + cmdline_size + 0x200;
+
+fdt_alloc_retry:
+	status = efi_high_alloc(sys_table, new_fdt_size, 0, &new_fdt_addr,
+			    dram_base + ZIMAGE_OFFSET_LIMIT);
+	if (status != EFI_SUCCESS) {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to allocate memory for new device tree.\n");
+		goto fail_free_initrd;
+	}
+
+	/* Now that we have done our final memory allocation (and free)
+	 * we can get the memory map key needed
+	 * forexit_boot_services.*/
+	status = efi_get_memory_map(sys_table, &memory_map, &map_size,
+				    &desc_size, &mmap_key);
+	if (status != EFI_SUCCESS)
+		goto fail_free_new_fdt;
+
+	status = update_fdt(sys_table,
+			    fdt, (void *)new_fdt_addr, new_fdt_size,
+			    cmdline_ptr,
+			    initrd_addr, initrd_size,
+			    memory_map, map_size, desc_size);
+
+	if (status != EFI_SUCCESS) {
+		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 += new_fdt_size/4;
+			goto fdt_alloc_retry;
+		}
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to constuct new device tree.\n");
+		goto fail_free_initrd;
+	}
+
+	/* 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) {
+		efi_printk(sys_table, PRINTK_PREFIX"ERROR: exit boot services failed.\n");
+		goto fail_free_mmap;
+	}
+
+
+	/* Now we need to return the FDT address to the calling
+	 * assembly to this 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_zimage:
+	efi_free(sys_table, zimage_size, *zimage_addr);
+
+fail_free_kernel_reserve:
+	efi_free(sys_table, kernel_reserve_addr, kernel_reserve_size);
+
+fail_free_fdt:
+	efi_free(sys_table, fdt_size, fdt_addr);
+
+fail_free_cmdline:
+	efi_free(sys_table, cmdline_size, (u32)cmdline_ptr);
+
+fail:
+	return EFI_STUB_ERROR;
+}
diff --git a/arch/arm/boot/compressed/efi-stub.h b/arch/arm/boot/compressed/efi-stub.h
new file mode 100644
index 0000000..0fe9376
--- /dev/null
+++ b/arch/arm/boot/compressed/efi-stub.h
@@ -0,0 +1,5 @@ 
+#ifndef _ARM_EFI_STUB_H
+#define _ARM_EFI_STUB_H
+/* Error code returned to ASM code instead of valid FDT address. */
+#define EFI_STUB_ERROR		(~0)
+#endif
diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S
index 75189f1..5401a3a 100644
--- a/arch/arm/boot/compressed/head.S
+++ b/arch/arm/boot/compressed/head.S
@@ -10,6 +10,7 @@ 
  */
 #include <linux/linkage.h>
 #include <asm/assembler.h>
+#include "efi-stub.h"
 
 	.arch	armv7-a
 /*
@@ -120,21 +121,99 @@ 
  */
 		.align
 		.arm				@ Always enter in ARM state
+		.text
 start:
 		.type	start,#function
-		.rept	7
+#ifdef CONFIG_EFI_STUB
+		@ Magic MSDOS signature for PE/COFF + ADD opcode
+		.word	0x62805a4d
+#else
+		mov	r0, r0
+#endif
+		.rept	5
 		mov	r0, r0
 		.endr
-   ARM(		mov	r0, r0		)
-   ARM(		b	1f		)
- THUMB(		adr	r12, BSYM(1f)	)
- THUMB(		bx	r12		)
+
+		adrl	r12, BSYM(zimage_continue)
+ ARM(		mov     pc, r12 )
+ THUMB(		bx	r12     )
+		@ zimage_continue will be in ARM or thumb mode as configured
 
 		.word	0x016f2818		@ Magic numbers to help the loader
 		.word	start			@ absolute load/run zImage address
 		.word	_edata			@ zImage end address
+
+#ifdef CONFIG_EFI_STUB
+		@ Portions of the MSDOS file header must be at offset
+		@ 0x3c from the start of the file.  All PE/COFF headers
+		@ are kept contiguous for simplicity.
+#include "efi-header.S"
+
+efi_stub_entry:
+		@ The EFI stub entry point is not at a fixed address, however
+		@ this address must be set in the PE/COFF header.
+		@ EFI entry point is in A32 mode, switch to T32 if configured.
+ THUMB(		adr	r12, BSYM(1f)	)
+ THUMB(		bx	r12		)
  THUMB(		.thumb			)
 1:
+		@ 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 return of new entry point of
+		@ zImage, as EFI stub may copy the kernel.  Pass address
+		@ of space in r2 - EFI stub will fill in the pointer.
+
+		sub	sp, sp, #8		@ we only need 4 bytes,
+						@ but keep stack 8 byte aligned.
+		mov	r2, sp
+		@ Pass our actual runtime start address in pointer data
+		adr	r11, LC0		@ address of LC0 at run time
+		ldr	r12, [r11, #0]		@ address of LC0 at link time
+
+		sub	r3, r11, r12		@ calculate the delta offset
+		str	r3, [r2, #0]
+		bl	efi_entry
+
+		@ get new zImage entry address from stack, put into r3
+		ldr	r3, [sp, #0]
+		add	sp, sp, #8  		@ restore stack
+
+		@ Check for error return from EFI stub
+		mov	r1, #EFI_STUB_ERROR
+		cmp	r0, r1
+		beq	efi_load_fail
+
+
+		@ Save return values of efi_entry
+		stmfd	sp!, {r0, r3}
+		bl	cache_clean_flush
+		bl	cache_off
+		ldmfd   sp!, {r0, r3}
+
+		@ Set parameters for booting zImage according to boot protocol
+		@ put FDT address in r2, it was returned by efi_entry()
+		@ r1 is FDT machine type, and r0 needs to be 0
+		mov	r2, r0
+		mov	r1, #0xFFFFFFFF
+		mov	r0, #0
+
+		@ Branch to (possibly) relocated zImage that is in r3
+		@ Make sure we are in A32 mode, as zImage requires
+ THUMB(		bx	r3		)
+ ARM(		mov	pc, r3		)
+
+efi_load_fail:
+		@ Return EFI_LOAD_ERROR to EFI firmware on error.
+		@ Switch back to ARM mode for EFI is done based on
+		@ return address on stack
+		ldr	r0, =0x80000001
+		ldmfd	sp!, {fp, pc}
+#endif
+
+ THUMB(		.thumb			)
+zimage_continue:
 		mrs	r9, cpsr
 #ifdef CONFIG_ARM_VIRT_EXT
 		bl	__hyp_stub_install	@ get into SVC mode, reversibly
@@ -167,7 +246,6 @@  not_angel:
 		 * by the linker here, but it should preserve r7, r8, and r9.
 		 */
 
-		.text
 
 #ifdef CONFIG_AUTO_ZRELADDR
 		@ determine final kernel image address