xref: /openbmc/linux/drivers/char/ipmi/ipmi_si_hotmod.c (revision 25880f7d61a4f2fdda29cadae55985431df82c5c)
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  */
8*25880f7dSJoe Perches 
9*25880f7dSJoe Perches #define pr_fmt(fmt) "ipmi_hotmod: " fmt
10*25880f7dSJoe Perches 
1144814ec9SCorey Minyard #include <linux/moduleparam.h>
1244814ec9SCorey Minyard #include <linux/ipmi.h>
1344814ec9SCorey Minyard #include "ipmi_si.h"
1444814ec9SCorey Minyard 
156297fabdSCorey Minyard static int hotmod_handler(const char *val, const struct kernel_param *kp);
1644814ec9SCorey Minyard 
1744814ec9SCorey Minyard module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200);
1844814ec9SCorey Minyard MODULE_PARM_DESC(hotmod, "Add and remove interfaces.  See"
1944814ec9SCorey Minyard 		 " Documentation/IPMI.txt in the kernel sources for the"
2044814ec9SCorey Minyard 		 " gory details.");
2144814ec9SCorey Minyard 
2244814ec9SCorey Minyard /*
2344814ec9SCorey Minyard  * Parms come in as <op1>[:op2[:op3...]].  ops are:
2444814ec9SCorey Minyard  *   add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]]
2544814ec9SCorey Minyard  * Options are:
2644814ec9SCorey Minyard  *   rsp=<regspacing>
2744814ec9SCorey Minyard  *   rsi=<regsize>
2844814ec9SCorey Minyard  *   rsh=<regshift>
2944814ec9SCorey Minyard  *   irq=<irq>
3044814ec9SCorey Minyard  *   ipmb=<ipmb addr>
3144814ec9SCorey Minyard  */
3244814ec9SCorey Minyard enum hotmod_op { HM_ADD, HM_REMOVE };
3344814ec9SCorey Minyard struct hotmod_vals {
3444814ec9SCorey Minyard 	const char *name;
3544814ec9SCorey Minyard 	const int  val;
3644814ec9SCorey Minyard };
3744814ec9SCorey Minyard 
3844814ec9SCorey Minyard static const struct hotmod_vals hotmod_ops[] = {
3944814ec9SCorey Minyard 	{ "add",	HM_ADD },
4044814ec9SCorey Minyard 	{ "remove",	HM_REMOVE },
4144814ec9SCorey Minyard 	{ NULL }
4244814ec9SCorey Minyard };
4344814ec9SCorey Minyard 
4444814ec9SCorey Minyard static const struct hotmod_vals hotmod_si[] = {
4544814ec9SCorey Minyard 	{ "kcs",	SI_KCS },
4644814ec9SCorey Minyard 	{ "smic",	SI_SMIC },
4744814ec9SCorey Minyard 	{ "bt",		SI_BT },
4844814ec9SCorey Minyard 	{ NULL }
4944814ec9SCorey Minyard };
5044814ec9SCorey Minyard 
5144814ec9SCorey Minyard static const struct hotmod_vals hotmod_as[] = {
5244814ec9SCorey Minyard 	{ "mem",	IPMI_MEM_ADDR_SPACE },
5344814ec9SCorey Minyard 	{ "i/o",	IPMI_IO_ADDR_SPACE },
5444814ec9SCorey Minyard 	{ NULL }
5544814ec9SCorey Minyard };
5644814ec9SCorey Minyard 
5744814ec9SCorey Minyard static int parse_str(const struct hotmod_vals *v, int *val, char *name,
5844814ec9SCorey Minyard 		     char **curr)
5944814ec9SCorey Minyard {
6044814ec9SCorey Minyard 	char *s;
6144814ec9SCorey Minyard 	int  i;
6244814ec9SCorey Minyard 
6344814ec9SCorey Minyard 	s = strchr(*curr, ',');
6444814ec9SCorey Minyard 	if (!s) {
65*25880f7dSJoe Perches 		pr_warn("No hotmod %s given\n", name);
6644814ec9SCorey Minyard 		return -EINVAL;
6744814ec9SCorey Minyard 	}
6844814ec9SCorey Minyard 	*s = '\0';
6944814ec9SCorey Minyard 	s++;
7044814ec9SCorey Minyard 	for (i = 0; v[i].name; i++) {
7144814ec9SCorey Minyard 		if (strcmp(*curr, v[i].name) == 0) {
7244814ec9SCorey Minyard 			*val = v[i].val;
7344814ec9SCorey Minyard 			*curr = s;
7444814ec9SCorey Minyard 			return 0;
7544814ec9SCorey Minyard 		}
7644814ec9SCorey Minyard 	}
7744814ec9SCorey Minyard 
78*25880f7dSJoe Perches 	pr_warn("Invalid hotmod %s '%s'\n", name, *curr);
7944814ec9SCorey Minyard 	return -EINVAL;
8044814ec9SCorey Minyard }
8144814ec9SCorey Minyard 
8244814ec9SCorey Minyard static int check_hotmod_int_op(const char *curr, const char *option,
8344814ec9SCorey Minyard 			       const char *name, int *val)
8444814ec9SCorey Minyard {
8544814ec9SCorey Minyard 	char *n;
8644814ec9SCorey Minyard 
8744814ec9SCorey Minyard 	if (strcmp(curr, name) == 0) {
8844814ec9SCorey Minyard 		if (!option) {
89*25880f7dSJoe Perches 			pr_warn("No option given for '%s'\n", curr);
9044814ec9SCorey Minyard 			return -EINVAL;
9144814ec9SCorey Minyard 		}
9244814ec9SCorey Minyard 		*val = simple_strtoul(option, &n, 0);
9344814ec9SCorey Minyard 		if ((*n != '\0') || (*option == '\0')) {
94*25880f7dSJoe Perches 			pr_warn("Bad option given for '%s'\n", curr);
9544814ec9SCorey Minyard 			return -EINVAL;
9644814ec9SCorey Minyard 		}
9744814ec9SCorey Minyard 		return 1;
9844814ec9SCorey Minyard 	}
9944814ec9SCorey Minyard 	return 0;
10044814ec9SCorey Minyard }
10144814ec9SCorey Minyard 
1026297fabdSCorey Minyard static int hotmod_handler(const char *val, const struct kernel_param *kp)
10344814ec9SCorey Minyard {
10444814ec9SCorey Minyard 	char *str = kstrdup(val, GFP_KERNEL);
10544814ec9SCorey Minyard 	int  rv;
10644814ec9SCorey Minyard 	char *next, *curr, *s, *n, *o;
10744814ec9SCorey Minyard 	enum hotmod_op op;
10844814ec9SCorey Minyard 	enum si_type si_type;
10944814ec9SCorey Minyard 	int  addr_space;
11044814ec9SCorey Minyard 	unsigned long addr;
11144814ec9SCorey Minyard 	int regspacing;
11244814ec9SCorey Minyard 	int regsize;
11344814ec9SCorey Minyard 	int regshift;
11444814ec9SCorey Minyard 	int irq;
11544814ec9SCorey Minyard 	int ipmb;
11644814ec9SCorey Minyard 	int ival;
11744814ec9SCorey Minyard 	int len;
11844814ec9SCorey Minyard 
11944814ec9SCorey Minyard 	if (!str)
12044814ec9SCorey Minyard 		return -ENOMEM;
12144814ec9SCorey Minyard 
12244814ec9SCorey Minyard 	/* Kill any trailing spaces, as we can get a "\n" from echo. */
12344814ec9SCorey Minyard 	len = strlen(str);
12444814ec9SCorey Minyard 	ival = len - 1;
12544814ec9SCorey Minyard 	while ((ival >= 0) && isspace(str[ival])) {
12644814ec9SCorey Minyard 		str[ival] = '\0';
12744814ec9SCorey Minyard 		ival--;
12844814ec9SCorey Minyard 	}
12944814ec9SCorey Minyard 
13044814ec9SCorey Minyard 	for (curr = str; curr; curr = next) {
13144814ec9SCorey Minyard 		regspacing = 1;
13244814ec9SCorey Minyard 		regsize = 1;
13344814ec9SCorey Minyard 		regshift = 0;
13444814ec9SCorey Minyard 		irq = 0;
13544814ec9SCorey Minyard 		ipmb = 0; /* Choose the default if not specified */
13644814ec9SCorey Minyard 
13744814ec9SCorey Minyard 		next = strchr(curr, ':');
13844814ec9SCorey Minyard 		if (next) {
13944814ec9SCorey Minyard 			*next = '\0';
14044814ec9SCorey Minyard 			next++;
14144814ec9SCorey Minyard 		}
14244814ec9SCorey Minyard 
14344814ec9SCorey Minyard 		rv = parse_str(hotmod_ops, &ival, "operation", &curr);
14444814ec9SCorey Minyard 		if (rv)
14544814ec9SCorey Minyard 			break;
14644814ec9SCorey Minyard 		op = ival;
14744814ec9SCorey Minyard 
14844814ec9SCorey Minyard 		rv = parse_str(hotmod_si, &ival, "interface type", &curr);
14944814ec9SCorey Minyard 		if (rv)
15044814ec9SCorey Minyard 			break;
15144814ec9SCorey Minyard 		si_type = ival;
15244814ec9SCorey Minyard 
15344814ec9SCorey Minyard 		rv = parse_str(hotmod_as, &addr_space, "address space", &curr);
15444814ec9SCorey Minyard 		if (rv)
15544814ec9SCorey Minyard 			break;
15644814ec9SCorey Minyard 
15744814ec9SCorey Minyard 		s = strchr(curr, ',');
15844814ec9SCorey Minyard 		if (s) {
15944814ec9SCorey Minyard 			*s = '\0';
16044814ec9SCorey Minyard 			s++;
16144814ec9SCorey Minyard 		}
16244814ec9SCorey Minyard 		addr = simple_strtoul(curr, &n, 0);
16344814ec9SCorey Minyard 		if ((*n != '\0') || (*curr == '\0')) {
164*25880f7dSJoe Perches 			pr_warn("Invalid hotmod address '%s'\n", curr);
16544814ec9SCorey Minyard 			break;
16644814ec9SCorey Minyard 		}
16744814ec9SCorey Minyard 
16844814ec9SCorey Minyard 		while (s) {
16944814ec9SCorey Minyard 			curr = s;
17044814ec9SCorey Minyard 			s = strchr(curr, ',');
17144814ec9SCorey Minyard 			if (s) {
17244814ec9SCorey Minyard 				*s = '\0';
17344814ec9SCorey Minyard 				s++;
17444814ec9SCorey Minyard 			}
17544814ec9SCorey Minyard 			o = strchr(curr, '=');
17644814ec9SCorey Minyard 			if (o) {
17744814ec9SCorey Minyard 				*o = '\0';
17844814ec9SCorey Minyard 				o++;
17944814ec9SCorey Minyard 			}
18044814ec9SCorey Minyard 			rv = check_hotmod_int_op(curr, o, "rsp", &regspacing);
18144814ec9SCorey Minyard 			if (rv < 0)
18244814ec9SCorey Minyard 				goto out;
18344814ec9SCorey Minyard 			else if (rv)
18444814ec9SCorey Minyard 				continue;
18544814ec9SCorey Minyard 			rv = check_hotmod_int_op(curr, o, "rsi", &regsize);
18644814ec9SCorey Minyard 			if (rv < 0)
18744814ec9SCorey Minyard 				goto out;
18844814ec9SCorey Minyard 			else if (rv)
18944814ec9SCorey Minyard 				continue;
19044814ec9SCorey Minyard 			rv = check_hotmod_int_op(curr, o, "rsh", &regshift);
19144814ec9SCorey Minyard 			if (rv < 0)
19244814ec9SCorey Minyard 				goto out;
19344814ec9SCorey Minyard 			else if (rv)
19444814ec9SCorey Minyard 				continue;
19544814ec9SCorey Minyard 			rv = check_hotmod_int_op(curr, o, "irq", &irq);
19644814ec9SCorey Minyard 			if (rv < 0)
19744814ec9SCorey Minyard 				goto out;
19844814ec9SCorey Minyard 			else if (rv)
19944814ec9SCorey Minyard 				continue;
20044814ec9SCorey Minyard 			rv = check_hotmod_int_op(curr, o, "ipmb", &ipmb);
20144814ec9SCorey Minyard 			if (rv < 0)
20244814ec9SCorey Minyard 				goto out;
20344814ec9SCorey Minyard 			else if (rv)
20444814ec9SCorey Minyard 				continue;
20544814ec9SCorey Minyard 
20644814ec9SCorey Minyard 			rv = -EINVAL;
207*25880f7dSJoe Perches 			pr_warn("Invalid hotmod option '%s'\n", curr);
20844814ec9SCorey Minyard 			goto out;
20944814ec9SCorey Minyard 		}
21044814ec9SCorey Minyard 
21144814ec9SCorey Minyard 		if (op == HM_ADD) {
21244814ec9SCorey Minyard 			struct si_sm_io io;
21344814ec9SCorey Minyard 
21444814ec9SCorey Minyard 			memset(&io, 0, sizeof(io));
21544814ec9SCorey Minyard 			io.addr_source = SI_HOTMOD;
21644814ec9SCorey Minyard 			io.si_type = si_type;
21744814ec9SCorey Minyard 			io.addr_data = addr;
21844814ec9SCorey Minyard 			io.addr_type = addr_space;
21944814ec9SCorey Minyard 
22044814ec9SCorey Minyard 			io.addr = NULL;
22144814ec9SCorey Minyard 			io.regspacing = regspacing;
22244814ec9SCorey Minyard 			if (!io.regspacing)
22344814ec9SCorey Minyard 				io.regspacing = DEFAULT_REGSPACING;
22444814ec9SCorey Minyard 			io.regsize = regsize;
22544814ec9SCorey Minyard 			if (!io.regsize)
22644814ec9SCorey Minyard 				io.regsize = DEFAULT_REGSIZE;
22744814ec9SCorey Minyard 			io.regshift = regshift;
22844814ec9SCorey Minyard 			io.irq = irq;
22944814ec9SCorey Minyard 			if (io.irq)
23044814ec9SCorey Minyard 				io.irq_setup = ipmi_std_irq_setup;
23144814ec9SCorey Minyard 			io.slave_addr = ipmb;
23244814ec9SCorey Minyard 
23344814ec9SCorey Minyard 			rv = ipmi_si_add_smi(&io);
23444814ec9SCorey Minyard 			if (rv)
23544814ec9SCorey Minyard 				goto out;
23644814ec9SCorey Minyard 		} else {
23744814ec9SCorey Minyard 			ipmi_si_remove_by_data(addr_space, si_type, addr);
23844814ec9SCorey Minyard 		}
23944814ec9SCorey Minyard 	}
24044814ec9SCorey Minyard 	rv = len;
24144814ec9SCorey Minyard out:
24244814ec9SCorey Minyard 	kfree(str);
24344814ec9SCorey Minyard 	return rv;
24444814ec9SCorey Minyard }
245