| // 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/driver-api/ipmi.rst 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; | |
| h->iftype = IPMI_PLAT_IF_SI; | |
| 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) | |
| { | |
| int rv; | |
| struct ipmi_plat_data h; | |
| char *str, *curr, *next; | |
| str = kstrdup(val, GFP_KERNEL); | |
| if (!str) | |
| return -ENOMEM; | |
| /* Kill any trailing spaces, as we can get a "\n" from echo. */ | |
| for (curr = strstrip(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); | |
| } | |
| put_device(dev); | |
| } | |
| } | |
| rv = strlen(val); | |
| out: | |
| kfree(str); | |
| return rv; | |
| } | |
| void ipmi_si_hotmod_exit(void) | |
| { | |
| ipmi_remove_platform_device_by_name("hotmod-ipmi-si"); | |
| } |