diff options
author | Graeme Gregory <graeme.gregory@linaro.org> | 2013-12-04 11:45:58 +0000 |
---|---|---|
committer | Graeme Gregory <graeme.gregory@linaro.org> | 2013-12-04 11:45:58 +0000 |
commit | 42cea6f4e4daec5a77dbe773427095e44a99ff89 (patch) | |
tree | 40e23fdc0a6492cd7af6a791414f9ca8e5c154c2 | |
parent | 854f43c9533162bac63294f2c822d4926126dd74 (diff) | |
parent | 38f4385e40d069533c9400d44501c243e7e2ec47 (diff) |
Merge branch 'topic-armv7-uefi-runtime-services' of git://git.linaro.org/people/leiflindholm/linux into leg-kernel
-rw-r--r-- | Documentation/arm/00-INDEX | 5 | ||||
-rw-r--r-- | Documentation/arm/early_ioremap.txt | 23 | ||||
-rw-r--r-- | Documentation/arm/uefi.txt | 47 | ||||
-rw-r--r-- | arch/arm/Kconfig | 24 | ||||
-rw-r--r-- | arch/arm/include/asm/efi.h | 22 | ||||
-rw-r--r-- | arch/arm/include/asm/fixmap.h | 29 | ||||
-rw-r--r-- | arch/arm/include/asm/io.h | 23 | ||||
-rw-r--r-- | arch/arm/kernel/Makefile | 2 | ||||
-rw-r--r-- | arch/arm/kernel/efi.c | 485 | ||||
-rw-r--r-- | arch/arm/kernel/efi_phys.S | 59 | ||||
-rw-r--r-- | arch/arm/kernel/setup.c | 9 | ||||
-rw-r--r-- | arch/arm/mm/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mm/early_ioremap.c | 271 | ||||
-rw-r--r-- | arch/arm/mm/mmu.c | 2 | ||||
-rw-r--r-- | arch/ia64/include/asm/io.h | 1 | ||||
-rw-r--r-- | arch/ia64/kernel/efi.c | 54 | ||||
-rw-r--r-- | arch/x86/platform/efi/efi.c | 126 | ||||
-rw-r--r-- | drivers/firmware/efi/efi.c | 140 | ||||
-rw-r--r-- | include/linux/efi.h | 10 | ||||
-rw-r--r-- | init/main.c | 4 |
20 files changed, 1176 insertions, 161 deletions
diff --git a/Documentation/arm/00-INDEX b/Documentation/arm/00-INDEX index 36420e116c90..87e01d145222 100644 --- a/Documentation/arm/00-INDEX +++ b/Documentation/arm/00-INDEX @@ -24,6 +24,8 @@ SPEAr - ST SPEAr platform Linux Overview VFP/ - Release notes for Linux Kernel Vector Floating Point support code +early_ioremap.txt + - documentation of the early_ioremap() functionality empeg/ - Ltd's Empeg MP3 Car Audio Player mem_alignment @@ -34,3 +36,6 @@ nwfpe/ - NWFPE floating point emulator documentation swp_emulation - SWP/SWPB emulation handler/logging description + +uefi.txt + - [U]EFI configuration and runtime services documentation diff --git a/Documentation/arm/early_ioremap.txt b/Documentation/arm/early_ioremap.txt new file mode 100644 index 000000000000..6096cfad45fa --- /dev/null +++ b/Documentation/arm/early_ioremap.txt @@ -0,0 +1,23 @@ +early_ioremap()/early_memremap() and early_iounmap() provide a mechanism for +temporarily mapping in small blocks of memory, identified by their physical +address, into the fixmap virtual address block before paging_init() has run +and more flexible mapping functions are available. + +Due to its direct method, it also gets around potential need for special +handling of regions that end up in highmem. + +It supports up to 7 simultaneously mapped regions of up to 128KB each. +All mappings created by early_ioremap() are non-shareable device memory. +All mappings created by early_memremap() are uncached normal memory. + +Any residual mappings will be overridden by subsequent kmap() calls (but do +use early_iounmap()). + +Specify 'early_ioremap_debug' on the kernel commandline for verbose output. + +SYNOPSIS + #include <linux/io.h> + + void *early_ioremap(resource_size_t phys_addr, unsigned long size); + void *early_memremap(resource_size_t phys_addr, unsigned long size); + void early_iounmap(void *addr, unsigned long size); diff --git a/Documentation/arm/uefi.txt b/Documentation/arm/uefi.txt new file mode 100644 index 000000000000..e6e4d4177bd8 --- /dev/null +++ b/Documentation/arm/uefi.txt @@ -0,0 +1,47 @@ +UEFI, the Unified Extensible Firmware Interface is a speifcication +governing the behaviours of compatible firmware interfaces. It is +maintained by the UEFI Forum - http://www.uefi.org/. + +Since UEFI is an evolution of its predecessor 'EFI', the terms EFI and +UEFI are used somewhat interchangeably in this document and associated +source code. + +The implementation depends on receiving the UEFI runtime memory map and a +pointer to the System Table in a Flattened Device Tree - so is only available +with CONFIG_OF. + +It parses the FDT /chosen node for the following parameters: +- 'linux,efi-system-table': + Physical address of the system table. (required) + 64-bit value since an ARMv7 plattform may support LPAE, and to facilitate + code sharing with arm64. Top 32 bits will be ignored, since UEFI specification + mandates a 1:1 mapping of all RAM. +- 'linux,efi-mmap': + The EFI memory map as an embedded property. (required) + An array of type EFI_MEMORY_DESCRIPTOR as described by the UEFI + specification, current version described in Linux by efi_memory_desc_t. + The memory map is represented in little-endian, not DT, byte order. + This map needs to contain at least the regions to be preserved for runtime + services, but would normally just be the map retreieved by calling UEFI + GetMemoryMap() immediately before ExitBootServices(). +- 'linux,efi-mmap-desc-size': + Size of each descriptor in the memory map. (override default) +- 'linux,efi-mmap-desc-ver': + Memory descriptor format version. (override default) + +It also depends on early_memremap() to parse the UEFI configuration tables. + +For actually enabling [U]EFI support, enable: +- CONFIG_EFI=y +- CONFIG_EFI_VARS=y or m + +After the kernel has mapped the required regions into its address space, +a SetVirtualAddressMap() call is made into UEFI in order to update +relocations. This call must be performed with all the code in a 1:1 +mapping. This implementation achieves this by temporarily disabling the +MMU for the duration of this call. This can only be done safely: +- before secondary CPUs are brought online. +- after early_initcalls have completed, since it uses setup_mm_for_reboot(). + +For verbose debug messages, specify 'uefi_debug' on the kernel command +line. diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 7a6d6d87a6e5..57d8f6b21705 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1877,6 +1877,28 @@ config UACCESS_WITH_MEMCPY However, if the CPU data cache is using a write-allocate mode, this option is unlikely to provide any performance gain. +config EARLY_IOREMAP + bool "Provide early_ioremap() support for kernel initialization." + depends on MMU + help + Provides a mechanism for kernel initialisation code to temporarily + map, in a highmem-agnostic way, memory pages in before paging_init(). + It generates its map entries in kmap region (0xfff00000) before kmap + is initialized. + +config EFI + bool "UEFI runtime service support" + depends on OF && !CPU_BIG_ENDIAN + select UCS2_STRING + select EARLY_IOREMAP + ---help--- + This enables the kernel to use UEFI runtime services that are + available (such as the UEFI variable services). + + This option is only useful on systems that have UEFI firmware. + However, even with this option, the resultant kernel will + continue to boot on non-UEFI platforms. + config SECCOMP bool prompt "Enable seccomp to safely compute untrusted bytecode" @@ -2313,6 +2335,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 000000000000..aead94c75660 --- /dev/null +++ b/arch/arm/include/asm/efi.h @@ -0,0 +1,22 @@ +#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) +#define efi_unmap(cookie) __arm_iounmap((cookie)) +#define efi_iounmap(cookie) __arm_iounmap((cookie)) + +#endif /* _ASM_ARM_EFI_H */ diff --git a/arch/arm/include/asm/fixmap.h b/arch/arm/include/asm/fixmap.h index bbae919bceb4..8b2507e517cf 100644 --- a/arch/arm/include/asm/fixmap.h +++ b/arch/arm/include/asm/fixmap.h @@ -1,6 +1,8 @@ #ifndef _ASM_FIXMAP_H #define _ASM_FIXMAP_H +#include <linux/bug.h> + /* * Nothing too fancy for now. * @@ -20,13 +22,38 @@ #define FIX_KMAP_BEGIN 0 #define FIX_KMAP_END (FIXADDR_SIZE >> PAGE_SHIFT) +/* + * 224 temporary boot-time mappings, used by early_ioremap(), + * before ioremap() is functional. + * + * (P)re-using the FIXADDR region, which is used for highmem + * later on, and statically aligned to 1MB. + */ +#define NR_FIX_BTMAPS 32 +#define FIX_BTMAPS_SLOTS 7 +#define TOTAL_FIX_BTMAPS (NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS) +#define FIX_BTMAP_BEGIN FIX_KMAP_BEGIN +#define FIX_BTMAP_END (FIX_KMAP_END - 1) + +#define clear_fixmap(idx) \ + __set_fixmap(idx, 0, __pgprot(0)) + #define __fix_to_virt(x) (FIXADDR_START + ((x) << PAGE_SHIFT)) #define __virt_to_fix(x) (((x) - FIXADDR_START) >> PAGE_SHIFT) extern void __this_fixmap_does_not_exist(void); -static inline unsigned long fix_to_virt(const unsigned int idx) +static __always_inline unsigned long fix_to_virt(const unsigned int idx) { + /* + * this branch gets completely eliminated after inlining, + * except when someone tries to use fixaddr indices in an + * illegal way. (such as mixing up address types or using + * out-of-range indices). + * + * If it doesn't get removed, the linker will complain + * loudly with a reasonably clear error message.. + */ if (idx >= FIX_KMAP_END) __this_fixmap_does_not_exist(); return __fix_to_virt(idx); diff --git a/arch/arm/include/asm/io.h b/arch/arm/include/asm/io.h index d070741b2b37..826cd88fd798 100644 --- a/arch/arm/include/asm/io.h +++ b/arch/arm/include/asm/io.h @@ -26,6 +26,7 @@ #include <linux/types.h> #include <asm/byteorder.h> #include <asm/memory.h> +#include <asm/pgtable.h> #include <asm-generic/pci_iomap.h> /* @@ -397,5 +398,27 @@ extern int devmem_is_allowed(unsigned long pfn); extern void register_isa_ports(unsigned int mmio, unsigned int io, unsigned int io_shift); +/* + * early_ioremap(), early_memremap() and early_iounmap() are for + * temporary early boot-time mappings, before the real ioremap() + * is functional. + * A boot-time mapping is currently limited to at most 16 pages. + * + * This is all squashed by paging_init(). + */ +#ifdef CONFIG_EARLY_IOREMAP +extern void early_ioremap_init(void); +extern void early_ioremap_reset(void); +extern void __iomem *early_remap(resource_size_t phys_addr, + unsigned long size, u32 prot); +#define early_ioremap(x, y) early_remap(x, y, L_PTE_MT_DEV_NONSHARED) +#define early_memremap(x, y) early_remap(x, y, L_PTE_MT_WRITEALLOC) + +extern void early_iounmap(void __iomem *addr, unsigned long size); +#else +#define early_ioremap_init() +#define early_ioremap_reset() +#endif + #endif /* __KERNEL__ */ #endif /* __ASM_ARM_IO_H */ diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 85bf7fb26c59..0ce89074b450 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -98,4 +98,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 000000000000..a7c2c8de1f5b --- /dev/null +++ b/arch/arm/kernel/efi.c @@ -0,0 +1,485 @@ +/* + * 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/sched.h> +#include <linux/slab.h> + +#include <asm/cacheflush.h> +#include <asm/efi.h> +#include <asm/idmap.h> +#include <asm/tlbflush.h> + +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; +static u32 efi_mmap_desc_size; +static u32 efi_mmap_desc_ver; + +static unsigned long arm_efi_facility; + +/* + * Default memory map descriptor information + */ +#define DESC_SIZE 48 +#define DESC_VER 1 + +/* + * If you're planning to wire up a debugger and debug the UEFI side ... + */ +#undef KEEP_ALL_REGIONS + +/* + * If you need to (temporarily) support buggy firmware. + */ +#define KEEP_BOOT_SERVICES_REGIONS + +/* + * Returns 1 if 'facility' is enabled, 0 otherwise. + */ +int efi_enabled(int facility) +{ + return test_bit(facility, &arm_efi_facility) != 0; +} +EXPORT_SYMBOL(efi_enabled); + +static int uefi_debug __initdata; +static int __init uefi_debug_setup(char *str) +{ + uefi_debug = 1; + + return 0; +} +early_param("uefi_debug", uefi_debug_setup); + +static int __init fdt_find_efi_params(unsigned long node, const char *uname, + int depth, void *data) +{ + unsigned long len; + void *prop; + + if (depth != 1 || + (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0)) + return 0; + + pr_info("Getting EFI parameters from FDT.\n"); + + prop = of_get_flat_dt_prop(node, "linux,efi-system-table", &len); + if (!prop) + return 0; + efi_system_table = of_read_ulong(prop, len/4); + + prop = of_get_flat_dt_prop(node, "linux,efi-mmap", &len); + if (!prop) + return 0; + efi_boot_mmap = (u32) prop; + efi_boot_mmap_size = len; + + prop = of_get_flat_dt_prop(node, "linux,efi-mmap-desc-size", NULL); + if (prop) + efi_mmap_desc_size = of_read_ulong(prop, 1); + else + efi_mmap_desc_size = DESC_SIZE; + + prop = of_get_flat_dt_prop(node, "linux,efi-mmap-desc-ver", NULL); + if (prop) + efi_mmap_desc_ver = of_read_ulong(prop, 1); + else + efi_mmap_desc_ver = DESC_VER; + + if (uefi_debug) { + pr_info(" EFI system table @ 0x%08x\n", + (unsigned int) efi_system_table); + pr_info(" EFI mmap @ 0x%08x\n", + (unsigned int) efi_boot_mmap); + pr_info(" EFI mmap size = 0x%08x\n", + (unsigned int) efi_boot_mmap_size); + pr_info(" EFI mmap descriptor size = 0x%08x\n", + (unsigned int) efi_mmap_desc_size); + pr_info(" EFI mmap descriptor version = 0x%08x\n", + (unsigned int) efi_mmap_desc_ver); + } + + return 1; +} + +static int __init uefi_init(void) +{ + efi_char16_t *c16; + char vendor[100] = "unknown"; + int i, retval; + + 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) + pr_warn("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'; + } + + pr_info("EFI v%u.%.02u by %s\n", + efi.systab->hdr.revision >> 16, + efi.systab->hdr.revision & 0xffff, vendor); + + retval = efi_config_init(NULL); + if (retval == 0) + set_bit(EFI_CONFIG_TABLES, &arm_efi_facility); + + early_iounmap(c16, sizeof(vendor)); + early_iounmap(efi.systab, sizeof(efi_system_table_t)); + + return retval; +} + +static __init int is_discardable_region(efi_memory_desc_t *md) +{ +#ifdef KEEP_ALL_REGIONS + return 0; +#endif + + 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; +} + +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}, +}; + +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 = (void *) (u32) efi_boot_mmap; + memmap.desc_size = efi_mmap_desc_size; + memmap.desc_version = efi_mmap_desc_ver; + memmap.map_end = (void *) memmap.phys_map + efi_boot_mmap_size; + memmap.nr_map = 0; + + if (uefi_debug) + pr_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 (uefi_debug) + pr_info(" %8llu pages @ %016llx (%s)\n", + md->num_pages, md->phys_addr, + memory_type_name_map[md->type].name); + + if (md->type != EFI_MEMORY_MAPPED_IO) { + remove_sections(md->phys_addr, + md->num_pages * PAGE_SIZE); + count++; + } + memmap.nr_map++; + } + + if (uefi_debug) + pr_info("%d regions preserved.\n", memmap.nr_map); + + return 0; +} + +int __init efi_memblock_arm_reserve_range(void) +{ + if (!of_scan_flat_dt(fdt_find_efi_params, NULL)) + return 0; + + set_bit(EFI_BOOT, &arm_efi_facility); + + uefi_init(); + + remove_regions(); + + 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(); +} + +/* + * Memory map was previously extracted from flattened device tree for + * reserving regions. Now we need to grab it from the unflattened tree + * in order to access it for remapping purposes. + */ +static void __init *get_runtime_mmap(void) +{ + struct device_node *node; + int len; + void *map; + + node = of_find_node_by_path("/chosen"); + if (node != NULL) { + map = (void *) of_get_property(node, "linux,efi-mmap", &len); + + if (len != efi_boot_mmap_size) { + pr_info(" EFI mmap size mismatch!\n"); + map = NULL; + } + } else { + map = NULL; + } + + return map; +} + +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; + + /* + * 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 (uefi_debug) + pr_info(" %016llx-%016llx => 0x%08x : (%s)\n", + paddr, paddr + size - 1, (u32)va, + md->attribute & EFI_MEMORY_WB ? "WB" : "I/O"); + + return 1; +} + +static int __init remap_regions(void) +{ + void *p, *next; + efi_memory_desc_t *md; + + memmap.phys_map = get_runtime_mmap(); + if (!memmap.phys_map) + return 0; + 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 0; + + 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)) + return 0; + + next += memmap.desc_size; + } + + memmap.map_end = next; + efi.memmap = &memmap; + + efi.systab = efi_lookup_mapped_addr(efi_system_table); + if (efi.systab) + set_bit(EFI_SYSTEM_TABLES, &arm_efi_facility); + /* + * efi.systab->runtime is a 32-bit pointer to something guaranteed by + * the UEFI specification to be 1:1 mapped in a 4GB address space. + */ + runtime = efi_lookup_mapped_addr((u32)efi.systab->runtime); + + return 1; +} + + +/* + * This function switches the EFI runtime services to virtual mode. + * This operation must be performed only once in the system's lifetime, + * including any kecec calls. + * + * This must be done with a 1:1 mapping. The current implementation + * resolves this by disabling the MMU. + */ +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; +} + +/* + * Called explicitly from init/mm.c + */ +void __init efi_enter_virtual_mode(void) +{ + efi_status_t status; + + if (!efi_enabled(EFI_BOOT)) { + pr_info("EFI services will not be available.\n"); + return; + } else { + pr_info("Remapping and enabling EFI services.\n"); + } + + /* Map the regions we memblock_remove:d earlier into kernel + address space */ + if (!remap_regions()) { + pr_info("Failed to remap EFI regions - runtime services will not be available.\n"); + return; + } + + /* 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); + + if (status != EFI_SUCCESS) { + pr_info("Failed to set EFI virtual address map!\n"); + return; + } + + /* 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; + set_bit(EFI_RUNTIME_SERVICES, &arm_efi_facility); +} diff --git a/arch/arm/kernel/efi_phys.S b/arch/arm/kernel/efi_phys.S new file mode 100644 index 000000000000..e36cc17a17a2 --- /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 return + 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 0e1e2b3afa45..87ff95d0ccb2 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -30,12 +30,14 @@ #include <linux/bug.h> #include <linux/compiler.h> #include <linux/sort.h> +#include <linux/efi.h> #include <asm/unified.h> #include <asm/cp15.h> #include <asm/cpu.h> #include <asm/cputype.h> #include <asm/elf.h> +#include <asm/io.h> #include <asm/procinfo.h> #include <asm/psci.h> #include <asm/sections.h> @@ -56,6 +58,7 @@ #include <asm/unwind.h> #include <asm/memblock.h> #include <asm/virt.h> +#include <asm/efi.h> #include "atags.h" @@ -877,10 +880,16 @@ void __init setup_arch(char **cmdline_p) parse_early_param(); + early_ioremap_init(); + sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL); sanity_check_meminfo(); arm_memblock_init(&meminfo, mdesc); +#ifdef CONFIG_EFI + efi_memblock_arm_reserve_range(); +#endif + paging_init(mdesc); request_standard_resources(mdesc); diff --git a/arch/arm/mm/Makefile b/arch/arm/mm/Makefile index 787fb08256c9..31bd7b1c145c 100644 --- a/arch/arm/mm/Makefile +++ b/arch/arm/mm/Makefile @@ -16,6 +16,7 @@ endif obj-$(CONFIG_MODULES) += proc-syms.o obj-$(CONFIG_ALIGNMENT_TRAP) += alignment.o +obj-$(CONFIG_EARLY_IOREMAP) += early_ioremap.o obj-$(CONFIG_HIGHMEM) += highmem.o obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o diff --git a/arch/arm/mm/early_ioremap.c b/arch/arm/mm/early_ioremap.c new file mode 100644 index 000000000000..0a012e0b2d55 --- /dev/null +++ b/arch/arm/mm/early_ioremap.c @@ -0,0 +1,271 @@ +/* + * early_ioremap() support for ARM + * + * Based on existing support in arch/x86/mm/ioremap.c + * + * Restrictions: currently only functional before paging_init() + */ + +#include <linux/init.h> +#include <linux/io.h> + +#include <asm/fixmap.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/tlbflush.h> + +#include <asm/mach/map.h> + +static int early_ioremap_debug __initdata; + +static int __init early_ioremap_debug_setup(char *str) +{ + early_ioremap_debug = 1; + + return 0; +} +early_param("early_ioremap_debug", early_ioremap_debug_setup); + +static pte_t __initdata bm_pte[PTRS_PER_PTE] __aligned(PTRS_PER_PTE * sizeof(pte_t)); +static int after_paging_init __initdata; + +static inline pmd_t * __init early_ioremap_pmd(unsigned long addr) +{ + unsigned int index = pgd_index(addr); + pgd_t *pgd = cpu_get_pgd() + index; + pud_t *pud = pud_offset(pgd, addr); + pmd_t *pmd = pmd_offset(pud, addr); + + return pmd; +} + +static inline pte_t * __init early_ioremap_pte(unsigned long addr) +{ + return &bm_pte[pte_index(addr)]; +} + +static unsigned long slot_virt[FIX_BTMAPS_SLOTS] __initdata; + +void __init early_ioremap_init(void) +{ + pmd_t *pmd; + int i; + u64 desc; + + if (early_ioremap_debug) + pr_info("early_ioremap_init()\n"); + + for (i = 0; i < FIX_BTMAPS_SLOTS; i++) { + slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN + NR_FIX_BTMAPS*i); + if (early_ioremap_debug) + pr_info(" %lu byte slot @ 0x%08x\n", + NR_FIX_BTMAPS * PAGE_SIZE, (u32)slot_virt[i]); + } + + pmd = early_ioremap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)); + desc = *pmd; + memset(bm_pte, 0, sizeof(bm_pte)); + + pmd_populate_kernel(NULL, pmd, bm_pte); + desc = *pmd; + + BUILD_BUG_ON((__fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT) + != (__fix_to_virt(FIX_BTMAP_END) >> PMD_SHIFT)); + + if (pmd != early_ioremap_pmd(fix_to_virt(FIX_BTMAP_END))) { + WARN_ON(1); + pr_warn("pmd %p != %p\n", + pmd, early_ioremap_pmd(fix_to_virt(FIX_BTMAP_END))); + pr_warn("fix_to_virt(FIX_BTMAP_BEGIN): %08lx\n", + fix_to_virt(FIX_BTMAP_BEGIN)); + pr_warn("fix_to_virt(FIX_BTMAP_END): %08lx\n", + fix_to_virt(FIX_BTMAP_END)); + pr_warn("FIX_BTMAP_END: %lu\n", FIX_BTMAP_END); + pr_warn("FIX_BTMAP_BEGIN: %d\n", FIX_BTMAP_BEGIN); + } +} + +void __init early_ioremap_reset(void) +{ + after_paging_init = 1; +} + +static void __init __early_set_fixmap(unsigned long idx, + phys_addr_t phys, pgprot_t flags) +{ + unsigned long addr = __fix_to_virt(idx); + pte_t *pte; + u64 desc; + + if (idx >= FIX_KMAP_END) { + BUG(); + return; + } + pte = early_ioremap_pte(addr); + + if (pgprot_val(flags)) + set_pte_at(NULL, FIXADDR_START, pte, + pfn_pte(phys >> PAGE_SHIFT, flags)); + else + pte_clear(NULL, addr, pte); + flush_tlb_kernel_range(addr, addr + PAGE_SIZE); + desc = *pte; +} + +static inline void __init early_set_fixmap(unsigned long idx, + phys_addr_t phys, pgprot_t prot) +{ + __early_set_fixmap(idx, phys, prot); +} + +static inline void __init early_clear_fixmap(unsigned long idx) +{ + __early_set_fixmap(idx, 0, __pgprot(0)); +} + +static void __iomem *prev_map[FIX_BTMAPS_SLOTS] __initdata; +static unsigned long prev_size[FIX_BTMAPS_SLOTS] __initdata; + +static void __init __iomem * +__early_remap(resource_size_t phys_addr, unsigned long size, pgprot_t prot) +{ + unsigned long offset; + resource_size_t last_addr; + unsigned int nrpages; + unsigned long idx; + int i, slot; + + slot = -1; + for (i = 0; i < FIX_BTMAPS_SLOTS; i++) { + if (!prev_map[i]) { + slot = i; + break; + } + } + + if (slot < 0) { + pr_info("early_iomap(%08llx, %08lx) not found slot\n", + (u64)phys_addr, size); + WARN_ON(1); + return NULL; + } + + if (early_ioremap_debug) { + pr_info("early_ioremap(%08llx, %08lx) [%d] => ", + (u64)phys_addr, size, slot); + } + + /* Don't allow wraparound or zero size */ + last_addr = phys_addr + size - 1; + if (!size || last_addr < phys_addr) { + WARN_ON(1); + return NULL; + } + + prev_size[slot] = size; + /* + * Mappings have to be page-aligned + */ + offset = phys_addr & ~PAGE_MASK; + phys_addr &= PAGE_MASK; + size = PAGE_ALIGN(last_addr + 1) - phys_addr; + + /* + * Mappings have to fit in the FIX_BTMAP area. + */ + nrpages = size >> PAGE_SHIFT; + if (nrpages > NR_FIX_BTMAPS) { + WARN_ON(1); + return NULL; + } + + /* + * Ok, go for it.. + */ + idx = FIX_BTMAP_BEGIN + slot * NR_FIX_BTMAPS; + while (nrpages > 0) { + early_set_fixmap(idx, phys_addr, prot); + phys_addr += PAGE_SIZE; + idx++; + --nrpages; + } + if (early_ioremap_debug) + pr_cont("%08lx + %08lx\n", offset, slot_virt[slot]); + + prev_map[slot] = (void __iomem *)(offset + slot_virt[slot]); + return prev_map[slot]; +} + +/* Remap an IO device */ +void __init __iomem * +early_remap(resource_size_t phys_addr, unsigned long size, u32 prot) +{ + if (after_paging_init) { + WARN_ON(1); + return NULL; + } + + /* + * PAGE_KERNEL depends on not-yet-initialised variables. + * We don't care about coherency or executability of early_ioremap + * pages anyway. + */ + prot |= L_PTE_YOUNG | L_PTE_PRESENT; + return __early_remap(phys_addr, size, prot); +} + + +void __init early_iounmap(void __iomem *addr, unsigned long size) +{ + unsigned long virt_addr; + unsigned long offset; + unsigned int nrpages; + unsigned long idx; + int i, slot; + + if (after_paging_init) { + WARN_ON(1); + return; + } + + slot = -1; + for (i = 0; i < FIX_BTMAPS_SLOTS; i++) { + if (prev_map[i] == addr) { + slot = i; + break; + } + } + + if (slot < 0) { + pr_info("early_iounmap(%p, %08lx) not found slot\n", + addr, size); + WARN_ON(1); + return; + } + + if (prev_size[slot] != size) { + pr_info("early_iounmap(%p, %08lx) [%d] size not consistent %08lx\n", + addr, size, slot, prev_size[slot]); + WARN_ON(1); + return; + } + + if (early_ioremap_debug) + pr_info("early_iounmap(%p, %08lx) [%d]\n", addr, size, slot); + + virt_addr = (unsigned long)addr; + if (virt_addr < fix_to_virt(FIX_BTMAP_BEGIN)) { + WARN_ON(1); + return; + } + offset = virt_addr & ~PAGE_MASK; + nrpages = PAGE_ALIGN(offset + size) >> PAGE_SHIFT; + + idx = FIX_BTMAP_BEGIN + slot * NR_FIX_BTMAPS; + while (nrpages > 0) { + early_clear_fixmap(idx); + idx++; + --nrpages; + } + prev_map[slot] = NULL; +} diff --git a/arch/arm/mm/mmu.c b/arch/arm/mm/mmu.c index 6cdbfe0bf013..ffa88e24ba81 100644 --- a/arch/arm/mm/mmu.c +++ b/arch/arm/mm/mmu.c @@ -28,6 +28,7 @@ #include <asm/highmem.h> #include <asm/system_info.h> #include <asm/traps.h> +#include <asm/io.h> #include <asm/mach/arch.h> #include <asm/mach/map.h> @@ -1363,4 +1364,5 @@ void __init paging_init(const struct machine_desc *mdesc) empty_zero_page = virt_to_page(zero_page); __flush_dcache_page(NULL, empty_zero_page); + early_ioremap_reset(); } diff --git a/arch/ia64/include/asm/io.h b/arch/ia64/include/asm/io.h index 74a7cc3293bc..0d2bcb37ec35 100644 --- a/arch/ia64/include/asm/io.h +++ b/arch/ia64/include/asm/io.h @@ -424,6 +424,7 @@ extern void __iomem * ioremap(unsigned long offset, unsigned long size); extern void __iomem * ioremap_nocache (unsigned long offset, unsigned long size); extern void iounmap (volatile void __iomem *addr); extern void __iomem * early_ioremap (unsigned long phys_addr, unsigned long size); +#define early_memremap(phys_addr, size) early_ioremap(phys_addr, size) extern void early_iounmap (volatile void __iomem *addr, unsigned long size); static inline void __iomem * ioremap_cache (unsigned long phys_addr, unsigned long size) { diff --git a/arch/ia64/kernel/efi.c b/arch/ia64/kernel/efi.c index 51bce594eb83..2e5545917137 100644 --- a/arch/ia64/kernel/efi.c +++ b/arch/ia64/kernel/efi.c @@ -44,10 +44,15 @@ #define EFI_DEBUG 0 +static __initdata unsigned long palo_phys; + +static __initdata efi_config_table_type_t arch_tables[] = { + {PROCESSOR_ABSTRACTION_LAYER_OVERWRITE_GUID, "PALO", &palo_phys}, + {NULL_GUID, NULL, NULL}, +}; + extern efi_status_t efi_call_phys (void *, ...); -struct efi efi; -EXPORT_SYMBOL(efi); static efi_runtime_services_t *runtime; static u64 mem_limit = ~0UL, max_addr = ~0UL, min_addr = 0UL; @@ -423,9 +428,9 @@ static u8 __init palo_checksum(u8 *buffer, u32 length) * Parse and handle PALO table which is published at: * http://www.dig64.org/home/DIG64_PALO_R1_0.pdf */ -static void __init handle_palo(unsigned long palo_phys) +static void __init handle_palo(unsigned long phys_addr) { - struct palo_table *palo = __va(palo_phys); + struct palo_table *palo = __va(phys_addr); u8 checksum; if (strncmp(palo->signature, PALO_SIG, sizeof(PALO_SIG) - 1)) { @@ -467,12 +472,10 @@ void __init efi_init (void) { void *efi_map_start, *efi_map_end; - efi_config_table_t *config_tables; efi_char16_t *c16; u64 efi_desc_size; char *cp, vendor[100] = "unknown"; int i; - unsigned long palo_phys; /* * It's too early to be able to use the standard kernel command line @@ -514,8 +517,6 @@ efi_init (void) efi.systab->hdr.revision >> 16, efi.systab->hdr.revision & 0xffff); - config_tables = __va(efi.systab->tables); - /* Show what we know for posterity */ c16 = __va(efi.systab->fw_vendor); if (c16) { @@ -528,43 +529,10 @@ efi_init (void) efi.systab->hdr.revision >> 16, efi.systab->hdr.revision & 0xffff, vendor); - efi.mps = EFI_INVALID_TABLE_ADDR; - efi.acpi = EFI_INVALID_TABLE_ADDR; - efi.acpi20 = EFI_INVALID_TABLE_ADDR; - efi.smbios = EFI_INVALID_TABLE_ADDR; - efi.sal_systab = EFI_INVALID_TABLE_ADDR; - efi.boot_info = EFI_INVALID_TABLE_ADDR; - efi.hcdp = EFI_INVALID_TABLE_ADDR; - efi.uga = EFI_INVALID_TABLE_ADDR; - palo_phys = EFI_INVALID_TABLE_ADDR; - for (i = 0; i < (int) efi.systab->nr_tables; i++) { - if (efi_guidcmp(config_tables[i].guid, MPS_TABLE_GUID) == 0) { - efi.mps = config_tables[i].table; - printk(" MPS=0x%lx", config_tables[i].table); - } else if (efi_guidcmp(config_tables[i].guid, ACPI_20_TABLE_GUID) == 0) { - efi.acpi20 = config_tables[i].table; - printk(" ACPI 2.0=0x%lx", config_tables[i].table); - } else if (efi_guidcmp(config_tables[i].guid, ACPI_TABLE_GUID) == 0) { - efi.acpi = config_tables[i].table; - printk(" ACPI=0x%lx", config_tables[i].table); - } else if (efi_guidcmp(config_tables[i].guid, SMBIOS_TABLE_GUID) == 0) { - efi.smbios = config_tables[i].table; - printk(" SMBIOS=0x%lx", config_tables[i].table); - } else if (efi_guidcmp(config_tables[i].guid, SAL_SYSTEM_TABLE_GUID) == 0) { - efi.sal_systab = config_tables[i].table; - printk(" SALsystab=0x%lx", config_tables[i].table); - } else if (efi_guidcmp(config_tables[i].guid, HCDP_TABLE_GUID) == 0) { - efi.hcdp = config_tables[i].table; - printk(" HCDP=0x%lx", config_tables[i].table); - } else if (efi_guidcmp(config_tables[i].guid, - PROCESSOR_ABSTRACTION_LAYER_OVERWRITE_GUID) == 0) { - palo_phys = config_tables[i].table; - printk(" PALO=0x%lx", config_tables[i].table); - } - } - printk("\n"); + if (efi_config_init(arch_tables) != 0) + return; if (palo_phys != EFI_INVALID_TABLE_ADDR) handle_palo(palo_phys); diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c index c7e22ab29a5a..92c02344a060 100644 --- a/arch/x86/platform/efi/efi.c +++ b/arch/x86/platform/efi/efi.c @@ -60,19 +60,6 @@ static efi_char16_t efi_dummy_name[6] = { 'D', 'U', 'M', 'M', 'Y', 0 }; -struct efi __read_mostly efi = { - .mps = EFI_INVALID_TABLE_ADDR, - .acpi = EFI_INVALID_TABLE_ADDR, - .acpi20 = EFI_INVALID_TABLE_ADDR, - .smbios = EFI_INVALID_TABLE_ADDR, - .sal_systab = EFI_INVALID_TABLE_ADDR, - .boot_info = EFI_INVALID_TABLE_ADDR, - .hcdp = EFI_INVALID_TABLE_ADDR, - .uga = EFI_INVALID_TABLE_ADDR, - .uv_systab = EFI_INVALID_TABLE_ADDR, -}; -EXPORT_SYMBOL(efi); - struct efi_memory_map memmap; static struct efi efi_phys __initdata; @@ -80,6 +67,13 @@ static efi_system_table_t efi_systab __initdata; unsigned long x86_efi_facility; +static __initdata efi_config_table_type_t arch_tables[] = { +#ifdef CONFIG_X86_UV + {UV_SYSTEM_TABLE_GUID, "UVsystab", &efi.uv_systab}, +#endif + {NULL_GUID, NULL, NULL}, +}; + /* * Returns 1 if 'facility' is enabled, 0 otherwise. */ @@ -399,6 +393,8 @@ int __init efi_memblock_x86_reserve_range(void) memblock_reserve(pmap, memmap.nr_map * memmap.desc_size); + efi.memmap = &memmap; + return 0; } @@ -578,80 +574,6 @@ static int __init efi_systab_init(void *phys) return 0; } -static int __init efi_config_init(u64 tables, int nr_tables) -{ - void *config_tables, *tablep; - int i, sz; - - if (efi_enabled(EFI_64BIT)) - sz = sizeof(efi_config_table_64_t); - else - sz = sizeof(efi_config_table_32_t); - - /* - * Let's see what config tables the firmware passed to us. - */ - config_tables = early_ioremap(tables, nr_tables * sz); - if (config_tables == NULL) { - pr_err("Could not map Configuration table!\n"); - return -ENOMEM; - } - - tablep = config_tables; - pr_info(""); - for (i = 0; i < efi.systab->nr_tables; i++) { - efi_guid_t guid; - unsigned long table; - - if (efi_enabled(EFI_64BIT)) { - u64 table64; - guid = ((efi_config_table_64_t *)tablep)->guid; - table64 = ((efi_config_table_64_t *)tablep)->table; - table = table64; -#ifdef CONFIG_X86_32 - if (table64 >> 32) { - pr_cont("\n"); - pr_err("Table located above 4GB, disabling EFI.\n"); - early_iounmap(config_tables, - efi.systab->nr_tables * sz); - return -EINVAL; - } -#endif - } else { - guid = ((efi_config_table_32_t *)tablep)->guid; - table = ((efi_config_table_32_t *)tablep)->table; - } - if (!efi_guidcmp(guid, MPS_TABLE_GUID)) { - efi.mps = table; - pr_cont(" MPS=0x%lx ", table); - } else if (!efi_guidcmp(guid, ACPI_20_TABLE_GUID)) { - efi.acpi20 = table; - pr_cont(" ACPI 2.0=0x%lx ", table); - } else if (!efi_guidcmp(guid, ACPI_TABLE_GUID)) { - efi.acpi = table; - pr_cont(" ACPI=0x%lx ", table); - } else if (!efi_guidcmp(guid, SMBIOS_TABLE_GUID)) { - efi.smbios = table; - pr_cont(" SMBIOS=0x%lx ", table); -#ifdef CONFIG_X86_UV - } else if (!efi_guidcmp(guid, UV_SYSTEM_TABLE_GUID)) { - efi.uv_systab = table; - pr_cont(" UVsystab=0x%lx ", table); -#endif - } else if (!efi_guidcmp(guid, HCDP_TABLE_GUID)) { - efi.hcdp = table; - pr_cont(" HCDP=0x%lx ", table); - } else if (!efi_guidcmp(guid, UGA_IO_PROTOCOL_GUID)) { - efi.uga = table; - pr_cont(" UGA=0x%lx ", table); - } - tablep += sz; - } - pr_cont("\n"); - early_iounmap(config_tables, efi.systab->nr_tables * sz); - return 0; -} - static int __init efi_runtime_init(void) { efi_runtime_services_t *runtime; @@ -745,7 +667,7 @@ void __init efi_init(void) efi.systab->hdr.revision >> 16, efi.systab->hdr.revision & 0xffff, vendor); - if (efi_config_init(efi.systab->tables, efi.systab->nr_tables)) + if (efi_config_init(arch_tables)) return; set_bit(EFI_CONFIG_TABLES, &x86_efi_facility); @@ -816,34 +738,6 @@ static void __init runtime_code_page_mkexec(void) } } -/* - * We can't ioremap data in EFI boot services RAM, because we've already mapped - * it as RAM. So, look it up in the existing EFI memory map instead. Only - * callable after efi_enter_virtual_mode and before efi_free_boot_services. - */ -void __iomem *efi_lookup_mapped_addr(u64 phys_addr) -{ - void *p; - if (WARN_ON(!memmap.map)) - return NULL; - for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { - efi_memory_desc_t *md = p; - u64 size = md->num_pages << EFI_PAGE_SHIFT; - u64 end = md->phys_addr + size; - if (!(md->attribute & EFI_MEMORY_RUNTIME) && - md->type != EFI_BOOT_SERVICES_CODE && - md->type != EFI_BOOT_SERVICES_DATA) - continue; - if (!md->virt_addr) - continue; - if (phys_addr >= md->phys_addr && phys_addr < end) { - phys_addr += md->virt_addr - md->phys_addr; - return (__force void __iomem *)(unsigned long)phys_addr; - } - } - return NULL; -} - void efi_memory_uc(u64 addr, unsigned long size) { unsigned long page_shift = 1UL << EFI_PAGE_SHIFT; diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 5145fa344ad5..2e2fbdec0845 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -13,11 +13,27 @@ * This file is released under the GPLv2. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kobject.h> #include <linux/module.h> #include <linux/init.h> #include <linux/device.h> #include <linux/efi.h> +#include <linux/io.h> + +struct efi __read_mostly efi = { + .mps = EFI_INVALID_TABLE_ADDR, + .acpi = EFI_INVALID_TABLE_ADDR, + .acpi20 = EFI_INVALID_TABLE_ADDR, + .smbios = EFI_INVALID_TABLE_ADDR, + .sal_systab = EFI_INVALID_TABLE_ADDR, + .boot_info = EFI_INVALID_TABLE_ADDR, + .hcdp = EFI_INVALID_TABLE_ADDR, + .uga = EFI_INVALID_TABLE_ADDR, + .uv_systab = EFI_INVALID_TABLE_ADDR, +}; +EXPORT_SYMBOL(efi); static struct kobject *efi_kobj; static struct kobject *efivars_kobj; @@ -132,3 +148,127 @@ err_put: } subsys_initcall(efisubsys_init); + + +/* + * We can't ioremap data in EFI boot services RAM, because we've already mapped + * it as RAM. So, look it up in the existing EFI memory map instead. Only + * callable after efi_enter_virtual_mode and before efi_free_boot_services. + */ +void __iomem *efi_lookup_mapped_addr(u64 phys_addr) +{ + struct efi_memory_map *map; + void *p; + map = efi.memmap; + if (!map) + return NULL; + if (WARN_ON(!map->map)) + return NULL; + for (p = map->map; p < map->map_end; p += map->desc_size) { + efi_memory_desc_t *md = p; + u64 size = md->num_pages << EFI_PAGE_SHIFT; + u64 end = md->phys_addr + size; + if (!(md->attribute & EFI_MEMORY_RUNTIME) && + md->type != EFI_BOOT_SERVICES_CODE && + md->type != EFI_BOOT_SERVICES_DATA) + continue; + if (!md->virt_addr) + continue; + if (phys_addr >= md->phys_addr && phys_addr < end) { + phys_addr += md->virt_addr - md->phys_addr; + return (__force void __iomem *)(unsigned long)phys_addr; + } + } + return NULL; +} + +static __initdata efi_config_table_type_t common_tables[] = { + {ACPI_20_TABLE_GUID, "ACPI 2.0", &efi.acpi20}, + {ACPI_TABLE_GUID, "ACPI", &efi.acpi}, + {HCDP_TABLE_GUID, "HCDP", &efi.hcdp}, + {MPS_TABLE_GUID, "MPS", &efi.mps}, + {SAL_SYSTEM_TABLE_GUID, "SALsystab", &efi.sal_systab}, + {SMBIOS_TABLE_GUID, "SMBIOS", &efi.smbios}, + {UGA_IO_PROTOCOL_GUID, "UGA", &efi.uga}, + {NULL_GUID, NULL, 0}, +}; + +static __init int match_config_table(efi_guid_t *guid, + unsigned long table, + efi_config_table_type_t *table_types) +{ + u8 str[EFI_VARIABLE_GUID_LEN + 1]; + int i; + + if (table_types) { + efi_guid_unparse(guid, str); + + for (i = 0; efi_guidcmp(table_types[i].guid, NULL_GUID); i++) { + efi_guid_unparse(&table_types[i].guid, str); + + if (!efi_guidcmp(*guid, table_types[i].guid)) { + *(table_types[i].ptr) = table; + pr_cont(" %s=0x%lx ", + table_types[i].name, table); + return 1; + } + } + } + + return 0; +} + +int __init efi_config_init(efi_config_table_type_t *arch_tables) +{ + void *config_tables, *tablep; + int i, sz; + + if (efi_enabled(EFI_64BIT)) + sz = sizeof(efi_config_table_64_t); + else + sz = sizeof(efi_config_table_32_t); + + /* + * Let's see what config tables the firmware passed to us. + */ + config_tables = early_memremap(efi.systab->tables, + efi.systab->nr_tables * sz); + if (config_tables == NULL) { + pr_err("Could not map Configuration table!\n"); + return -ENOMEM; + } + + tablep = config_tables; + pr_info(""); + for (i = 0; i < efi.systab->nr_tables; i++) { + efi_guid_t guid; + unsigned long table; + + if (efi_enabled(EFI_64BIT)) { + u64 table64; + guid = ((efi_config_table_64_t *)tablep)->guid; + table64 = ((efi_config_table_64_t *)tablep)->table; + table = table64; +#ifndef CONFIG_64BIT + if (table64 >> 32) { + pr_cont("\n"); + pr_err("Table located above 4GB, disabling EFI.\n"); + early_iounmap(config_tables, + efi.systab->nr_tables * sz); + return -EINVAL; + } +#endif + } else { + guid = ((efi_config_table_32_t *)tablep)->guid; + table = ((efi_config_table_32_t *)tablep)->table; + } + + if (!match_config_table(&guid, table, common_tables)) + match_config_table(&guid, table, arch_tables); + + tablep += sz; + } + pr_cont("\n"); + early_iounmap(config_tables, efi.systab->nr_tables * sz); + return 0; +} diff --git a/include/linux/efi.h b/include/linux/efi.h index 5f8f176154f7..ba4d175e1d56 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -404,6 +404,12 @@ typedef struct { unsigned long table; } efi_config_table_t; +typedef struct { + efi_guid_t guid; + const char *name; + unsigned long *ptr; +} efi_config_table_type_t; + #define EFI_SYSTEM_TABLE_SIGNATURE ((u64)0x5453595320494249ULL) #define EFI_2_30_SYSTEM_TABLE_REVISION ((2 << 16) | (30)) @@ -552,6 +558,7 @@ extern struct efi { efi_get_next_high_mono_count_t *get_next_high_mono_count; efi_reset_system_t *reset_system; efi_set_virtual_address_map_t *set_virtual_address_map; + struct efi_memory_map *memmap; } efi; static inline int @@ -587,6 +594,7 @@ static inline efi_status_t efi_query_variable_store(u32 attributes, unsigned lon } #endif extern void __iomem *efi_lookup_mapped_addr(u64 phys_addr); +extern int efi_config_init(efi_config_table_type_t *arch_tables); extern u64 efi_get_iobase (void); extern u32 efi_mem_type (unsigned long phys_addr); extern u64 efi_mem_attributes (unsigned long phys_addr); @@ -636,7 +644,7 @@ extern int __init efi_setup_pcdp_console(char *); #define EFI_64BIT 5 /* Is the firmware 64-bit? */ #ifdef CONFIG_EFI -# ifdef CONFIG_X86 +# if defined(CONFIG_X86) || defined(CONFIG_ARM) extern int efi_enabled(int facility); # else static inline int efi_enabled(int facility) diff --git a/init/main.c b/init/main.c index 63d3e8f2970c..9b46119b6e35 100644 --- a/init/main.c +++ b/init/main.c @@ -877,6 +877,10 @@ static noinline void __init kernel_init_freeable(void) smp_prepare_cpus(setup_max_cpus); do_pre_smp_initcalls(); + + if (IS_ENABLED(CONFIG_ARM) && efi_enabled(EFI_BOOT)) + efi_enter_virtual_mode(); + lockup_detector_init(); smp_init(); |