GNU Linux-libre 5.10.153-gnu1
[releases.git] / drivers / char / ipmi / ipmi_si_hotmod.c
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 }