1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * PCI HotPlug Controller Core |
4 | * |
5 | * Copyright (C) 2001-2002 Greg Kroah-Hartman (greg@kroah.com) |
6 | * Copyright (C) 2001-2002 IBM Corp. |
7 | * |
8 | * All rights reserved. |
9 | * |
10 | * Send feedback to <kristen.c.accardi@intel.com> |
11 | * |
12 | * Authors: |
13 | * Greg Kroah-Hartman <greg@kroah.com> |
14 | * Scott Murray <scottm@somanetworks.com> |
15 | */ |
16 | |
17 | #include <linux/module.h> /* try_module_get & module_put */ |
18 | #include <linux/moduleparam.h> |
19 | #include <linux/kernel.h> |
20 | #include <linux/types.h> |
21 | #include <linux/list.h> |
22 | #include <linux/kobject.h> |
23 | #include <linux/sysfs.h> |
24 | #include <linux/pagemap.h> |
25 | #include <linux/init.h> |
26 | #include <linux/mount.h> |
27 | #include <linux/namei.h> |
28 | #include <linux/mutex.h> |
29 | #include <linux/pci.h> |
30 | #include <linux/pci_hotplug.h> |
31 | #include <linux/uaccess.h> |
32 | #include "../pci.h" |
33 | #include "cpci_hotplug.h" |
34 | |
35 | #define MY_NAME "pci_hotplug" |
36 | |
37 | #define dbg(fmt, arg...) do { if (debug) printk(KERN_DEBUG "%s: %s: " fmt, MY_NAME, __func__, ## arg); } while (0) |
38 | #define err(format, arg...) printk(KERN_ERR "%s: " format, MY_NAME, ## arg) |
39 | #define info(format, arg...) printk(KERN_INFO "%s: " format, MY_NAME, ## arg) |
40 | #define warn(format, arg...) printk(KERN_WARNING "%s: " format, MY_NAME, ## arg) |
41 | |
42 | /* local variables */ |
43 | static bool debug; |
44 | |
45 | static LIST_HEAD(pci_hotplug_slot_list); |
46 | static DEFINE_MUTEX(pci_hp_mutex); |
47 | |
48 | /* Weee, fun with macros... */ |
49 | #define GET_STATUS(name, type) \ |
50 | static int get_##name(struct hotplug_slot *slot, type *value) \ |
51 | { \ |
52 | const struct hotplug_slot_ops *ops = slot->ops; \ |
53 | int retval = 0; \ |
54 | if (!try_module_get(slot->owner)) \ |
55 | return -ENODEV; \ |
56 | if (ops->get_##name) \ |
57 | retval = ops->get_##name(slot, value); \ |
58 | module_put(slot->owner); \ |
59 | return retval; \ |
60 | } |
61 | |
62 | GET_STATUS(power_status, u8) |
63 | GET_STATUS(attention_status, u8) |
64 | GET_STATUS(latch_status, u8) |
65 | GET_STATUS(adapter_status, u8) |
66 | |
67 | static ssize_t power_read_file(struct pci_slot *pci_slot, char *buf) |
68 | { |
69 | int retval; |
70 | u8 value; |
71 | |
72 | retval = get_power_status(slot: pci_slot->hotplug, value: &value); |
73 | if (retval) |
74 | return retval; |
75 | |
76 | return sysfs_emit(buf, fmt: "%d\n" , value); |
77 | } |
78 | |
79 | static ssize_t power_write_file(struct pci_slot *pci_slot, const char *buf, |
80 | size_t count) |
81 | { |
82 | struct hotplug_slot *slot = pci_slot->hotplug; |
83 | unsigned long lpower; |
84 | u8 power; |
85 | int retval = 0; |
86 | |
87 | lpower = simple_strtoul(buf, NULL, 10); |
88 | power = (u8)(lpower & 0xff); |
89 | dbg("power = %d\n" , power); |
90 | |
91 | if (!try_module_get(module: slot->owner)) { |
92 | retval = -ENODEV; |
93 | goto exit; |
94 | } |
95 | switch (power) { |
96 | case 0: |
97 | if (slot->ops->disable_slot) |
98 | retval = slot->ops->disable_slot(slot); |
99 | break; |
100 | |
101 | case 1: |
102 | if (slot->ops->enable_slot) |
103 | retval = slot->ops->enable_slot(slot); |
104 | break; |
105 | |
106 | default: |
107 | err("Illegal value specified for power\n" ); |
108 | retval = -EINVAL; |
109 | } |
110 | module_put(module: slot->owner); |
111 | |
112 | exit: |
113 | if (retval) |
114 | return retval; |
115 | return count; |
116 | } |
117 | |
118 | static struct pci_slot_attribute hotplug_slot_attr_power = { |
119 | .attr = {.name = "power" , .mode = S_IFREG | S_IRUGO | S_IWUSR}, |
120 | .show = power_read_file, |
121 | .store = power_write_file |
122 | }; |
123 | |
124 | static ssize_t attention_read_file(struct pci_slot *pci_slot, char *buf) |
125 | { |
126 | int retval; |
127 | u8 value; |
128 | |
129 | retval = get_attention_status(slot: pci_slot->hotplug, value: &value); |
130 | if (retval) |
131 | return retval; |
132 | |
133 | return sysfs_emit(buf, fmt: "%d\n" , value); |
134 | } |
135 | |
136 | static ssize_t attention_write_file(struct pci_slot *pci_slot, const char *buf, |
137 | size_t count) |
138 | { |
139 | struct hotplug_slot *slot = pci_slot->hotplug; |
140 | const struct hotplug_slot_ops *ops = slot->ops; |
141 | unsigned long lattention; |
142 | u8 attention; |
143 | int retval = 0; |
144 | |
145 | lattention = simple_strtoul(buf, NULL, 10); |
146 | attention = (u8)(lattention & 0xff); |
147 | dbg(" - attention = %d\n" , attention); |
148 | |
149 | if (!try_module_get(module: slot->owner)) { |
150 | retval = -ENODEV; |
151 | goto exit; |
152 | } |
153 | if (ops->set_attention_status) |
154 | retval = ops->set_attention_status(slot, attention); |
155 | module_put(module: slot->owner); |
156 | |
157 | exit: |
158 | if (retval) |
159 | return retval; |
160 | return count; |
161 | } |
162 | |
163 | static struct pci_slot_attribute hotplug_slot_attr_attention = { |
164 | .attr = {.name = "attention" , .mode = S_IFREG | S_IRUGO | S_IWUSR}, |
165 | .show = attention_read_file, |
166 | .store = attention_write_file |
167 | }; |
168 | |
169 | static ssize_t latch_read_file(struct pci_slot *pci_slot, char *buf) |
170 | { |
171 | int retval; |
172 | u8 value; |
173 | |
174 | retval = get_latch_status(slot: pci_slot->hotplug, value: &value); |
175 | if (retval) |
176 | return retval; |
177 | |
178 | return sysfs_emit(buf, fmt: "%d\n" , value); |
179 | } |
180 | |
181 | static struct pci_slot_attribute hotplug_slot_attr_latch = { |
182 | .attr = {.name = "latch" , .mode = S_IFREG | S_IRUGO}, |
183 | .show = latch_read_file, |
184 | }; |
185 | |
186 | static ssize_t presence_read_file(struct pci_slot *pci_slot, char *buf) |
187 | { |
188 | int retval; |
189 | u8 value; |
190 | |
191 | retval = get_adapter_status(slot: pci_slot->hotplug, value: &value); |
192 | if (retval) |
193 | return retval; |
194 | |
195 | return sysfs_emit(buf, fmt: "%d\n" , value); |
196 | } |
197 | |
198 | static struct pci_slot_attribute hotplug_slot_attr_presence = { |
199 | .attr = {.name = "adapter" , .mode = S_IFREG | S_IRUGO}, |
200 | .show = presence_read_file, |
201 | }; |
202 | |
203 | static ssize_t test_write_file(struct pci_slot *pci_slot, const char *buf, |
204 | size_t count) |
205 | { |
206 | struct hotplug_slot *slot = pci_slot->hotplug; |
207 | unsigned long ltest; |
208 | u32 test; |
209 | int retval = 0; |
210 | |
211 | ltest = simple_strtoul(buf, NULL, 10); |
212 | test = (u32)(ltest & 0xffffffff); |
213 | dbg("test = %d\n" , test); |
214 | |
215 | if (!try_module_get(module: slot->owner)) { |
216 | retval = -ENODEV; |
217 | goto exit; |
218 | } |
219 | if (slot->ops->hardware_test) |
220 | retval = slot->ops->hardware_test(slot, test); |
221 | module_put(module: slot->owner); |
222 | |
223 | exit: |
224 | if (retval) |
225 | return retval; |
226 | return count; |
227 | } |
228 | |
229 | static struct pci_slot_attribute hotplug_slot_attr_test = { |
230 | .attr = {.name = "test" , .mode = S_IFREG | S_IRUGO | S_IWUSR}, |
231 | .store = test_write_file |
232 | }; |
233 | |
234 | static bool has_power_file(struct pci_slot *pci_slot) |
235 | { |
236 | struct hotplug_slot *slot = pci_slot->hotplug; |
237 | |
238 | if ((!slot) || (!slot->ops)) |
239 | return false; |
240 | if ((slot->ops->enable_slot) || |
241 | (slot->ops->disable_slot) || |
242 | (slot->ops->get_power_status)) |
243 | return true; |
244 | return false; |
245 | } |
246 | |
247 | static bool has_attention_file(struct pci_slot *pci_slot) |
248 | { |
249 | struct hotplug_slot *slot = pci_slot->hotplug; |
250 | |
251 | if ((!slot) || (!slot->ops)) |
252 | return false; |
253 | if ((slot->ops->set_attention_status) || |
254 | (slot->ops->get_attention_status)) |
255 | return true; |
256 | return false; |
257 | } |
258 | |
259 | static bool has_latch_file(struct pci_slot *pci_slot) |
260 | { |
261 | struct hotplug_slot *slot = pci_slot->hotplug; |
262 | |
263 | if ((!slot) || (!slot->ops)) |
264 | return false; |
265 | if (slot->ops->get_latch_status) |
266 | return true; |
267 | return false; |
268 | } |
269 | |
270 | static bool has_adapter_file(struct pci_slot *pci_slot) |
271 | { |
272 | struct hotplug_slot *slot = pci_slot->hotplug; |
273 | |
274 | if ((!slot) || (!slot->ops)) |
275 | return false; |
276 | if (slot->ops->get_adapter_status) |
277 | return true; |
278 | return false; |
279 | } |
280 | |
281 | static bool has_test_file(struct pci_slot *pci_slot) |
282 | { |
283 | struct hotplug_slot *slot = pci_slot->hotplug; |
284 | |
285 | if ((!slot) || (!slot->ops)) |
286 | return false; |
287 | if (slot->ops->hardware_test) |
288 | return true; |
289 | return false; |
290 | } |
291 | |
292 | static int fs_add_slot(struct pci_slot *pci_slot) |
293 | { |
294 | int retval = 0; |
295 | |
296 | /* Create symbolic link to the hotplug driver module */ |
297 | pci_hp_create_module_link(pci_slot); |
298 | |
299 | if (has_power_file(pci_slot)) { |
300 | retval = sysfs_create_file(kobj: &pci_slot->kobj, |
301 | attr: &hotplug_slot_attr_power.attr); |
302 | if (retval) |
303 | goto exit_power; |
304 | } |
305 | |
306 | if (has_attention_file(pci_slot)) { |
307 | retval = sysfs_create_file(kobj: &pci_slot->kobj, |
308 | attr: &hotplug_slot_attr_attention.attr); |
309 | if (retval) |
310 | goto exit_attention; |
311 | } |
312 | |
313 | if (has_latch_file(pci_slot)) { |
314 | retval = sysfs_create_file(kobj: &pci_slot->kobj, |
315 | attr: &hotplug_slot_attr_latch.attr); |
316 | if (retval) |
317 | goto exit_latch; |
318 | } |
319 | |
320 | if (has_adapter_file(pci_slot)) { |
321 | retval = sysfs_create_file(kobj: &pci_slot->kobj, |
322 | attr: &hotplug_slot_attr_presence.attr); |
323 | if (retval) |
324 | goto exit_adapter; |
325 | } |
326 | |
327 | if (has_test_file(pci_slot)) { |
328 | retval = sysfs_create_file(kobj: &pci_slot->kobj, |
329 | attr: &hotplug_slot_attr_test.attr); |
330 | if (retval) |
331 | goto exit_test; |
332 | } |
333 | |
334 | goto exit; |
335 | |
336 | exit_test: |
337 | if (has_adapter_file(pci_slot)) |
338 | sysfs_remove_file(kobj: &pci_slot->kobj, |
339 | attr: &hotplug_slot_attr_presence.attr); |
340 | exit_adapter: |
341 | if (has_latch_file(pci_slot)) |
342 | sysfs_remove_file(kobj: &pci_slot->kobj, attr: &hotplug_slot_attr_latch.attr); |
343 | exit_latch: |
344 | if (has_attention_file(pci_slot)) |
345 | sysfs_remove_file(kobj: &pci_slot->kobj, |
346 | attr: &hotplug_slot_attr_attention.attr); |
347 | exit_attention: |
348 | if (has_power_file(pci_slot)) |
349 | sysfs_remove_file(kobj: &pci_slot->kobj, attr: &hotplug_slot_attr_power.attr); |
350 | exit_power: |
351 | pci_hp_remove_module_link(pci_slot); |
352 | exit: |
353 | return retval; |
354 | } |
355 | |
356 | static void fs_remove_slot(struct pci_slot *pci_slot) |
357 | { |
358 | if (has_power_file(pci_slot)) |
359 | sysfs_remove_file(kobj: &pci_slot->kobj, attr: &hotplug_slot_attr_power.attr); |
360 | |
361 | if (has_attention_file(pci_slot)) |
362 | sysfs_remove_file(kobj: &pci_slot->kobj, |
363 | attr: &hotplug_slot_attr_attention.attr); |
364 | |
365 | if (has_latch_file(pci_slot)) |
366 | sysfs_remove_file(kobj: &pci_slot->kobj, attr: &hotplug_slot_attr_latch.attr); |
367 | |
368 | if (has_adapter_file(pci_slot)) |
369 | sysfs_remove_file(kobj: &pci_slot->kobj, |
370 | attr: &hotplug_slot_attr_presence.attr); |
371 | |
372 | if (has_test_file(pci_slot)) |
373 | sysfs_remove_file(kobj: &pci_slot->kobj, attr: &hotplug_slot_attr_test.attr); |
374 | |
375 | pci_hp_remove_module_link(pci_slot); |
376 | } |
377 | |
378 | static struct hotplug_slot *get_slot_from_name(const char *name) |
379 | { |
380 | struct hotplug_slot *slot; |
381 | |
382 | list_for_each_entry(slot, &pci_hotplug_slot_list, slot_list) { |
383 | if (strcmp(hotplug_slot_name(slot), name) == 0) |
384 | return slot; |
385 | } |
386 | return NULL; |
387 | } |
388 | |
389 | /** |
390 | * __pci_hp_register - register a hotplug_slot with the PCI hotplug subsystem |
391 | * @bus: bus this slot is on |
392 | * @slot: pointer to the &struct hotplug_slot to register |
393 | * @devnr: device number |
394 | * @name: name registered with kobject core |
395 | * @owner: caller module owner |
396 | * @mod_name: caller module name |
397 | * |
398 | * Prepares a hotplug slot for in-kernel use and immediately publishes it to |
399 | * user space in one go. Drivers may alternatively carry out the two steps |
400 | * separately by invoking pci_hp_initialize() and pci_hp_add(). |
401 | * |
402 | * Returns 0 if successful, anything else for an error. |
403 | */ |
404 | int __pci_hp_register(struct hotplug_slot *slot, struct pci_bus *bus, |
405 | int devnr, const char *name, |
406 | struct module *owner, const char *mod_name) |
407 | { |
408 | int result; |
409 | |
410 | result = __pci_hp_initialize(slot, bus, nr: devnr, name, owner, mod_name); |
411 | if (result) |
412 | return result; |
413 | |
414 | result = pci_hp_add(slot); |
415 | if (result) |
416 | pci_hp_destroy(slot); |
417 | |
418 | return result; |
419 | } |
420 | EXPORT_SYMBOL_GPL(__pci_hp_register); |
421 | |
422 | /** |
423 | * __pci_hp_initialize - prepare hotplug slot for in-kernel use |
424 | * @slot: pointer to the &struct hotplug_slot to initialize |
425 | * @bus: bus this slot is on |
426 | * @devnr: slot number |
427 | * @name: name registered with kobject core |
428 | * @owner: caller module owner |
429 | * @mod_name: caller module name |
430 | * |
431 | * Allocate and fill in a PCI slot for use by a hotplug driver. Once this has |
432 | * been called, the driver may invoke hotplug_slot_name() to get the slot's |
433 | * unique name. The driver must be prepared to handle a ->reset_slot callback |
434 | * from this point on. |
435 | * |
436 | * Returns 0 on success or a negative int on error. |
437 | */ |
438 | int __pci_hp_initialize(struct hotplug_slot *slot, struct pci_bus *bus, |
439 | int devnr, const char *name, struct module *owner, |
440 | const char *mod_name) |
441 | { |
442 | struct pci_slot *pci_slot; |
443 | |
444 | if (slot == NULL) |
445 | return -ENODEV; |
446 | if (slot->ops == NULL) |
447 | return -EINVAL; |
448 | |
449 | slot->owner = owner; |
450 | slot->mod_name = mod_name; |
451 | |
452 | /* |
453 | * No problems if we call this interface from both ACPI_PCI_SLOT |
454 | * driver and call it here again. If we've already created the |
455 | * pci_slot, the interface will simply bump the refcount. |
456 | */ |
457 | pci_slot = pci_create_slot(parent: bus, slot_nr: devnr, name, hotplug: slot); |
458 | if (IS_ERR(ptr: pci_slot)) |
459 | return PTR_ERR(ptr: pci_slot); |
460 | |
461 | slot->pci_slot = pci_slot; |
462 | pci_slot->hotplug = slot; |
463 | return 0; |
464 | } |
465 | EXPORT_SYMBOL_GPL(__pci_hp_initialize); |
466 | |
467 | /** |
468 | * pci_hp_add - publish hotplug slot to user space |
469 | * @slot: pointer to the &struct hotplug_slot to publish |
470 | * |
471 | * Make a hotplug slot's sysfs interface available and inform user space of its |
472 | * addition by sending a uevent. The hotplug driver must be prepared to handle |
473 | * all &struct hotplug_slot_ops callbacks from this point on. |
474 | * |
475 | * Returns 0 on success or a negative int on error. |
476 | */ |
477 | int pci_hp_add(struct hotplug_slot *slot) |
478 | { |
479 | struct pci_slot *pci_slot = slot->pci_slot; |
480 | int result; |
481 | |
482 | result = fs_add_slot(pci_slot); |
483 | if (result) |
484 | return result; |
485 | |
486 | kobject_uevent(kobj: &pci_slot->kobj, action: KOBJ_ADD); |
487 | mutex_lock(&pci_hp_mutex); |
488 | list_add(new: &slot->slot_list, head: &pci_hotplug_slot_list); |
489 | mutex_unlock(lock: &pci_hp_mutex); |
490 | dbg("Added slot %s to the list\n" , hotplug_slot_name(slot)); |
491 | return 0; |
492 | } |
493 | EXPORT_SYMBOL_GPL(pci_hp_add); |
494 | |
495 | /** |
496 | * pci_hp_deregister - deregister a hotplug_slot with the PCI hotplug subsystem |
497 | * @slot: pointer to the &struct hotplug_slot to deregister |
498 | * |
499 | * The @slot must have been registered with the pci hotplug subsystem |
500 | * previously with a call to pci_hp_register(). |
501 | * |
502 | * Returns 0 if successful, anything else for an error. |
503 | */ |
504 | void pci_hp_deregister(struct hotplug_slot *slot) |
505 | { |
506 | pci_hp_del(slot); |
507 | pci_hp_destroy(slot); |
508 | } |
509 | EXPORT_SYMBOL_GPL(pci_hp_deregister); |
510 | |
511 | /** |
512 | * pci_hp_del - unpublish hotplug slot from user space |
513 | * @slot: pointer to the &struct hotplug_slot to unpublish |
514 | * |
515 | * Remove a hotplug slot's sysfs interface. |
516 | * |
517 | * Returns 0 on success or a negative int on error. |
518 | */ |
519 | void pci_hp_del(struct hotplug_slot *slot) |
520 | { |
521 | struct hotplug_slot *temp; |
522 | |
523 | if (WARN_ON(!slot)) |
524 | return; |
525 | |
526 | mutex_lock(&pci_hp_mutex); |
527 | temp = get_slot_from_name(name: hotplug_slot_name(slot)); |
528 | if (WARN_ON(temp != slot)) { |
529 | mutex_unlock(lock: &pci_hp_mutex); |
530 | return; |
531 | } |
532 | |
533 | list_del(entry: &slot->slot_list); |
534 | mutex_unlock(lock: &pci_hp_mutex); |
535 | dbg("Removed slot %s from the list\n" , hotplug_slot_name(slot)); |
536 | fs_remove_slot(pci_slot: slot->pci_slot); |
537 | } |
538 | EXPORT_SYMBOL_GPL(pci_hp_del); |
539 | |
540 | /** |
541 | * pci_hp_destroy - remove hotplug slot from in-kernel use |
542 | * @slot: pointer to the &struct hotplug_slot to destroy |
543 | * |
544 | * Destroy a PCI slot used by a hotplug driver. Once this has been called, |
545 | * the driver may no longer invoke hotplug_slot_name() to get the slot's |
546 | * unique name. The driver no longer needs to handle a ->reset_slot callback |
547 | * from this point on. |
548 | * |
549 | * Returns 0 on success or a negative int on error. |
550 | */ |
551 | void pci_hp_destroy(struct hotplug_slot *slot) |
552 | { |
553 | struct pci_slot *pci_slot = slot->pci_slot; |
554 | |
555 | slot->pci_slot = NULL; |
556 | pci_slot->hotplug = NULL; |
557 | pci_destroy_slot(slot: pci_slot); |
558 | } |
559 | EXPORT_SYMBOL_GPL(pci_hp_destroy); |
560 | |
561 | static int __init pci_hotplug_init(void) |
562 | { |
563 | int result; |
564 | |
565 | result = cpci_hotplug_init(debug); |
566 | if (result) { |
567 | err("cpci_hotplug_init with error %d\n" , result); |
568 | return result; |
569 | } |
570 | |
571 | return result; |
572 | } |
573 | device_initcall(pci_hotplug_init); |
574 | |
575 | /* |
576 | * not really modular, but the easiest way to keep compat with existing |
577 | * bootargs behaviour is to continue using module_param here. |
578 | */ |
579 | module_param(debug, bool, 0644); |
580 | MODULE_PARM_DESC(debug, "Debugging mode enabled or not" ); |
581 | |