[PATCH 6/7] Add EFI stub for ARM

Dave Martin Dave.Martin at arm.com
Mon Aug 5 10:11:49 EDT 2013


On Fri, Aug 02, 2013 at 02:29:07PM -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.
> 
> Signed-off-by: Roy Franz <roy.franz at linaro.org>
> ---
>  arch/arm/boot/compressed/Makefile     |   18 +-
>  arch/arm/boot/compressed/efi-header.S |  114 ++++++++
>  arch/arm/boot/compressed/efi-stub.c   |  514 +++++++++++++++++++++++++++++++++
>  arch/arm/boot/compressed/head.S       |   90 +++++-
>  4 files changed, 732 insertions(+), 4 deletions(-)
>  create mode 100644 arch/arm/boot/compressed/efi-header.S
>  create mode 100644 arch/arm/boot/compressed/efi-stub.c
> 
> diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile
> index 7ac1610..c62826a 100644
> --- a/arch/arm/boot/compressed/Makefile
> +++ b/arch/arm/boot/compressed/Makefile
> @@ -106,8 +106,22 @@ $(addprefix $(obj)/,$(libfdt) $(libfdt_hdrs)): $(obj)/%: $(srctree)/scripts/dtc/
>  $(addprefix $(obj)/,$(libfdt_objs) atags_to_fdt.o): \
>  	$(addprefix $(obj)/,$(libfdt_hdrs))
>  
> +$(addprefix $(obj)/,$(libfdt_objs) 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 \
> @@ -125,7 +139,7 @@ ORIG_CFLAGS := $(KBUILD_CFLAGS)
>  KBUILD_CFLAGS = $(subst -pg, , $(ORIG_CFLAGS))
>  endif
>  
> -ccflags-y := -fpic -mno-single-pic-base -fno-builtin -I$(obj)
> +ccflags-y := -fpic -mno-single-pic-base -fno-builtin -I$(obj) -fno-stack-protector
>  asflags-y := -DZIMAGE
>  
>  # Supply kernel BSS size to the decompressor via a linker symbol.
> diff --git a/arch/arm/boot/compressed/efi-header.S b/arch/arm/boot/compressed/efi-header.S
> new file mode 100644
> index 0000000..6ff32cc
> --- /dev/null
> +++ b/arch/arm/boot/compressed/efi-header.S
> @@ -0,0 +1,114 @@
> +@ Copyright (C) 2013 Linaro Ltd;  <roy.franz at 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:
> +
> +
> +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	0				@ 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..41fa1e2
> --- /dev/null
> +++ b/arch/arm/boot/compressed/efi-stub.c
> @@ -0,0 +1,514 @@
> +/*
> + * linux/arch/arm/boot/compressed/efi-stub.c
> + *
> + * Copyright (C) 2013 Linaro Ltd;  <roy.franz at 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>
> +
> +
> +/* Error code returned to ASM code instead of valid FDT address. */
> +#define EFI_STUB_ERROR		(~0)
> +
> +/* 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 MAX_ZIMAGE_OFFSET	0x08000000
> +#define MIN_ZIMAGE_OFFSET	MAX_UNCOMP_KERNEL_SIZE
> +
> +#define MAX_CMDLINE_LEN		500
> +
> +struct fdt_region {
> +	u64 base;
> +	u64 size;
> +};
> +
> +/*
> + * Additional size that could be used for FDT entries added by
> + * the UEFI OS Loader Estimation based on:
> + * EDID (300bytes) + bootargs  + initrd region (20bytes)
> + * + system memory region (20bytes) + mp_core entries (200
> + * bytes)
> + */
> +#define FDT_ADDITIONAL_ENTRIES_SIZE     (0x300 + MAX_CMDLINE_LEN)
> +
> +/* Include shared EFI stub code */
> +#include "../../../../drivers/firmware/efi/efi-stub-helper.c"
> +
> +
> +static int is_linux_reserved_region(int memory_type)
> +{
> +	switch (memory_type) {
> +	case EfiRuntimeServicesCode:
> +	case EfiRuntimeServicesData:
> +	case EfiUnusableMemory:
> +	case EfiACPIReclaimMemory:
> +	case EfiACPIMemoryNVS:
> +		return 1;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +
> +static int relocate_kernel(efi_system_table_t *sys_table,
> +			   unsigned long *load_addr, unsigned long *load_size,
> +			   unsigned long min_addr, unsigned long max_addr)
> +{
> +	/* Get current address of kernel. */
> +	unsigned long cur_zimage_addr = *load_addr;
> +	unsigned long zimage_size = *load_size;
> +	unsigned long new_addr = 0;
> +	unsigned long nr_pages;
> +
> +	efi_status_t status;
> +
> +	if (!load_addr || !load_size)
> +		return EFI_INVALID_PARAMETER;
> +
> +	*load_size = 0;
> +	if (cur_zimage_addr > min_addr
> +	    && (cur_zimage_addr + zimage_size) < max_addr) {
> +		/* We don't need to do anything, as kernel 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.
> +	 */
> +	nr_pages = round_up(zimage_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
> +
> +	status = efi_low_alloc(sys_table, zimage_size, 0,
> +			   &new_addr, min_addr);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, "Failed to alloc 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, "Failed to alloc 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 and size */
> +	*load_addr = new_addr;
> +	*load_size = zimage_size;
> +
> +
> +	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;
> +	}
> +
> +	if (options_size > MAX_CMDLINE_LEN)
> +		options_size = MAX_CMDLINE_LEN;
> +
> +	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_and_exit_boot(efi_system_table_t *sys_table,
> +				    void *handle, unsigned long dram_base,
> +				    void *orig_fdt, u64 *orig_fdt_size,
> +				    char *cmdline_ptr,
> +				    unsigned long *cmdline_size,
> +				    u64 initrd_addr, u64 initrd_size)
> +{
> +	unsigned long new_fdt_size;
> +	unsigned long new_fdt_addr;
> +	void *fdt;
> +	int node;
> +	int status;
> +	int i;
> +	unsigned long map_size, desc_size;
> +	unsigned long mmap_key;
> +	efi_memory_desc_t *memory_map;
> +	unsigned long fdt_val;
> +
> +	new_fdt_size = *orig_fdt_size + FDT_ADDITIONAL_ENTRIES_SIZE;
> +	status = efi_high_alloc(sys_table, new_fdt_size, 0, &new_fdt_addr,
> +			    dram_base + MAX_ZIMAGE_OFFSET);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, "ERROR: Unable to allocate memory for new device tree.\n");
> +		goto fail;
> +	}
> +
> +
> +	fdt = (void *)new_fdt_addr;
> +	status = fdt_open_into(orig_fdt, fdt, new_fdt_size);
> +	if (status != 0) {
> +		efi_printk(sys_table, "ERROR: Device Tree open_int failed.\n");
> +		goto fail_free_new_fdt;
> +	}
> +	/* We are done with the original DTB, so free it. */
> +	efi_free(sys_table, *orig_fdt_size, (u32)orig_fdt);
> +	*orig_fdt_size = 0;
> +
> +	node = fdt_subnode_offset(fdt, 0, "chosen");
> +	if (node < 0) {
> +		node = fdt_add_subnode(fdt, 0, "chosen");
> +		if (node < 0) {
> +			efi_printk(sys_table, "Error on finding 'chosen' node\n");
> +			goto fail_free_new_fdt;
> +		}
> +	}
> +
> +	if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) {
> +		status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
> +				     strlen(cmdline_ptr) + 1);
> +		if (status) {
> +			efi_printk(sys_table, "Failed to set new bootarg\n");
> +			goto fail_free_new_fdt;
> +		}
> +	}
> +	/* We are done with original command line, so free it. */
> +	efi_free(sys_table, *cmdline_size, (u32)cmdline_ptr);
> +	*cmdline_size = 0;
> +
> +	/* 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) {
> +			efi_printk(sys_table, "Failed to set new 'linux,initrd-start'\n");
> +			goto fail_free_new_fdt;
> +		}
> +		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) {
> +			efi_printk(sys_table, "Failed to set new 'linux,initrd-end'\n");
> +			goto fail_free_new_fdt;
> +		}
> +	}
> +
> +	/* Update memory map in the device tree. The memory node must
> +	 * be present in the tree.*/
> +	node = fdt_subnode_offset(fdt, 0, "memory");
> +	if (node < 0) {
> +		efi_printk(sys_table, "ERROR: FDT memory node does not exist in DTB.\n");
> +		goto fail_free_new_fdt;
> +	}
> +
> +	status = efi_get_memory_map(sys_table, &memory_map, &map_size,
> +				    &desc_size, &mmap_key);
> +	if (status != EFI_SUCCESS)
> +		goto fail_free_new_fdt;
> +
> +	for (i = 0; i < (map_size / sizeof(efi_memory_desc_t)); i++) {
> +		efi_memory_desc_t *desc;
> +		unsigned long m = (unsigned long)memory_map;
> +		desc = (efi_memory_desc_t *)(m + (i * desc_size));
> +
> +		if (is_linux_reserved_region(desc->type)) {
> +			status = fdt_add_mem_rsv(fdt, desc->phys_addr,
> +						 desc->num_pages * EFI_PAGE_SIZE);
> +			if (status != 0) {
> +				efi_printk(sys_table, "ERROR: Failed to add 'memreserve' to fdt.\n");
> +				goto fail_free_mmap;
> +			}
> +		}
> +	}
> +
> +
> +	/* Add FDT entries for EFI runtime services in chosen node.
> +	 * We need to add the final memory map, so this is done at
> +	 * the very end.
> +	 */
> +	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) {
> +		efi_printk(sys_table, "Failed to set new 'efi-system-table'\n");
> +		goto fail_free_new_fdt;
> +	}
> +	fdt_val = cpu_to_fdt32(desc_size);
> +	status = fdt_setprop(fdt, node, "efi-mmap-desc-size",
> +			     &fdt_val, sizeof(fdt_val));
> +	if (status) {
> +		efi_printk(sys_table, "Failed to set new 'efi-mmap-desc-size'\n");
> +		goto fail_free_new_fdt;
> +	}
> +	fdt_val = cpu_to_fdt32(map_size);
> +	status = fdt_setprop(fdt, node, "efi-runtime-mmap-size",
> +			     &fdt_val, sizeof(fdt_val));
> +	if (status) {
> +		efi_printk(sys_table, "Failed to set new 'efi-runtime-mmap-size'\n");
> +		goto fail_free_new_fdt;
> +	}
> +	fdt_val = cpu_to_fdt32((unsigned long)memory_map);
> +	status = fdt_setprop(fdt, node, "efi-runtime-mmap",
> +			     &fdt_val, sizeof(fdt_val));
> +	if (status) {
> +		efi_printk(sys_table, "Failed to set new 'efi-runtime-mmap'\n");
> +		goto fail_free_new_fdt;
> +	}
> +
> +	/* Now we need to exit boot services.  We need the key from
> +	 * the most recent read of the memory map to do this.  We can't
> +	 * free this buffer in the normal case, but do free it when
> +	 * exit_boot_services() fails or adding the memory map to the FDT
> +	 * fails.
> +	 */
> +	status = efi_call_phys2(sys_table->boottime->exit_boot_services,
> +				handle, mmap_key);
> +
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, "exit boot services failed.\n");
> +		goto fail_free_mmap;
> +	}
> +
> +	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:
> +	return 0;
> +}
> +
> +
> +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;  /* Original DTB */
> +	u64 fdt_size = 0;
> +	u64 kernel_reserve_addr;
> +	u64 kernel_reserve_size = 0;
> +	char *cmdline_ptr;
> +	unsigned long cmdline_size = 0;
> +	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, "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, "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, "ERROR converting command line to ascii.\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, "Error loading dtb blob\n");
> +		goto fail_free_cmdline;
> +	}
> +
> +	err = fdt_check_header((void *)(unsigned long)fdt_addr);
> +	if (err != 0) {
> +		efi_printk(sys_table, "ERROR: Device Tree header not valid\n");
> +		goto fail_free_dtb;
> +	}
> +	if (fdt_totalsize((void *)(unsigned long)fdt_addr) > fdt_size) {
> +		efi_printk(sys_table, "ERROR: Incomplete device tree.\n");
> +		goto fail_free_dtb;
> +
> +	}
> +
> +
> +	/* 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, "Error: no 'memory' node in device tree.\n");
> +		goto fail_free_dtb;
> +	}
> +
> +	/* 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, "ERROR allocating memory for uncompressed kernel.\n");
> +		goto fail_free_dtb;
> +	}
> +
> +	/* 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 + MAX_ZIMAGE_OFFSET);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, "Failed to relocate kernel\n");
> +		goto fail_free_kernel_reserve;
> +	}
> +
> +	status = handle_cmdline_files(sys_table, image, cmdline_ptr, "initrd=",
> +				      dram_base + MAX_ZIMAGE_OFFSET,
> +				      &initrd_addr, &initrd_size);
> +	if (status != EFI_SUCCESS) {
> +		efi_printk(sys_table, "Error loading initrd\n");
> +		goto fail_free_zimage;
> +	}
> +
> +	new_fdt_addr = update_fdt_and_exit_boot(sys_table, handle,
> +						dram_base, fdt, &fdt_size,
> +						cmdline_ptr, &cmdline_size,
> +						initrd_addr, initrd_size);
> +
> +	if (new_fdt_addr == 0) {
> +		efi_printk(sys_table, "Error updating device tree and exiting boot services.\n");
> +		goto fail_free_initrd;
> +	}
> +
> +
> +	/* 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_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_dtb:
> +	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/head.S b/arch/arm/boot/compressed/head.S
> index 75189f1..4c70b9e 100644
> --- a/arch/arm/boot/compressed/head.S
> +++ b/arch/arm/boot/compressed/head.S
> @@ -122,19 +122,106 @@
>  		.arm				@ Always enter in ARM state
>  start:
>  		.type	start,#function
> -		.rept	7
> +#ifdef CONFIG_EFI_STUB
> +		@ Magic MSDOS signature for PE/COFF + ADD opcode
> +		.word	0x62805a4d

What about BE32?

In that case, the instruction is a coprocessor load, that loads from a
random address to a coprocessor that almost certainly doesn't exist.
This will probably fault.

Since BE32 is only for older platforms (<v6) and this is not easily
solvable, it might be sensible to make the EFI stub support depend on
!CPU_ENDIAN_BE32.

> +#else
> +		mov	r0, r0
> +#endif
> +		.rept	5

You reduced the .rept count by 2, but only inserted one extra word,
perhaps because of the extra, but buggy, insertion below.

>  		mov	r0, r0
>  		.endr
>     ARM(		mov	r0, r0		)
>     ARM(		b	1f		)
>   THUMB(		adr	r12, BSYM(1f)	)
>   THUMB(		bx	r12		)

Can't you just replace 1f with zimage_continue directly in the above
lines, instead of the subsequent extra branch:

> + THUMB(		.thumb			)
> +1:
> +		b	zimage_continue

This also avoids having two labels both called '1'.

I believe the magic word is expected to be in a predictable offset,
but the size of the preceding branch is unpredictable in Thumb
(you could use b.w, or possibly remove the branch altogether, as
explained above).

>  		.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:
> +		.text
> +		@ 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.
> +		.arm
> +   ARM(		mov	r0, r0		)
> +   ARM(		b	1f		)

Those above two instructions are effectively just no-op padding.  Do you
need them at all?

> + 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}
> +
> +		@ Save args to EFI app across got fixup call
> +		stmfd	sp!, {r0, r1}
> +		ldmfd	sp!, {r0, r1}
> +
> +		@ 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, #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, #8  @ restore stack
> +
> +		@ Check for error return from EFI stub (0xFFFFFFFF)
> +		ldr	r1, =0xffffffff
> +		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}
> +
> +		@ put DTB address in r2, it was returned by EFI entry
> +		mov	r2, r0
> +		ldr	r1, =0xffffffff		@ DTB machine type
> +		mov	r0, #0  @ r0 is 0
> +
> +		@ Branch to (possibly) relocated zImage entry that is in r3
> +		bx	r3
> +
> +efi_load_fail:
> +	@ We need to exit THUMB mode here, to return to EFI firmware.

If you lose these 4 lines:

> + THUMB(		adr	r12, BSYM(1f)	)
> + THUMB(		bx	r12		)
> +1:
> +		.arm

...

> +		@ Return EFI_LOAD_ERROR to EFI firmware on error.
> +		ldr	r0, =0x80000001

and replace these

> +		ldmfd	sp!, {fp, lr}
> +		mov	pc, lr

with:

		ldmfd	sp!, {fp, pc}

then the Thumb-ness on return will be determined by whatever is loaded
into pc.

There's no special need to switch back to ARM before the final return,
provided that "mov pc,<Rm>" is not used (that will never switch from
Thumb to ARM).


Cheers
---Dave

> + THUMB(		.thumb			)

Then you can also remove the above line.

> +#endif
> +
> +zimage_continue:
>  		mrs	r9, cpsr
>  #ifdef CONFIG_ARM_VIRT_EXT
>  		bl	__hyp_stub_install	@ get into SVC mode, reversibly
> @@ -167,7 +254,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 at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel



More information about the linux-arm-kernel mailing list