2005-04-17 06:20:36 +08:00
/*
* ipmi_poweroff . c
*
* MontaVista IPMI Poweroff extension to sys_reboot
*
* Author : MontaVista Software , Inc .
* Steven Dake < sdake @ mvista . com >
* Corey Minyard < cminyard @ mvista . com >
* source @ mvista . com
*
* Copyright 2002 , 2004 MontaVista Software Inc .
*
* 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 SOFTWARE IS PROVIDED ` ` AS IS ' ' AND ANY EXPRESS OR IMPLIED
* WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED .
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT , INDIRECT ,
* INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING ,
* BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS
* OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR
* TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
* 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 .
*/
2005-06-24 13:01:44 +08:00
# include <linux/config.h>
2005-04-17 06:20:36 +08:00
# include <linux/module.h>
2005-06-24 13:01:42 +08:00
# include <linux/moduleparam.h>
# include <linux/proc_fs.h>
2005-04-17 06:20:36 +08:00
# include <linux/string.h>
2005-06-24 13:01:44 +08:00
# include <linux/completion.h>
# include <linux/kdev_t.h>
2005-04-17 06:20:36 +08:00
# include <linux/ipmi.h>
# include <linux/ipmi_smi.h>
# define PFX "IPMI poweroff: "
# define IPMI_POWEROFF_VERSION "v33"
/* Where to we insert our poweroff function? */
extern void ( * pm_power_off ) ( void ) ;
2005-06-24 13:01:42 +08:00
/* Definitions for controlling power off (if the system supports it). It
* conveniently matches the IPMI chassis control values . */
# define IPMI_CHASSIS_POWER_DOWN 0 /* power down, the default. */
# define IPMI_CHASSIS_POWER_CYCLE 0x02 /* power cycle */
/* the IPMI data command */
static int poweroff_control = IPMI_CHASSIS_POWER_DOWN ;
/* parameter definition to allow user to flag power cycle */
module_param ( poweroff_control , int , IPMI_CHASSIS_POWER_DOWN ) ;
MODULE_PARM_DESC ( poweroff_control , " Set to 2 to enable power cycle instead of power down. Power cycle is contingent on hardware support, otherwise it defaults back to power down. " ) ;
2005-04-17 06:20:36 +08:00
/* Stuff from the get device id command. */
static unsigned int mfg_id ;
static unsigned int prod_id ;
static unsigned char capabilities ;
/* We use our own messages for this operation, we don't let the system
allocate them , since we may be in a panic situation . The whole
thing is single - threaded , anyway , so multiple messages are not
required . */
static void dummy_smi_free ( struct ipmi_smi_msg * msg )
{
}
static void dummy_recv_free ( struct ipmi_recv_msg * msg )
{
}
static struct ipmi_smi_msg halt_smi_msg =
{
. done = dummy_smi_free
} ;
static struct ipmi_recv_msg halt_recv_msg =
{
. done = dummy_recv_free
} ;
/*
* Code to send a message and wait for the reponse .
*/
static void receive_handler ( struct ipmi_recv_msg * recv_msg , void * handler_data )
{
2005-06-24 13:01:44 +08:00
struct completion * comp = recv_msg - > user_msg_data ;
2005-04-17 06:20:36 +08:00
2005-06-24 13:01:44 +08:00
if ( comp )
complete ( comp ) ;
2005-04-17 06:20:36 +08:00
}
static struct ipmi_user_hndl ipmi_poweroff_handler =
{
. ipmi_recv_hndl = receive_handler
} ;
static int ipmi_request_wait_for_response ( ipmi_user_t user ,
struct ipmi_addr * addr ,
struct kernel_ipmi_msg * send_msg )
{
2005-06-24 13:01:44 +08:00
int rv ;
struct completion comp ;
2005-04-17 06:20:36 +08:00
2005-06-24 13:01:44 +08:00
init_completion ( & comp ) ;
2005-04-17 06:20:36 +08:00
2005-06-24 13:01:44 +08:00
rv = ipmi_request_supply_msgs ( user , addr , 0 , send_msg , & comp ,
2005-04-17 06:20:36 +08:00
& halt_smi_msg , & halt_recv_msg , 0 ) ;
if ( rv )
return rv ;
2005-06-24 13:01:44 +08:00
wait_for_completion ( & comp ) ;
2005-04-17 06:20:36 +08:00
return halt_recv_msg . msg . data [ 0 ] ;
}
2005-06-24 13:01:44 +08:00
/* We are in run-to-completion mode, no completion is desired. */
2005-04-17 06:20:36 +08:00
static int ipmi_request_in_rc_mode ( ipmi_user_t user ,
struct ipmi_addr * addr ,
struct kernel_ipmi_msg * send_msg )
{
2005-06-24 13:01:44 +08:00
int rv ;
2005-04-17 06:20:36 +08:00
rv = ipmi_request_supply_msgs ( user , addr , 0 , send_msg , NULL ,
& halt_smi_msg , & halt_recv_msg , 0 ) ;
if ( rv )
return rv ;
return halt_recv_msg . msg . data [ 0 ] ;
}
/*
* ATCA Support
*/
# define IPMI_NETFN_ATCA 0x2c
# define IPMI_ATCA_SET_POWER_CMD 0x11
# define IPMI_ATCA_GET_ADDR_INFO_CMD 0x01
# define IPMI_PICMG_ID 0
static int ipmi_atca_detect ( ipmi_user_t user )
{
struct ipmi_system_interface_addr smi_addr ;
struct kernel_ipmi_msg send_msg ;
int rv ;
unsigned char data [ 1 ] ;
/*
* Configure IPMI address for local access
*/
smi_addr . addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE ;
smi_addr . channel = IPMI_BMC_CHANNEL ;
smi_addr . lun = 0 ;
/*
* Use get address info to check and see if we are ATCA
*/
send_msg . netfn = IPMI_NETFN_ATCA ;
send_msg . cmd = IPMI_ATCA_GET_ADDR_INFO_CMD ;
data [ 0 ] = IPMI_PICMG_ID ;
send_msg . data = data ;
send_msg . data_len = sizeof ( data ) ;
rv = ipmi_request_wait_for_response ( user ,
( struct ipmi_addr * ) & smi_addr ,
& send_msg ) ;
return ! rv ;
}
static void ipmi_poweroff_atca ( ipmi_user_t user )
{
struct ipmi_system_interface_addr smi_addr ;
struct kernel_ipmi_msg send_msg ;
int rv ;
unsigned char data [ 4 ] ;
/*
* Configure IPMI address for local access
*/
smi_addr . addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE ;
smi_addr . channel = IPMI_BMC_CHANNEL ;
smi_addr . lun = 0 ;
printk ( KERN_INFO PFX " Powering down via ATCA power command \n " ) ;
/*
* Power down
*/
send_msg . netfn = IPMI_NETFN_ATCA ;
send_msg . cmd = IPMI_ATCA_SET_POWER_CMD ;
data [ 0 ] = IPMI_PICMG_ID ;
data [ 1 ] = 0 ; /* FRU id */
data [ 2 ] = 0 ; /* Power Level */
data [ 3 ] = 0 ; /* Don't change saved presets */
send_msg . data = data ;
send_msg . data_len = sizeof ( data ) ;
rv = ipmi_request_in_rc_mode ( user ,
( struct ipmi_addr * ) & smi_addr ,
& send_msg ) ;
if ( rv ) {
printk ( KERN_ERR PFX " Unable to send ATCA powerdown message, "
" IPMI error 0x%x \n " , rv ) ;
goto out ;
}
out :
return ;
}
/*
* CPI1 Support
*/
# define IPMI_NETFN_OEM_1 0xf8
# define OEM_GRP_CMD_SET_RESET_STATE 0x84
# define OEM_GRP_CMD_SET_POWER_STATE 0x82
# define IPMI_NETFN_OEM_8 0xf8
# define OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL 0x80
# define OEM_GRP_CMD_GET_SLOT_GA 0xa3
# define IPMI_NETFN_SENSOR_EVT 0x10
# define IPMI_CMD_GET_EVENT_RECEIVER 0x01
# define IPMI_CPI1_PRODUCT_ID 0x000157
# define IPMI_CPI1_MANUFACTURER_ID 0x0108
static int ipmi_cpi1_detect ( ipmi_user_t user )
{
return ( ( mfg_id = = IPMI_CPI1_MANUFACTURER_ID )
& & ( prod_id = = IPMI_CPI1_PRODUCT_ID ) ) ;
}
static void ipmi_poweroff_cpi1 ( ipmi_user_t user )
{
struct ipmi_system_interface_addr smi_addr ;
struct ipmi_ipmb_addr ipmb_addr ;
struct kernel_ipmi_msg send_msg ;
int rv ;
unsigned char data [ 1 ] ;
int slot ;
unsigned char hotswap_ipmb ;
unsigned char aer_addr ;
unsigned char aer_lun ;
/*
* Configure IPMI address for local access
*/
smi_addr . addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE ;
smi_addr . channel = IPMI_BMC_CHANNEL ;
smi_addr . lun = 0 ;
printk ( KERN_INFO PFX " Powering down via CPI1 power command \n " ) ;
/*
* Get IPMI ipmb address
*/
send_msg . netfn = IPMI_NETFN_OEM_8 > > 2 ;
send_msg . cmd = OEM_GRP_CMD_GET_SLOT_GA ;
send_msg . data = NULL ;
send_msg . data_len = 0 ;
rv = ipmi_request_in_rc_mode ( user ,
( struct ipmi_addr * ) & smi_addr ,
& send_msg ) ;
if ( rv )
goto out ;
slot = halt_recv_msg . msg . data [ 1 ] ;
hotswap_ipmb = ( slot > 9 ) ? ( 0xb0 + 2 * slot ) : ( 0xae + 2 * slot ) ;
/*
* Get active event receiver
*/
send_msg . netfn = IPMI_NETFN_SENSOR_EVT > > 2 ;
send_msg . cmd = IPMI_CMD_GET_EVENT_RECEIVER ;
send_msg . data = NULL ;
send_msg . data_len = 0 ;
rv = ipmi_request_in_rc_mode ( user ,
( struct ipmi_addr * ) & smi_addr ,
& send_msg ) ;
if ( rv )
goto out ;
aer_addr = halt_recv_msg . msg . data [ 1 ] ;
aer_lun = halt_recv_msg . msg . data [ 2 ] ;
/*
* Setup IPMB address target instead of local target
*/
ipmb_addr . addr_type = IPMI_IPMB_ADDR_TYPE ;
ipmb_addr . channel = 0 ;
ipmb_addr . slave_addr = aer_addr ;
ipmb_addr . lun = aer_lun ;
/*
* Send request hotswap control to remove blade from dpv
*/
send_msg . netfn = IPMI_NETFN_OEM_8 > > 2 ;
send_msg . cmd = OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL ;
send_msg . data = & hotswap_ipmb ;
send_msg . data_len = 1 ;
ipmi_request_in_rc_mode ( user ,
( struct ipmi_addr * ) & ipmb_addr ,
& send_msg ) ;
/*
* Set reset asserted
*/
send_msg . netfn = IPMI_NETFN_OEM_1 > > 2 ;
send_msg . cmd = OEM_GRP_CMD_SET_RESET_STATE ;
send_msg . data = data ;
data [ 0 ] = 1 ; /* Reset asserted state */
send_msg . data_len = 1 ;
rv = ipmi_request_in_rc_mode ( user ,
( struct ipmi_addr * ) & smi_addr ,
& send_msg ) ;
if ( rv )
goto out ;
/*
* Power down
*/
send_msg . netfn = IPMI_NETFN_OEM_1 > > 2 ;
send_msg . cmd = OEM_GRP_CMD_SET_POWER_STATE ;
send_msg . data = data ;
data [ 0 ] = 1 ; /* Power down state */
send_msg . data_len = 1 ;
rv = ipmi_request_in_rc_mode ( user ,
( struct ipmi_addr * ) & smi_addr ,
& send_msg ) ;
if ( rv )
goto out ;
out :
return ;
}
/*
* Standard chassis support
*/
# define IPMI_NETFN_CHASSIS_REQUEST 0
# define IPMI_CHASSIS_CONTROL_CMD 0x02
static int ipmi_chassis_detect ( ipmi_user_t user )
{
/* Chassis support, use it. */
return ( capabilities & 0x80 ) ;
}
static void ipmi_poweroff_chassis ( ipmi_user_t user )
{
struct ipmi_system_interface_addr smi_addr ;
struct kernel_ipmi_msg send_msg ;
int rv ;
unsigned char data [ 1 ] ;
/*
* Configure IPMI address for local access
*/
smi_addr . addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE ;
smi_addr . channel = IPMI_BMC_CHANNEL ;
smi_addr . lun = 0 ;
2005-06-24 13:01:42 +08:00
powercyclefailed :
printk ( KERN_INFO PFX " Powering %s via IPMI chassis control command \n " ,
( ( poweroff_control ! = IPMI_CHASSIS_POWER_CYCLE ) ? " down " : " cycle " ) ) ;
2005-04-17 06:20:36 +08:00
/*
* Power down
*/
send_msg . netfn = IPMI_NETFN_CHASSIS_REQUEST ;
send_msg . cmd = IPMI_CHASSIS_CONTROL_CMD ;
2005-06-24 13:01:42 +08:00
data [ 0 ] = poweroff_control ;
2005-04-17 06:20:36 +08:00
send_msg . data = data ;
send_msg . data_len = sizeof ( data ) ;
rv = ipmi_request_in_rc_mode ( user ,
( struct ipmi_addr * ) & smi_addr ,
& send_msg ) ;
if ( rv ) {
2005-06-24 13:01:42 +08:00
switch ( poweroff_control ) {
case IPMI_CHASSIS_POWER_CYCLE :
/* power cycle failed, default to power down */
printk ( KERN_ERR PFX " Unable to send chassis power " \
" cycle message, IPMI error 0x%x \n " , rv ) ;
poweroff_control = IPMI_CHASSIS_POWER_DOWN ;
goto powercyclefailed ;
case IPMI_CHASSIS_POWER_DOWN :
default :
printk ( KERN_ERR PFX " Unable to send chassis power " \
" down message, IPMI error 0x%x \n " , rv ) ;
break ;
}
2005-04-17 06:20:36 +08:00
}
return ;
}
/* Table of possible power off functions. */
struct poweroff_function {
char * platform_type ;
int ( * detect ) ( ipmi_user_t user ) ;
void ( * poweroff_func ) ( ipmi_user_t user ) ;
} ;
static struct poweroff_function poweroff_functions [ ] = {
{ . platform_type = " ATCA " ,
. detect = ipmi_atca_detect ,
. poweroff_func = ipmi_poweroff_atca } ,
{ . platform_type = " CPI1 " ,
. detect = ipmi_cpi1_detect ,
. poweroff_func = ipmi_poweroff_cpi1 } ,
/* Chassis should generally be last, other things should override
it . */
{ . platform_type = " chassis " ,
. detect = ipmi_chassis_detect ,
. poweroff_func = ipmi_poweroff_chassis } ,
} ;
# define NUM_PO_FUNCS (sizeof(poweroff_functions) \
/ sizeof ( struct poweroff_function ) )
/* Our local state. */
static int ready = 0 ;
static ipmi_user_t ipmi_user ;
static void ( * specific_poweroff_func ) ( ipmi_user_t user ) = NULL ;
/* Holds the old poweroff function so we can restore it on removal. */
static void ( * old_poweroff_func ) ( void ) ;
/* Called on a powerdown request. */
static void ipmi_poweroff_function ( void )
{
if ( ! ready )
return ;
/* Use run-to-completion mode, since interrupts may be off. */
ipmi_user_set_run_to_completion ( ipmi_user , 1 ) ;
specific_poweroff_func ( ipmi_user ) ;
ipmi_user_set_run_to_completion ( ipmi_user , 0 ) ;
}
/* Wait for an IPMI interface to be installed, the first one installed
will be grabbed by this code and used to perform the powerdown . */
static void ipmi_po_new_smi ( int if_num )
{
struct ipmi_system_interface_addr smi_addr ;
struct kernel_ipmi_msg send_msg ;
int rv ;
int i ;
if ( ready )
return ;
2005-06-24 13:01:42 +08:00
rv = ipmi_create_user ( if_num , & ipmi_poweroff_handler , NULL ,
& ipmi_user ) ;
2005-04-17 06:20:36 +08:00
if ( rv ) {
printk ( KERN_ERR PFX " could not create IPMI user, error %d \n " ,
rv ) ;
return ;
}
/*
* Do a get device ide and store some results , since this is
* used by several functions .
*/
smi_addr . addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE ;
smi_addr . channel = IPMI_BMC_CHANNEL ;
smi_addr . lun = 0 ;
send_msg . netfn = IPMI_NETFN_APP_REQUEST ;
send_msg . cmd = IPMI_GET_DEVICE_ID_CMD ;
send_msg . data = NULL ;
send_msg . data_len = 0 ;
rv = ipmi_request_wait_for_response ( ipmi_user ,
( struct ipmi_addr * ) & smi_addr ,
& send_msg ) ;
if ( rv ) {
printk ( KERN_ERR PFX " Unable to send IPMI get device id info, "
" IPMI error 0x%x \n " , rv ) ;
goto out_err ;
}
if ( halt_recv_msg . msg . data_len < 12 ) {
printk ( KERN_ERR PFX " (chassis) IPMI get device id info too, "
" short, was %d bytes, needed %d bytes \n " ,
halt_recv_msg . msg . data_len , 12 ) ;
goto out_err ;
}
mfg_id = ( halt_recv_msg . msg . data [ 7 ]
| ( halt_recv_msg . msg . data [ 8 ] < < 8 )
| ( halt_recv_msg . msg . data [ 9 ] < < 16 ) ) ;
prod_id = ( halt_recv_msg . msg . data [ 10 ]
| ( halt_recv_msg . msg . data [ 11 ] < < 8 ) ) ;
capabilities = halt_recv_msg . msg . data [ 6 ] ;
/* Scan for a poweroff method */
for ( i = 0 ; i < NUM_PO_FUNCS ; i + + ) {
if ( poweroff_functions [ i ] . detect ( ipmi_user ) )
goto found ;
}
out_err :
printk ( KERN_ERR PFX " Unable to find a poweroff function that "
" will work, giving up \n " ) ;
ipmi_destroy_user ( ipmi_user ) ;
return ;
found :
printk ( KERN_INFO PFX " Found a %s style poweroff function \n " ,
poweroff_functions [ i ] . platform_type ) ;
specific_poweroff_func = poweroff_functions [ i ] . poweroff_func ;
old_poweroff_func = pm_power_off ;
pm_power_off = ipmi_poweroff_function ;
ready = 1 ;
}
static void ipmi_po_smi_gone ( int if_num )
{
/* This can never be called, because once poweroff driver is
registered , the interface can ' t go away until the power
driver is unregistered . */
}
static struct ipmi_smi_watcher smi_watcher =
{
. owner = THIS_MODULE ,
. new_smi = ipmi_po_new_smi ,
. smi_gone = ipmi_po_smi_gone
} ;
2005-06-24 13:01:42 +08:00
# ifdef CONFIG_PROC_FS
/* displays properties to proc */
static int proc_read_chassctrl ( char * page , char * * start , off_t off , int count ,
int * eof , void * data )
{
return sprintf ( page , " %d \t [ 0=powerdown 2=powercycle ] \n " ,
poweroff_control ) ;
}
/* process property writes from proc */
static int proc_write_chassctrl ( struct file * file , const char * buffer ,
unsigned long count , void * data )
{
int rv = count ;
unsigned int newval = 0 ;
sscanf ( buffer , " %d " , & newval ) ;
switch ( newval ) {
case IPMI_CHASSIS_POWER_CYCLE :
printk ( KERN_INFO PFX " power cycle is now enabled \n " ) ;
poweroff_control = newval ;
break ;
case IPMI_CHASSIS_POWER_DOWN :
poweroff_control = IPMI_CHASSIS_POWER_DOWN ;
break ;
default :
rv = - EINVAL ;
break ;
}
return rv ;
}
# endif /* CONFIG_PROC_FS */
2005-04-17 06:20:36 +08:00
/*
* Startup and shutdown functions .
*/
static int ipmi_poweroff_init ( void )
{
2005-06-24 13:01:42 +08:00
int rv ;
struct proc_dir_entry * file ;
2005-04-17 06:20:36 +08:00
printk ( " Copyright (C) 2004 MontaVista Software - "
" IPMI Powerdown via sys_reboot version "
IPMI_POWEROFF_VERSION " . \n " ) ;
2005-06-24 13:01:42 +08:00
switch ( poweroff_control ) {
case IPMI_CHASSIS_POWER_CYCLE :
printk ( KERN_INFO PFX " Power cycle is enabled. \n " ) ;
break ;
case IPMI_CHASSIS_POWER_DOWN :
default :
poweroff_control = IPMI_CHASSIS_POWER_DOWN ;
break ;
}
2005-04-17 06:20:36 +08:00
rv = ipmi_smi_watcher_register ( & smi_watcher ) ;
2005-06-24 13:01:42 +08:00
if ( rv ) {
2005-04-17 06:20:36 +08:00
printk ( KERN_ERR PFX " Unable to register SMI watcher: %d \n " , rv ) ;
2005-06-24 13:01:42 +08:00
goto out_err ;
}
# ifdef CONFIG_PROC_FS
file = create_proc_entry ( " poweroff_control " , 0 , proc_ipmi_root ) ;
if ( ! file ) {
printk ( KERN_ERR PFX " Unable to create proc power control \n " ) ;
} else {
file - > nlink = 1 ;
file - > read_proc = proc_read_chassctrl ;
file - > write_proc = proc_write_chassctrl ;
file - > owner = THIS_MODULE ;
}
# endif
2005-04-17 06:20:36 +08:00
2005-06-24 13:01:42 +08:00
out_err :
2005-04-17 06:20:36 +08:00
return rv ;
}
# ifdef MODULE
static __exit void ipmi_poweroff_cleanup ( void )
{
int rv ;
2005-06-24 13:01:42 +08:00
# ifdef CONFIG_PROC_FS
remove_proc_entry ( " poweroff_control " , proc_ipmi_root ) ;
# endif
2005-04-17 06:20:36 +08:00
ipmi_smi_watcher_unregister ( & smi_watcher ) ;
if ( ready ) {
rv = ipmi_destroy_user ( ipmi_user ) ;
if ( rv )
printk ( KERN_ERR PFX " could not cleanup the IPMI "
" user: 0x%x \n " , rv ) ;
pm_power_off = old_poweroff_func ;
}
}
module_exit ( ipmi_poweroff_cleanup ) ;
# endif
module_init ( ipmi_poweroff_init ) ;
MODULE_LICENSE ( " GPL " ) ;