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