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