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
parse_str(const struct hotmod_vals * v,unsigned int * val,char * name,const char ** curr)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
check_hotmod_int_op(const char * curr,const char * option,const char * name,unsigned int * val)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
parse_hotmod_str(const char * curr,enum hotmod_op * op,struct ipmi_plat_data * h)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
hotmod_handler(const char * val,const struct kernel_param * kp)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
ipmi_si_hotmod_exit(void)234 void ipmi_si_hotmod_exit(void)
235 {
236 ipmi_remove_platform_device_by_name("hotmod-ipmi-si");
237 }
238