2007-09-14 23:33:46 +04:00
/*
* A hwmon driver for the IBM PowerExecutive temperature / power sensors
* Copyright ( C ) 2007 IBM
*
* Author : Darrick J . Wong < djwong @ us . ibm . com >
*
* 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 . 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 . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/ipmi.h>
# include <linux/module.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
# include <linux/jiffies.h>
# include <linux/mutex.h>
# define REFRESH_INTERVAL (2 * HZ)
# define DRVNAME "ibmpex"
# define PEX_GET_VERSION 1
# define PEX_GET_SENSOR_COUNT 2
# define PEX_GET_SENSOR_NAME 3
# define PEX_RESET_HIGH_LOW 4
# define PEX_GET_SENSOR_DATA 6
# define PEX_NET_FUNCTION 0x3A
# define PEX_COMMAND 0x3C
static inline u16 extract_value ( const char * data , int offset )
{
2009-01-07 01:41:37 +03:00
return be16_to_cpup ( ( __be16 * ) & data [ offset ] ) ;
2007-09-14 23:33:46 +04:00
}
# define TEMP_SENSOR 1
# define POWER_SENSOR 2
# define PEX_SENSOR_TYPE_LEN 3
static u8 const power_sensor_sig [ ] = { 0x70 , 0x77 , 0x72 } ;
static u8 const temp_sensor_sig [ ] = { 0x74 , 0x65 , 0x6D } ;
# define PEX_MULT_LEN 2
static u8 const watt_sensor_sig [ ] = { 0x41 , 0x43 } ;
# define PEX_NUM_SENSOR_FUNCS 3
static char const * const power_sensor_name_templates [ ] = {
" %s%d_average " ,
" %s%d_average_lowest " ,
" %s%d_average_highest "
} ;
static char const * const temp_sensor_name_templates [ ] = {
" %s%d_input " ,
" %s%d_input_lowest " ,
" %s%d_input_highest "
} ;
static void ibmpex_msg_handler ( struct ipmi_recv_msg * msg , void * user_msg_data ) ;
static void ibmpex_register_bmc ( int iface , struct device * dev ) ;
static void ibmpex_bmc_gone ( int iface ) ;
struct ibmpex_sensor_data {
int in_use ;
s16 values [ PEX_NUM_SENSOR_FUNCS ] ;
int multiplier ;
struct sensor_device_attribute_2 attr [ PEX_NUM_SENSOR_FUNCS ] ;
} ;
struct ibmpex_bmc_data {
struct list_head list ;
struct device * hwmon_dev ;
struct device * bmc_device ;
struct mutex lock ;
char valid ;
unsigned long last_updated ; /* In jiffies */
struct ipmi_addr address ;
struct completion read_complete ;
ipmi_user_t user ;
int interface ;
struct kernel_ipmi_msg tx_message ;
unsigned char tx_msg_data [ IPMI_MAX_MSG_LENGTH ] ;
long tx_msgid ;
unsigned char rx_msg_data [ IPMI_MAX_MSG_LENGTH ] ;
unsigned long rx_msg_len ;
unsigned char rx_result ;
int rx_recv_type ;
unsigned char sensor_major ;
unsigned char sensor_minor ;
unsigned char num_sensors ;
struct ibmpex_sensor_data * sensors ;
} ;
struct ibmpex_driver_data {
struct list_head bmc_data ;
struct ipmi_smi_watcher bmc_events ;
struct ipmi_user_hndl ipmi_hndlrs ;
} ;
static struct ibmpex_driver_data driver_data = {
. bmc_data = LIST_HEAD_INIT ( driver_data . bmc_data ) ,
. bmc_events = {
. owner = THIS_MODULE ,
. new_smi = ibmpex_register_bmc ,
. smi_gone = ibmpex_bmc_gone ,
} ,
. ipmi_hndlrs = {
. ipmi_recv_hndl = ibmpex_msg_handler ,
} ,
} ;
static int ibmpex_send_message ( struct ibmpex_bmc_data * data )
{
int err ;
err = ipmi_validate_addr ( & data - > address , sizeof ( data - > address ) ) ;
if ( err )
goto out ;
data - > tx_msgid + + ;
err = ipmi_request_settime ( data - > user , & data - > address , data - > tx_msgid ,
& data - > tx_message , data , 0 , 0 , 0 ) ;
if ( err )
goto out1 ;
return 0 ;
out1 :
2007-10-20 03:35:07 +04:00
dev_err ( data - > bmc_device , " request_settime=%x \n " , err ) ;
2007-09-14 23:33:46 +04:00
return err ;
out :
2007-10-20 03:35:07 +04:00
dev_err ( data - > bmc_device , " validate_addr=%x \n " , err ) ;
2007-09-14 23:33:46 +04:00
return err ;
}
static int ibmpex_ver_check ( struct ibmpex_bmc_data * data )
{
data - > tx_msg_data [ 0 ] = PEX_GET_VERSION ;
data - > tx_message . data_len = 1 ;
ibmpex_send_message ( data ) ;
wait_for_completion ( & data - > read_complete ) ;
if ( data - > rx_result | | data - > rx_msg_len ! = 6 )
return - ENOENT ;
data - > sensor_major = data - > rx_msg_data [ 0 ] ;
data - > sensor_minor = data - > rx_msg_data [ 1 ] ;
2007-10-20 03:35:07 +04:00
dev_info ( data - > bmc_device , " Found BMC with sensor interface "
" v%d.%d %d-%02d-%02d on interface %d \n " ,
data - > sensor_major ,
data - > sensor_minor ,
extract_value ( data - > rx_msg_data , 2 ) ,
data - > rx_msg_data [ 4 ] ,
data - > rx_msg_data [ 5 ] ,
data - > interface ) ;
2007-09-14 23:33:46 +04:00
return 0 ;
}
static int ibmpex_query_sensor_count ( struct ibmpex_bmc_data * data )
{
data - > tx_msg_data [ 0 ] = PEX_GET_SENSOR_COUNT ;
data - > tx_message . data_len = 1 ;
ibmpex_send_message ( data ) ;
wait_for_completion ( & data - > read_complete ) ;
if ( data - > rx_result | | data - > rx_msg_len ! = 1 )
return - ENOENT ;
return data - > rx_msg_data [ 0 ] ;
}
static int ibmpex_query_sensor_name ( struct ibmpex_bmc_data * data , int sensor )
{
data - > tx_msg_data [ 0 ] = PEX_GET_SENSOR_NAME ;
data - > tx_msg_data [ 1 ] = sensor ;
data - > tx_message . data_len = 2 ;
ibmpex_send_message ( data ) ;
wait_for_completion ( & data - > read_complete ) ;
if ( data - > rx_result | | data - > rx_msg_len < 1 )
return - ENOENT ;
return 0 ;
}
static int ibmpex_query_sensor_data ( struct ibmpex_bmc_data * data , int sensor )
{
data - > tx_msg_data [ 0 ] = PEX_GET_SENSOR_DATA ;
data - > tx_msg_data [ 1 ] = sensor ;
data - > tx_message . data_len = 2 ;
ibmpex_send_message ( data ) ;
wait_for_completion ( & data - > read_complete ) ;
if ( data - > rx_result | | data - > rx_msg_len < 26 ) {
2007-10-20 03:35:07 +04:00
dev_err ( data - > bmc_device , " Error reading sensor %d. \n " ,
sensor ) ;
2007-09-14 23:33:46 +04:00
return - ENOENT ;
}
return 0 ;
}
static int ibmpex_reset_high_low_data ( struct ibmpex_bmc_data * data )
{
data - > tx_msg_data [ 0 ] = PEX_RESET_HIGH_LOW ;
data - > tx_message . data_len = 1 ;
ibmpex_send_message ( data ) ;
wait_for_completion ( & data - > read_complete ) ;
return 0 ;
}
static void ibmpex_update_device ( struct ibmpex_bmc_data * data )
{
int i , err ;
mutex_lock ( & data - > lock ) ;
if ( time_before ( jiffies , data - > last_updated + REFRESH_INTERVAL ) & &
data - > valid )
goto out ;
for ( i = 0 ; i < data - > num_sensors ; i + + ) {
if ( ! data - > sensors [ i ] . in_use )
continue ;
err = ibmpex_query_sensor_data ( data , i ) ;
if ( err )
continue ;
data - > sensors [ i ] . values [ 0 ] =
extract_value ( data - > rx_msg_data , 16 ) ;
data - > sensors [ i ] . values [ 1 ] =
extract_value ( data - > rx_msg_data , 18 ) ;
data - > sensors [ i ] . values [ 2 ] =
extract_value ( data - > rx_msg_data , 20 ) ;
}
data - > last_updated = jiffies ;
data - > valid = 1 ;
out :
mutex_unlock ( & data - > lock ) ;
}
static struct ibmpex_bmc_data * get_bmc_data ( int iface )
{
struct ibmpex_bmc_data * p , * next ;
list_for_each_entry_safe ( p , next , & driver_data . bmc_data , list )
if ( p - > interface = = iface )
return p ;
return NULL ;
}
static ssize_t show_name ( struct device * dev , struct device_attribute * devattr ,
char * buf )
{
return sprintf ( buf , " %s \n " , DRVNAME ) ;
}
static SENSOR_DEVICE_ATTR ( name , S_IRUGO , show_name , NULL , 0 ) ;
static ssize_t ibmpex_show_sensor ( struct device * dev ,
struct device_attribute * devattr ,
char * buf )
{
struct sensor_device_attribute_2 * attr = to_sensor_dev_attr_2 ( devattr ) ;
struct ibmpex_bmc_data * data = dev_get_drvdata ( dev ) ;
int mult = data - > sensors [ attr - > index ] . multiplier ;
ibmpex_update_device ( data ) ;
return sprintf ( buf , " %d \n " ,
data - > sensors [ attr - > index ] . values [ attr - > nr ] * mult ) ;
}
static ssize_t ibmpex_reset_high_low ( struct device * dev ,
struct device_attribute * devattr ,
const char * buf ,
size_t count )
{
struct ibmpex_bmc_data * data = dev_get_drvdata ( dev ) ;
ibmpex_reset_high_low_data ( data ) ;
return count ;
}
static SENSOR_DEVICE_ATTR ( reset_high_low , S_IWUSR , NULL ,
ibmpex_reset_high_low , 0 ) ;
static int is_power_sensor ( const char * sensor_id , int len )
{
if ( len < PEX_SENSOR_TYPE_LEN )
return 0 ;
if ( ! memcmp ( sensor_id , power_sensor_sig , PEX_SENSOR_TYPE_LEN ) )
return 1 ;
return 0 ;
}
static int is_temp_sensor ( const char * sensor_id , int len )
{
if ( len < PEX_SENSOR_TYPE_LEN )
return 0 ;
if ( ! memcmp ( sensor_id , temp_sensor_sig , PEX_SENSOR_TYPE_LEN ) )
return 1 ;
return 0 ;
}
2008-03-20 03:00:47 +03:00
static int power_sensor_multiplier ( struct ibmpex_bmc_data * data ,
const char * sensor_id , int len )
2007-09-14 23:33:46 +04:00
{
int i ;
2008-03-20 03:00:47 +03:00
if ( data - > sensor_major = = 2 )
return 1000000 ;
2007-09-14 23:33:46 +04:00
for ( i = PEX_SENSOR_TYPE_LEN ; i < len - 1 ; i + + )
if ( ! memcmp ( & sensor_id [ i ] , watt_sensor_sig , PEX_MULT_LEN ) )
return 1000000 ;
return 100000 ;
}
static int create_sensor ( struct ibmpex_bmc_data * data , int type ,
int counter , int sensor , int func )
{
int err ;
char * n ;
n = kmalloc ( 32 , GFP_KERNEL ) ;
if ( ! n )
return - ENOMEM ;
if ( type = = TEMP_SENSOR )
sprintf ( n , temp_sensor_name_templates [ func ] , " temp " , counter ) ;
else if ( type = = POWER_SENSOR )
sprintf ( n , power_sensor_name_templates [ func ] , " power " , counter ) ;
data - > sensors [ sensor ] . attr [ func ] . dev_attr . attr . name = n ;
data - > sensors [ sensor ] . attr [ func ] . dev_attr . attr . mode = S_IRUGO ;
data - > sensors [ sensor ] . attr [ func ] . dev_attr . show = ibmpex_show_sensor ;
data - > sensors [ sensor ] . attr [ func ] . index = sensor ;
data - > sensors [ sensor ] . attr [ func ] . nr = func ;
err = device_create_file ( data - > bmc_device ,
& data - > sensors [ sensor ] . attr [ func ] . dev_attr ) ;
if ( err ) {
data - > sensors [ sensor ] . attr [ func ] . dev_attr . attr . name = NULL ;
kfree ( n ) ;
return err ;
}
return 0 ;
}
static int ibmpex_find_sensors ( struct ibmpex_bmc_data * data )
{
int i , j , err ;
int sensor_type ;
int sensor_counter ;
int num_power = 0 ;
int num_temp = 0 ;
err = ibmpex_query_sensor_count ( data ) ;
if ( err < = 0 )
return - ENOENT ;
data - > num_sensors = err ;
data - > sensors = kzalloc ( data - > num_sensors * sizeof ( * data - > sensors ) ,
GFP_KERNEL ) ;
if ( ! data - > sensors )
return - ENOMEM ;
for ( i = 0 ; i < data - > num_sensors ; i + + ) {
err = ibmpex_query_sensor_name ( data , i ) ;
if ( err )
continue ;
if ( is_power_sensor ( data - > rx_msg_data , data - > rx_msg_len ) ) {
sensor_type = POWER_SENSOR ;
num_power + + ;
sensor_counter = num_power ;
data - > sensors [ i ] . multiplier =
2008-03-20 03:00:47 +03:00
power_sensor_multiplier ( data ,
data - > rx_msg_data ,
data - > rx_msg_len ) ;
2007-09-14 23:33:46 +04:00
} else if ( is_temp_sensor ( data - > rx_msg_data ,
data - > rx_msg_len ) ) {
sensor_type = TEMP_SENSOR ;
num_temp + + ;
sensor_counter = num_temp ;
2008-03-20 03:00:48 +03:00
data - > sensors [ i ] . multiplier = 1000 ;
2007-09-14 23:33:46 +04:00
} else
continue ;
data - > sensors [ i ] . in_use = 1 ;
/* Create attributes */
for ( j = 0 ; j < PEX_NUM_SENSOR_FUNCS ; j + + ) {
err = create_sensor ( data , sensor_type , sensor_counter ,
i , j ) ;
if ( err )
goto exit_remove ;
}
}
err = device_create_file ( data - > bmc_device ,
& sensor_dev_attr_reset_high_low . dev_attr ) ;
if ( err )
goto exit_remove ;
err = device_create_file ( data - > bmc_device ,
& sensor_dev_attr_name . dev_attr ) ;
if ( err )
goto exit_remove ;
return 0 ;
exit_remove :
device_remove_file ( data - > bmc_device ,
& sensor_dev_attr_reset_high_low . dev_attr ) ;
device_remove_file ( data - > bmc_device , & sensor_dev_attr_name . dev_attr ) ;
for ( i = 0 ; i < data - > num_sensors ; i + + )
for ( j = 0 ; j < PEX_NUM_SENSOR_FUNCS ; j + + ) {
if ( ! data - > sensors [ i ] . attr [ j ] . dev_attr . attr . name )
continue ;
device_remove_file ( data - > bmc_device ,
& data - > sensors [ i ] . attr [ j ] . dev_attr ) ;
kfree ( data - > sensors [ i ] . attr [ j ] . dev_attr . attr . name ) ;
}
kfree ( data - > sensors ) ;
return err ;
}
static void ibmpex_register_bmc ( int iface , struct device * dev )
{
struct ibmpex_bmc_data * data ;
int err ;
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data ) {
2007-10-20 03:35:07 +04:00
dev_err ( dev , " Insufficient memory for BMC interface. \n " ) ;
2007-09-14 23:33:46 +04:00
return ;
}
data - > address . addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE ;
data - > address . channel = IPMI_BMC_CHANNEL ;
data - > address . data [ 0 ] = 0 ;
data - > interface = iface ;
data - > bmc_device = dev ;
/* Create IPMI messaging interface user */
err = ipmi_create_user ( data - > interface , & driver_data . ipmi_hndlrs ,
data , & data - > user ) ;
if ( err < 0 ) {
2007-10-20 03:35:07 +04:00
dev_err ( dev , " Unable to register user with IPMI "
" interface %d \n " , data - > interface ) ;
2007-09-14 23:33:46 +04:00
goto out ;
}
mutex_init ( & data - > lock ) ;
/* Initialize message */
data - > tx_msgid = 0 ;
init_completion ( & data - > read_complete ) ;
data - > tx_message . netfn = PEX_NET_FUNCTION ;
data - > tx_message . cmd = PEX_COMMAND ;
data - > tx_message . data = data - > tx_msg_data ;
/* Does this BMC support PowerExecutive? */
err = ibmpex_ver_check ( data ) ;
if ( err )
goto out_user ;
/* Register the BMC as a HWMON class device */
data - > hwmon_dev = hwmon_device_register ( data - > bmc_device ) ;
if ( IS_ERR ( data - > hwmon_dev ) ) {
2007-10-20 03:35:07 +04:00
dev_err ( data - > bmc_device , " Unable to register hwmon "
" device for IPMI interface %d \n " ,
data - > interface ) ;
2007-10-10 02:08:24 +04:00
goto out_user ;
2007-09-14 23:33:46 +04:00
}
/* finally add the new bmc data to the bmc data list */
dev_set_drvdata ( dev , data ) ;
list_add_tail ( & data - > list , & driver_data . bmc_data ) ;
/* Now go find all the sensors */
err = ibmpex_find_sensors ( data ) ;
if ( err ) {
2007-10-20 03:35:07 +04:00
dev_err ( data - > bmc_device , " Error %d finding sensors \n " , err ) ;
2007-09-14 23:33:46 +04:00
goto out_register ;
}
return ;
out_register :
hwmon_device_unregister ( data - > hwmon_dev ) ;
out_user :
ipmi_destroy_user ( data - > user ) ;
out :
kfree ( data ) ;
}
static void ibmpex_bmc_delete ( struct ibmpex_bmc_data * data )
{
int i , j ;
device_remove_file ( data - > bmc_device ,
& sensor_dev_attr_reset_high_low . dev_attr ) ;
device_remove_file ( data - > bmc_device , & sensor_dev_attr_name . dev_attr ) ;
for ( i = 0 ; i < data - > num_sensors ; i + + )
for ( j = 0 ; j < PEX_NUM_SENSOR_FUNCS ; j + + ) {
if ( ! data - > sensors [ i ] . attr [ j ] . dev_attr . attr . name )
continue ;
device_remove_file ( data - > bmc_device ,
& data - > sensors [ i ] . attr [ j ] . dev_attr ) ;
kfree ( data - > sensors [ i ] . attr [ j ] . dev_attr . attr . name ) ;
}
list_del ( & data - > list ) ;
dev_set_drvdata ( data - > bmc_device , NULL ) ;
hwmon_device_unregister ( data - > hwmon_dev ) ;
ipmi_destroy_user ( data - > user ) ;
kfree ( data - > sensors ) ;
kfree ( data ) ;
}
static void ibmpex_bmc_gone ( int iface )
{
struct ibmpex_bmc_data * data = get_bmc_data ( iface ) ;
if ( ! data )
return ;
ibmpex_bmc_delete ( data ) ;
}
static void ibmpex_msg_handler ( struct ipmi_recv_msg * msg , void * user_msg_data )
{
struct ibmpex_bmc_data * data = ( struct ibmpex_bmc_data * ) user_msg_data ;
if ( msg - > msgid ! = data - > tx_msgid ) {
2007-10-20 03:35:07 +04:00
dev_err ( data - > bmc_device , " Mismatch between received msgid "
" (%02x) and transmitted msgid (%02x)! \n " ,
( int ) msg - > msgid ,
( int ) data - > tx_msgid ) ;
2007-09-14 23:33:46 +04:00
ipmi_free_recv_msg ( msg ) ;
return ;
}
data - > rx_recv_type = msg - > recv_type ;
if ( msg - > msg . data_len > 0 )
data - > rx_result = msg - > msg . data [ 0 ] ;
else
data - > rx_result = IPMI_UNKNOWN_ERR_COMPLETION_CODE ;
if ( msg - > msg . data_len > 1 ) {
data - > rx_msg_len = msg - > msg . data_len - 1 ;
memcpy ( data - > rx_msg_data , msg - > msg . data + 1 , data - > rx_msg_len ) ;
} else
data - > rx_msg_len = 0 ;
ipmi_free_recv_msg ( msg ) ;
complete ( & data - > read_complete ) ;
}
static int __init ibmpex_init ( void )
{
return ipmi_smi_watcher_register ( & driver_data . bmc_events ) ;
}
static void __exit ibmpex_exit ( void )
{
struct ibmpex_bmc_data * p , * next ;
ipmi_smi_watcher_unregister ( & driver_data . bmc_events ) ;
list_for_each_entry_safe ( p , next , & driver_data . bmc_data , list )
ibmpex_bmc_delete ( p ) ;
}
MODULE_AUTHOR ( " Darrick J. Wong <djwong@us.ibm.com> " ) ;
MODULE_DESCRIPTION ( " IBM PowerExecutive power/temperature sensor driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( ibmpex_init ) ;
module_exit ( ibmpex_exit ) ;
2008-10-17 19:51:19 +04:00
MODULE_ALIAS ( " dmi:bvnIBM:*:pnIBMSystemx3350-* " ) ;
MODULE_ALIAS ( " dmi:bvnIBM:*:pnIBMSystemx3550-* " ) ;
MODULE_ALIAS ( " dmi:bvnIBM:*:pnIBMSystemx3650-* " ) ;
MODULE_ALIAS ( " dmi:bvnIBM:*:pnIBMSystemx3655-* " ) ;
MODULE_ALIAS ( " dmi:bvnIBM:*:pnIBMSystemx3755-* " ) ;