2014-04-15 21:59:30 -04:00
/*
* 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 ( & params , 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 )
{
2014-05-23 14:16:56 +01:00
if ( ! efi_enabled ( EFI_BOOT ) )
return ;
2014-04-15 21:59:30 -04:00
/* 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 ) ;