Commit | Line | Data |
---|---|---|
243ac210 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
44814ec9 CM |
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 | ||
6297fabd | 14 | static int hotmod_handler(const char *val, const struct kernel_param *kp); |
44814ec9 CM |
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 | ||
6297fabd | 101 | static int hotmod_handler(const char *val, const struct kernel_param *kp) |
44814ec9 CM |
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", ®spacing); | |
180 | if (rv < 0) | |
181 | goto out; | |
182 | else if (rv) | |
183 | continue; | |
184 | rv = check_hotmod_int_op(curr, o, "rsi", ®size); | |
185 | if (rv < 0) | |
186 | goto out; | |
187 | else if (rv) | |
188 | continue; | |
189 | rv = check_hotmod_int_op(curr, o, "rsh", ®shift); | |
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 | } |