| // 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; |
| } |