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
17static int hotmod_handler(const char *val, const struct kernel_param *kp);
18
19module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200);
20MODULE_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 */
33enum hotmod_op { HM_ADD, HM_REMOVE };
34struct hotmod_vals {
35	const char *name;
36	const int  val;
37};
38
39static const struct hotmod_vals hotmod_ops[] = {
40	{ "add",	HM_ADD },
41	{ "remove",	HM_REMOVE },
42	{ NULL }
43};
44
45static const struct hotmod_vals hotmod_si[] = {
46	{ "kcs",	SI_KCS },
47	{ "smic",	SI_SMIC },
48	{ "bt",		SI_BT },
49	{ NULL }
50};
51
52static const struct hotmod_vals hotmod_as[] = {
53	{ "mem",	IPMI_MEM_ADDR_SPACE },
54	{ "i/o",	IPMI_IO_ADDR_SPACE },
55	{ NULL }
56};
57
58static 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
83static 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
103static 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
183static atomic_t hotmod_nr;
184
185static 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);
229out:
230	kfree(str);
231	return rv;
232}
233
234void ipmi_si_hotmod_exit(void)
235{
236	ipmi_remove_platform_device_by_name("hotmod-ipmi-si");
237}
238