2005-04-16 15:20:36 -07:00
/*
* Extensible Firmware Interface
*
* Based on Extensible Firmware Interface Specification version 1.0
*
* Copyright ( C ) 1999 VA Linux Systems
* Copyright ( C ) 1999 Walt Drummond < drummond @ valinux . com >
* Copyright ( C ) 1999 - 2002 Hewlett - Packard Co .
* David Mosberger - Tang < davidm @ hpl . hp . com >
* Stephane Eranian < eranian @ hpl . hp . com >
*
* All EFI Runtime Services are not implemented yet as EFI only
* supports physical mode addressing on SoftSDV . This is to be fixed
* in a future version . - - drummond 1999 - 07 - 20
*
* Implemented EFI runtime services and virtual mode calls . - - davidm
*
* Goutham Rao : < goutham . rao @ intel . com >
* Skip non - WB memory and ignore empty memory ranges .
*/
# include <linux/config.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/mm.h>
# include <linux/types.h>
# include <linux/time.h>
# include <linux/spinlock.h>
# include <linux/bootmem.h>
# include <linux/ioport.h>
# include <linux/module.h>
# include <linux/efi.h>
# include <asm/setup.h>
# include <asm/io.h>
# include <asm/page.h>
# include <asm/pgtable.h>
# include <asm/processor.h>
# include <asm/desc.h>
# include <asm/tlbflush.h>
# define EFI_DEBUG 0
# define PFX "EFI: "
extern efi_status_t asmlinkage efi_call_phys ( void * , . . . ) ;
struct efi efi ;
EXPORT_SYMBOL ( efi ) ;
2005-04-16 15:25:53 -07:00
static struct efi efi_phys ;
struct efi_memory_map memmap ;
2005-04-16 15:20:36 -07:00
/*
* We require an early boot_ioremap mapping mechanism initially
*/
extern void * boot_ioremap ( unsigned long , unsigned long ) ;
/*
* To make EFI call EFI runtime service in physical addressing mode we need
* prelog / epilog before / after the invocation to disable interrupt , to
* claim EFI runtime service handler exclusively and to duplicate a memory in
* low memory space say 0 - 3 G .
*/
static unsigned long efi_rt_eflags ;
static DEFINE_SPINLOCK ( efi_rt_lock ) ;
static pgd_t efi_bak_pg_dir_pointer [ 2 ] ;
static void efi_call_phys_prelog ( void )
{
unsigned long cr4 ;
unsigned long temp ;
spin_lock ( & efi_rt_lock ) ;
local_irq_save ( efi_rt_eflags ) ;
/*
* If I don ' t have PSE , I should just duplicate two entries in page
* directory . If I have PSE , I just need to duplicate one entry in
* page directory .
*/
__asm__ __volatile__ ( " movl %%cr4, %0 " : " =r " ( cr4 ) ) ;
if ( cr4 & X86_CR4_PSE ) {
efi_bak_pg_dir_pointer [ 0 ] . pgd =
swapper_pg_dir [ pgd_index ( 0 ) ] . pgd ;
swapper_pg_dir [ 0 ] . pgd =
swapper_pg_dir [ pgd_index ( PAGE_OFFSET ) ] . pgd ;
} else {
efi_bak_pg_dir_pointer [ 0 ] . pgd =
swapper_pg_dir [ pgd_index ( 0 ) ] . pgd ;
efi_bak_pg_dir_pointer [ 1 ] . pgd =
swapper_pg_dir [ pgd_index ( 0x400000 ) ] . pgd ;
swapper_pg_dir [ pgd_index ( 0 ) ] . pgd =
swapper_pg_dir [ pgd_index ( PAGE_OFFSET ) ] . pgd ;
temp = PAGE_OFFSET + 0x400000 ;
swapper_pg_dir [ pgd_index ( 0x400000 ) ] . pgd =
swapper_pg_dir [ pgd_index ( temp ) ] . pgd ;
}
/*
* After the lock is released , the original page table is restored .
*/
local_flush_tlb ( ) ;
cpu_gdt_descr [ 0 ] . address = __pa ( cpu_gdt_descr [ 0 ] . address ) ;
__asm__ __volatile__ ( " lgdt %0 " : " =m "
( * ( struct Xgt_desc_struct * ) __pa ( & cpu_gdt_descr [ 0 ] ) ) ) ;
}
static void efi_call_phys_epilog ( void )
{
unsigned long cr4 ;
cpu_gdt_descr [ 0 ] . address =
( unsigned long ) __va ( cpu_gdt_descr [ 0 ] . address ) ;
__asm__ __volatile__ ( " lgdt %0 " : " =m " ( cpu_gdt_descr ) ) ;
__asm__ __volatile__ ( " movl %%cr4, %0 " : " =r " ( cr4 ) ) ;
if ( cr4 & X86_CR4_PSE ) {
swapper_pg_dir [ pgd_index ( 0 ) ] . pgd =
efi_bak_pg_dir_pointer [ 0 ] . pgd ;
} else {
swapper_pg_dir [ pgd_index ( 0 ) ] . pgd =
efi_bak_pg_dir_pointer [ 0 ] . pgd ;
swapper_pg_dir [ pgd_index ( 0x400000 ) ] . pgd =
efi_bak_pg_dir_pointer [ 1 ] . pgd ;
}
/*
* After the lock is released , the original page table is restored .
*/
local_flush_tlb ( ) ;
local_irq_restore ( efi_rt_eflags ) ;
spin_unlock ( & efi_rt_lock ) ;
}
static efi_status_t
phys_efi_set_virtual_address_map ( unsigned long memory_map_size ,
unsigned long descriptor_size ,
u32 descriptor_version ,
efi_memory_desc_t * virtual_map )
{
efi_status_t status ;
efi_call_phys_prelog ( ) ;
status = efi_call_phys ( efi_phys . set_virtual_address_map ,
memory_map_size , descriptor_size ,
descriptor_version , virtual_map ) ;
efi_call_phys_epilog ( ) ;
return status ;
}
static efi_status_t
phys_efi_get_time ( efi_time_t * tm , efi_time_cap_t * tc )
{
efi_status_t status ;
efi_call_phys_prelog ( ) ;
status = efi_call_phys ( efi_phys . get_time , tm , tc ) ;
efi_call_phys_epilog ( ) ;
return status ;
}
inline int efi_set_rtc_mmss ( unsigned long nowtime )
{
int real_seconds , real_minutes ;
efi_status_t status ;
efi_time_t eft ;
efi_time_cap_t cap ;
spin_lock ( & efi_rt_lock ) ;
status = efi . get_time ( & eft , & cap ) ;
spin_unlock ( & efi_rt_lock ) ;
if ( status ! = EFI_SUCCESS )
panic ( " Ooops, efitime: can't read time! \n " ) ;
real_seconds = nowtime % 60 ;
real_minutes = nowtime / 60 ;
if ( ( ( abs ( real_minutes - eft . minute ) + 15 ) / 30 ) & 1 )
real_minutes + = 30 ;
real_minutes % = 60 ;
eft . minute = real_minutes ;
eft . second = real_seconds ;
if ( status ! = EFI_SUCCESS ) {
printk ( " Ooops: efitime: can't read time! \n " ) ;
return - 1 ;
}
return 0 ;
}
/*
* This should only be used during kernel init and before runtime
* services have been remapped , therefore , we ' ll need to call in physical
* mode . Note , this call isn ' t used later , so mark it __init .
*/
inline unsigned long __init efi_get_time ( void )
{
efi_status_t status ;
efi_time_t eft ;
efi_time_cap_t cap ;
status = phys_efi_get_time ( & eft , & cap ) ;
if ( status ! = EFI_SUCCESS )
printk ( " Oops: efitime: can't read time status: 0x%lx \n " , status ) ;
return mktime ( eft . year , eft . month , eft . day , eft . hour ,
eft . minute , eft . second ) ;
}
int is_available_memory ( efi_memory_desc_t * md )
{
if ( ! ( md - > attribute & EFI_MEMORY_WB ) )
return 0 ;
switch ( md - > type ) {
case EFI_LOADER_CODE :
case EFI_LOADER_DATA :
case EFI_BOOT_SERVICES_CODE :
case EFI_BOOT_SERVICES_DATA :
case EFI_CONVENTIONAL_MEMORY :
return 1 ;
}
return 0 ;
}
/*
* We need to map the EFI memory map again after paging_init ( ) .
*/
void __init efi_map_memmap ( void )
{
memmap . map = NULL ;
memmap . map = ( efi_memory_desc_t * )
bt_ioremap ( ( unsigned long ) memmap . phys_map ,
( memmap . nr_map * sizeof ( efi_memory_desc_t ) ) ) ;
if ( memmap . map = = NULL )
printk ( KERN_ERR PFX " Could not remap the EFI memmap! \n " ) ;
}
# if EFI_DEBUG
static void __init print_efi_memmap ( void )
{
efi_memory_desc_t * md ;
int i ;
for ( i = 0 ; i < memmap . nr_map ; i + + ) {
md = & memmap . map [ i ] ;
printk ( KERN_INFO " mem%02u: type=%u, attr=0x%llx, "
" range=[0x%016llx-0x%016llx) (%lluMB) \n " ,
i , md - > type , md - > attribute , md - > phys_addr ,
md - > phys_addr + ( md - > num_pages < < EFI_PAGE_SHIFT ) ,
( md - > num_pages > > ( 20 - EFI_PAGE_SHIFT ) ) ) ;
}
}
# endif /* EFI_DEBUG */
/*
* Walks the EFI memory map and calls CALLBACK once for each EFI
* memory descriptor that has memory that is available for kernel use .
*/
void efi_memmap_walk ( efi_freemem_callback_t callback , void * arg )
{
int prev_valid = 0 ;
struct range {
unsigned long start ;
unsigned long end ;
} prev , curr ;
efi_memory_desc_t * md ;
unsigned long start , end ;
int i ;
for ( i = 0 ; i < memmap . nr_map ; i + + ) {
md = & memmap . map [ i ] ;
if ( ( md - > num_pages = = 0 ) | | ( ! is_available_memory ( md ) ) )
continue ;
curr . start = md - > phys_addr ;
curr . end = curr . start + ( md - > num_pages < < EFI_PAGE_SHIFT ) ;
if ( ! prev_valid ) {
prev = curr ;
prev_valid = 1 ;
} else {
if ( curr . start < prev . start )
printk ( KERN_INFO PFX " Unordered memory map \n " ) ;
if ( prev . end = = curr . start )
prev . end = curr . end ;
else {
start =
( unsigned long ) ( PAGE_ALIGN ( prev . start ) ) ;
end = ( unsigned long ) ( prev . end & PAGE_MASK ) ;
if ( ( end > start )
& & ( * callback ) ( start , end , arg ) < 0 )
return ;
prev = curr ;
}
}
}
if ( prev_valid ) {
start = ( unsigned long ) PAGE_ALIGN ( prev . start ) ;
end = ( unsigned long ) ( prev . end & PAGE_MASK ) ;
if ( end > start )
( * callback ) ( start , end , arg ) ;
}
}
void __init efi_init ( void )
{
efi_config_table_t * config_tables ;
efi_runtime_services_t * runtime ;
efi_char16_t * c16 ;
char vendor [ 100 ] = " unknown " ;
unsigned long num_config_tables ;
int i = 0 ;
memset ( & efi , 0 , sizeof ( efi ) ) ;
memset ( & efi_phys , 0 , sizeof ( efi_phys ) ) ;
efi_phys . systab = EFI_SYSTAB ;
memmap . phys_map = EFI_MEMMAP ;
memmap . nr_map = EFI_MEMMAP_SIZE / EFI_MEMDESC_SIZE ;
memmap . desc_version = EFI_MEMDESC_VERSION ;
efi . systab = ( efi_system_table_t * )
boot_ioremap ( ( unsigned long ) efi_phys . systab ,
sizeof ( efi_system_table_t ) ) ;
/*
* Verify the EFI Table
*/
if ( efi . systab = = NULL )
printk ( KERN_ERR PFX " Woah! Couldn't map the EFI system table. \n " ) ;
if ( efi . systab - > hdr . signature ! = EFI_SYSTEM_TABLE_SIGNATURE )
printk ( KERN_ERR PFX " Woah! EFI system table signature incorrect \n " ) ;
if ( ( efi . systab - > hdr . revision ^ EFI_SYSTEM_TABLE_REVISION ) > > 16 ! = 0 )
printk ( KERN_ERR PFX
" Warning: EFI system table major version mismatch: "
" got %d.%02d, expected %d.%02d \n " ,
efi . systab - > hdr . revision > > 16 ,
efi . systab - > hdr . revision & 0xffff ,
EFI_SYSTEM_TABLE_REVISION > > 16 ,
EFI_SYSTEM_TABLE_REVISION & 0xffff ) ;
/*
* Grab some details from the system table
*/
num_config_tables = efi . systab - > nr_tables ;
config_tables = ( efi_config_table_t * ) efi . systab - > tables ;
runtime = efi . systab - > runtime ;
/*
* Show what we know for posterity
*/
c16 = ( efi_char16_t * ) boot_ioremap ( efi . systab - > fw_vendor , 2 ) ;
if ( c16 ) {
for ( i = 0 ; i < sizeof ( vendor ) & & * c16 ; + + i )
vendor [ i ] = * c16 + + ;
vendor [ i ] = ' \0 ' ;
} else
printk ( KERN_ERR PFX " Could not map the firmware vendor! \n " ) ;
printk ( KERN_INFO PFX " EFI v%u.%.02u by %s \n " ,
efi . systab - > hdr . revision > > 16 ,
efi . systab - > hdr . revision & 0xffff , vendor ) ;
/*
* Let ' s see what config tables the firmware passed to us .
*/
config_tables = ( efi_config_table_t * )
boot_ioremap ( ( unsigned long ) config_tables ,
num_config_tables * sizeof ( efi_config_table_t ) ) ;
if ( config_tables = = NULL )
printk ( KERN_ERR PFX " Could not map EFI Configuration Table! \n " ) ;
for ( i = 0 ; i < num_config_tables ; i + + ) {
if ( efi_guidcmp ( config_tables [ i ] . guid , MPS_TABLE_GUID ) = = 0 ) {
efi . mps = ( void * ) config_tables [ i ] . table ;
printk ( KERN_INFO " MPS=0x%lx " , config_tables [ i ] . table ) ;
} else
if ( efi_guidcmp ( config_tables [ i ] . guid , ACPI_20_TABLE_GUID ) = = 0 ) {
efi . acpi20 = __va ( config_tables [ i ] . table ) ;
printk ( KERN_INFO " ACPI 2.0=0x%lx " , config_tables [ i ] . table ) ;
} else
if ( efi_guidcmp ( config_tables [ i ] . guid , ACPI_TABLE_GUID ) = = 0 ) {
efi . acpi = __va ( config_tables [ i ] . table ) ;
printk ( KERN_INFO " ACPI=0x%lx " , config_tables [ i ] . table ) ;
} else
if ( efi_guidcmp ( config_tables [ i ] . guid , SMBIOS_TABLE_GUID ) = = 0 ) {
efi . smbios = ( void * ) config_tables [ i ] . table ;
printk ( KERN_INFO " SMBIOS=0x%lx " , config_tables [ i ] . table ) ;
} else
if ( efi_guidcmp ( config_tables [ i ] . guid , HCDP_TABLE_GUID ) = = 0 ) {
efi . hcdp = ( void * ) config_tables [ i ] . table ;
printk ( KERN_INFO " HCDP=0x%lx " , config_tables [ i ] . table ) ;
} else
if ( efi_guidcmp ( config_tables [ i ] . guid , UGA_IO_PROTOCOL_GUID ) = = 0 ) {
efi . uga = ( void * ) config_tables [ i ] . table ;
printk ( KERN_INFO " UGA=0x%lx " , config_tables [ i ] . table ) ;
}
}
printk ( " \n " ) ;
/*
* Check out the runtime services table . We need to map
* the runtime services table so that we can grab the physical
* address of several of the EFI runtime functions , needed to
* set the firmware into virtual mode .
*/
runtime = ( efi_runtime_services_t * ) boot_ioremap ( ( unsigned long )
runtime ,
sizeof ( efi_runtime_services_t ) ) ;
if ( runtime ! = NULL ) {
/*
* We will only need * early * access to the following
* two EFI runtime services before set_virtual_address_map
* is invoked .
*/
efi_phys . get_time = ( efi_get_time_t * ) runtime - > get_time ;
efi_phys . set_virtual_address_map =
( efi_set_virtual_address_map_t * )
runtime - > set_virtual_address_map ;
} else
printk ( KERN_ERR PFX " Could not map the runtime service table! \n " ) ;
/* Map the EFI memory map for use until paging_init() */
memmap . map = ( efi_memory_desc_t * )
boot_ioremap ( ( unsigned long ) EFI_MEMMAP , EFI_MEMMAP_SIZE ) ;
if ( memmap . map = = NULL )
printk ( KERN_ERR PFX " Could not map the EFI memory map! \n " ) ;
if ( EFI_MEMDESC_SIZE ! = sizeof ( efi_memory_desc_t ) ) {
printk ( KERN_WARNING PFX " Warning! Kernel-defined memdesc doesn't "
" match the one from EFI! \n " ) ;
}
# if EFI_DEBUG
print_efi_memmap ( ) ;
# endif
}
/*
* This function will switch the EFI runtime services to virtual mode .
* Essentially , look through the EFI memmap and map every region that
* has the runtime attribute bit set in its memory descriptor and update
* that memory descriptor with the virtual address obtained from ioremap ( ) .
* This enables the runtime services to be called without having to
* thunk back into physical mode for every invocation .
*/
void __init efi_enter_virtual_mode ( void )
{
efi_memory_desc_t * md ;
efi_status_t status ;
int i ;
efi . systab = NULL ;
for ( i = 0 ; i < memmap . nr_map ; i + + ) {
md = & memmap . map [ i ] ;
if ( md - > attribute & EFI_MEMORY_RUNTIME ) {
md - > virt_addr =
( unsigned long ) ioremap ( md - > phys_addr ,
md - > num_pages < < EFI_PAGE_SHIFT ) ;
if ( ! ( unsigned long ) md - > virt_addr ) {
printk ( KERN_ERR PFX " ioremap of 0x%lX failed \n " ,
( unsigned long ) md - > phys_addr ) ;
}
if ( ( ( unsigned long ) md - > phys_addr < =
( unsigned long ) efi_phys . systab ) & &
( ( unsigned long ) efi_phys . systab <
md - > phys_addr +
( ( unsigned long ) md - > num_pages < <
EFI_PAGE_SHIFT ) ) ) {
unsigned long addr ;
addr = md - > virt_addr - md - > phys_addr +
( unsigned long ) efi_phys . systab ;
efi . systab = ( efi_system_table_t * ) addr ;
}
}
}
if ( ! efi . systab )
BUG ( ) ;
status = phys_efi_set_virtual_address_map (
sizeof ( efi_memory_desc_t ) * memmap . nr_map ,
sizeof ( efi_memory_desc_t ) ,
memmap . desc_version ,
memmap . phys_map ) ;
if ( status ! = EFI_SUCCESS ) {
printk ( KERN_ALERT " You are screwed! "
" Unable to switch EFI into virtual mode "
" (status=%lx) \n " , status ) ;
panic ( " EFI call to SetVirtualAddressMap() failed! " ) ;
}
/*
* Now that EFI is in virtual mode , update the function
* pointers in the runtime service table to the new virtual addresses .
*/
efi . get_time = ( efi_get_time_t * ) efi . systab - > runtime - > get_time ;
efi . set_time = ( efi_set_time_t * ) efi . systab - > runtime - > set_time ;
efi . get_wakeup_time = ( efi_get_wakeup_time_t * )
efi . systab - > runtime - > get_wakeup_time ;
efi . set_wakeup_time = ( efi_set_wakeup_time_t * )
efi . systab - > runtime - > set_wakeup_time ;
efi . get_variable = ( efi_get_variable_t * )
efi . systab - > runtime - > get_variable ;
efi . get_next_variable = ( efi_get_next_variable_t * )
efi . systab - > runtime - > get_next_variable ;
efi . set_variable = ( efi_set_variable_t * )
efi . systab - > runtime - > set_variable ;
efi . get_next_high_mono_count = ( efi_get_next_high_mono_count_t * )
efi . systab - > runtime - > get_next_high_mono_count ;
efi . reset_system = ( efi_reset_system_t * )
efi . systab - > runtime - > reset_system ;
}
void __init
efi_initialize_iomem_resources ( struct resource * code_resource ,
struct resource * data_resource )
{
struct resource * res ;
efi_memory_desc_t * md ;
int i ;
for ( i = 0 ; i < memmap . nr_map ; i + + ) {
md = & memmap . map [ i ] ;
if ( ( md - > phys_addr + ( md - > num_pages < < EFI_PAGE_SHIFT ) ) >
0x100000000ULL )
continue ;
res = alloc_bootmem_low ( sizeof ( struct resource ) ) ;
switch ( md - > type ) {
case EFI_RESERVED_TYPE :
res - > name = " Reserved Memory " ;
break ;
case EFI_LOADER_CODE :
res - > name = " Loader Code " ;
break ;
case EFI_LOADER_DATA :
res - > name = " Loader Data " ;
break ;
case EFI_BOOT_SERVICES_DATA :
res - > name = " BootServices Data " ;
break ;
case EFI_BOOT_SERVICES_CODE :
res - > name = " BootServices Code " ;
break ;
case EFI_RUNTIME_SERVICES_CODE :
res - > name = " Runtime Service Code " ;
break ;
case EFI_RUNTIME_SERVICES_DATA :
res - > name = " Runtime Service Data " ;
break ;
case EFI_CONVENTIONAL_MEMORY :
res - > name = " Conventional Memory " ;
break ;
case EFI_UNUSABLE_MEMORY :
res - > name = " Unusable Memory " ;
break ;
case EFI_ACPI_RECLAIM_MEMORY :
res - > name = " ACPI Reclaim " ;
break ;
case EFI_ACPI_MEMORY_NVS :
res - > name = " ACPI NVS " ;
break ;
case EFI_MEMORY_MAPPED_IO :
res - > name = " Memory Mapped IO " ;
break ;
case EFI_MEMORY_MAPPED_IO_PORT_SPACE :
res - > name = " Memory Mapped IO Port Space " ;
break ;
default :
res - > name = " Reserved " ;
break ;
}
res - > start = md - > phys_addr ;
res - > end = res - > start + ( ( md - > num_pages < < EFI_PAGE_SHIFT ) - 1 ) ;
res - > flags = IORESOURCE_MEM | IORESOURCE_BUSY ;
if ( request_resource ( & iomem_resource , res ) < 0 )
printk ( KERN_ERR PFX " Failed to allocate res %s : 0x%lx-0x%lx \n " ,
res - > name , res - > start , res - > end ) ;
/*
* We don ' t know which region contains kernel data so we try
* it repeatedly and let the resource manager test it .
*/
if ( md - > type = = EFI_CONVENTIONAL_MEMORY ) {
request_resource ( res , code_resource ) ;
request_resource ( res , data_resource ) ;
}
}
}
/*
* Convenience functions to obtain memory types and attributes
*/
u32 efi_mem_type ( unsigned long phys_addr )
{
efi_memory_desc_t * md ;
int i ;
for ( i = 0 ; i < memmap . nr_map ; i + + ) {
md = & memmap . map [ i ] ;
if ( ( md - > phys_addr < = phys_addr ) & & ( phys_addr <
( md - > phys_addr + ( md - > num_pages < < EFI_PAGE_SHIFT ) ) ) )
return md - > type ;
}
return 0 ;
}
u64 efi_mem_attributes ( unsigned long phys_addr )
{
efi_memory_desc_t * md ;
int i ;
for ( i = 0 ; i < memmap . nr_map ; i + + ) {
md = & memmap . map [ i ] ;
if ( ( md - > phys_addr < = phys_addr ) & & ( phys_addr <
( md - > phys_addr + ( md - > num_pages < < EFI_PAGE_SHIFT ) ) ) )
return md - > attribute ;
}
return 0 ;
}