]> git.proxmox.com Git - mirror_ubuntu-kernels.git/blob - drivers/char/ipmi/ipmi_si_hotmod.c
HID: logitech-dj: fix spelling in printk
[mirror_ubuntu-kernels.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 }