| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * ipmi_si_hotmod.c |
| * |
| * Handling for dynamically adding/removing IPMI devices through |
| * a module parameter (and thus sysfs). |
| */ |
| |
| #define pr_fmt(fmt) "ipmi_hotmod: " fmt |
| |
| #include <linux/moduleparam.h> |
| #include <linux/ipmi.h> |
| #include <linux/atomic.h> |
| #include "ipmi_si.h" |
| #include "ipmi_plat_data.h" |
| |
| 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, unsigned int *val, char *name, |
| const char **curr) |
| { |
| char *s; |
| int i; |
| |
| s = strchr(*curr, ','); |
| if (!s) { |
| pr_warn("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("Invalid hotmod %s '%s'\n", name, *curr); |
| return -EINVAL; |
| } |
| |
| static int check_hotmod_int_op(const char *curr, const char *option, |
| const char *name, unsigned int *val) |
| { |
| char *n; |
| |
| if (strcmp(curr, name) == 0) { |
| if (!option) { |
| pr_warn("No option given for '%s'\n", curr); |
| return -EINVAL; |
| } |
| *val = simple_strtoul(option, &n, 0); |
| if ((*n != '\0') || (*option == '\0')) { |
| pr_warn("Bad option given for '%s'\n", curr); |
| return -EINVAL; |
| } |
| return 1; |
| } |
| return 0; |
| } |
| |
| 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; |
| |
| 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; |
| |
| static int hotmod_handler(const char *val, const struct kernel_param *kp) |
| { |
| char *str = kstrdup(val, GFP_KERNEL), *curr, *next; |
| int rv; |
| struct ipmi_plat_data h; |
| unsigned int len; |
| int ival; |
| |
| 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) { |
| enum hotmod_op op; |
| |
| next = strchr(curr, ':'); |
| if (next) { |
| *next = '\0'; |
| next++; |
| } |
| |
| memset(&h, 0, sizeof(h)); |
| rv = parse_hotmod_str(curr, &op, &h); |
| if (rv) |
| goto out; |
| |
| if (op == HM_ADD) { |
| ipmi_platform_add("hotmod-ipmi-si", |
| atomic_inc_return(&hotmod_nr), |
| &h); |
| } else { |
| 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); |
| } |
| if (dev) |
| put_device(dev); |
| } |
| } |
| rv = len; |
| out: |
| kfree(str); |
| return rv; |
| } |
| |
| void ipmi_si_hotmod_exit(void) |
| { |
| ipmi_remove_platform_device_by_name("hotmod-ipmi-si"); |
| } |