2005-04-17 02:20:36 +04:00
/*
* ACPI PCI Hot Plug IBM Extension
*
* Copyright ( C ) 2004 Vernon Mauery < vernux @ us . ibm . com >
* Copyright ( C ) 2004 IBM Corp .
*
* All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or ( at
* your option ) any later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE , GOOD TITLE or
* NON INFRINGEMENT . See the GNU General Public License for more
* details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
* Send feedback to < vernux @ us . ibm . com >
*
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <acpi/acpi_bus.h>
# include <linux/sysfs.h>
# include <linux/kobject.h>
# include <asm/uaccess.h>
# include <linux/moduleparam.h>
# include "acpiphp.h"
# define DRIVER_VERSION "1.0.1"
# define DRIVER_AUTHOR "Irene Zubarev <zubarev@us.ibm.com>, Vernon Mauery <vernux@us.ibm.com>"
# define DRIVER_DESC "ACPI Hot Plug PCI Controller Driver IBM extension"
static int debug ;
MODULE_AUTHOR ( DRIVER_AUTHOR ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_VERSION ( DRIVER_VERSION ) ;
module_param ( debug , bool , 0644 ) ;
MODULE_PARM_DESC ( debug , " Debugging mode enabled or not " ) ;
# define MY_NAME "acpiphp_ibm"
# undef dbg
# define dbg(format, arg...) \
do { \
if ( debug ) \
printk ( KERN_DEBUG " %s: " format , \
MY_NAME , # # arg ) ; \
} while ( 0 )
# define FOUND_APCI 0x61504349
/* these are the names for the IBM ACPI pseudo-device */
# define IBM_HARDWARE_ID1 "IBM37D0"
# define IBM_HARDWARE_ID2 "IBM37D4"
# define hpslot_to_sun(A) (((struct slot *)((A)->private))->acpi_slot->sun)
/* union apci_descriptor - allows access to the
* various device descriptors that are embedded in the
* aPCI table
*/
union apci_descriptor {
struct {
char sig [ 4 ] ;
u8 len ;
} header ;
struct {
u8 type ;
u8 len ;
u16 slot_id ;
u8 bus_id ;
u8 dev_num ;
u8 slot_num ;
u8 slot_attr [ 2 ] ;
u8 attn ;
u8 status [ 2 ] ;
u8 sun ;
u8 res [ 3 ] ;
} slot ;
struct {
u8 type ;
u8 len ;
} generic ;
} ;
/* struct notification - keeps info about the device
* that cause the ACPI notification event
*/
struct notification {
struct acpi_device * device ;
u8 event ;
} ;
static int ibm_set_attention_status ( struct hotplug_slot * slot , u8 status ) ;
static int ibm_get_attention_status ( struct hotplug_slot * slot , u8 * status ) ;
static void ibm_handle_events ( acpi_handle handle , u32 event , void * context ) ;
static int ibm_get_table_from_acpi ( char * * bufp ) ;
static ssize_t ibm_read_apci_table ( struct kobject * kobj ,
char * buffer , loff_t pos , size_t size ) ;
static acpi_status __init ibm_find_acpi_device ( acpi_handle handle ,
u32 lvl , void * context , void * * rv ) ;
static int __init ibm_acpiphp_init ( void ) ;
static void __exit ibm_acpiphp_exit ( void ) ;
static acpi_handle ibm_acpi_handle ;
static struct notification ibm_note ;
static struct bin_attribute ibm_apci_table_attr = {
. attr = {
. name = " apci_table " ,
. owner = THIS_MODULE ,
. mode = S_IRUGO ,
} ,
. read = ibm_read_apci_table ,
. write = NULL ,
} ;
static struct acpiphp_attention_info ibm_attention_info =
{
. set_attn = ibm_set_attention_status ,
. get_attn = ibm_get_attention_status ,
. owner = THIS_MODULE ,
} ;
/**
* ibm_slot_from_id - workaround for bad ibm hardware
* @ id : the slot number that linux refers to the slot by
*
* Description : this method returns the aCPI slot descriptor
* corresponding to the Linux slot number . This descriptor
* has info about the aPCI slot id and attention status .
* This descriptor must be freed using kfree when done .
* */
static union apci_descriptor * ibm_slot_from_id ( int id )
{
int ind = 0 , size ;
union apci_descriptor * ret = NULL , * des ;
char * table ;
size = ibm_get_table_from_acpi ( & table ) ;
des = ( union apci_descriptor * ) table ;
if ( memcmp ( des - > header . sig , " aPCI " , 4 ) ! = 0 )
goto ibm_slot_done ;
des = ( union apci_descriptor * ) & table [ ind + = des - > header . len ] ;
while ( ind < size & & ( des - > generic . type ! = 0x82 | |
des - > slot . slot_num ! = id ) ) {
des = ( union apci_descriptor * ) & table [ ind + = des - > generic . len ] ;
}
if ( ind < size & & des - > slot . slot_num = = id )
ret = des ;
ibm_slot_done :
if ( ret ) {
ret = kmalloc ( sizeof ( union apci_descriptor ) , GFP_KERNEL ) ;
memcpy ( ret , des , sizeof ( union apci_descriptor ) ) ;
}
kfree ( table ) ;
return ret ;
}
/**
* ibm_set_attention_status - callback method to set the attention LED
* @ slot : the hotplug_slot to work with
* @ status : what to set the LED to ( 0 or 1 )
*
* Description : this method is registered with the acpiphp module as a
* callback to do the device specific task of setting the LED status
* */
static int ibm_set_attention_status ( struct hotplug_slot * slot , u8 status )
{
union acpi_object args [ 2 ] ;
struct acpi_object_list params = { . pointer = args , . count = 2 } ;
acpi_status stat ;
unsigned long rc ;
union apci_descriptor * ibm_slot ;
ibm_slot = ibm_slot_from_id ( hpslot_to_sun ( slot ) ) ;
dbg ( " %s: set slot %d (%d) attention status to %d \n " , __FUNCTION__ ,
ibm_slot - > slot . slot_num , ibm_slot - > slot . slot_id ,
( status ? 1 : 0 ) ) ;
args [ 0 ] . type = ACPI_TYPE_INTEGER ;
args [ 0 ] . integer . value = ibm_slot - > slot . slot_id ;
args [ 1 ] . type = ACPI_TYPE_INTEGER ;
args [ 1 ] . integer . value = ( status ) ? 1 : 0 ;
kfree ( ibm_slot ) ;
stat = acpi_evaluate_integer ( ibm_acpi_handle , " APLS " , & params , & rc ) ;
if ( ACPI_FAILURE ( stat ) ) {
err ( " APLS evaluation failed: 0x%08x \n " , stat ) ;
return - ENODEV ;
} else if ( ! rc ) {
err ( " APLS method failed: 0x%08lx \n " , rc ) ;
return - ERANGE ;
}
return 0 ;
}
/**
* ibm_get_attention_status - callback method to get attention LED status
* @ slot : the hotplug_slot to work with
* @ status : returns what the LED is set to ( 0 or 1 )
*
* Description : this method is registered with the acpiphp module as a
* callback to do the device specific task of getting the LED status
*
* Because there is no direct method of getting the LED status directly
* from an ACPI call , we read the aPCI table and parse out our
* slot descriptor to read the status from that .
* */
static int ibm_get_attention_status ( struct hotplug_slot * slot , u8 * status )
{
union apci_descriptor * ibm_slot ;
ibm_slot = ibm_slot_from_id ( hpslot_to_sun ( slot ) ) ;
if ( ibm_slot - > slot . attn & 0xa0 | | ibm_slot - > slot . status [ 1 ] & 0x08 )
* status = 1 ;
else
* status = 0 ;
dbg ( " %s: get slot %d (%d) attention status is %d \n " , __FUNCTION__ ,
ibm_slot - > slot . slot_num , ibm_slot - > slot . slot_id ,
* status ) ;
kfree ( ibm_slot ) ;
return 0 ;
}
/**
* ibm_handle_events - listens for ACPI events for the IBM37D0 device
* @ handle : an ACPI handle to the device that caused the event
* @ event : the event info ( device specific )
* @ context : passed context ( our notification struct )
*
* Description : this method is registered as a callback with the ACPI
* subsystem it is called when this device has an event to notify the OS of
*
* The events actually come from the device as two events that get
* synthesized into one event with data by this function . The event
* ID comes first and then the slot number that caused it . We report
* this as one event to the OS .
*
* From section 5.6 .2 .2 of the ACPI 2.0 spec , I understand that the OSPM will
* only re - enable the interrupt that causes this event AFTER this method
* has returned , thereby enforcing serial access for the notification struct .
* */
static void ibm_handle_events ( acpi_handle handle , u32 event , void * context )
{
u8 detail = event & 0x0f ;
u8 subevent = event & 0xf0 ;
struct notification * note = context ;
dbg ( " %s: Received notification %02x \n " , __FUNCTION__ , event ) ;
if ( subevent = = 0x80 ) {
dbg ( " %s: generationg bus event \n " , __FUNCTION__ ) ;
acpi_bus_generate_event ( note - > device , note - > event , detail ) ;
} else
note - > event = event ;
}
/**
* ibm_get_table_from_acpi - reads the APLS buffer from ACPI
* @ bufp : address to pointer to allocate for the table
*
* Description : this method reads the APLS buffer in from ACPI and
* stores the " stripped " table into a single buffer
* it allocates and passes the address back in bufp
*
* If NULL is passed in as buffer , this method only calculates
* the size of the table and returns that without filling
* in the buffer
*
* returns < 0 on error or the size of the table on success
* */
static int ibm_get_table_from_acpi ( char * * bufp )
{
union acpi_object * package ;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER , NULL } ;
acpi_status status ;
char * lbuf = NULL ;
int i , size = - EIO ;
status = acpi_evaluate_object ( ibm_acpi_handle , " APCI " , NULL , & buffer ) ;
if ( ACPI_FAILURE ( status ) ) {
err ( " %s: APCI evaluation failed \n " , __FUNCTION__ ) ;
return - ENODEV ;
}
package = ( union acpi_object * ) buffer . pointer ;
2006-01-08 22:11:59 +03:00
if ( ! ( package ) | |
2005-04-17 02:20:36 +04:00
( package - > type ! = ACPI_TYPE_PACKAGE ) | |
! ( package - > package . elements ) ) {
err ( " %s: Invalid APCI object \n " , __FUNCTION__ ) ;
goto read_table_done ;
}
for ( size = 0 , i = 0 ; i < package - > package . count ; i + + ) {
if ( package - > package . elements [ i ] . type ! = ACPI_TYPE_BUFFER ) {
err ( " %s: Invalid APCI element %d \n " , __FUNCTION__ , i ) ;
goto read_table_done ;
}
size + = package - > package . elements [ i ] . buffer . length ;
}
if ( bufp = = NULL )
goto read_table_done ;
lbuf = kmalloc ( size , GFP_KERNEL ) ;
dbg ( " %s: element count: %i, ASL table size: %i, &table = 0x%p \n " ,
__FUNCTION__ , package - > package . count , size , lbuf ) ;
if ( lbuf ) {
* bufp = lbuf ;
memset ( lbuf , 0 , size ) ;
} else {
size = - ENOMEM ;
goto read_table_done ;
}
size = 0 ;
for ( i = 0 ; i < package - > package . count ; i + + ) {
memcpy ( & lbuf [ size ] ,
package - > package . elements [ i ] . buffer . pointer ,
package - > package . elements [ i ] . buffer . length ) ;
size + = package - > package . elements [ i ] . buffer . length ;
}
read_table_done :
kfree ( buffer . pointer ) ;
return size ;
}
/**
* ibm_read_apci_table - callback for the sysfs apci_table file
* @ kobj : the kobject this binary attribute is a part of
* @ buffer : the kernel space buffer to fill
* @ pos : the offset into the file
* @ size : the number of bytes requested
*
* Description : gets registered with sysfs as the reader callback
* to be executed when / sys / bus / pci / slots / apci_table gets read
*
* Since we don ' t get notified on open and close for this file ,
* things get really tricky here . . .
* our solution is to only allow reading the table in all at once
* */
static ssize_t ibm_read_apci_table ( struct kobject * kobj ,
char * buffer , loff_t pos , size_t size )
{
int bytes_read = - EINVAL ;
char * table = NULL ;
dbg ( " %s: pos = %d, size = %zd \n " , __FUNCTION__ , ( int ) pos , size ) ;
if ( pos = = 0 ) {
bytes_read = ibm_get_table_from_acpi ( & table ) ;
if ( bytes_read > 0 & & bytes_read < = size )
memcpy ( buffer , table , bytes_read ) ;
kfree ( table ) ;
}
return bytes_read ;
}
/**
* ibm_find_acpi_device - callback to find our ACPI device
* @ handle : the ACPI handle of the device we are inspecting
* @ lvl : depth into the namespace tree
* @ context : a pointer to our handle to fill when we find the device
* @ rv : a return value to fill if desired
*
* Description : used as a callback when calling acpi_walk_namespace
* to find our device . When this method returns non - zero
* acpi_walk_namespace quits its search and returns our value
* */
static acpi_status __init ibm_find_acpi_device ( acpi_handle handle ,
u32 lvl , void * context , void * * rv )
{
acpi_handle * phandle = ( acpi_handle * ) context ;
acpi_status status ;
struct acpi_device_info info ;
struct acpi_buffer info_buffer = {
. length = sizeof ( struct acpi_device_info ) ,
. pointer = & info ,
} ;
status = acpi_get_object_info ( handle , & info_buffer ) ;
if ( ACPI_FAILURE ( status ) ) {
err ( " %s: Failed to get device information " , __FUNCTION__ ) ;
return 0 ;
}
info . hardware_id . value [ sizeof ( info . hardware_id . value ) - 1 ] = ' \0 ' ;
2006-01-08 22:11:59 +03:00
if ( info . current_status & & ( info . valid & ACPI_VALID_HID ) & &
2005-04-17 02:20:36 +04:00
( ! strcmp ( info . hardware_id . value , IBM_HARDWARE_ID1 ) | |
! strcmp ( info . hardware_id . value , IBM_HARDWARE_ID2 ) ) ) {
dbg ( " found hardware: %s, handle: %p \n " , info . hardware_id . value ,
handle ) ;
* phandle = handle ;
/* returning non-zero causes the search to stop
* and returns this value to the caller of
* acpi_walk_namespace , but it also causes some warnings
* in the acpi debug code to print . . .
*/
return FOUND_APCI ;
}
return 0 ;
}
static int __init ibm_acpiphp_init ( void )
{
int retval = 0 ;
acpi_status status ;
struct acpi_device * device ;
struct kobject * sysdir = & pci_hotplug_slots_subsys . kset . kobj ;
dbg ( " %s \n " , __FUNCTION__ ) ;
if ( acpi_walk_namespace ( ACPI_TYPE_DEVICE , ACPI_ROOT_OBJECT ,
ACPI_UINT32_MAX , ibm_find_acpi_device ,
& ibm_acpi_handle , NULL ) ! = FOUND_APCI ) {
err ( " %s: acpi_walk_namespace failed \n " , __FUNCTION__ ) ;
retval = - ENODEV ;
goto init_return ;
}
dbg ( " %s: found IBM aPCI device \n " , __FUNCTION__ ) ;
if ( acpi_bus_get_device ( ibm_acpi_handle , & device ) ) {
err ( " %s: acpi_bus_get_device failed \n " , __FUNCTION__ ) ;
retval = - ENODEV ;
goto init_return ;
}
if ( acpiphp_register_attention ( & ibm_attention_info ) ) {
retval = - ENODEV ;
goto init_return ;
}
ibm_note . device = device ;
2006-01-08 22:11:59 +03:00
status = acpi_install_notify_handler ( ibm_acpi_handle ,
ACPI_DEVICE_NOTIFY , ibm_handle_events ,
2005-04-17 02:20:36 +04:00
& ibm_note ) ;
if ( ACPI_FAILURE ( status ) ) {
2006-01-08 22:11:59 +03:00
err ( " %s: Failed to register notification handler \n " ,
2005-04-17 02:20:36 +04:00
__FUNCTION__ ) ;
retval = - EBUSY ;
goto init_cleanup ;
}
ibm_apci_table_attr . size = ibm_get_table_from_acpi ( NULL ) ;
retval = sysfs_create_bin_file ( sysdir , & ibm_apci_table_attr ) ;
return retval ;
init_cleanup :
acpiphp_unregister_attention ( & ibm_attention_info ) ;
init_return :
return retval ;
}
static void __exit ibm_acpiphp_exit ( void )
{
acpi_status status ;
struct kobject * sysdir = & pci_hotplug_slots_subsys . kset . kobj ;
dbg ( " %s \n " , __FUNCTION__ ) ;
if ( acpiphp_unregister_attention ( & ibm_attention_info ) )
err ( " %s: attention info deregistration failed " , __FUNCTION__ ) ;
2006-01-08 22:11:59 +03:00
status = acpi_remove_notify_handler (
2005-04-17 02:20:36 +04:00
ibm_acpi_handle ,
ACPI_DEVICE_NOTIFY ,
ibm_handle_events ) ;
2006-01-08 22:11:59 +03:00
if ( ACPI_FAILURE ( status ) )
err ( " %s: Notification handler removal failed \n " , __FUNCTION__ ) ;
/* remove the /sys entries */
2006-07-11 10:05:25 +04:00
sysfs_remove_bin_file ( sysdir , & ibm_apci_table_attr ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( ibm_acpiphp_init ) ;
module_exit ( ibm_acpiphp_exit ) ;