1243ac210SCorey Minyard // SPDX-License-Identifier: GPL-2.0+
244814ec9SCorey Minyard /*
344814ec9SCorey Minyard * ipmi_si_hotmod.c
444814ec9SCorey Minyard *
544814ec9SCorey Minyard * Handling for dynamically adding/removing IPMI devices through
644814ec9SCorey Minyard * a module parameter (and thus sysfs).
744814ec9SCorey Minyard */
825880f7dSJoe Perches
925880f7dSJoe Perches #define pr_fmt(fmt) "ipmi_hotmod: " fmt
1025880f7dSJoe Perches
1144814ec9SCorey Minyard #include <linux/moduleparam.h>
1244814ec9SCorey Minyard #include <linux/ipmi.h>
133bb8ea40SCorey Minyard #include <linux/atomic.h>
1444814ec9SCorey Minyard #include "ipmi_si.h"
153bb8ea40SCorey Minyard #include "ipmi_plat_data.h"
1644814ec9SCorey Minyard
176297fabdSCorey Minyard static int hotmod_handler(const char *val, const struct kernel_param *kp);
1844814ec9SCorey Minyard
1944814ec9SCorey Minyard module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200);
20*07cbd87bSAndy Shevchenko MODULE_PARM_DESC(hotmod,
21*07cbd87bSAndy Shevchenko "Add and remove interfaces. See Documentation/driver-api/ipmi.rst in the kernel sources for the gory details.");
2244814ec9SCorey Minyard
2344814ec9SCorey Minyard /*
2444814ec9SCorey Minyard * Parms come in as <op1>[:op2[:op3...]]. ops are:
2544814ec9SCorey Minyard * add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]]
2644814ec9SCorey Minyard * Options are:
2744814ec9SCorey Minyard * rsp=<regspacing>
2844814ec9SCorey Minyard * rsi=<regsize>
2944814ec9SCorey Minyard * rsh=<regshift>
3044814ec9SCorey Minyard * irq=<irq>
3144814ec9SCorey Minyard * ipmb=<ipmb addr>
3244814ec9SCorey Minyard */
3344814ec9SCorey Minyard enum hotmod_op { HM_ADD, HM_REMOVE };
3444814ec9SCorey Minyard struct hotmod_vals {
3544814ec9SCorey Minyard const char *name;
3644814ec9SCorey Minyard const int val;
3744814ec9SCorey Minyard };
3844814ec9SCorey Minyard
3944814ec9SCorey Minyard static const struct hotmod_vals hotmod_ops[] = {
4044814ec9SCorey Minyard { "add", HM_ADD },
4144814ec9SCorey Minyard { "remove", HM_REMOVE },
4244814ec9SCorey Minyard { NULL }
4344814ec9SCorey Minyard };
4444814ec9SCorey Minyard
4544814ec9SCorey Minyard static const struct hotmod_vals hotmod_si[] = {
4644814ec9SCorey Minyard { "kcs", SI_KCS },
4744814ec9SCorey Minyard { "smic", SI_SMIC },
4844814ec9SCorey Minyard { "bt", SI_BT },
4944814ec9SCorey Minyard { NULL }
5044814ec9SCorey Minyard };
5144814ec9SCorey Minyard
5244814ec9SCorey Minyard static const struct hotmod_vals hotmod_as[] = {
5344814ec9SCorey Minyard { "mem", IPMI_MEM_ADDR_SPACE },
5444814ec9SCorey Minyard { "i/o", IPMI_IO_ADDR_SPACE },
5544814ec9SCorey Minyard { NULL }
5644814ec9SCorey Minyard };
5744814ec9SCorey Minyard
parse_str(const struct hotmod_vals * v,unsigned int * val,char * name,const char ** curr)583bb8ea40SCorey Minyard static int parse_str(const struct hotmod_vals *v, unsigned int *val, char *name,
593bb8ea40SCorey Minyard const char **curr)
6044814ec9SCorey Minyard {
6144814ec9SCorey Minyard char *s;
6244814ec9SCorey Minyard int i;
6344814ec9SCorey Minyard
6444814ec9SCorey Minyard s = strchr(*curr, ',');
6544814ec9SCorey Minyard if (!s) {
6625880f7dSJoe Perches pr_warn("No hotmod %s given\n", name);
6744814ec9SCorey Minyard return -EINVAL;
6844814ec9SCorey Minyard }
6944814ec9SCorey Minyard *s = '\0';
7044814ec9SCorey Minyard s++;
7144814ec9SCorey Minyard for (i = 0; v[i].name; i++) {
7244814ec9SCorey Minyard if (strcmp(*curr, v[i].name) == 0) {
7344814ec9SCorey Minyard *val = v[i].val;
7444814ec9SCorey Minyard *curr = s;
7544814ec9SCorey Minyard return 0;
7644814ec9SCorey Minyard }
7744814ec9SCorey Minyard }
7844814ec9SCorey Minyard
7925880f7dSJoe Perches pr_warn("Invalid hotmod %s '%s'\n", name, *curr);
8044814ec9SCorey Minyard return -EINVAL;
8144814ec9SCorey Minyard }
8244814ec9SCorey Minyard
check_hotmod_int_op(const char * curr,const char * option,const char * name,unsigned int * val)8344814ec9SCorey Minyard static int check_hotmod_int_op(const char *curr, const char *option,
843bb8ea40SCorey Minyard const char *name, unsigned int *val)
8544814ec9SCorey Minyard {
8644814ec9SCorey Minyard char *n;
8744814ec9SCorey Minyard
8844814ec9SCorey Minyard if (strcmp(curr, name) == 0) {
8944814ec9SCorey Minyard if (!option) {
9025880f7dSJoe Perches pr_warn("No option given for '%s'\n", curr);
9144814ec9SCorey Minyard return -EINVAL;
9244814ec9SCorey Minyard }
9344814ec9SCorey Minyard *val = simple_strtoul(option, &n, 0);
9444814ec9SCorey Minyard if ((*n != '\0') || (*option == '\0')) {
9525880f7dSJoe Perches pr_warn("Bad option given for '%s'\n", curr);
9644814ec9SCorey Minyard return -EINVAL;
9744814ec9SCorey Minyard }
9844814ec9SCorey Minyard return 1;
9944814ec9SCorey Minyard }
10044814ec9SCorey Minyard return 0;
10144814ec9SCorey Minyard }
10244814ec9SCorey Minyard
parse_hotmod_str(const char * curr,enum hotmod_op * op,struct ipmi_plat_data * h)1033bb8ea40SCorey Minyard static int parse_hotmod_str(const char *curr, enum hotmod_op *op,
1043bb8ea40SCorey Minyard struct ipmi_plat_data *h)
10544814ec9SCorey Minyard {
1063bb8ea40SCorey Minyard char *s, *o;
10744814ec9SCorey Minyard int rv;
1083bb8ea40SCorey Minyard unsigned int ival;
10944814ec9SCorey Minyard
110d7323638SCorey Minyard h->iftype = IPMI_PLAT_IF_SI;
11144814ec9SCorey Minyard rv = parse_str(hotmod_ops, &ival, "operation", &curr);
11244814ec9SCorey Minyard if (rv)
1133bb8ea40SCorey Minyard return rv;
1143bb8ea40SCorey Minyard *op = ival;
11544814ec9SCorey Minyard
11644814ec9SCorey Minyard rv = parse_str(hotmod_si, &ival, "interface type", &curr);
11744814ec9SCorey Minyard if (rv)
1183bb8ea40SCorey Minyard return rv;
1193bb8ea40SCorey Minyard h->type = ival;
12044814ec9SCorey Minyard
121f6296bdcSCorey Minyard rv = parse_str(hotmod_as, &ival, "address space", &curr);
12244814ec9SCorey Minyard if (rv)
1233bb8ea40SCorey Minyard return rv;
1243bb8ea40SCorey Minyard h->space = ival;
12544814ec9SCorey Minyard
12644814ec9SCorey Minyard s = strchr(curr, ',');
12744814ec9SCorey Minyard if (s) {
12844814ec9SCorey Minyard *s = '\0';
12944814ec9SCorey Minyard s++;
13044814ec9SCorey Minyard }
1313bb8ea40SCorey Minyard rv = kstrtoul(curr, 0, &h->addr);
1323bb8ea40SCorey Minyard if (rv) {
1333bb8ea40SCorey Minyard pr_warn("Invalid hotmod address '%s': %d\n", curr, rv);
1343bb8ea40SCorey Minyard return rv;
13544814ec9SCorey Minyard }
13644814ec9SCorey Minyard
13744814ec9SCorey Minyard while (s) {
13844814ec9SCorey Minyard curr = s;
13944814ec9SCorey Minyard s = strchr(curr, ',');
14044814ec9SCorey Minyard if (s) {
14144814ec9SCorey Minyard *s = '\0';
14244814ec9SCorey Minyard s++;
14344814ec9SCorey Minyard }
14444814ec9SCorey Minyard o = strchr(curr, '=');
14544814ec9SCorey Minyard if (o) {
14644814ec9SCorey Minyard *o = '\0';
14744814ec9SCorey Minyard o++;
14844814ec9SCorey Minyard }
1493bb8ea40SCorey Minyard rv = check_hotmod_int_op(curr, o, "rsp", &h->regspacing);
15044814ec9SCorey Minyard if (rv < 0)
1513bb8ea40SCorey Minyard return rv;
15244814ec9SCorey Minyard else if (rv)
15344814ec9SCorey Minyard continue;
1543bb8ea40SCorey Minyard rv = check_hotmod_int_op(curr, o, "rsi", &h->regsize);
15544814ec9SCorey Minyard if (rv < 0)
1563bb8ea40SCorey Minyard return rv;
15744814ec9SCorey Minyard else if (rv)
15844814ec9SCorey Minyard continue;
1593bb8ea40SCorey Minyard rv = check_hotmod_int_op(curr, o, "rsh", &h->regshift);
16044814ec9SCorey Minyard if (rv < 0)
1613bb8ea40SCorey Minyard return rv;
16244814ec9SCorey Minyard else if (rv)
16344814ec9SCorey Minyard continue;
1643bb8ea40SCorey Minyard rv = check_hotmod_int_op(curr, o, "irq", &h->irq);
16544814ec9SCorey Minyard if (rv < 0)
1663bb8ea40SCorey Minyard return rv;
16744814ec9SCorey Minyard else if (rv)
16844814ec9SCorey Minyard continue;
1693bb8ea40SCorey Minyard rv = check_hotmod_int_op(curr, o, "ipmb", &h->slave_addr);
17044814ec9SCorey Minyard if (rv < 0)
1713bb8ea40SCorey Minyard return rv;
17244814ec9SCorey Minyard else if (rv)
17344814ec9SCorey Minyard continue;
17444814ec9SCorey Minyard
17525880f7dSJoe Perches pr_warn("Invalid hotmod option '%s'\n", curr);
1763bb8ea40SCorey Minyard return -EINVAL;
17744814ec9SCorey Minyard }
17844814ec9SCorey Minyard
1793bb8ea40SCorey Minyard h->addr_source = SI_HOTMOD;
1803bb8ea40SCorey Minyard return 0;
1813bb8ea40SCorey Minyard }
18244814ec9SCorey Minyard
1833bb8ea40SCorey Minyard static atomic_t hotmod_nr;
18444814ec9SCorey Minyard
hotmod_handler(const char * val,const struct kernel_param * kp)1853bb8ea40SCorey Minyard static int hotmod_handler(const char *val, const struct kernel_param *kp)
1863bb8ea40SCorey Minyard {
1873bb8ea40SCorey Minyard int rv;
1883bb8ea40SCorey Minyard struct ipmi_plat_data h;
189d14ce8c7SAndy Shevchenko char *str, *curr, *next;
19044814ec9SCorey Minyard
191d14ce8c7SAndy Shevchenko str = kstrdup(val, GFP_KERNEL);
1923bb8ea40SCorey Minyard if (!str)
1933bb8ea40SCorey Minyard return -ENOMEM;
1943bb8ea40SCorey Minyard
1953bb8ea40SCorey Minyard /* Kill any trailing spaces, as we can get a "\n" from echo. */
196d14ce8c7SAndy Shevchenko for (curr = strstrip(str); curr; curr = next) {
1973bb8ea40SCorey Minyard enum hotmod_op op;
1983bb8ea40SCorey Minyard
1993bb8ea40SCorey Minyard next = strchr(curr, ':');
2003bb8ea40SCorey Minyard if (next) {
2013bb8ea40SCorey Minyard *next = '\0';
2023bb8ea40SCorey Minyard next++;
2033bb8ea40SCorey Minyard }
2043bb8ea40SCorey Minyard
2053bb8ea40SCorey Minyard memset(&h, 0, sizeof(h));
2063bb8ea40SCorey Minyard rv = parse_hotmod_str(curr, &op, &h);
20744814ec9SCorey Minyard if (rv)
20844814ec9SCorey Minyard goto out;
2093bb8ea40SCorey Minyard
2103bb8ea40SCorey Minyard if (op == HM_ADD) {
2113bb8ea40SCorey Minyard ipmi_platform_add("hotmod-ipmi-si",
2123bb8ea40SCorey Minyard atomic_inc_return(&hotmod_nr),
2133bb8ea40SCorey Minyard &h);
21444814ec9SCorey Minyard } else {
215bdb57b7bSCorey Minyard struct device *dev;
216bdb57b7bSCorey Minyard
217bdb57b7bSCorey Minyard dev = ipmi_si_remove_by_data(h.space, h.type, h.addr);
218bdb57b7bSCorey Minyard if (dev && dev_is_platform(dev)) {
219bdb57b7bSCorey Minyard struct platform_device *pdev;
220bdb57b7bSCorey Minyard
221bdb57b7bSCorey Minyard pdev = to_platform_device(dev);
222bdb57b7bSCorey Minyard if (strcmp(pdev->name, "hotmod-ipmi-si") == 0)
223bdb57b7bSCorey Minyard platform_device_unregister(pdev);
224bdb57b7bSCorey Minyard }
225bdb57b7bSCorey Minyard put_device(dev);
22644814ec9SCorey Minyard }
22744814ec9SCorey Minyard }
228d14ce8c7SAndy Shevchenko rv = strlen(val);
22944814ec9SCorey Minyard out:
23044814ec9SCorey Minyard kfree(str);
23144814ec9SCorey Minyard return rv;
23244814ec9SCorey Minyard }
233bdb57b7bSCorey Minyard
ipmi_si_hotmod_exit(void)234bdb57b7bSCorey Minyard void ipmi_si_hotmod_exit(void)
235bdb57b7bSCorey Minyard {
236e17c6571SCorey Minyard ipmi_remove_platform_device_by_name("hotmod-ipmi-si");
237bdb57b7bSCorey Minyard }
238