6db4831e98
Android 14
244 lines
4.8 KiB
C
244 lines
4.8 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* ipmi_si_hotmod.c
|
|
*
|
|
* Handling for dynamically adding/removing IPMI devices through
|
|
* a module parameter (and thus sysfs).
|
|
*/
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/ipmi.h>
|
|
#include "ipmi_si.h"
|
|
|
|
#define PFX "ipmi_hotmod: "
|
|
|
|
static int hotmod_handler(const char *val, const struct kernel_param *kp);
|
|
|
|
module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200);
|
|
MODULE_PARM_DESC(hotmod, "Add and remove interfaces. See"
|
|
" Documentation/IPMI.txt in the kernel sources for the"
|
|
" gory details.");
|
|
|
|
/*
|
|
* 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 }
|
|
};
|
|
|
|
static int parse_str(const struct hotmod_vals *v, int *val, char *name,
|
|
char **curr)
|
|
{
|
|
char *s;
|
|
int i;
|
|
|
|
s = strchr(*curr, ',');
|
|
if (!s) {
|
|
pr_warn(PFX "No hotmod %s given.\n", name);
|
|
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;
|
|
}
|
|
}
|
|
|
|
pr_warn(PFX "Invalid hotmod %s '%s'\n", name, *curr);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int check_hotmod_int_op(const char *curr, const char *option,
|
|
const char *name, int *val)
|
|
{
|
|
char *n;
|
|
|
|
if (strcmp(curr, name) == 0) {
|
|
if (!option) {
|
|
pr_warn(PFX "No option given for '%s'\n", curr);
|
|
return -EINVAL;
|
|
}
|
|
*val = simple_strtoul(option, &n, 0);
|
|
if ((*n != '\0') || (*option == '\0')) {
|
|
pr_warn(PFX "Bad option given for '%s'\n", curr);
|
|
return -EINVAL;
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int hotmod_handler(const char *val, const struct kernel_param *kp)
|
|
{
|
|
char *str = kstrdup(val, GFP_KERNEL);
|
|
int rv;
|
|
char *next, *curr, *s, *n, *o;
|
|
enum hotmod_op op;
|
|
enum si_type si_type;
|
|
int addr_space;
|
|
unsigned long addr;
|
|
int regspacing;
|
|
int regsize;
|
|
int regshift;
|
|
int irq;
|
|
int ipmb;
|
|
int ival;
|
|
int len;
|
|
|
|
if (!str)
|
|
return -ENOMEM;
|
|
|
|
/* Kill any trailing spaces, as we can get a "\n" from echo. */
|
|
len = strlen(str);
|
|
ival = len - 1;
|
|
while ((ival >= 0) && isspace(str[ival])) {
|
|
str[ival] = '\0';
|
|
ival--;
|
|
}
|
|
|
|
for (curr = str; curr; curr = next) {
|
|
regspacing = 1;
|
|
regsize = 1;
|
|
regshift = 0;
|
|
irq = 0;
|
|
ipmb = 0; /* Choose the default if not specified */
|
|
|
|
next = strchr(curr, ':');
|
|
if (next) {
|
|
*next = '\0';
|
|
next++;
|
|
}
|
|
|
|
rv = parse_str(hotmod_ops, &ival, "operation", &curr);
|
|
if (rv)
|
|
break;
|
|
op = ival;
|
|
|
|
rv = parse_str(hotmod_si, &ival, "interface type", &curr);
|
|
if (rv)
|
|
break;
|
|
si_type = ival;
|
|
|
|
rv = parse_str(hotmod_as, &addr_space, "address space", &curr);
|
|
if (rv)
|
|
break;
|
|
|
|
s = strchr(curr, ',');
|
|
if (s) {
|
|
*s = '\0';
|
|
s++;
|
|
}
|
|
addr = simple_strtoul(curr, &n, 0);
|
|
if ((*n != '\0') || (*curr == '\0')) {
|
|
pr_warn(PFX "Invalid hotmod address '%s'\n", curr);
|
|
break;
|
|
}
|
|
|
|
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", ®spacing);
|
|
if (rv < 0)
|
|
goto out;
|
|
else if (rv)
|
|
continue;
|
|
rv = check_hotmod_int_op(curr, o, "rsi", ®size);
|
|
if (rv < 0)
|
|
goto out;
|
|
else if (rv)
|
|
continue;
|
|
rv = check_hotmod_int_op(curr, o, "rsh", ®shift);
|
|
if (rv < 0)
|
|
goto out;
|
|
else if (rv)
|
|
continue;
|
|
rv = check_hotmod_int_op(curr, o, "irq", &irq);
|
|
if (rv < 0)
|
|
goto out;
|
|
else if (rv)
|
|
continue;
|
|
rv = check_hotmod_int_op(curr, o, "ipmb", &ipmb);
|
|
if (rv < 0)
|
|
goto out;
|
|
else if (rv)
|
|
continue;
|
|
|
|
rv = -EINVAL;
|
|
pr_warn(PFX "Invalid hotmod option '%s'\n", curr);
|
|
goto out;
|
|
}
|
|
|
|
if (op == HM_ADD) {
|
|
struct si_sm_io io;
|
|
|
|
memset(&io, 0, sizeof(io));
|
|
io.addr_source = SI_HOTMOD;
|
|
io.si_type = si_type;
|
|
io.addr_data = addr;
|
|
io.addr_type = addr_space;
|
|
|
|
io.addr = NULL;
|
|
io.regspacing = regspacing;
|
|
if (!io.regspacing)
|
|
io.regspacing = DEFAULT_REGSPACING;
|
|
io.regsize = regsize;
|
|
if (!io.regsize)
|
|
io.regsize = DEFAULT_REGSIZE;
|
|
io.regshift = regshift;
|
|
io.irq = irq;
|
|
if (io.irq)
|
|
io.irq_setup = ipmi_std_irq_setup;
|
|
io.slave_addr = ipmb;
|
|
|
|
rv = ipmi_si_add_smi(&io);
|
|
if (rv)
|
|
goto out;
|
|
} else {
|
|
ipmi_si_remove_by_data(addr_space, si_type, addr);
|
|
}
|
|
}
|
|
rv = len;
|
|
out:
|
|
kfree(str);
|
|
return rv;
|
|
}
|