123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- // 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");
- }
|