123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- // 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 "ipmi_si.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, int *val, char *name,
- 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, 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 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("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("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;
- }
|