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