]>
Commit | Line | Data |
---|---|---|
1e9c9310 SL |
1 | From e60de9df336c3e715143b3d1c04885b0ec69e9c1 Mon Sep 17 00:00:00 2001 |
2 | From: Corey Minyard <cminyard@mvista.com> | |
3 | Date: Tue, 26 Mar 2019 11:10:04 -0500 | |
4 | Subject: ipmi_si: Fix crash when using hard-coded device | |
5 | ||
6 | Backport from 41b766d661bf94a364960862cfc248a78313dbd3 | |
7 | ||
8 | When excuting a command like: | |
9 | modprobe ipmi_si ports=0xffc0e3 type=bt | |
10 | The system would get an oops. | |
11 | ||
12 | The trouble here is that ipmi_si_hardcode_find_bmc() is called before | |
13 | ipmi_si_platform_init(), but initialization of the hard-coded device | |
14 | creates an IPMI platform device, which won't be initialized yet. | |
15 | ||
16 | The real trouble is that hard-coded devices aren't created with | |
17 | any device, and the fixup is done later. So do it right, create the | |
18 | hard-coded devices as normal platform devices. | |
19 | ||
20 | This required adding some new resource types to the IPMI platform | |
21 | code for passing information required by the hard-coded device | |
22 | and adding some code to remove the hard-coded platform devices | |
23 | on module removal. | |
24 | ||
25 | To enforce the "hard-coded devices passed by the user take priority | |
26 | over firmware devices" rule, some special code was added to check | |
27 | and see if a hard-coded device already exists. | |
28 | ||
29 | The backport required some minor fixups and adding the device | |
30 | id table that had been added in another change and was used | |
31 | in this one. | |
32 | ||
33 | Reported-by: Yang Yingliang <yangyingliang@huawei.com> | |
34 | Cc: stable@vger.kernel.org # v4.15+ | |
35 | Signed-off-by: Corey Minyard <cminyard@mvista.com> | |
36 | Tested-by: Yang Yingliang <yangyingliang@huawei.com> | |
37 | Signed-off-by: Sasha Levin <sashal@kernel.org> | |
af18a487 | 38 | Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
1e9c9310 | 39 | --- |
af18a487 GKH |
40 | drivers/char/ipmi/ipmi_si.h | 4 |
41 | drivers/char/ipmi/ipmi_si_hardcode.c | 232 +++++++++++++++++++++++++---------- | |
42 | drivers/char/ipmi/ipmi_si_intf.c | 22 ++- | |
43 | drivers/char/ipmi/ipmi_si_platform.c | 30 +++- | |
44 | 4 files changed, 216 insertions(+), 72 deletions(-) | |
1e9c9310 | 45 | |
1e9c9310 SL |
46 | --- a/drivers/char/ipmi/ipmi_si.h |
47 | +++ b/drivers/char/ipmi/ipmi_si.h | |
af18a487 | 48 | @@ -25,7 +25,9 @@ void ipmi_irq_finish_setup(struct si_sm_ |
1e9c9310 SL |
49 | int ipmi_si_remove_by_dev(struct device *dev); |
50 | void ipmi_si_remove_by_data(int addr_space, enum si_type si_type, | |
51 | unsigned long addr); | |
52 | -int ipmi_si_hardcode_find_bmc(void); | |
53 | +void ipmi_hardcode_init(void); | |
54 | +void ipmi_si_hardcode_exit(void); | |
55 | +int ipmi_si_hardcode_match(int addr_type, unsigned long addr); | |
56 | void ipmi_si_platform_init(void); | |
57 | void ipmi_si_platform_shutdown(void); | |
58 | ||
1e9c9310 SL |
59 | --- a/drivers/char/ipmi/ipmi_si_hardcode.c |
60 | +++ b/drivers/char/ipmi/ipmi_si_hardcode.c | |
61 | @@ -1,6 +1,7 @@ | |
62 | // SPDX-License-Identifier: GPL-2.0+ | |
63 | ||
64 | #include <linux/moduleparam.h> | |
65 | +#include <linux/platform_device.h> | |
66 | #include "ipmi_si.h" | |
67 | ||
68 | #define PFX "ipmi_hardcode: " | |
69 | @@ -11,23 +12,22 @@ | |
70 | ||
71 | #define SI_MAX_PARMS 4 | |
72 | ||
73 | -static char *si_type[SI_MAX_PARMS]; | |
74 | #define MAX_SI_TYPE_STR 30 | |
75 | -static char si_type_str[MAX_SI_TYPE_STR]; | |
76 | +static char si_type_str[MAX_SI_TYPE_STR] __initdata; | |
77 | static unsigned long addrs[SI_MAX_PARMS]; | |
78 | static unsigned int num_addrs; | |
79 | static unsigned int ports[SI_MAX_PARMS]; | |
80 | static unsigned int num_ports; | |
81 | -static int irqs[SI_MAX_PARMS]; | |
82 | -static unsigned int num_irqs; | |
83 | -static int regspacings[SI_MAX_PARMS]; | |
84 | -static unsigned int num_regspacings; | |
85 | -static int regsizes[SI_MAX_PARMS]; | |
86 | -static unsigned int num_regsizes; | |
87 | -static int regshifts[SI_MAX_PARMS]; | |
88 | -static unsigned int num_regshifts; | |
89 | -static int slave_addrs[SI_MAX_PARMS]; /* Leaving 0 chooses the default value */ | |
90 | -static unsigned int num_slave_addrs; | |
91 | +static int irqs[SI_MAX_PARMS] __initdata; | |
92 | +static unsigned int num_irqs __initdata; | |
93 | +static int regspacings[SI_MAX_PARMS] __initdata; | |
94 | +static unsigned int num_regspacings __initdata; | |
95 | +static int regsizes[SI_MAX_PARMS] __initdata; | |
96 | +static unsigned int num_regsizes __initdata; | |
97 | +static int regshifts[SI_MAX_PARMS] __initdata; | |
98 | +static unsigned int num_regshifts __initdata; | |
99 | +static int slave_addrs[SI_MAX_PARMS] __initdata; | |
100 | +static unsigned int num_slave_addrs __initdata; | |
101 | ||
102 | module_param_string(type, si_type_str, MAX_SI_TYPE_STR, 0); | |
103 | MODULE_PARM_DESC(type, "Defines the type of each interface, each" | |
af18a487 | 104 | @@ -72,12 +72,133 @@ MODULE_PARM_DESC(slave_addrs, "Set the d |
1e9c9310 SL |
105 | " overridden by this parm. This is an array indexed" |
106 | " by interface number."); | |
107 | ||
108 | -int ipmi_si_hardcode_find_bmc(void) | |
109 | +static struct platform_device *ipmi_hc_pdevs[SI_MAX_PARMS]; | |
110 | + | |
111 | +static void __init ipmi_hardcode_init_one(const char *si_type_str, | |
112 | + unsigned int i, | |
113 | + unsigned long addr, | |
114 | + unsigned int flags) | |
af18a487 | 115 | +{ |
1e9c9310 SL |
116 | + struct platform_device *pdev; |
117 | + unsigned int num_r = 1, size; | |
118 | + struct resource r[4]; | |
119 | + struct property_entry p[6]; | |
120 | + enum si_type si_type; | |
121 | + unsigned int regspacing, regsize; | |
122 | + int rv; | |
123 | + | |
124 | + memset(p, 0, sizeof(p)); | |
125 | + memset(r, 0, sizeof(r)); | |
126 | + | |
127 | + if (!si_type_str || !*si_type_str || strcmp(si_type_str, "kcs") == 0) { | |
128 | + size = 2; | |
129 | + si_type = SI_KCS; | |
130 | + } else if (strcmp(si_type_str, "smic") == 0) { | |
131 | + size = 2; | |
132 | + si_type = SI_SMIC; | |
133 | + } else if (strcmp(si_type_str, "bt") == 0) { | |
134 | + size = 3; | |
135 | + si_type = SI_BT; | |
136 | + } else if (strcmp(si_type_str, "invalid") == 0) { | |
137 | + /* | |
138 | + * Allow a firmware-specified interface to be | |
139 | + * disabled. | |
140 | + */ | |
141 | + size = 1; | |
142 | + si_type = SI_TYPE_INVALID; | |
143 | + } else { | |
144 | + pr_warn("Interface type specified for interface %d, was invalid: %s\n", | |
145 | + i, si_type_str); | |
146 | + return; | |
147 | + } | |
148 | + | |
149 | + regsize = regsizes[i]; | |
150 | + if (regsize == 0) | |
151 | + regsize = DEFAULT_REGSIZE; | |
152 | + | |
153 | + p[0] = PROPERTY_ENTRY_U8("ipmi-type", si_type); | |
154 | + p[1] = PROPERTY_ENTRY_U8("slave-addr", slave_addrs[i]); | |
155 | + p[2] = PROPERTY_ENTRY_U8("addr-source", SI_HARDCODED); | |
156 | + p[3] = PROPERTY_ENTRY_U8("reg-shift", regshifts[i]); | |
157 | + p[4] = PROPERTY_ENTRY_U8("reg-size", regsize); | |
158 | + /* Last entry must be left NULL to terminate it. */ | |
159 | + | |
160 | + /* | |
161 | + * Register spacing is derived from the resources in | |
162 | + * the IPMI platform code. | |
163 | + */ | |
164 | + regspacing = regspacings[i]; | |
165 | + if (regspacing == 0) | |
166 | + regspacing = regsize; | |
167 | + | |
168 | + r[0].start = addr; | |
169 | + r[0].end = r[0].start + regsize - 1; | |
170 | + r[0].name = "IPMI Address 1"; | |
171 | + r[0].flags = flags; | |
172 | + | |
173 | + if (size > 1) { | |
174 | + r[1].start = r[0].start + regspacing; | |
175 | + r[1].end = r[1].start + regsize - 1; | |
176 | + r[1].name = "IPMI Address 2"; | |
177 | + r[1].flags = flags; | |
178 | + num_r++; | |
179 | + } | |
180 | + | |
181 | + if (size > 2) { | |
182 | + r[2].start = r[1].start + regspacing; | |
183 | + r[2].end = r[2].start + regsize - 1; | |
184 | + r[2].name = "IPMI Address 3"; | |
185 | + r[2].flags = flags; | |
186 | + num_r++; | |
187 | + } | |
188 | + | |
189 | + if (irqs[i]) { | |
190 | + r[num_r].start = irqs[i]; | |
191 | + r[num_r].end = irqs[i]; | |
192 | + r[num_r].name = "IPMI IRQ"; | |
193 | + r[num_r].flags = IORESOURCE_IRQ; | |
194 | + num_r++; | |
195 | + } | |
196 | + | |
197 | + pdev = platform_device_alloc("hardcode-ipmi-si", i); | |
198 | + if (!pdev) { | |
199 | + pr_err("Error allocating IPMI platform device %d\n", i); | |
200 | + return; | |
201 | + } | |
202 | + | |
203 | + rv = platform_device_add_resources(pdev, r, num_r); | |
204 | + if (rv) { | |
205 | + dev_err(&pdev->dev, | |
206 | + "Unable to add hard-code resources: %d\n", rv); | |
207 | + goto err; | |
208 | + } | |
209 | + | |
210 | + rv = platform_device_add_properties(pdev, p); | |
211 | + if (rv) { | |
212 | + dev_err(&pdev->dev, | |
213 | + "Unable to add hard-code properties: %d\n", rv); | |
214 | + goto err; | |
215 | + } | |
216 | + | |
217 | + rv = platform_device_add(pdev); | |
218 | + if (rv) { | |
219 | + dev_err(&pdev->dev, | |
220 | + "Unable to add hard-code device: %d\n", rv); | |
221 | + goto err; | |
222 | + } | |
223 | + | |
224 | + ipmi_hc_pdevs[i] = pdev; | |
225 | + return; | |
226 | + | |
227 | +err: | |
228 | + platform_device_put(pdev); | |
229 | +} | |
230 | + | |
231 | +void __init ipmi_hardcode_init(void) | |
af18a487 GKH |
232 | { |
233 | - int ret = -ENODEV; | |
234 | - int i; | |
235 | - struct si_sm_io io; | |
1e9c9310 SL |
236 | + unsigned int i; |
237 | char *str; | |
238 | + char *si_type[SI_MAX_PARMS]; | |
239 | ||
240 | /* Parse out the si_type string into its components. */ | |
241 | str = si_type_str; | |
242 | @@ -94,54 +215,45 @@ int ipmi_si_hardcode_find_bmc(void) | |
243 | } | |
244 | } | |
245 | ||
246 | - memset(&io, 0, sizeof(io)); | |
247 | for (i = 0; i < SI_MAX_PARMS; i++) { | |
248 | - if (!ports[i] && !addrs[i]) | |
249 | - continue; | |
250 | - | |
251 | - io.addr_source = SI_HARDCODED; | |
252 | - pr_info(PFX "probing via hardcoded address\n"); | |
af18a487 GKH |
253 | + if (i < num_ports && ports[i]) |
254 | + ipmi_hardcode_init_one(si_type[i], i, ports[i], | |
255 | + IORESOURCE_IO); | |
256 | + if (i < num_addrs && addrs[i]) | |
257 | + ipmi_hardcode_init_one(si_type[i], i, addrs[i], | |
258 | + IORESOURCE_MEM); | |
259 | + } | |
260 | +} | |
261 | ||
1e9c9310 SL |
262 | - if (!si_type[i] || strcmp(si_type[i], "kcs") == 0) { |
263 | - io.si_type = SI_KCS; | |
264 | - } else if (strcmp(si_type[i], "smic") == 0) { | |
265 | - io.si_type = SI_SMIC; | |
266 | - } else if (strcmp(si_type[i], "bt") == 0) { | |
267 | - io.si_type = SI_BT; | |
268 | - } else { | |
269 | - pr_warn(PFX "Interface type specified for interface %d, was invalid: %s\n", | |
270 | - i, si_type[i]); | |
271 | - continue; | |
272 | - } | |
af18a487 GKH |
273 | +void ipmi_si_hardcode_exit(void) |
274 | +{ | |
275 | + unsigned int i; | |
1e9c9310 SL |
276 | |
277 | - if (ports[i]) { | |
278 | - /* An I/O port */ | |
279 | - io.addr_data = ports[i]; | |
280 | - io.addr_type = IPMI_IO_ADDR_SPACE; | |
281 | - } else if (addrs[i]) { | |
282 | - /* A memory port */ | |
283 | - io.addr_data = addrs[i]; | |
284 | - io.addr_type = IPMI_MEM_ADDR_SPACE; | |
285 | - } else { | |
286 | - pr_warn(PFX "Interface type specified for interface %d, but port and address were not set or set to zero.\n", | |
287 | - i); | |
288 | - continue; | |
289 | - } | |
af18a487 GKH |
290 | + for (i = 0; i < SI_MAX_PARMS; i++) { |
291 | + if (ipmi_hc_pdevs[i]) | |
292 | + platform_device_unregister(ipmi_hc_pdevs[i]); | |
293 | + } | |
294 | +} | |
1e9c9310 SL |
295 | |
296 | - io.addr = NULL; | |
297 | - io.regspacing = regspacings[i]; | |
298 | - if (!io.regspacing) | |
299 | - io.regspacing = DEFAULT_REGSPACING; | |
300 | - io.regsize = regsizes[i]; | |
301 | - if (!io.regsize) | |
302 | - io.regsize = DEFAULT_REGSIZE; | |
303 | - io.regshift = regshifts[i]; | |
304 | - io.irq = irqs[i]; | |
305 | - if (io.irq) | |
306 | - io.irq_setup = ipmi_std_irq_setup; | |
307 | - io.slave_addr = slave_addrs[i]; | |
1e9c9310 SL |
308 | +/* |
309 | + * Returns true of the given address exists as a hardcoded address, | |
310 | + * false if not. | |
311 | + */ | |
312 | +int ipmi_si_hardcode_match(int addr_type, unsigned long addr) | |
313 | +{ | |
314 | + unsigned int i; | |
af18a487 GKH |
315 | |
316 | - ret = ipmi_si_add_smi(&io); | |
1e9c9310 SL |
317 | + if (addr_type == IPMI_IO_ADDR_SPACE) { |
318 | + for (i = 0; i < num_ports; i++) { | |
319 | + if (ports[i] == addr) | |
320 | + return 1; | |
321 | + } | |
322 | + } else { | |
323 | + for (i = 0; i < num_addrs; i++) { | |
324 | + if (addrs[i] == addr) | |
325 | + return 1; | |
326 | + } | |
af18a487 GKH |
327 | } |
328 | - return ret; | |
1e9c9310 SL |
329 | + |
330 | + return 0; | |
331 | } | |
1e9c9310 SL |
332 | --- a/drivers/char/ipmi/ipmi_si_intf.c |
333 | +++ b/drivers/char/ipmi/ipmi_si_intf.c | |
334 | @@ -1862,6 +1862,18 @@ int ipmi_si_add_smi(struct si_sm_io *io) | |
335 | int rv = 0; | |
336 | struct smi_info *new_smi, *dup; | |
337 | ||
338 | + /* | |
339 | + * If the user gave us a hard-coded device at the same | |
340 | + * address, they presumably want us to use it and not what is | |
341 | + * in the firmware. | |
342 | + */ | |
343 | + if (io->addr_source != SI_HARDCODED && | |
344 | + ipmi_si_hardcode_match(io->addr_type, io->addr_data)) { | |
345 | + dev_info(io->dev, | |
346 | + "Hard-coded device at this address already exists"); | |
347 | + return -ENODEV; | |
348 | + } | |
349 | + | |
350 | if (!io->io_setup) { | |
351 | if (io->addr_type == IPMI_IO_ADDR_SPACE) { | |
352 | io->io_setup = ipmi_si_port_setup; | |
af18a487 | 353 | @@ -2094,7 +2106,7 @@ static int try_smi_init(struct smi_info |
1e9c9310 SL |
354 | return rv; |
355 | } | |
356 | ||
357 | -static int init_ipmi_si(void) | |
358 | +static int __init init_ipmi_si(void) | |
359 | { | |
360 | struct smi_info *e; | |
361 | enum ipmi_addr_src type = SI_INVALID; | |
362 | @@ -2102,12 +2114,9 @@ static int init_ipmi_si(void) | |
363 | if (initialized) | |
364 | return 0; | |
365 | ||
366 | + ipmi_hardcode_init(); | |
367 | pr_info("IPMI System Interface driver.\n"); | |
368 | ||
369 | - /* If the user gave us a device, they presumably want us to use it */ | |
370 | - if (!ipmi_si_hardcode_find_bmc()) | |
371 | - goto do_scan; | |
372 | - | |
373 | ipmi_si_platform_init(); | |
374 | ||
375 | ipmi_si_pci_init(); | |
376 | @@ -2118,7 +2127,6 @@ static int init_ipmi_si(void) | |
377 | with multiple BMCs we assume that there will be several instances | |
378 | of a given type so if we succeed in registering a type then also | |
379 | try to register everything else of the same type */ | |
380 | -do_scan: | |
381 | mutex_lock(&smi_infos_lock); | |
382 | list_for_each_entry(e, &smi_infos, link) { | |
383 | /* Try to register a device if it has an IRQ and we either | |
384 | @@ -2304,6 +2312,8 @@ static void cleanup_ipmi_si(void) | |
385 | list_for_each_entry_safe(e, tmp_e, &smi_infos, link) | |
386 | cleanup_one_si(e); | |
387 | mutex_unlock(&smi_infos_lock); | |
388 | + | |
389 | + ipmi_si_hardcode_exit(); | |
390 | } | |
391 | module_exit(cleanup_ipmi_si); | |
392 | ||
1e9c9310 SL |
393 | --- a/drivers/char/ipmi/ipmi_si_platform.c |
394 | +++ b/drivers/char/ipmi/ipmi_si_platform.c | |
af18a487 | 395 | @@ -126,8 +126,6 @@ ipmi_get_info_from_resources(struct plat |
1e9c9310 SL |
396 | if (res_second->start > io->addr_data) |
397 | io->regspacing = res_second->start - io->addr_data; | |
398 | } | |
399 | - io->regsize = DEFAULT_REGSIZE; | |
400 | - io->regshift = 0; | |
401 | ||
402 | return res; | |
403 | } | |
af18a487 | 404 | @@ -135,7 +133,7 @@ ipmi_get_info_from_resources(struct plat |
1e9c9310 SL |
405 | static int platform_ipmi_probe(struct platform_device *pdev) |
406 | { | |
407 | struct si_sm_io io; | |
408 | - u8 type, slave_addr, addr_source; | |
409 | + u8 type, slave_addr, addr_source, regsize, regshift; | |
410 | int rv; | |
411 | ||
412 | rv = device_property_read_u8(&pdev->dev, "addr-source", &addr_source); | |
af18a487 | 413 | @@ -147,7 +145,7 @@ static int platform_ipmi_probe(struct pl |
1e9c9310 SL |
414 | if (addr_source == SI_SMBIOS) { |
415 | if (!si_trydmi) | |
416 | return -ENODEV; | |
417 | - } else { | |
418 | + } else if (addr_source != SI_HARDCODED) { | |
419 | if (!si_tryplatform) | |
420 | return -ENODEV; | |
421 | } | |
af18a487 | 422 | @@ -167,11 +165,23 @@ static int platform_ipmi_probe(struct pl |
1e9c9310 SL |
423 | case SI_BT: |
424 | io.si_type = type; | |
425 | break; | |
426 | + case SI_TYPE_INVALID: /* User disabled this in hardcode. */ | |
427 | + return -ENODEV; | |
428 | default: | |
429 | dev_err(&pdev->dev, "ipmi-type property is invalid\n"); | |
430 | return -EINVAL; | |
431 | } | |
432 | ||
433 | + io.regsize = DEFAULT_REGSIZE; | |
434 | + rv = device_property_read_u8(&pdev->dev, "reg-size", ®size); | |
435 | + if (!rv) | |
436 | + io.regsize = regsize; | |
437 | + | |
438 | + io.regshift = 0; | |
439 | + rv = device_property_read_u8(&pdev->dev, "reg-shift", ®shift); | |
440 | + if (!rv) | |
441 | + io.regshift = regshift; | |
442 | + | |
443 | if (!ipmi_get_info_from_resources(pdev, &io)) | |
444 | return -EINVAL; | |
445 | ||
af18a487 | 446 | @@ -191,7 +201,8 @@ static int platform_ipmi_probe(struct pl |
1e9c9310 SL |
447 | |
448 | io.dev = &pdev->dev; | |
449 | ||
450 | - pr_info("ipmi_si: SMBIOS: %s %#lx regsize %d spacing %d irq %d\n", | |
451 | + pr_info("ipmi_si: %s: %s %#lx regsize %d spacing %d irq %d\n", | |
452 | + ipmi_addr_src_to_str(addr_source), | |
453 | (io.addr_type == IPMI_IO_ADDR_SPACE) ? "io" : "mem", | |
454 | io.addr_data, io.regsize, io.regspacing, io.irq); | |
455 | ||
af18a487 | 456 | @@ -356,6 +367,9 @@ static int acpi_ipmi_probe(struct platfo |
1e9c9310 SL |
457 | goto err_free; |
458 | } | |
459 | ||
460 | + io.regsize = DEFAULT_REGSIZE; | |
461 | + io.regshift = 0; | |
462 | + | |
463 | res = ipmi_get_info_from_resources(pdev, &io); | |
464 | if (!res) { | |
465 | rv = -EINVAL; | |
af18a487 | 466 | @@ -417,6 +431,11 @@ static int ipmi_remove(struct platform_d |
1e9c9310 SL |
467 | return ipmi_si_remove_by_dev(&pdev->dev); |
468 | } | |
469 | ||
470 | +static const struct platform_device_id si_plat_ids[] = { | |
471 | + { "hardcode-ipmi-si", 0 }, | |
472 | + { } | |
473 | +}; | |
474 | + | |
475 | struct platform_driver ipmi_platform_driver = { | |
476 | .driver = { | |
477 | .name = DEVICE_NAME, | |
af18a487 | 478 | @@ -425,6 +444,7 @@ struct platform_driver ipmi_platform_dri |
1e9c9310 SL |
479 | }, |
480 | .probe = ipmi_probe, | |
481 | .remove = ipmi_remove, | |
482 | + .id_table = si_plat_ids | |
483 | }; | |
484 | ||
485 | void ipmi_si_platform_init(void) |