f3d455bbf3ccdfaface913f93e1e6ac55df10e8e
[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         enum ipmi_addr_space 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, &ival, "address space", &curr);
154                 if (rv)
155                         break;
156                 addr_space = ival;
157
158                 s = strchr(curr, ',');
159                 if (s) {
160                         *s = '\0';
161                         s++;
162                 }
163                 addr = simple_strtoul(curr, &n, 0);
164                 if ((*n != '\0') || (*curr == '\0')) {
165                         pr_warn("Invalid hotmod address '%s'\n", curr);
166                         break;
167                 }
168
169                 while (s) {
170                         curr = s;
171                         s = strchr(curr, ',');
172                         if (s) {
173                                 *s = '\0';
174                                 s++;
175                         }
176                         o = strchr(curr, '=');
177                         if (o) {
178                                 *o = '\0';
179                                 o++;
180                         }
181                         rv = check_hotmod_int_op(curr, o, "rsp", &regspacing);
182                         if (rv < 0)
183                                 goto out;
184                         else if (rv)
185                                 continue;
186                         rv = check_hotmod_int_op(curr, o, "rsi", &regsize);
187                         if (rv < 0)
188                                 goto out;
189                         else if (rv)
190                                 continue;
191                         rv = check_hotmod_int_op(curr, o, "rsh", &regshift);
192                         if (rv < 0)
193                                 goto out;
194                         else if (rv)
195                                 continue;
196                         rv = check_hotmod_int_op(curr, o, "irq", &irq);
197                         if (rv < 0)
198                                 goto out;
199                         else if (rv)
200                                 continue;
201                         rv = check_hotmod_int_op(curr, o, "ipmb", &ipmb);
202                         if (rv < 0)
203                                 goto out;
204                         else if (rv)
205                                 continue;
206
207                         rv = -EINVAL;
208                         pr_warn("Invalid hotmod option '%s'\n", curr);
209                         goto out;
210                 }
211
212                 if (op == HM_ADD) {
213                         struct si_sm_io io;
214
215                         memset(&io, 0, sizeof(io));
216                         io.addr_source = SI_HOTMOD;
217                         io.si_type = si_type;
218                         io.addr_data = addr;
219                         io.addr_space = addr_space;
220
221                         io.addr = NULL;
222                         io.regspacing = regspacing;
223                         if (!io.regspacing)
224                                 io.regspacing = DEFAULT_REGSPACING;
225                         io.regsize = regsize;
226                         if (!io.regsize)
227                                 io.regsize = DEFAULT_REGSIZE;
228                         io.regshift = regshift;
229                         io.irq = irq;
230                         if (io.irq)
231                                 io.irq_setup = ipmi_std_irq_setup;
232                         io.slave_addr = ipmb;
233
234                         rv = ipmi_si_add_smi(&io);
235                         if (rv)
236                                 goto out;
237                 } else {
238                         ipmi_si_remove_by_data(addr_space, si_type, addr);
239                 }
240         }
241         rv = len;
242 out:
243         kfree(str);
244         return rv;
245 }