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