2018-02-20 07:30:22 -06:00
// SPDX-License-Identifier: GPL-2.0+
2017-09-12 22:28:49 -05:00
/*
* ipmi_si_hotmod . c
*
* Handling for dynamically adding / removing IPMI devices through
* a module parameter ( and thus sysfs ) .
*/
2018-05-09 08:15:48 -07:00
# define pr_fmt(fmt) "ipmi_hotmod: " fmt
2017-09-12 22:28:49 -05:00
# include <linux/moduleparam.h>
# include <linux/ipmi.h>
2019-02-21 14:23:07 -06:00
# include <linux/atomic.h>
2017-09-12 22:28:49 -05:00
# include "ipmi_si.h"
2019-02-21 14:23:07 -06:00
# include "ipmi_plat_data.h"
2017-09-12 22:28:49 -05:00
2017-11-02 11:19:15 -05:00
static int hotmod_handler ( const char * val , const struct kernel_param * kp ) ;
2017-09-12 22:28:49 -05:00
module_param_call ( hotmod , hotmod_handler , NULL , NULL , 0200 ) ;
2021-04-02 20:43:34 +03:00
MODULE_PARM_DESC ( hotmod ,
" Add and remove interfaces. See Documentation/driver-api/ipmi.rst in the kernel sources for the gory details. " ) ;
2017-09-12 22:28:49 -05:00
/*
* Parms come in as < op1 > [ : op2 [ : op3 . . . ] ] . ops are :
* add | remove , kcs | bt | smic , mem | i / o , < address > [ , < opt1 > [ , < opt2 > [ , . . . ] ] ]
* Options are :
* rsp = < regspacing >
* rsi = < regsize >
* rsh = < regshift >
* irq = < irq >
* ipmb = < ipmb addr >
*/
enum hotmod_op { HM_ADD , HM_REMOVE } ;
struct hotmod_vals {
const char * name ;
const int val ;
} ;
static const struct hotmod_vals hotmod_ops [ ] = {
{ " add " , HM_ADD } ,
{ " remove " , HM_REMOVE } ,
{ NULL }
} ;
static const struct hotmod_vals hotmod_si [ ] = {
{ " kcs " , SI_KCS } ,
{ " smic " , SI_SMIC } ,
{ " bt " , SI_BT } ,
{ NULL }
} ;
static const struct hotmod_vals hotmod_as [ ] = {
{ " mem " , IPMI_MEM_ADDR_SPACE } ,
{ " i/o " , IPMI_IO_ADDR_SPACE } ,
{ NULL }
} ;
2019-02-21 14:23:07 -06:00
static int parse_str ( const struct hotmod_vals * v , unsigned int * val , char * name ,
const char * * curr )
2017-09-12 22:28:49 -05:00
{
char * s ;
int i ;
s = strchr ( * curr , ' , ' ) ;
if ( ! s ) {
2018-05-09 08:15:48 -07:00
pr_warn ( " No hotmod %s given \n " , name ) ;
2017-09-12 22:28:49 -05:00
return - EINVAL ;
}
* s = ' \0 ' ;
s + + ;
for ( i = 0 ; v [ i ] . name ; i + + ) {
if ( strcmp ( * curr , v [ i ] . name ) = = 0 ) {
* val = v [ i ] . val ;
* curr = s ;
return 0 ;
}
}
2018-05-09 08:15:48 -07:00
pr_warn ( " Invalid hotmod %s '%s' \n " , name , * curr ) ;
2017-09-12 22:28:49 -05:00
return - EINVAL ;
}
static int check_hotmod_int_op ( const char * curr , const char * option ,
2019-02-21 14:23:07 -06:00
const char * name , unsigned int * val )
2017-09-12 22:28:49 -05:00
{
char * n ;
if ( strcmp ( curr , name ) = = 0 ) {
if ( ! option ) {
2018-05-09 08:15:48 -07:00
pr_warn ( " No option given for '%s' \n " , curr ) ;
2017-09-12 22:28:49 -05:00
return - EINVAL ;
}
* val = simple_strtoul ( option , & n , 0 ) ;
if ( ( * n ! = ' \0 ' ) | | ( * option = = ' \0 ' ) ) {
2018-05-09 08:15:48 -07:00
pr_warn ( " Bad option given for '%s' \n " , curr ) ;
2017-09-12 22:28:49 -05:00
return - EINVAL ;
}
return 1 ;
}
return 0 ;
}
2019-02-21 14:23:07 -06:00
static int parse_hotmod_str ( const char * curr , enum hotmod_op * op ,
struct ipmi_plat_data * h )
{
char * s , * o ;
int rv ;
unsigned int ival ;
2019-04-24 08:32:02 -05:00
h - > iftype = IPMI_PLAT_IF_SI ;
2019-02-21 14:23:07 -06:00
rv = parse_str ( hotmod_ops , & ival , " operation " , & curr ) ;
if ( rv )
return rv ;
* op = ival ;
rv = parse_str ( hotmod_si , & ival , " interface type " , & curr ) ;
if ( rv )
return rv ;
h - > type = ival ;
rv = parse_str ( hotmod_as , & ival , " address space " , & curr ) ;
if ( rv )
return rv ;
h - > space = ival ;
s = strchr ( curr , ' , ' ) ;
if ( s ) {
* s = ' \0 ' ;
s + + ;
}
rv = kstrtoul ( curr , 0 , & h - > addr ) ;
if ( rv ) {
pr_warn ( " Invalid hotmod address '%s': %d \n " , curr , rv ) ;
return rv ;
}
while ( s ) {
curr = s ;
s = strchr ( curr , ' , ' ) ;
if ( s ) {
* s = ' \0 ' ;
s + + ;
}
o = strchr ( curr , ' = ' ) ;
if ( o ) {
* o = ' \0 ' ;
o + + ;
}
rv = check_hotmod_int_op ( curr , o , " rsp " , & h - > regspacing ) ;
if ( rv < 0 )
return rv ;
else if ( rv )
continue ;
rv = check_hotmod_int_op ( curr , o , " rsi " , & h - > regsize ) ;
if ( rv < 0 )
return rv ;
else if ( rv )
continue ;
rv = check_hotmod_int_op ( curr , o , " rsh " , & h - > regshift ) ;
if ( rv < 0 )
return rv ;
else if ( rv )
continue ;
rv = check_hotmod_int_op ( curr , o , " irq " , & h - > irq ) ;
if ( rv < 0 )
return rv ;
else if ( rv )
continue ;
rv = check_hotmod_int_op ( curr , o , " ipmb " , & h - > slave_addr ) ;
if ( rv < 0 )
return rv ;
else if ( rv )
continue ;
pr_warn ( " Invalid hotmod option '%s' \n " , curr ) ;
return - EINVAL ;
}
h - > addr_source = SI_HOTMOD ;
return 0 ;
}
static atomic_t hotmod_nr ;
2017-11-02 11:19:15 -05:00
static int hotmod_handler ( const char * val , const struct kernel_param * kp )
2017-09-12 22:28:49 -05:00
{
int rv ;
2019-02-21 14:23:07 -06:00
struct ipmi_plat_data h ;
2021-04-02 20:43:32 +03:00
char * str , * curr , * next ;
2017-09-12 22:28:49 -05:00
2021-04-02 20:43:32 +03:00
str = kstrdup ( val , GFP_KERNEL ) ;
2017-09-12 22:28:49 -05:00
if ( ! str )
return - ENOMEM ;
/* Kill any trailing spaces, as we can get a "\n" from echo. */
2021-04-02 20:43:32 +03:00
for ( curr = strstrip ( str ) ; curr ; curr = next ) {
2019-02-21 14:23:07 -06:00
enum hotmod_op op ;
2017-09-12 22:28:49 -05:00
next = strchr ( curr , ' : ' ) ;
if ( next ) {
* next = ' \0 ' ;
next + + ;
}
2019-02-21 14:23:07 -06:00
memset ( & h , 0 , sizeof ( h ) ) ;
rv = parse_hotmod_str ( curr , & op , & h ) ;
2017-09-12 22:28:49 -05:00
if ( rv )
goto out ;
if ( op = = HM_ADD ) {
2019-02-21 14:23:07 -06:00
ipmi_platform_add ( " hotmod-ipmi-si " ,
atomic_inc_return ( & hotmod_nr ) ,
& h ) ;
2017-09-12 22:28:49 -05:00
} else {
2019-02-21 17:21:50 -06:00
struct device * dev ;
dev = ipmi_si_remove_by_data ( h . space , h . type , h . addr ) ;
if ( dev & & dev_is_platform ( dev ) ) {
struct platform_device * pdev ;
pdev = to_platform_device ( dev ) ;
if ( strcmp ( pdev - > name , " hotmod-ipmi-si " ) = = 0 )
platform_device_unregister ( pdev ) ;
}
2021-04-02 20:43:33 +03:00
put_device ( dev ) ;
2017-09-12 22:28:49 -05:00
}
}
2021-04-02 20:43:32 +03:00
rv = strlen ( val ) ;
2017-09-12 22:28:49 -05:00
out :
kfree ( str ) ;
return rv ;
}
2019-02-21 17:21:50 -06:00
void ipmi_si_hotmod_exit ( void )
{
2019-02-21 17:41:47 -06:00
ipmi_remove_platform_device_by_name ( " hotmod-ipmi-si " ) ;
2019-02-21 17:21:50 -06:00
}