Merge tag 'mtd/for-5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux
[linux-2.6-microblaze.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,
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 }