2007-09-20 14:22:03 -06:00
/*
* OpRegion handler to allow AML to call native firmware
*
* ( c ) Copyright 2007 Hewlett - Packard Development Company , L . P .
* Bjorn Helgaas < bjorn . helgaas @ hp . com >
*
* 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 .
*
* This driver implements HP Open Source Review Board proposal 1842 ,
* which was approved on 9 / 20 / 2006.
*
* For technical documentation , see the HP SPPA Firmware EAS , Appendix F .
*
* ACPI does not define a mechanism for AML methods to call native firmware
* interfaces such as PAL or SAL . This OpRegion handler adds such a mechanism .
* After the handler is installed , an AML method can call native firmware by
* storing the arguments and firmware entry point to specific offsets in the
* OpRegion . When AML reads the " return value " offset from the OpRegion , this
* handler loads up the arguments , makes the firmware call , and returns the
* result .
*/
# include <linux/module.h>
# include <acpi/acpi_bus.h>
# include <acpi/acpi_drivers.h>
# include <asm/sal.h>
MODULE_AUTHOR ( " Bjorn Helgaas <bjorn.helgaas@hp.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " ACPI opregion handler for native firmware calls " ) ;
2012-01-13 09:32:18 +10:30
static bool force_register ;
2007-09-20 14:22:03 -06:00
module_param_named ( force , force_register , bool , 0 ) ;
MODULE_PARM_DESC ( force , " Install opregion handler even without HPQ5001 device " ) ;
# define AML_NFW_SPACE 0xA1
struct ia64_pdesc {
void * ip ;
void * gp ;
} ;
/*
* N . B . The layout of this structure is defined in the HP SPPA FW EAS , and
* the member offsets are embedded in AML methods .
*/
struct ia64_nfw_context {
u64 arg [ 8 ] ;
struct ia64_sal_retval ret ;
u64 ip ;
u64 gp ;
u64 pad [ 2 ] ;
} ;
static void * virt_map ( u64 address )
{
if ( address & ( 1UL < < 63 ) )
return ( void * ) ( __IA64_UNCACHED_OFFSET | address ) ;
return __va ( address ) ;
}
static void aml_nfw_execute ( struct ia64_nfw_context * c )
{
struct ia64_pdesc virt_entry ;
ia64_sal_handler entry ;
virt_entry . ip = virt_map ( c - > ip ) ;
virt_entry . gp = virt_map ( c - > gp ) ;
entry = ( ia64_sal_handler ) & virt_entry ;
IA64_FW_CALL ( entry , c - > ret ,
c - > arg [ 0 ] , c - > arg [ 1 ] , c - > arg [ 2 ] , c - > arg [ 3 ] ,
c - > arg [ 4 ] , c - > arg [ 5 ] , c - > arg [ 6 ] , c - > arg [ 7 ] ) ;
}
2010-01-28 10:53:19 +08:00
static void aml_nfw_read_arg ( u8 * offset , u32 bit_width , u64 * value )
2007-09-20 14:22:03 -06:00
{
switch ( bit_width ) {
case 8 :
* value = * ( u8 * ) offset ;
break ;
case 16 :
* value = * ( u16 * ) offset ;
break ;
case 32 :
* value = * ( u32 * ) offset ;
break ;
case 64 :
* value = * ( u64 * ) offset ;
break ;
}
}
2010-01-28 10:53:19 +08:00
static void aml_nfw_write_arg ( u8 * offset , u32 bit_width , u64 * value )
2007-09-20 14:22:03 -06:00
{
switch ( bit_width ) {
case 8 :
* ( u8 * ) offset = * value ;
break ;
case 16 :
* ( u16 * ) offset = * value ;
break ;
case 32 :
* ( u32 * ) offset = * value ;
break ;
case 64 :
* ( u64 * ) offset = * value ;
break ;
}
}
static acpi_status aml_nfw_handler ( u32 function , acpi_physical_address address ,
2010-01-28 10:53:19 +08:00
u32 bit_width , u64 * value , void * handler_context ,
2007-09-20 14:22:03 -06:00
void * region_context )
{
struct ia64_nfw_context * context = handler_context ;
u8 * offset = ( u8 * ) context + address ;
if ( bit_width ! = 8 & & bit_width ! = 16 & &
bit_width ! = 32 & & bit_width ! = 64 )
return AE_BAD_PARAMETER ;
if ( address + ( bit_width > > 3 ) > sizeof ( struct ia64_nfw_context ) )
return AE_BAD_PARAMETER ;
switch ( function ) {
case ACPI_READ :
if ( address = = offsetof ( struct ia64_nfw_context , ret ) )
aml_nfw_execute ( context ) ;
aml_nfw_read_arg ( offset , bit_width , value ) ;
break ;
case ACPI_WRITE :
aml_nfw_write_arg ( offset , bit_width , value ) ;
break ;
}
return AE_OK ;
}
static struct ia64_nfw_context global_context ;
static int global_handler_registered ;
static int aml_nfw_add_global_handler ( void )
{
acpi_status status ;
if ( global_handler_registered )
return 0 ;
status = acpi_install_address_space_handler ( ACPI_ROOT_OBJECT ,
AML_NFW_SPACE , aml_nfw_handler , NULL , & global_context ) ;
if ( ACPI_FAILURE ( status ) )
return - ENODEV ;
global_handler_registered = 1 ;
printk ( KERN_INFO " Global 0x%02X opregion handler registered \n " ,
AML_NFW_SPACE ) ;
return 0 ;
}
static int aml_nfw_remove_global_handler ( void )
{
acpi_status status ;
if ( ! global_handler_registered )
return 0 ;
status = acpi_remove_address_space_handler ( ACPI_ROOT_OBJECT ,
AML_NFW_SPACE , aml_nfw_handler ) ;
if ( ACPI_FAILURE ( status ) )
return - ENODEV ;
global_handler_registered = 0 ;
printk ( KERN_INFO " Global 0x%02X opregion handler removed \n " ,
AML_NFW_SPACE ) ;
return 0 ;
}
static int aml_nfw_add ( struct acpi_device * device )
{
/*
* We would normally allocate a new context structure and install
* the address space handler for the specific device we found .
* But the HP - UX implementation shares a single global context
* and always puts the handler at the root , so we ' ll do the same .
*/
return aml_nfw_add_global_handler ( ) ;
}
static int aml_nfw_remove ( struct acpi_device * device , int type )
{
return aml_nfw_remove_global_handler ( ) ;
}
static const struct acpi_device_id aml_nfw_ids [ ] = {
{ " HPQ5001 " , 0 } ,
{ " " , 0 }
} ;
static struct acpi_driver acpi_aml_nfw_driver = {
. name = " native firmware " ,
. ids = aml_nfw_ids ,
. ops = {
. add = aml_nfw_add ,
. remove = aml_nfw_remove ,
} ,
} ;
static int __init aml_nfw_init ( void )
{
int result ;
if ( force_register )
aml_nfw_add_global_handler ( ) ;
result = acpi_bus_register_driver ( & acpi_aml_nfw_driver ) ;
if ( result < 0 ) {
aml_nfw_remove_global_handler ( ) ;
return result ;
}
return 0 ;
}
static void __exit aml_nfw_exit ( void )
{
acpi_bus_unregister_driver ( & acpi_aml_nfw_driver ) ;
aml_nfw_remove_global_handler ( ) ;
}
module_init ( aml_nfw_init ) ;
module_exit ( aml_nfw_exit ) ;