2014-11-12 10:41:05 +03:00
/*
* PowerNV OPAL IPMI driver
*
* Copyright 2014 IBM Corp .
*
* 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 .
*/
# define pr_fmt(fmt) "ipmi-powernv: " fmt
# include <linux/ipmi_smi.h>
# include <linux/list.h>
# include <linux/module.h>
# include <linux/of.h>
2015-05-15 07:06:38 +03:00
# include <linux/of_irq.h>
# include <linux/interrupt.h>
2014-11-12 10:41:05 +03:00
# include <asm/opal.h>
struct ipmi_smi_powernv {
u64 interface_id ;
ipmi_smi_t intf ;
2015-05-15 07:06:38 +03:00
unsigned int irq ;
2014-11-12 10:41:05 +03:00
/**
* We assume that there can only be one outstanding request , so
* keep the pending message in cur_msg . We protect this from concurrent
* updates through send & recv calls , ( and consequently opal_msg , which
* is in - use when cur_msg is set ) with msg_lock
*/
spinlock_t msg_lock ;
struct ipmi_smi_msg * cur_msg ;
struct opal_ipmi_msg * opal_msg ;
} ;
static int ipmi_powernv_start_processing ( void * send_info , ipmi_smi_t intf )
{
struct ipmi_smi_powernv * smi = send_info ;
smi - > intf = intf ;
return 0 ;
}
static void send_error_reply ( struct ipmi_smi_powernv * smi ,
struct ipmi_smi_msg * msg , u8 completion_code )
{
msg - > rsp [ 0 ] = msg - > data [ 0 ] | 0x4 ;
msg - > rsp [ 1 ] = msg - > data [ 1 ] ;
msg - > rsp [ 2 ] = completion_code ;
msg - > rsp_size = 3 ;
ipmi_smi_msg_received ( smi - > intf , msg ) ;
}
static void ipmi_powernv_send ( void * send_info , struct ipmi_smi_msg * msg )
{
struct ipmi_smi_powernv * smi = send_info ;
struct opal_ipmi_msg * opal_msg ;
unsigned long flags ;
int comp , rc ;
size_t size ;
/* ensure data_len will fit in the opal_ipmi_msg buffer... */
if ( msg - > data_size > IPMI_MAX_MSG_LENGTH ) {
comp = IPMI_REQ_LEN_EXCEEDED_ERR ;
goto err ;
}
/* ... and that we at least have netfn and cmd bytes */
if ( msg - > data_size < 2 ) {
comp = IPMI_REQ_LEN_INVALID_ERR ;
goto err ;
}
spin_lock_irqsave ( & smi - > msg_lock , flags ) ;
if ( smi - > cur_msg ) {
comp = IPMI_NODE_BUSY_ERR ;
goto err_unlock ;
}
/* format our data for the OPAL API */
opal_msg = smi - > opal_msg ;
opal_msg - > version = OPAL_IPMI_MSG_FORMAT_VERSION_1 ;
opal_msg - > netfn = msg - > data [ 0 ] ;
opal_msg - > cmd = msg - > data [ 1 ] ;
if ( msg - > data_size > 2 )
memcpy ( opal_msg - > data , msg - > data + 2 , msg - > data_size - 2 ) ;
/* data_size already includes the netfn and cmd bytes */
size = sizeof ( * opal_msg ) + msg - > data_size - 2 ;
pr_devel ( " %s: opal_ipmi_send(0x%llx, %p, %ld) \n " , __func__ ,
smi - > interface_id , opal_msg , size ) ;
rc = opal_ipmi_send ( smi - > interface_id , opal_msg , size ) ;
pr_devel ( " %s: -> %d \n " , __func__ , rc ) ;
if ( ! rc ) {
smi - > cur_msg = msg ;
spin_unlock_irqrestore ( & smi - > msg_lock , flags ) ;
return ;
}
comp = IPMI_ERR_UNSPECIFIED ;
err_unlock :
spin_unlock_irqrestore ( & smi - > msg_lock , flags ) ;
err :
send_error_reply ( smi , msg , comp ) ;
}
static int ipmi_powernv_recv ( struct ipmi_smi_powernv * smi )
{
struct opal_ipmi_msg * opal_msg ;
struct ipmi_smi_msg * msg ;
unsigned long flags ;
uint64_t size ;
int rc ;
pr_devel ( " %s: opal_ipmi_recv(%llx, msg, sz) \n " , __func__ ,
smi - > interface_id ) ;
spin_lock_irqsave ( & smi - > msg_lock , flags ) ;
if ( ! smi - > cur_msg ) {
2015-04-10 10:32:20 +03:00
spin_unlock_irqrestore ( & smi - > msg_lock , flags ) ;
2014-11-12 10:41:05 +03:00
pr_warn ( " no current message? \n " ) ;
return 0 ;
}
msg = smi - > cur_msg ;
opal_msg = smi - > opal_msg ;
size = cpu_to_be64 ( sizeof ( * opal_msg ) + IPMI_MAX_MSG_LENGTH ) ;
rc = opal_ipmi_recv ( smi - > interface_id ,
opal_msg ,
& size ) ;
size = be64_to_cpu ( size ) ;
pr_devel ( " %s: -> %d (size %lld) \n " , __func__ ,
rc , rc = = 0 ? size : 0 ) ;
if ( rc ) {
2015-07-16 14:16:54 +03:00
/* If came via the poll, and response was not yet ready */
if ( rc = = OPAL_EMPTY ) {
spin_unlock_irqrestore ( & smi - > msg_lock , flags ) ;
return 0 ;
}
smi - > cur_msg = NULL ;
2014-11-12 10:41:05 +03:00
spin_unlock_irqrestore ( & smi - > msg_lock , flags ) ;
2015-07-16 14:16:54 +03:00
send_error_reply ( smi , msg , IPMI_ERR_UNSPECIFIED ) ;
2014-11-12 10:41:05 +03:00
return 0 ;
}
if ( size < sizeof ( * opal_msg ) ) {
spin_unlock_irqrestore ( & smi - > msg_lock , flags ) ;
pr_warn ( " unexpected IPMI message size %lld \n " , size ) ;
return 0 ;
}
if ( opal_msg - > version ! = OPAL_IPMI_MSG_FORMAT_VERSION_1 ) {
spin_unlock_irqrestore ( & smi - > msg_lock , flags ) ;
pr_warn ( " unexpected IPMI message format (version %d) \n " ,
opal_msg - > version ) ;
return 0 ;
}
msg - > rsp [ 0 ] = opal_msg - > netfn ;
msg - > rsp [ 1 ] = opal_msg - > cmd ;
if ( size > sizeof ( * opal_msg ) )
memcpy ( & msg - > rsp [ 2 ] , opal_msg - > data , size - sizeof ( * opal_msg ) ) ;
msg - > rsp_size = 2 + size - sizeof ( * opal_msg ) ;
smi - > cur_msg = NULL ;
spin_unlock_irqrestore ( & smi - > msg_lock , flags ) ;
ipmi_smi_msg_received ( smi - > intf , msg ) ;
return 0 ;
}
static void ipmi_powernv_request_events ( void * send_info )
{
}
static void ipmi_powernv_set_run_to_completion ( void * send_info ,
bool run_to_completion )
{
}
static void ipmi_powernv_poll ( void * send_info )
{
struct ipmi_smi_powernv * smi = send_info ;
ipmi_powernv_recv ( smi ) ;
}
2017-01-15 22:15:00 +03:00
static const struct ipmi_smi_handlers ipmi_powernv_smi_handlers = {
2014-11-12 10:41:05 +03:00
. owner = THIS_MODULE ,
. start_processing = ipmi_powernv_start_processing ,
. sender = ipmi_powernv_send ,
. request_events = ipmi_powernv_request_events ,
. set_run_to_completion = ipmi_powernv_set_run_to_completion ,
. poll = ipmi_powernv_poll ,
} ;
2015-05-15 07:06:38 +03:00
static irqreturn_t ipmi_opal_event ( int irq , void * data )
2014-11-12 10:41:05 +03:00
{
2015-05-15 07:06:38 +03:00
struct ipmi_smi_powernv * smi = data ;
2014-11-12 10:41:05 +03:00
2015-05-15 07:06:38 +03:00
ipmi_powernv_recv ( smi ) ;
return IRQ_HANDLED ;
2014-11-12 10:41:05 +03:00
}
static int ipmi_powernv_probe ( struct platform_device * pdev )
{
struct ipmi_smi_powernv * ipmi ;
struct device * dev ;
u32 prop ;
int rc ;
if ( ! pdev | | ! pdev - > dev . of_node )
return - ENODEV ;
dev = & pdev - > dev ;
ipmi = devm_kzalloc ( dev , sizeof ( * ipmi ) , GFP_KERNEL ) ;
if ( ! ipmi )
return - ENOMEM ;
spin_lock_init ( & ipmi - > msg_lock ) ;
rc = of_property_read_u32 ( dev - > of_node , " ibm,ipmi-interface-id " ,
& prop ) ;
if ( rc ) {
dev_warn ( dev , " No interface ID property \n " ) ;
goto err_free ;
}
ipmi - > interface_id = prop ;
rc = of_property_read_u32 ( dev - > of_node , " interrupts " , & prop ) ;
if ( rc ) {
dev_warn ( dev , " No interrupts property \n " ) ;
goto err_free ;
}
2015-05-15 07:06:38 +03:00
ipmi - > irq = irq_of_parse_and_map ( dev - > of_node , 0 ) ;
if ( ! ipmi - > irq ) {
dev_info ( dev , " Unable to map irq from device tree \n " ) ;
ipmi - > irq = opal_event_request ( prop ) ;
}
2014-11-12 10:41:05 +03:00
2018-01-18 04:43:19 +03:00
rc = request_irq ( ipmi - > irq , ipmi_opal_event , IRQ_TYPE_LEVEL_HIGH ,
" opal-ipmi " , ipmi ) ;
if ( rc ) {
2015-05-15 07:06:38 +03:00
dev_warn ( dev , " Unable to request irq \n " ) ;
goto err_dispose ;
2014-11-12 10:41:05 +03:00
}
ipmi - > opal_msg = devm_kmalloc ( dev ,
sizeof ( * ipmi - > opal_msg ) + IPMI_MAX_MSG_LENGTH ,
GFP_KERNEL ) ;
if ( ! ipmi - > opal_msg ) {
rc = - ENOMEM ;
goto err_unregister ;
}
2017-09-01 00:45:40 +03:00
rc = ipmi_register_smi ( & ipmi_powernv_smi_handlers , ipmi , dev , 0 ) ;
2014-11-12 10:41:05 +03:00
if ( rc ) {
dev_warn ( dev , " IPMI SMI registration failed (%d) \n " , rc ) ;
goto err_free_msg ;
}
dev_set_drvdata ( dev , ipmi ) ;
return 0 ;
err_free_msg :
devm_kfree ( dev , ipmi - > opal_msg ) ;
err_unregister :
2015-05-15 07:06:38 +03:00
free_irq ( ipmi - > irq , ipmi ) ;
err_dispose :
irq_dispose_mapping ( ipmi - > irq ) ;
2014-11-12 10:41:05 +03:00
err_free :
devm_kfree ( dev , ipmi ) ;
return rc ;
}
static int ipmi_powernv_remove ( struct platform_device * pdev )
{
struct ipmi_smi_powernv * smi = dev_get_drvdata ( & pdev - > dev ) ;
ipmi_unregister_smi ( smi - > intf ) ;
2015-05-15 07:06:38 +03:00
free_irq ( smi - > irq , smi ) ;
irq_dispose_mapping ( smi - > irq ) ;
2014-11-12 10:41:05 +03:00
return 0 ;
}
static const struct of_device_id ipmi_powernv_match [ ] = {
{ . compatible = " ibm,opal-ipmi " } ,
{ } ,
} ;
static struct platform_driver powernv_ipmi_driver = {
. driver = {
. name = " ipmi-powernv " ,
. of_match_table = ipmi_powernv_match ,
} ,
. probe = ipmi_powernv_probe ,
. remove = ipmi_powernv_remove ,
} ;
module_platform_driver ( powernv_ipmi_driver ) ;
MODULE_DEVICE_TABLE ( of , ipmi_powernv_match ) ;
MODULE_DESCRIPTION ( " powernv IPMI driver " ) ;
MODULE_AUTHOR ( " Jeremy Kerr <jk@ozlabs.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;