>From 4e410d8e615701ab5320c1a85087457553609088 Mon Sep 17 00:00:00 2001
From: Leif Lindholm <leif.lindholm@linaro.org>
Date: Thu, 25 Apr 2013 22:21:52 +0000
Subject: [PATCH 2/2] Initial ARMv7-A (U)EFI runtime services support.

Includes some nasty hacks to work around ordering dependencies in early
kernel code. Efivars facility functional, verified with efibootmgr.

Aligned Kconfig support to have options similar to X86 and added.

Depends on early_ioremap() support.
---
 arch/arm/Kconfig           |    2 +
 arch/arm/include/asm/efi.h |   20 ++
 arch/arm/kernel/Makefile   |    2 +
 arch/arm/kernel/efi.c      |  439 ++++++++++++++++++++++++++++++++++++++++++++
 arch/arm/kernel/efi_phys.S |   59 ++++++
 arch/arm/kernel/setup.c    |    6 +
 arch/arm/mm/mmu.c          |    1 +
 drivers/firmware/Kconfig   |    2 +-
 init/main.c                |    8 +-
 9 files changed, 537 insertions(+), 2 deletions(-)
 create mode 100644 arch/arm/include/asm/efi.h
 create mode 100644 arch/arm/kernel/efi.c
 create mode 100644 arch/arm/kernel/efi_phys.S

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 9b5ad96..4910929 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -2423,6 +2423,8 @@ source "net/Kconfig"
 
 source "drivers/Kconfig"
 
+source "drivers/firmware/Kconfig"
+
 source "fs/Kconfig"
 
 source "arch/arm/Kconfig.debug"
diff --git a/arch/arm/include/asm/efi.h b/arch/arm/include/asm/efi.h
new file mode 100644
index 0000000..8985443
--- /dev/null
+++ b/arch/arm/include/asm/efi.h
@@ -0,0 +1,20 @@
+#ifndef _ASM_ARM_EFI_H
+#define _ASM_ARM_EFI_H
+
+#include <asm/mach/map.h>
+
+extern int efi_memblock_arm_reserve_range(void);
+
+typedef efi_status_t efi_phys_call_t (u32 memory_map_size,
+				      u32 descriptor_size,
+				      u32 descriptor_version,
+				      efi_memory_desc_t *dsc,
+				      efi_set_virtual_address_map_t *f);
+
+extern efi_status_t efi_phys_call (u32, u32, u32, efi_memory_desc_t *,
+				   efi_set_virtual_address_map_t *);
+
+#define efi_remap(cookie, size) __arm_ioremap((cookie), (size), MT_MEMORY)
+#define efi_ioremap(cookie, size) __arm_ioremap((cookie), (size), MT_DEVICE)
+
+#endif /* _ASM_ARM_EFI_H */
diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile
index dd9d90a..bfe8372 100644
--- a/arch/arm/kernel/Makefile
+++ b/arch/arm/kernel/Makefile
@@ -87,4 +87,6 @@ obj-y				+= psci.o
 obj-$(CONFIG_SMP)		+= psci_smp.o
 endif
 
+obj-$(CONFIG_EFI)		+= efi.o efi_phys.o
+
 extra-y := $(head-y) vmlinux.lds
