1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * ipmi_si_hotmod.c
4  *
5  * Handling for dynamically adding/removing IPMI devices through
6  * a module parameter (and thus sysfs).
7  */
8 
9 #define pr_fmt(fmt) "ipmi_hotmod: " fmt
10 
11 #include <linux/moduleparam.h>
12 #include <linux/ipmi.h>
13 #include "ipmi_si.h"
14 
15 static int hotmod_handler(const char *val, const struct kernel_param *kp);
16 
17 module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200);
18 MODULE_PARM_DESC(hotmod, "Add and remove interfaces.  See"
19 		 " Documentation/IPMI.txt in the kernel sources for the"
20 		 " gory details.");
21 
22 /*
23  * Parms come in as <op1>[:op2[:op3...]].  ops are:
24  *   add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]]
25  * Options are:
26  *   rsp=<regspacing>
27  *   rsi=<regsize>
28  *   rsh=<regshift>
29  *   irq=<irq>
30  *   ipmb=<ipmb addr>
31  */
32 enum hotmod_op { HM_ADD, HM_REMOVE };
33 struct hotmod_vals {
34 	const char *name;
35 	const int  val;
36 };
37 
38 static const struct hotmod_vals hotmod_ops[] = {
39 	{ "add",	HM_ADD },
40 	{ "remove",	HM_REMOVE },
41 	{ NULL }
42 };
43 
44 static const struct hotmod_vals hotmod_si[] = {
45 	{ "kcs",	SI_KCS },
46 	{ "smic",	SI_SMIC },
47 	{ "bt",		SI_BT },
48 	{ NULL }
49 };
50 
51 static const struct hotmod_vals hotmod_as[] = {
52 	{ "mem",	IPMI_MEM_ADDR_SPACE },
53 	{ "i/o",	IPMI_IO_ADDR_SPACE },
54 	{ NULL }
55 };
56 
57 static int parse_str(const struct hotmod_vals *v, int *val, char *name,
58 		     char **curr)
59 {
60 	char *s;
61 	int  i;
62 
63 	s = strchr(*curr, ',');
64 	if (!s) {
65 		pr_warn("No hotmod %s given\n", name);
66 		return -EINVAL;
67 	}
68 	*s = '\0';
69 	s++;
70 	for (i = 0; v[i].name; i++) {
71 		if (strcmp(*curr, v[i].name) == 0) {
72 			*val = v[i].val;
73 			*curr = s;
74 			return 0;
75 		}
76 	}
77 
78 	pr_warn("Invalid hotmod %s '%s'\n", name, *curr);
79 	return -EINVAL;
80 }
81 
82 static int check_hotmod_int_op(const char *curr, const char *option,
83 			       const char *name, int *val)
84 {
85 	char *n;
86 
87 	if (strcmp(curr, name) == 0) {
88 		if (!option) {
89 			pr_warn("No option given for '%s'\n", curr);
90 			return -EINVAL;
91 		}
92 		*val = simple_strtoul(option, &n, 0);
93 		if ((*n != '\0') || (*option == '\0')) {
94 			pr_warn("Bad option given for '%s'\n", curr);
95 			return -EINVAL;
96 		}
97 		return 1;
98 	}
99 	return 0;
100 }
101 
102 static int hotmod_handler(const char *val, const struct kernel_param *kp)
103 {
104 	char *str = kstrdup(val, GFP_KERNEL);
105 	int  rv;
106 	char *next, *curr, *s, *n, *o;
107 	enum hotmod_op op;
108 	enum si_type si_type;
109 	int  addr_space;
110 	unsigned long addr;
111 	int regspacing;
112 	int regsize;
113 	int regshift;
114 	int irq;
115 	int ipmb;
116 	int ival;
117 	int len;
118 
119 	if (!str)
120 		return -ENOMEM;
121 
122 	/* Kill any trailing spaces, as we can get a "\n" from echo. */
123 	len = strlen(str);
124 	ival = len - 1;
125 	while ((ival >= 0) && isspace(str[ival])) {
126 		str[ival] = '\0';
127 		ival--;
128 	}
129 
130 	for (curr = str; curr; curr = next) {
131 		regspacing = 1;
132 		regsize = 1;
133 		regshift = 0;
134 		irq = 0;
135 		ipmb = 0; /* Choose the default if not specified */
136 
137 		next = strchr(curr, ':');
138 		if (next) {
139 			*next = '\0';
140 			next++;
141 		}
142 
143 		rv = parse_str(hotmod_ops, &ival, "operation", &curr);
144 		if (rv)
145 			break;
146 		op = ival;
147 
148 		rv = parse_str(hotmod_si, &ival, "interface type", &curr);
149 		if (rv)
150 			break;
151 		si_type = ival;
152 
153 		rv = parse_str(hotmod_as, &addr_space, "address space", &curr);
154 		if (rv)
155 			break;
156 
157 		s = strchr(curr, ',');
158 		if (s) {
159 			*s = '\0';
160 			s++;
161 		}
162 		addr = simple_strtoul(curr, &n, 0);
163 		if ((*n != '\0') || (*curr == '\0')) {
164 			pr_warn("Invalid hotmod address '%s'\n", curr);
165 			break;
166 		}
167 
168 		while (s) {
169 			curr = s;
170 			s = strchr(curr, ',');
171 			if (s) {
172 				*s = '\0';
173 				s++;
174 			}
175 			o = strchr(curr, '=');
176 			if (o) {
177 				*o = '\0';
178 				o++;
179 			}
180 			rv = check_hotmod_int_op(curr, o, "rsp", &regspacing);
181 			if (rv < 0)
182 				goto out;
183 			else if (rv)
184 				continue;
185 			rv = check_hotmod_int_op(curr, o, "rsi", &regsize);
186 			if (rv < 0)
187 				goto out;
188 			else if (rv)
189 				continue;
190 			rv = check_hotmod_int_op(curr, o, "rsh", &regshift);
191 			if (rv < 0)
192 				goto out;
193 			else if (rv)
194 				continue;
195 			rv = check_hotmod_int_op(curr, o, "irq", &irq);
196 			if (rv < 0)
197 				goto out;
198 			else if (rv)
199 				continue;
200 			rv = check_hotmod_int_op(curr, o, "ipmb", &ipmb);
201 			if (rv < 0)
202 				goto out;
203 			else if (rv)
204 				continue;
205 
206 			rv = -EINVAL;
207 			pr_warn("Invalid hotmod option '%s'\n", curr);
208 			goto out;
209 		}
210 
211 		if (op == HM_ADD) {
212 			struct si_sm_io io;
213 
214 			memset(&io, 0, sizeof(io));
215 			io.addr_source = SI_HOTMOD;
216 			io.si_type = si_type;
217 			io.addr_data = addr;
218 			io.addr_type = addr_space;
219 
220 			io.addr = NULL;
221 			io.regspacing = regspacing;
222 			if (!io.regspacing)
223 				io.regspacing = DEFAULT_REGSPACING;
224 			io.regsize = regsize;
225 			if (!io.regsize)
226 				io.regsize = DEFAULT_REGSIZE;
227 			io.regshift = regshift;
228 			io.irq = irq;
229 			if (io.irq)
230 				io.irq_setup = ipmi_std_irq_setup;
231 			io.slave_addr = ipmb;
232 
233 			rv = ipmi_si_add_smi(&io);
234 			if (rv)
235 				goto out;
236 		} else {
237 			ipmi_si_remove_by_data(addr_space, si_type, addr);
238 		}
239 	}
240 	rv = len;
241 out:
242 	kfree(str);
243 	return rv;
244 }
245