| /* |
| * Extensible Firmware Interface |
| * |
| * Based on Extensible Firmware Interface Specification version 2.4 |
| * |
| * Copyright (C) 2013, 2014 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/efi.h> |
| #include <linux/export.h> |
| #include <linux/memblock.h> |
| #include <linux/bootmem.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/tlbflush.h> |
| #include <asm/mmu_context.h> |
| |
| struct efi_memory_map memmap; |
| |
| static efi_runtime_services_t *runtime; |
| |
| static u64 efi_system_table; |
| |
| 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 is_normal_ram(efi_memory_desc_t *md) |
| { |
| if (md->attribute & EFI_MEMORY_WB) |
| return 1; |
| return 0; |
| } |
| |
| static void __init efi_setup_idmap(void) |
| { |
| struct memblock_region *r; |
| efi_memory_desc_t *md; |
| u64 paddr, npages, size; |
| |
| for_each_memblock(memory, r) |
| create_id_mapping(r->base, r->size, 0); |
| |
| /* map runtime io spaces */ |
| for_each_efi_memory_desc(&memmap, md) { |
| if (!(md->attribute & EFI_MEMORY_RUNTIME) || is_normal_ram(md)) |
| continue; |
| paddr = md->phys_addr; |
| npages = md->num_pages; |
| memrange_efi_to_native(&paddr, &npages); |
| size = npages << PAGE_SHIFT; |
| create_id_mapping(paddr, size, 1); |
| } |
| } |
| |
| static int __init uefi_init(void) |
| { |
| efi_char16_t *c16; |
| char vendor[100] = "unknown"; |
| int i, retval; |
| |
| efi.systab = early_memremap(efi_system_table, |
| sizeof(efi_system_table_t)); |
| if (efi.systab == NULL) { |
| pr_warn("Unable to map EFI system table.\n"); |
| return -ENOMEM; |
| } |
| |
| set_bit(EFI_BOOT, &efi.flags); |
| set_bit(EFI_64BIT, &efi.flags); |
| |
| /* |
| * Verify the EFI Table |
| */ |
| if (efi.systab->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) { |
| pr_err("System table signature incorrect\n"); |
| return -EINVAL; |
| } |
| if ((efi.systab->hdr.revision >> 16) < 2) |
| pr_warn("Warning: EFI system table version %d.%02d, expected 2.00 or greater\n", |
| efi.systab->hdr.revision >> 16, |
| efi.systab->hdr.revision & 0xffff); |
| |
| /* Show what we know for posterity */ |
| c16 = early_memremap(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, &efi.flags); |
| |
| early_memunmap(c16, sizeof(vendor)); |
| early_memunmap(efi.systab, sizeof(efi_system_table_t)); |
| |
| return retval; |
| } |
| |
| static __initdata char memory_type_name[][32] = { |
| {"Reserved"}, |
| {"Loader Code"}, |
| {"Loader Data"}, |
| {"Boot Code"}, |
| {"Boot Data"}, |
| {"Runtime Code"}, |
| {"Runtime Data"}, |
| {"Conventional Memory"}, |
| {"Unusable Memory"}, |
| {"ACPI Reclaim Memory"}, |
| {"ACPI Memory NVS"}, |
| {"Memory Mapped I/O"}, |
| {"MMIO Port Space"}, |
| {"PAL Code"}, |
| }; |
| |
| /* |
| * Return true for RAM regions we want to permanently reserve. |
| */ |
| static __init int is_reserve_region(efi_memory_desc_t *md) |
| { |
| if (!is_normal_ram(md)) |
| return 0; |
| |
| if (md->attribute & EFI_MEMORY_RUNTIME) |
| return 1; |
| |
| if (md->type == EFI_ACPI_RECLAIM_MEMORY || |
| md->type == EFI_RESERVED_TYPE) |
| return 1; |
| |
| return 0; |
| } |
| |
| static __init void reserve_regions(void) |
| { |
| efi_memory_desc_t *md; |
| u64 paddr, npages, size; |
| |
| if (uefi_debug) |
| pr_info("Processing EFI memory map:\n"); |
| |
| for_each_efi_memory_desc(&memmap, md) { |
| paddr = md->phys_addr; |
| npages = md->num_pages; |
| |
| if (uefi_debug) |
| pr_info(" 0x%012llx-0x%012llx [%s]", |
| paddr, paddr + (npages << EFI_PAGE_SHIFT) - 1, |
| memory_type_name[md->type]); |
| |
| memrange_efi_to_native(&paddr, &npages); |
| size = npages << PAGE_SHIFT; |
| |
| if (is_normal_ram(md)) |
| early_init_dt_add_memory_arch(paddr, size); |
| |
| if (is_reserve_region(md) || |
| md->type == EFI_BOOT_SERVICES_CODE || |
| md->type == EFI_BOOT_SERVICES_DATA) { |
| memblock_reserve(paddr, size); |
| if (uefi_debug) |
| pr_cont("*"); |
| } |
| |
| if (uefi_debug) |
| pr_cont("\n"); |
| } |
| } |
| |
| |
| static u64 __init free_one_region(u64 start, u64 end) |
| { |
| u64 size = end - start; |
| |
| if (uefi_debug) |
| pr_info(" EFI freeing: 0x%012llx-0x%012llx\n", start, end - 1); |
| |
| free_bootmem_late(start, size); |
| return size; |
| } |
| |
| static u64 __init free_region(u64 start, u64 end) |
| { |
| u64 map_start, map_end, total = 0; |
| |
| if (end <= start) |
| return total; |
| |
| map_start = (u64)memmap.phys_map; |
| map_end = PAGE_ALIGN(map_start + (memmap.map_end - memmap.map)); |
| map_start &= PAGE_MASK; |
| |
| if (start < map_end && end > map_start) { |
| /* region overlaps UEFI memmap */ |
| if (start < map_start) |
| total += free_one_region(start, map_start); |
| |
| if (map_end < end) |
| total += free_one_region(map_end, end); |
| } else |
| total += free_one_region(start, end); |
| |
| return total; |
| } |
| |
| static void __init free_boot_services(void) |
| { |
| u64 total_freed = 0; |
| u64 keep_end, free_start, free_end; |
| efi_memory_desc_t *md; |
| |
| /* |
| * If kernel uses larger pages than UEFI, we have to be careful |
| * not to inadvertantly free memory we want to keep if there is |
| * overlap at the kernel page size alignment. We do not want to |
| * free is_reserve_region() memory nor the UEFI memmap itself. |
| * |
| * The memory map is sorted, so we keep track of the end of |
| * any previous region we want to keep, remember any region |
| * we want to free and defer freeing it until we encounter |
| * the next region we want to keep. This way, before freeing |
| * it, we can clip it as needed to avoid freeing memory we |
| * want to keep for UEFI. |
| */ |
| |
| keep_end = 0; |
| free_start = 0; |
| |
| for_each_efi_memory_desc(&memmap, md) { |
| u64 paddr, npages, size; |
| |
| if (is_reserve_region(md)) { |
| /* |
| * We don't want to free any memory from this region. |
| */ |
| if (free_start) { |
| /* adjust free_end then free region */ |
| if (free_end > md->phys_addr) |
| free_end -= PAGE_SIZE; |
| total_freed += free_region(free_start, free_end); |
| free_start = 0; |
| } |
| keep_end = md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT); |
| continue; |
| } |
| |
| if (md->type != EFI_BOOT_SERVICES_CODE && |
| md->type != EFI_BOOT_SERVICES_DATA) { |
| /* no need to free this region */ |
| continue; |
| } |
| |
| /* |
| * We want to free memory from this region. |
| */ |
| paddr = md->phys_addr; |
| npages = md->num_pages; |
| memrange_efi_to_native(&paddr, &npages); |
| size = npages << PAGE_SHIFT; |
| |
| if (free_start) { |
| if (paddr <= free_end) |
| free_end = paddr + size; |
| else { |
| total_freed += free_region(free_start, free_end); |
| free_start = paddr; |
| free_end = paddr + size; |
| } |
| } else { |
| free_start = paddr; |
| free_end = paddr + size; |
| } |
| if (free_start < keep_end) { |
| free_start += PAGE_SIZE; |
| if (free_start >= free_end) |
| free_start = 0; |
| } |
| } |
| if (free_start) |
| total_freed += free_region(free_start, free_end); |
| |
| if (total_freed) |
| pr_info("Freed 0x%llx bytes of EFI boot services memory", |
| total_freed); |
| } |
| |
| void __init efi_init(void) |
| { |
| struct efi_fdt_params params; |
| |
| /* Grab UEFI information placed in FDT by stub */ |
| if (!efi_get_fdt_params(¶ms, uefi_debug)) |
| return; |
| |
| efi_system_table = params.system_table; |
| |
| memblock_reserve(params.mmap & PAGE_MASK, |
| PAGE_ALIGN(params.mmap_size + (params.mmap & ~PAGE_MASK))); |
| memmap.phys_map = (void *)params.mmap; |
| memmap.map = early_memremap(params.mmap, params.mmap_size); |
| memmap.map_end = memmap.map + params.mmap_size; |
| memmap.desc_size = params.desc_size; |
| memmap.desc_version = params.desc_ver; |
| |
| if (uefi_init() < 0) |
| return; |
| |
| reserve_regions(); |
| } |
| |
| void __init efi_idmap_init(void) |
| { |
| if (!efi_enabled(EFI_BOOT)) |
| return; |
| |
| /* boot time idmap_pg_dir is incomplete, so fill in missing parts */ |
| efi_setup_idmap(); |
| } |
| |
| static int __init remap_region(efi_memory_desc_t *md, void **new) |
| { |
| u64 paddr, vaddr, npages, size; |
| |
| paddr = md->phys_addr; |
| npages = md->num_pages; |
| memrange_efi_to_native(&paddr, &npages); |
| size = npages << PAGE_SHIFT; |
| |
| if (is_normal_ram(md)) |
| vaddr = (__force u64)ioremap_cache(paddr, size); |
| else |
| vaddr = (__force u64)ioremap(paddr, size); |
| |
| if (!vaddr) { |
| pr_err("Unable to remap 0x%llx pages @ %p\n", |
| npages, (void *)paddr); |
| return 0; |
| } |
| |
| /* adjust for any rounding when EFI and system pagesize differs */ |
| md->virt_addr = vaddr + (md->phys_addr - paddr); |
| |
| if (uefi_debug) |
| pr_info(" EFI remap 0x%012llx => %p\n", |
| md->phys_addr, (void *)md->virt_addr); |
| |
| memcpy(*new, md, memmap.desc_size); |
| *new += memmap.desc_size; |
| |
| return 1; |
| } |
| |
| /* |
| * Switch UEFI from an identity map to a kernel virtual map |
| */ |
| static int __init arm64_enter_virtual_mode(void) |
| { |
| efi_memory_desc_t *md; |
| phys_addr_t virtmap_phys; |
| void *virtmap, *virt_md; |
| efi_status_t status; |
| u64 mapsize; |
| int count = 0; |
| unsigned long flags; |
| |
| if (!efi_enabled(EFI_BOOT)) { |
| pr_info("EFI services will not be available.\n"); |
| return -1; |
| } |
| |
| pr_info("Remapping and enabling EFI services.\n"); |
| |
| /* replace early memmap mapping with permanent mapping */ |
| mapsize = memmap.map_end - memmap.map; |
| early_memunmap(memmap.map, mapsize); |
| memmap.map = (__force void *)ioremap_cache((phys_addr_t)memmap.phys_map, |
| mapsize); |
| memmap.map_end = memmap.map + mapsize; |
| |
| efi.memmap = &memmap; |
| |
| /* Map the runtime regions */ |
| virtmap = kmalloc(mapsize, GFP_KERNEL); |
| if (!virtmap) { |
| pr_err("Failed to allocate EFI virtual memmap\n"); |
| return -1; |
| } |
| virtmap_phys = virt_to_phys(virtmap); |
| virt_md = virtmap; |
| |
| for_each_efi_memory_desc(&memmap, md) { |
| if (!(md->attribute & EFI_MEMORY_RUNTIME)) |
| continue; |
| if (remap_region(md, &virt_md)) |
| ++count; |
| } |
| |
| efi.systab = (__force void *)efi_lookup_mapped_addr(efi_system_table); |
| if (efi.systab) |
| set_bit(EFI_SYSTEM_TABLES, &efi.flags); |
| |
| local_irq_save(flags); |
| cpu_switch_mm(idmap_pg_dir, &init_mm); |
| |
| /* Call SetVirtualAddressMap with the physical address of the map */ |
| runtime = efi.systab->runtime; |
| efi.set_virtual_address_map = runtime->set_virtual_address_map; |
| |
| status = efi.set_virtual_address_map(count * memmap.desc_size, |
| memmap.desc_size, |
| memmap.desc_version, |
| (efi_memory_desc_t *)virtmap_phys); |
| cpu_set_reserved_ttbr0(); |
| flush_tlb_all(); |
| local_irq_restore(flags); |
| |
| kfree(virtmap); |
| |
| free_boot_services(); |
| |
| if (status != EFI_SUCCESS) { |
| pr_err("Failed to set EFI virtual address map! [%lx]\n", |
| status); |
| return -1; |
| } |
| |
| /* Set up runtime services function pointers */ |
| runtime = efi.systab->runtime; |
| efi.get_time = runtime->get_time; |
| efi.set_time = runtime->set_time; |
| efi.get_wakeup_time = runtime->get_wakeup_time; |
| efi.set_wakeup_time = runtime->set_wakeup_time; |
| efi.get_variable = runtime->get_variable; |
| efi.get_next_variable = runtime->get_next_variable; |
| efi.set_variable = runtime->set_variable; |
| efi.query_variable_info = runtime->query_variable_info; |
| efi.update_capsule = runtime->update_capsule; |
| efi.query_capsule_caps = runtime->query_capsule_caps; |
| efi.get_next_high_mono_count = runtime->get_next_high_mono_count; |
| efi.reset_system = runtime->reset_system; |
| |
| set_bit(EFI_RUNTIME_SERVICES, &efi.flags); |
| |
| return 0; |
| } |
| early_initcall(arm64_enter_virtual_mode); |