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