diff --git a/arch/arm/kernel/efi.c b/arch/arm/kernel/efi.c
new file mode 100644
index 0000000..549fb78
--- /dev/null
+++ b/arch/arm/kernel/efi.c
@@ -0,0 +1,439 @@
+/*
+ * Extensible Firmware Interface
+ *
+ * Based on Extensible Firmware Interface Specification version 2.3.1
+ *
+ * Copyright (C) 2013 Linaro Ltd.
+ *
+ */
+
+#include <linux/efi.h>
+#include <linux/export.h>
+#include <linux/memblock.h>
+#include <linux/of.h>
+#include <linux/of_fdt.h>
+#include <linux/slab.h>
+
+#include <asm/cacheflush.h>
+#include <asm/efi.h>
+#include <asm/idmap.h>
+
+struct efi efi;
+EXPORT_SYMBOL(efi);
+struct efi_memory_map memmap;
+
+static efi_runtime_services_t *runtime;
+
+static phys_addr_t efi_system_table;
+static phys_addr_t efi_boot_mmap;
+static u32 efi_boot_mmap_size;
+
+/* Hardwired for now... */
+#define DESC_SIZE 48
+#define DESC_VER   1
+
+/* If you're planning to wire up a debugger and debug the UEFI side ... */
+#define KEEP_BOOT_SERVICES_REGIONS
+#define KEEP_ALL_REGIONS
+
+static int __init uefi_config_init(void);
+
+static int __init fdt_find_efi_params(unsigned long node, const char *uname,
+				      int depth, void *data)
+{
+	unsigned long len;
+	__be32 *prop;
+
+	if (depth != 1 ||
+	    (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
+		return 0;
+
+	printk(KERN_INFO "Getting EFI parameters from FDT:\n");
+
+	prop = of_get_flat_dt_prop(node, "efi-system-table", &len);
+	if (!prop)
+		return 0;
+	efi_system_table = of_read_ulong(prop, len/4);
+	printk(KERN_INFO "  EFI system table @ 0x%08x\n",
+	       (unsigned int) efi_system_table);
+
+	prop = of_get_flat_dt_prop(node, "efi-runtime-mmap", &len);
+	if (!prop)
+		return 0;
+	efi_boot_mmap = of_read_ulong(prop, len/4);
+	printk(KERN_INFO "  EFI mmap @ 0x%08x\n",
+	       (unsigned int) efi_boot_mmap);
+
+	prop = of_get_flat_dt_prop(node, "efi-runtime-mmap-size", &len);
+	if (!prop)
+		return 0;
+	efi_boot_mmap_size  = of_read_ulong(prop, len/4);
+	printk(KERN_INFO "  EFI mmap size = 0x%08x\n",
+	       (unsigned int) efi_boot_mmap_size);
+
+	return 1;
+}
+
+static int __init uefi_config_init(void)
+{
+	efi_char16_t *c16;
+	char vendor[100] = "unknown";
+	efi_config_table_t *config_tables;
+	u32 nr_tables;
+	int i;
+
+	efi.systab = early_ioremap(efi_system_table,
+				   sizeof(efi_system_table_t));
+
+	/*
+	 * Verify the EFI Table
+	 */
+	if (efi.systab == NULL)
+		panic("Whoa! Can't find EFI system table.\n");
+	if (efi.systab->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE)
+		panic("Whoa! EFI system table signature incorrect\n");
+	if ((efi.systab->hdr.revision >> 16) == 0)
+		printk(KERN_WARNING "Warning: EFI system table version "
+		       "%d.%02d, expected 1.00 or greater\n",
+		       efi.systab->hdr.revision >> 16,
+		       efi.systab->hdr.revision & 0xffff);
+
+	/* Show what we know for posterity */
+	c16 = (efi_char16_t *)early_ioremap(efi.systab->fw_vendor,
+					    sizeof(vendor));
+	if (c16) {
+		for (i = 0 ; i < (int) sizeof(vendor) - 1 && *c16 ; ++i)
+			vendor[i] = c16[i];
+		vendor[i] = '\0';
+	}
+
+	printk(KERN_INFO "EFI v%u.%.02u by %s\n",
+	       efi.systab->hdr.revision >> 16,
+	       efi.systab->hdr.revision & 0xffff, vendor);
+
+
+	nr_tables = efi.systab->nr_tables;
+
+	config_tables = early_ioremap(efi.systab->tables,
+				      sizeof(efi_config_table_t) * nr_tables);
+
+	for (i=0 ; i < nr_tables ; i++) {
+		efi_guid_t guid;
+		unsigned long table;
+		u8 str[38];
+
+		guid = config_tables[i].guid;
+		table = config_tables[i].table;
+
+		efi_guid_unparse(&guid, str);
+#if 0
+		printk(KERN_INFO "Table UUID: %s @ 0x%08x", str, table);
+#endif
+		if (!efi_guidcmp(guid, SMBIOS_TABLE_GUID)) {
+			efi.smbios = table;
+		}
+	}
+
+	early_iounmap(config_tables, sizeof(efi_config_table_t) * nr_tables);
+	early_iounmap(c16, sizeof(vendor));
+	early_iounmap(efi.systab,  sizeof(efi_system_table_t));
+
+	return 0;
+}
+
+static __init int is_discardable_region(efi_memory_desc_t *md)
+{
+	if (md->attribute & EFI_MEMORY_RUNTIME)
+		return 0;
+
+	switch (md->type) {
+#ifdef KEEP_BOOT_SERVICES_REGIONS
+	case EFI_BOOT_SERVICES_CODE:
+	case EFI_BOOT_SERVICES_DATA:
+#endif
+	/* Keep tables around for any future kexec operations */
+	case EFI_ACPI_RECLAIM_MEMORY:
+		return 0;
+	}
+
+	return 1;
+}
+
+#if 0
+static __initdata struct {
+	u32 type;
+	const char *name;
+}  memory_type_name_map[] = {
+	{EFI_RESERVED_TYPE, "EFI reserved"},
+	{EFI_LOADER_CODE, "EFI loader code"},
+	{EFI_LOADER_DATA, "EFI loader data"},
+	{EFI_BOOT_SERVICES_CODE, "EFI boot services code"},
+	{EFI_BOOT_SERVICES_DATA, "EFI boot services data"},
+	{EFI_RUNTIME_SERVICES_CODE, "EFI runtime services code"},
+	{EFI_RUNTIME_SERVICES_DATA, "EFI runtime services data"},
+	{EFI_CONVENTIONAL_MEMORY, "EFI conventional memory"},
+	{EFI_UNUSABLE_MEMORY, "EFI unusable memory"},
+	{EFI_ACPI_RECLAIM_MEMORY, "EFI ACPI reclaim memory"},
+	{EFI_ACPI_MEMORY_NVS, "EFI ACPI memory nvs"},
+	{EFI_MEMORY_MAPPED_IO, "EFI memory mapped i/o"},
+	{EFI_MEMORY_MAPPED_IO_PORT_SPACE, "EFI memory mapped i/o port space"},
+	{EFI_PAL_CODE, "EFI pal code"},
+	{EFI_MAX_MEMORY_TYPE, NULL},
+};
+#endif
+
+/*
+ * Remove all sections addr..addr+size
+ */
+static __init void remove_sections(phys_addr_t addr, unsigned long size)
+{
+	unsigned long section_offset;
+	unsigned long num_sections;
+
+	section_offset = addr - (addr & SECTION_MASK);
+	num_sections = size / SECTION_SIZE;
+	if (size % SECTION_SIZE)
+		num_sections++;
+
+	memblock_remove(addr - section_offset, num_sections * SECTION_SIZE);
+}
+
+static __init int remove_regions(void)
+{
+	efi_memory_desc_t *md;
+	int count = 0;
+
+	void *p;
+
+	memmap.phys_map = early_ioremap(efi_boot_mmap, efi_boot_mmap_size);
+
+	memmap.desc_size = DESC_SIZE;
+	memmap.desc_version = DESC_VER;
+	memmap.map_end = (void *) memmap.phys_map + efi_boot_mmap_size;
+	memmap.nr_map = 0;
+
+	printk(KERN_INFO "Processing EFI memory map:\n");
+	for (p = memmap.phys_map ; p < memmap.map_end; p += memmap.desc_size) {
+		md = p;
+		if (is_discardable_region(md))
+			continue;
+
+#if 0
+		printk (KERN_INFO "  %8llu pages @ %016llx (%s)\n",
+			md->num_pages,
+			md->phys_addr,
+			memory_type_name_map[md->type].name);
+#endif
+
+		if (md->type != EFI_MEMORY_MAPPED_IO) {
+			remove_sections(md->phys_addr,
+					md->num_pages * PAGE_SIZE);
+			count++;
+		}
+		memmap.nr_map++;
+	}
+	printk(KERN_INFO "%d regions preserved.\n", memmap.nr_map);
+
+	early_iounmap(memmap.phys_map, efi_boot_mmap_size);
+
+	return 0;
+}
+
+int __init efi_memblock_arm_reserve_range(void)
+{
+        /* Grab system table location out of FDT (or ACPI) */
+	of_scan_flat_dt(fdt_find_efi_params, NULL);
+
+	if (!efi_system_table || !efi_boot_mmap || !efi_boot_mmap_size)
+		return 0;
+
+	remove_regions();
+
+	uefi_config_init();
+
+	return 0;
+}
+
+/*
+ * Disable instrrupts, enable idmap and disable caches.
+ */
+static void __init phys_call_prologue(void)
+{
+	local_irq_disable();
+
+	/* Take out a flat memory mapping. */
+	setup_mm_for_reboot();
+
+	/* Clean and invalidate caches */
+	flush_cache_all();
+
+	/* Turn off caching */
+	cpu_proc_fin();
+
+	/* Push out any further dirty data, and ensure cache is empty */
+	flush_cache_all();
+}
+
+/*
+ * Restore original memory map and re-enable interrupts.
+ */
+static void __init phys_call_epilogue(void)
+{
+	static struct mm_struct *mm = &init_mm;
+
+	/* Restore original memory mapping */
+	cpu_switch_mm(mm->pgd, mm);
+
+	/* Flush branch predictor and TLBs */
+	local_flush_bp_all();
+#ifdef CONFIG_CPU_HAS_ASID
+	local_flush_tlb_all();
+#endif
+
+	local_irq_enable();
+}
+
+/*
+ * This function will switch the EFI runtime services to virtual mode.
+ * This operation must be performed only once in the system's lifetime,
+ * including any kecec:s.
+ * This call must be done with a 1:1 mapping. The current implementation
+ * resolves this by disabling the MMU. This may not be feasible in the
+ * future.
+ */
+efi_status_t  __init phys_set_virtual_address_map (u32 memory_map_size,
+						   u32 descriptor_size,
+						   u32 descriptor_version,
+						   efi_memory_desc_t *dsc)
+{
+	efi_phys_call_t *phys_set_map;
+	efi_status_t status;
+
+	phys_call_prologue();
+
+	phys_set_map = (void *)(unsigned long)virt_to_phys(efi_phys_call);
+
+	/* Called with caches disabled, returns with caches enabled */
+	status = phys_set_map(memory_map_size, descriptor_size,
+			      descriptor_version, dsc,
+			      efi.set_virtual_address_map);
+
+	phys_call_epilogue();
+
+	return status;
+}
+
+static int __init remap_region(efi_memory_desc_t *md, efi_memory_desc_t *entry)
+{
+        u64 va;
+	u64 paddr;
+	u64 size;
+
+	*entry = *md;
+	paddr = entry->phys_addr;
+	size = entry->num_pages << EFI_PAGE_SHIFT;
+
+	printk (KERN_INFO "  %016llx-%016llx : attribute = %016llx\n",
+		paddr, paddr + size - 1, md->attribute);
+
+	/*
+	 * Map everything writeback-capable as coherent memory,
+	 * anything else as device.
+	 */
+	if (md->attribute & EFI_MEMORY_WB)
+		va = (u64)((u32)efi_remap(paddr, size) & 0xffffffffUL);
+	else
+		va = (u64)((u32)efi_ioremap(paddr, size) & 0xffffffffUL);
+	if (!va)
+		return 0;
+	entry->virt_addr = va;
+
+	/*
+	 * If it was contained in this region, update runtime services
+	 * table pointer.
+	 */
+	if ((paddr <= (u32)runtime) && ((u32)runtime < (paddr + size))) {
+		u32 offset = (u32)runtime - paddr;
+		runtime = (void *)((u32)va + offset);
+		printk(KERN_INFO "Runtime @ %p\n", runtime);
+	}
+
+	return 1;
+}
+
+static void __init remap_regions(void)
+{
+	void *p, *next;
+	efi_memory_desc_t *md;
+
+	printk(KERN_INFO "%s: efi_system_table=0x%016llx\n",
+	       __FUNCTION__, efi_system_table);
+
+	efi.systab = efi_remap(efi_system_table, sizeof(efi_system_table_t));
+
+	memmap.phys_map = efi_remap(efi_boot_mmap, efi_boot_mmap_size);
+	memmap.map_end = (void *) memmap.phys_map + efi_boot_mmap_size;
+
+	/* Allocate space for the physical region map */
+	memmap.map = kzalloc(memmap.nr_map * memmap.desc_size, GFP_KERNEL);
+	if (!memmap.map)
+		return;
+
+	runtime = efi.systab->runtime;
+
+	next = memmap.map;
+	for (p = memmap.phys_map ; p < memmap.map_end ; p += memmap.desc_size) {
+		md = p;
+		if (is_discardable_region(md))
+			continue;
+
+		if (remap_region(p, next)) {
+			next += memmap.desc_size;
+		}
+	}
+
+	memmap.map_end = next;
+}
+
+
+/*
+ * Called explicitly from init/mm.c
+ */
+void __init efi_enter_virtual_mode (void)
+{
+	efi_status_t status;
+
+	if (efi.systab == NULL) {
+		printk(KERN_INFO "No EFI system table - EFI services will not be available.\n");
+		return;
+	} else {
+		printk(KERN_INFO "Remapping and enabling EFI services.\n");
+	}
+
+	/* Map the regions we memblock_remove:d earlier into kernel
+	   address space */
+	remap_regions();
+
+	printk(KERN_INFO "efi_system_table @ 0x%08x (phys)\n",
+	       (unsigned int) efi_system_table);
+	printk(KERN_INFO "efi_system_table @ 0x%08x (virt)\n",
+	       (unsigned int) efi.systab);
+
+	/* Call SetVirtualAddressMap with the physical address of the map */
+	efi.set_virtual_address_map =
+		(efi_set_virtual_address_map_t *)
+		runtime->set_virtual_address_map;
+	memmap.phys_map =
+		(efi_memory_desc_t *)(u32) __virt_to_phys((u32)memmap.map);
+
+	status = phys_set_virtual_address_map(memmap.nr_map * memmap.desc_size,
+					      memmap.desc_size,
+					      memmap.desc_version,
+					      memmap.phys_map);
+
+	/* Set up function pointers for efivars */
+	efi.get_variable = (efi_get_variable_t *)runtime->get_variable;
+	efi.get_next_variable =
+		(efi_get_next_variable_t *)runtime->get_next_variable;
+	efi.set_variable = (efi_set_variable_t *)runtime->set_variable;
+}
diff --git a/arch/arm/kernel/efi_phys.S b/arch/arm/kernel/efi_phys.S
new file mode 100644
index 0000000..7f515ea
--- /dev/null
+++ b/arch/arm/kernel/efi_phys.S
@@ -0,0 +1,59 @@
+/*
+ * arch/arm/kernel/efi_phys.S
+ *
+ * Copyright (C) 2013  Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/linkage.h>
+#define PAR_MASK 0xfff
+
+	.text
+@ efi_phys_call(a, b, c, d, *f)
+	.align  5
+        .pushsection    .idmap.text, "ax"
+ENTRY(efi_phys_call)
+	@ Save physical context
+	mov	r12, sp
+	push	{r4-r5, r12, lr}
+
+	@ Extract function pointer (don't write r12 beyond this)
+	ldr	r12, [sp, #16]
+
+	@ Convert sp to 32-bit physical
+	mov	lr, sp
+	ldr	r4, =PAR_MASK
+	and	r5, lr, r4			@ Extract lower 12 bits of sp
+	mcr	p15, 0, lr, c7, c8, 1		@ Write VA -> ATS1CPW
+	mrc	p15, 0, lr, c7, c4, 0		@ Physical Address Register
+	mvn	r4, r4
+	and	lr, lr, r4			@ Clear lower 12 bits of PA
+	add	lr, lr, r5			@ Calculate phys sp
+	mov	sp, lr				@ Update
+
+	@ Disable MMU
+        mrc     p15, 0, lr, c1, c0, 0           @ ctrl register
+        bic     lr, lr, #0x1                    @ ...............m
+        mcr     p15, 0, lr, c1, c0, 0           @ disable MMU
+	isb
+
+	@ Make call
+	blx	r12
+
+	pop	{r4-r5, r12, lr}
+
+	@ Enable MMU + Caches
+        mrc     p15, 0, r1, c1, c0, 0		@ ctrl register
+        orr     r1, r1, #0x1000			@ ...i............
+        orr     r1, r1, #0x0005			@ .............c.m
+        mcr     p15, 0, r1, c1, c0, 0		@ enable MMU
+	isb
+
+	@ Restore virtual sp and retur
+	mov	sp, r12
+	bx	lr
+ENDPROC(efi_phys_call)
+        .popsection
diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c
index d91cef5..0d10125 100644
--- a/arch/arm/kernel/setup.c
+++ b/arch/arm/kernel/setup.c
@@ -30,6 +30,7 @@
 #include <linux/bug.h>
 #include <linux/compiler.h>
 #include <linux/sort.h>
+#include <linux/efi.h>
 
 #include <asm/unified.h>
 #include <asm/cp15.h>
@@ -57,6 +58,7 @@
 #include <asm/unwind.h>
 #include <asm/memblock.h>
 #include <asm/virt.h>
+#include <asm/efi.h>
 
 #include "atags.h"
 
@@ -804,6 +806,10 @@ void __init setup_arch(char **cmdline_p)
 	sanity_check_meminfo();
 	arm_memblock_init(&meminfo, mdesc);
 
+#ifdef CONFIG_EFI
+       if (efi_enabled(EFI_BOOT))
+               efi_memblock_arm_reserve_range();
+#endif
 	paging_init(mdesc);
 	request_standard_resources(mdesc);
 
diff --git a/arch/arm/mm/mmu.c b/arch/arm/mm/mmu.c
index 95daa61..b6ee9ad 100644
--- a/arch/arm/mm/mmu.c
+++ b/arch/arm/mm/mmu.c
@@ -17,6 +17,7 @@
 #include <linux/fs.h>
 #include <linux/vmalloc.h>
 #include <linux/sizes.h>
+#include <linux/efi.h>
 
 #include <asm/cp15.h>
 #include <asm/cputype.h>
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 9387630..58d0095 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -28,7 +28,7 @@ config EDD_OFF
 
 config FIRMWARE_MEMMAP
     bool "Add firmware-provided memory map to sysfs" if EXPERT
-    default X86
+    default y if X86
     help
       Add the firmware-provided (unmodified) memory map to /sys/firmware/memmap.
       That memory map is used for example by kexec to set up parameter area
diff --git a/init/main.c b/init/main.c
index 7499e43..dadba6e 100644
--- a/init/main.c
+++ b/init/main.c
@@ -602,7 +602,7 @@ asmlinkage void __init start_kernel(void)
 	calibrate_delay();
 	pidmap_init();
 	anon_vma_init();
-#ifdef CONFIG_X86
+#if defined (CONFIG_X86)
 	if (efi_enabled(EFI_RUNTIME_SERVICES))
 		efi_enter_virtual_mode();
 #endif
@@ -873,6 +873,12 @@ static noinline void __init kernel_init_freeable(void)
 	smp_prepare_cpus(setup_max_cpus);
 
 	do_pre_smp_initcalls();
+
+#if defined (CONFIG_ARM)
+	if (efi_enabled(EFI_RUNTIME_SERVICES))
+		efi_enter_virtual_mode();
+#endif
+
 	lockup_detector_init();
 
 	smp_init();
-- 
1.7.10.4

