xref: /openbmc/linux/drivers/char/ipmi/ipmi_si_hotmod.c (revision d73236383eb1cd4b7b65c33a09f0ed45f6781f40)
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);
2044814ec9SCorey Minyard MODULE_PARM_DESC(hotmod, "Add and remove interfaces.  See"
2144814ec9SCorey Minyard 		 " Documentation/IPMI.txt in the kernel sources for the"
2244814ec9SCorey Minyard 		 " gory details.");
2344814ec9SCorey Minyard 
2444814ec9SCorey Minyard /*
2544814ec9SCorey Minyard  * Parms come in as <op1>[:op2[:op3...]].  ops are:
2644814ec9SCorey Minyard  *   add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]]
2744814ec9SCorey Minyard  * Options are:
2844814ec9SCorey Minyard  *   rsp=<regspacing>
2944814ec9SCorey Minyard  *   rsi=<regsize>
3044814ec9SCorey Minyard  *   rsh=<regshift>
3144814ec9SCorey Minyard  *   irq=<irq>
3244814ec9SCorey Minyard  *   ipmb=<ipmb addr>
3344814ec9SCorey Minyard  */
3444814ec9SCorey Minyard enum hotmod_op { HM_ADD, HM_REMOVE };
3544814ec9SCorey Minyard struct hotmod_vals {
3644814ec9SCorey Minyard 	const char *name;
3744814ec9SCorey Minyard 	const int  val;
3844814ec9SCorey Minyard };
3944814ec9SCorey Minyard 
4044814ec9SCorey Minyard static const struct hotmod_vals hotmod_ops[] = {
4144814ec9SCorey Minyard 	{ "add",	HM_ADD },
4244814ec9SCorey Minyard 	{ "remove",	HM_REMOVE },
4344814ec9SCorey Minyard 	{ NULL }
4444814ec9SCorey Minyard };
4544814ec9SCorey Minyard 
4644814ec9SCorey Minyard static const struct hotmod_vals hotmod_si[] = {
4744814ec9SCorey Minyard 	{ "kcs",	SI_KCS },
4844814ec9SCorey Minyard 	{ "smic",	SI_SMIC },
4944814ec9SCorey Minyard 	{ "bt",		SI_BT },
5044814ec9SCorey Minyard 	{ NULL }
5144814ec9SCorey Minyard };
5244814ec9SCorey Minyard 
5344814ec9SCorey Minyard static const struct hotmod_vals hotmod_as[] = {
5444814ec9SCorey Minyard 	{ "mem",	IPMI_MEM_ADDR_SPACE },
5544814ec9SCorey Minyard 	{ "i/o",	IPMI_IO_ADDR_SPACE },
5644814ec9SCorey Minyard 	{ NULL }
5744814ec9SCorey Minyard };
5844814ec9SCorey Minyard 
593bb8ea40SCorey Minyard static int parse_str(const struct hotmod_vals *v, unsigned int *val, char *name,
603bb8ea40SCorey Minyard 		     const char **curr)
6144814ec9SCorey Minyard {
6244814ec9SCorey Minyard 	char *s;
6344814ec9SCorey Minyard 	int  i;
6444814ec9SCorey Minyard 
6544814ec9SCorey Minyard 	s = strchr(*curr, ',');
6644814ec9SCorey Minyard 	if (!s) {
6725880f7dSJoe Perches 		pr_warn("No hotmod %s given\n", name);
6844814ec9SCorey Minyard 		return -EINVAL;
6944814ec9SCorey Minyard 	}
7044814ec9SCorey Minyard 	*s = '\0';
7144814ec9SCorey Minyard 	s++;
7244814ec9SCorey Minyard 	for (i = 0; v[i].name; i++) {
7344814ec9SCorey Minyard 		if (strcmp(*curr, v[i].name) == 0) {
7444814ec9SCorey Minyard 			*val = v[i].val;
7544814ec9SCorey Minyard 			*curr = s;
7644814ec9SCorey Minyard 			return 0;
7744814ec9SCorey Minyard 		}
7844814ec9SCorey Minyard 	}
7944814ec9SCorey Minyard 
8025880f7dSJoe Perches 	pr_warn("Invalid hotmod %s '%s'\n", name, *curr);
8144814ec9SCorey Minyard 	return -EINVAL;
8244814ec9SCorey Minyard }
8344814ec9SCorey Minyard 
8444814ec9SCorey Minyard static int check_hotmod_int_op(const char *curr, const char *option,
853bb8ea40SCorey Minyard 			       const char *name, unsigned int *val)
8644814ec9SCorey Minyard {
8744814ec9SCorey Minyard 	char *n;
8844814ec9SCorey Minyard 
8944814ec9SCorey Minyard 	if (strcmp(curr, name) == 0) {
9044814ec9SCorey Minyard 		if (!option) {
9125880f7dSJoe Perches 			pr_warn("No option given for '%s'\n", curr);
9244814ec9SCorey Minyard 			return -EINVAL;
9344814ec9SCorey Minyard 		}
9444814ec9SCorey Minyard 		*val = simple_strtoul(option, &n, 0);
9544814ec9SCorey Minyard 		if ((*n != '\0') || (*option == '\0')) {
9625880f7dSJoe Perches 			pr_warn("Bad option given for '%s'\n", curr);
9744814ec9SCorey Minyard 			return -EINVAL;
9844814ec9SCorey Minyard 		}
9944814ec9SCorey Minyard 		return 1;
10044814ec9SCorey Minyard 	}
10144814ec9SCorey Minyard 	return 0;
10244814ec9SCorey Minyard }
10344814ec9SCorey Minyard 
1043bb8ea40SCorey Minyard static int parse_hotmod_str(const char *curr, enum hotmod_op *op,
1053bb8ea40SCorey Minyard 			    struct ipmi_plat_data *h)
10644814ec9SCorey Minyard {
1073bb8ea40SCorey Minyard 	char *s, *o;
10844814ec9SCorey Minyard 	int rv;
1093bb8ea40SCorey Minyard 	unsigned int ival;
11044814ec9SCorey Minyard 
111*d7323638SCorey Minyard 	h->iftype = IPMI_PLAT_IF_SI;
11244814ec9SCorey Minyard 	rv = parse_str(hotmod_ops, &ival, "operation", &curr);
11344814ec9SCorey Minyard 	if (rv)
1143bb8ea40SCorey Minyard 		return rv;
1153bb8ea40SCorey Minyard 	*op = ival;
11644814ec9SCorey Minyard 
11744814ec9SCorey Minyard 	rv = parse_str(hotmod_si, &ival, "interface type", &curr);
11844814ec9SCorey Minyard 	if (rv)
1193bb8ea40SCorey Minyard 		return rv;
1203bb8ea40SCorey Minyard 	h->type = ival;
12144814ec9SCorey Minyard 
122f6296bdcSCorey Minyard 	rv = parse_str(hotmod_as, &ival, "address space", &curr);
12344814ec9SCorey Minyard 	if (rv)
1243bb8ea40SCorey Minyard 		return rv;
1253bb8ea40SCorey Minyard 	h->space = ival;
12644814ec9SCorey Minyard 
12744814ec9SCorey Minyard 	s = strchr(curr, ',');
12844814ec9SCorey Minyard 	if (s) {
12944814ec9SCorey Minyard 		*s = '\0';
13044814ec9SCorey Minyard 		s++;
13144814ec9SCorey Minyard 	}
1323bb8ea40SCorey Minyard 	rv = kstrtoul(curr, 0, &h->addr);
1333bb8ea40SCorey Minyard 	if (rv) {
1343bb8ea40SCorey Minyard 		pr_warn("Invalid hotmod address '%s': %d\n", curr, rv);
1353bb8ea40SCorey Minyard 		return rv;
13644814ec9SCorey Minyard 	}
13744814ec9SCorey Minyard 
13844814ec9SCorey Minyard 	while (s) {
13944814ec9SCorey Minyard 		curr = s;
14044814ec9SCorey Minyard 		s = strchr(curr, ',');
14144814ec9SCorey Minyard 		if (s) {
14244814ec9SCorey Minyard 			*s = '\0';
14344814ec9SCorey Minyard 			s++;
14444814ec9SCorey Minyard 		}
14544814ec9SCorey Minyard 		o = strchr(curr, '=');
14644814ec9SCorey Minyard 		if (o) {
14744814ec9SCorey Minyard 			*o = '\0';
14844814ec9SCorey Minyard 			o++;
14944814ec9SCorey Minyard 		}
1503bb8ea40SCorey Minyard 		rv = check_hotmod_int_op(curr, o, "rsp", &h->regspacing);
15144814ec9SCorey Minyard 		if (rv < 0)
1523bb8ea40SCorey Minyard 			return rv;
15344814ec9SCorey Minyard 		else if (rv)
15444814ec9SCorey Minyard 			continue;
1553bb8ea40SCorey Minyard 		rv = check_hotmod_int_op(curr, o, "rsi", &h->regsize);
15644814ec9SCorey Minyard 		if (rv < 0)
1573bb8ea40SCorey Minyard 			return rv;
15844814ec9SCorey Minyard 		else if (rv)
15944814ec9SCorey Minyard 			continue;
1603bb8ea40SCorey Minyard 		rv = check_hotmod_int_op(curr, o, "rsh", &h->regshift);
16144814ec9SCorey Minyard 		if (rv < 0)
1623bb8ea40SCorey Minyard 			return rv;
16344814ec9SCorey Minyard 		else if (rv)
16444814ec9SCorey Minyard 			continue;
1653bb8ea40SCorey Minyard 		rv = check_hotmod_int_op(curr, o, "irq", &h->irq);
16644814ec9SCorey Minyard 		if (rv < 0)
1673bb8ea40SCorey Minyard 			return rv;
16844814ec9SCorey Minyard 		else if (rv)
16944814ec9SCorey Minyard 			continue;
1703bb8ea40SCorey Minyard 		rv = check_hotmod_int_op(curr, o, "ipmb", &h->slave_addr);
17144814ec9SCorey Minyard 		if (rv < 0)
1723bb8ea40SCorey Minyard 			return rv;
17344814ec9SCorey Minyard 		else if (rv)
17444814ec9SCorey Minyard 			continue;
17544814ec9SCorey Minyard 
17625880f7dSJoe Perches 		pr_warn("Invalid hotmod option '%s'\n", curr);
1773bb8ea40SCorey Minyard 		return -EINVAL;
17844814ec9SCorey Minyard 	}
17944814ec9SCorey Minyard 
1803bb8ea40SCorey Minyard 	h->addr_source = SI_HOTMOD;
1813bb8ea40SCorey Minyard 	return 0;
1823bb8ea40SCorey Minyard }
18344814ec9SCorey Minyard 
1843bb8ea40SCorey Minyard static atomic_t hotmod_nr;
18544814ec9SCorey Minyard 
1863bb8ea40SCorey Minyard static int hotmod_handler(const char *val, const struct kernel_param *kp)
1873bb8ea40SCorey Minyard {
1883bb8ea40SCorey Minyard 	char *str = kstrdup(val, GFP_KERNEL), *curr, *next;
1893bb8ea40SCorey Minyard 	int  rv;
1903bb8ea40SCorey Minyard 	struct ipmi_plat_data h;
19103890359SDan Carpenter 	unsigned int len;
19203890359SDan Carpenter 	int ival;
19344814ec9SCorey Minyard 
1943bb8ea40SCorey Minyard 	if (!str)
1953bb8ea40SCorey Minyard 		return -ENOMEM;
1963bb8ea40SCorey Minyard 
1973bb8ea40SCorey Minyard 	/* Kill any trailing spaces, as we can get a "\n" from echo. */
1983bb8ea40SCorey Minyard 	len = strlen(str);
1993bb8ea40SCorey Minyard 	ival = len - 1;
2003bb8ea40SCorey Minyard 	while ((ival >= 0) && isspace(str[ival])) {
2013bb8ea40SCorey Minyard 		str[ival] = '\0';
2023bb8ea40SCorey Minyard 		ival--;
2033bb8ea40SCorey Minyard 	}
2043bb8ea40SCorey Minyard 
2053bb8ea40SCorey Minyard 	for (curr = str; curr; curr = next) {
2063bb8ea40SCorey Minyard 		enum hotmod_op op;
2073bb8ea40SCorey Minyard 
2083bb8ea40SCorey Minyard 		next = strchr(curr, ':');
2093bb8ea40SCorey Minyard 		if (next) {
2103bb8ea40SCorey Minyard 			*next = '\0';
2113bb8ea40SCorey Minyard 			next++;
2123bb8ea40SCorey Minyard 		}
2133bb8ea40SCorey Minyard 
2143bb8ea40SCorey Minyard 		memset(&h, 0, sizeof(h));
2153bb8ea40SCorey Minyard 		rv = parse_hotmod_str(curr, &op, &h);
21644814ec9SCorey Minyard 		if (rv)
21744814ec9SCorey Minyard 			goto out;
2183bb8ea40SCorey Minyard 
2193bb8ea40SCorey Minyard 		if (op == HM_ADD) {
2203bb8ea40SCorey Minyard 			ipmi_platform_add("hotmod-ipmi-si",
2213bb8ea40SCorey Minyard 					  atomic_inc_return(&hotmod_nr),
2223bb8ea40SCorey Minyard 					  &h);
22344814ec9SCorey Minyard 		} else {
224bdb57b7bSCorey Minyard 			struct device *dev;
225bdb57b7bSCorey Minyard 
226bdb57b7bSCorey Minyard 			dev = ipmi_si_remove_by_data(h.space, h.type, h.addr);
227bdb57b7bSCorey Minyard 			if (dev && dev_is_platform(dev)) {
228bdb57b7bSCorey Minyard 				struct platform_device *pdev;
229bdb57b7bSCorey Minyard 
230bdb57b7bSCorey Minyard 				pdev = to_platform_device(dev);
231bdb57b7bSCorey Minyard 				if (strcmp(pdev->name, "hotmod-ipmi-si") == 0)
232bdb57b7bSCorey Minyard 					platform_device_unregister(pdev);
233bdb57b7bSCorey Minyard 			}
234bdb57b7bSCorey Minyard 			if (dev)
235bdb57b7bSCorey Minyard 				put_device(dev);
23644814ec9SCorey Minyard 		}
23744814ec9SCorey Minyard 	}
23844814ec9SCorey Minyard 	rv = len;
23944814ec9SCorey Minyard out:
24044814ec9SCorey Minyard 	kfree(str);
24144814ec9SCorey Minyard 	return rv;
24244814ec9SCorey Minyard }
243bdb57b7bSCorey Minyard 
244bdb57b7bSCorey Minyard void ipmi_si_hotmod_exit(void)
245bdb57b7bSCorey Minyard {
246e17c6571SCorey Minyard 	ipmi_remove_platform_device_by_name("hotmod-ipmi-si");
247bdb57b7bSCorey Minyard }
248