1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * CompactPCI Hot Plug Driver |
4 | * |
5 | * Copyright (C) 2002,2005 SOMA Networks, Inc. |
6 | * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) |
7 | * Copyright (C) 2001 IBM Corp. |
8 | * |
9 | * All rights reserved. |
10 | * |
11 | * Send feedback to <scottm@somanetworks.com> |
12 | */ |
13 | |
14 | #include <linux/module.h> |
15 | #include <linux/kernel.h> |
16 | #include <linux/sched/signal.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/pci.h> |
19 | #include <linux/pci_hotplug.h> |
20 | #include <linux/init.h> |
21 | #include <linux/interrupt.h> |
22 | #include <linux/atomic.h> |
23 | #include <linux/delay.h> |
24 | #include <linux/kthread.h> |
25 | #include "cpci_hotplug.h" |
26 | |
27 | #define DRIVER_AUTHOR "Scott Murray <scottm@somanetworks.com>" |
28 | #define DRIVER_DESC "CompactPCI Hot Plug Core" |
29 | |
30 | #define MY_NAME "cpci_hotplug" |
31 | |
32 | #define dbg(format, arg...) \ |
33 | do { \ |
34 | if (cpci_debug) \ |
35 | printk(KERN_DEBUG "%s: " format "\n", \ |
36 | MY_NAME, ## arg); \ |
37 | } while (0) |
38 | #define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME, ## arg) |
39 | #define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME, ## arg) |
40 | #define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME, ## arg) |
41 | |
42 | /* local variables */ |
43 | static DECLARE_RWSEM(list_rwsem); |
44 | static LIST_HEAD(slot_list); |
45 | static int slots; |
46 | static atomic_t ; |
47 | int cpci_debug; |
48 | static struct cpci_hp_controller *controller; |
49 | static struct task_struct *cpci_thread; |
50 | static int thread_finished; |
51 | |
52 | static int enable_slot(struct hotplug_slot *slot); |
53 | static int disable_slot(struct hotplug_slot *slot); |
54 | static int set_attention_status(struct hotplug_slot *slot, u8 value); |
55 | static int get_power_status(struct hotplug_slot *slot, u8 *value); |
56 | static int get_attention_status(struct hotplug_slot *slot, u8 *value); |
57 | static int get_adapter_status(struct hotplug_slot *slot, u8 *value); |
58 | static int get_latch_status(struct hotplug_slot *slot, u8 *value); |
59 | |
60 | static const struct hotplug_slot_ops cpci_hotplug_slot_ops = { |
61 | .enable_slot = enable_slot, |
62 | .disable_slot = disable_slot, |
63 | .set_attention_status = set_attention_status, |
64 | .get_power_status = get_power_status, |
65 | .get_attention_status = get_attention_status, |
66 | .get_adapter_status = get_adapter_status, |
67 | .get_latch_status = get_latch_status, |
68 | }; |
69 | |
70 | static int |
71 | enable_slot(struct hotplug_slot *hotplug_slot) |
72 | { |
73 | struct slot *slot = to_slot(hotplug_slot); |
74 | int retval = 0; |
75 | |
76 | dbg("%s - physical_slot = %s" , __func__, slot_name(slot)); |
77 | |
78 | if (controller->ops->set_power) |
79 | retval = controller->ops->set_power(slot, 1); |
80 | return retval; |
81 | } |
82 | |
83 | static int |
84 | disable_slot(struct hotplug_slot *hotplug_slot) |
85 | { |
86 | struct slot *slot = to_slot(hotplug_slot); |
87 | int retval = 0; |
88 | |
89 | dbg("%s - physical_slot = %s" , __func__, slot_name(slot)); |
90 | |
91 | down_write(sem: &list_rwsem); |
92 | |
93 | /* Unconfigure device */ |
94 | dbg("%s - unconfiguring slot %s" , __func__, slot_name(slot)); |
95 | retval = cpci_unconfigure_slot(slot); |
96 | if (retval) { |
97 | err("%s - could not unconfigure slot %s" , |
98 | __func__, slot_name(slot)); |
99 | goto disable_error; |
100 | } |
101 | dbg("%s - finished unconfiguring slot %s" , __func__, slot_name(slot)); |
102 | |
103 | /* Clear EXT (by setting it) */ |
104 | if (cpci_clear_ext(slot)) { |
105 | err("%s - could not clear EXT for slot %s" , |
106 | __func__, slot_name(slot)); |
107 | retval = -ENODEV; |
108 | goto disable_error; |
109 | } |
110 | cpci_led_on(slot); |
111 | |
112 | if (controller->ops->set_power) { |
113 | retval = controller->ops->set_power(slot, 0); |
114 | if (retval) |
115 | goto disable_error; |
116 | } |
117 | |
118 | slot->adapter_status = 0; |
119 | |
120 | if (slot->extracting) { |
121 | slot->extracting = 0; |
122 | atomic_dec(v: &extracting); |
123 | } |
124 | disable_error: |
125 | up_write(sem: &list_rwsem); |
126 | return retval; |
127 | } |
128 | |
129 | static u8 |
130 | cpci_get_power_status(struct slot *slot) |
131 | { |
132 | u8 power = 1; |
133 | |
134 | if (controller->ops->get_power) |
135 | power = controller->ops->get_power(slot); |
136 | return power; |
137 | } |
138 | |
139 | static int |
140 | get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) |
141 | { |
142 | struct slot *slot = to_slot(hotplug_slot); |
143 | |
144 | *value = cpci_get_power_status(slot); |
145 | return 0; |
146 | } |
147 | |
148 | static int |
149 | get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) |
150 | { |
151 | struct slot *slot = to_slot(hotplug_slot); |
152 | |
153 | *value = cpci_get_attention_status(slot); |
154 | return 0; |
155 | } |
156 | |
157 | static int |
158 | set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) |
159 | { |
160 | return cpci_set_attention_status(slot: to_slot(hotplug_slot), status); |
161 | } |
162 | |
163 | static int |
164 | get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) |
165 | { |
166 | struct slot *slot = to_slot(hotplug_slot); |
167 | |
168 | *value = slot->adapter_status; |
169 | return 0; |
170 | } |
171 | |
172 | static int |
173 | get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) |
174 | { |
175 | struct slot *slot = to_slot(hotplug_slot); |
176 | |
177 | *value = slot->latch_status; |
178 | return 0; |
179 | } |
180 | |
181 | static void release_slot(struct slot *slot) |
182 | { |
183 | pci_dev_put(dev: slot->dev); |
184 | kfree(objp: slot); |
185 | } |
186 | |
187 | #define SLOT_NAME_SIZE 6 |
188 | |
189 | int |
190 | cpci_hp_register_bus(struct pci_bus *bus, u8 first, u8 last) |
191 | { |
192 | struct slot *slot; |
193 | char name[SLOT_NAME_SIZE]; |
194 | int status; |
195 | int i; |
196 | |
197 | if (!(controller && bus)) |
198 | return -ENODEV; |
199 | |
200 | /* |
201 | * Create a structure for each slot, and register that slot |
202 | * with the pci_hotplug subsystem. |
203 | */ |
204 | for (i = first; i <= last; ++i) { |
205 | slot = kzalloc(size: sizeof(struct slot), GFP_KERNEL); |
206 | if (!slot) { |
207 | status = -ENOMEM; |
208 | goto error; |
209 | } |
210 | |
211 | slot->bus = bus; |
212 | slot->number = i; |
213 | slot->devfn = PCI_DEVFN(i, 0); |
214 | |
215 | snprintf(buf: name, SLOT_NAME_SIZE, fmt: "%02x:%02x" , bus->number, i); |
216 | |
217 | slot->hotplug_slot.ops = &cpci_hotplug_slot_ops; |
218 | |
219 | dbg("registering slot %s" , name); |
220 | status = pci_hp_register(&slot->hotplug_slot, bus, i, name); |
221 | if (status) { |
222 | err("pci_hp_register failed with error %d" , status); |
223 | goto error_slot; |
224 | } |
225 | dbg("slot registered with name: %s" , slot_name(slot)); |
226 | |
227 | /* Add slot to our internal list */ |
228 | down_write(sem: &list_rwsem); |
229 | list_add(new: &slot->slot_list, head: &slot_list); |
230 | slots++; |
231 | up_write(sem: &list_rwsem); |
232 | } |
233 | return 0; |
234 | error_slot: |
235 | kfree(objp: slot); |
236 | error: |
237 | return status; |
238 | } |
239 | EXPORT_SYMBOL_GPL(cpci_hp_register_bus); |
240 | |
241 | int |
242 | cpci_hp_unregister_bus(struct pci_bus *bus) |
243 | { |
244 | struct slot *slot; |
245 | struct slot *tmp; |
246 | int status = 0; |
247 | |
248 | down_write(sem: &list_rwsem); |
249 | if (!slots) { |
250 | up_write(sem: &list_rwsem); |
251 | return -1; |
252 | } |
253 | list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) { |
254 | if (slot->bus == bus) { |
255 | list_del(entry: &slot->slot_list); |
256 | slots--; |
257 | |
258 | dbg("deregistering slot %s" , slot_name(slot)); |
259 | pci_hp_deregister(slot: &slot->hotplug_slot); |
260 | release_slot(slot); |
261 | } |
262 | } |
263 | up_write(sem: &list_rwsem); |
264 | return status; |
265 | } |
266 | EXPORT_SYMBOL_GPL(cpci_hp_unregister_bus); |
267 | |
268 | /* This is the interrupt mode interrupt handler */ |
269 | static irqreturn_t |
270 | cpci_hp_intr(int irq, void *data) |
271 | { |
272 | dbg("entered cpci_hp_intr" ); |
273 | |
274 | /* Check to see if it was our interrupt */ |
275 | if ((controller->irq_flags & IRQF_SHARED) && |
276 | !controller->ops->check_irq(controller->dev_id)) { |
277 | dbg("exited cpci_hp_intr, not our interrupt" ); |
278 | return IRQ_NONE; |
279 | } |
280 | |
281 | /* Disable ENUM interrupt */ |
282 | controller->ops->disable_irq(); |
283 | |
284 | /* Trigger processing by the event thread */ |
285 | wake_up_process(tsk: cpci_thread); |
286 | return IRQ_HANDLED; |
287 | } |
288 | |
289 | /* |
290 | * According to PICMG 2.1 R2.0, section 6.3.2, upon |
291 | * initialization, the system driver shall clear the |
292 | * INS bits of the cold-inserted devices. |
293 | */ |
294 | static int |
295 | init_slots(int clear_ins) |
296 | { |
297 | struct slot *slot; |
298 | struct pci_dev *dev; |
299 | |
300 | dbg("%s - enter" , __func__); |
301 | down_read(sem: &list_rwsem); |
302 | if (!slots) { |
303 | up_read(sem: &list_rwsem); |
304 | return -1; |
305 | } |
306 | list_for_each_entry(slot, &slot_list, slot_list) { |
307 | dbg("%s - looking at slot %s" , __func__, slot_name(slot)); |
308 | if (clear_ins && cpci_check_and_clear_ins(slot)) |
309 | dbg("%s - cleared INS for slot %s" , |
310 | __func__, slot_name(slot)); |
311 | dev = pci_get_slot(bus: slot->bus, PCI_DEVFN(slot->number, 0)); |
312 | if (dev) { |
313 | slot->adapter_status = 1; |
314 | slot->latch_status = 1; |
315 | slot->dev = dev; |
316 | } |
317 | } |
318 | up_read(sem: &list_rwsem); |
319 | dbg("%s - exit" , __func__); |
320 | return 0; |
321 | } |
322 | |
323 | static int |
324 | check_slots(void) |
325 | { |
326 | struct slot *slot; |
327 | int ; |
328 | int inserted; |
329 | u16 hs_csr; |
330 | |
331 | down_read(sem: &list_rwsem); |
332 | if (!slots) { |
333 | up_read(sem: &list_rwsem); |
334 | err("no slots registered, shutting down" ); |
335 | return -1; |
336 | } |
337 | extracted = inserted = 0; |
338 | list_for_each_entry(slot, &slot_list, slot_list) { |
339 | dbg("%s - looking at slot %s" , __func__, slot_name(slot)); |
340 | if (cpci_check_and_clear_ins(slot)) { |
341 | /* |
342 | * Some broken hardware (e.g. PLX 9054AB) asserts |
343 | * ENUM# twice... |
344 | */ |
345 | if (slot->dev) { |
346 | warn("slot %s already inserted" , |
347 | slot_name(slot)); |
348 | inserted++; |
349 | continue; |
350 | } |
351 | |
352 | /* Process insertion */ |
353 | dbg("%s - slot %s inserted" , __func__, slot_name(slot)); |
354 | |
355 | /* GSM, debug */ |
356 | hs_csr = cpci_get_hs_csr(slot); |
357 | dbg("%s - slot %s HS_CSR (1) = %04x" , |
358 | __func__, slot_name(slot), hs_csr); |
359 | |
360 | /* Configure device */ |
361 | dbg("%s - configuring slot %s" , |
362 | __func__, slot_name(slot)); |
363 | if (cpci_configure_slot(slot)) { |
364 | err("%s - could not configure slot %s" , |
365 | __func__, slot_name(slot)); |
366 | continue; |
367 | } |
368 | dbg("%s - finished configuring slot %s" , |
369 | __func__, slot_name(slot)); |
370 | |
371 | /* GSM, debug */ |
372 | hs_csr = cpci_get_hs_csr(slot); |
373 | dbg("%s - slot %s HS_CSR (2) = %04x" , |
374 | __func__, slot_name(slot), hs_csr); |
375 | |
376 | slot->latch_status = 1; |
377 | slot->adapter_status = 1; |
378 | |
379 | cpci_led_off(slot); |
380 | |
381 | /* GSM, debug */ |
382 | hs_csr = cpci_get_hs_csr(slot); |
383 | dbg("%s - slot %s HS_CSR (3) = %04x" , |
384 | __func__, slot_name(slot), hs_csr); |
385 | |
386 | inserted++; |
387 | } else if (cpci_check_ext(slot)) { |
388 | /* Process extraction request */ |
389 | dbg("%s - slot %s extracted" , |
390 | __func__, slot_name(slot)); |
391 | |
392 | /* GSM, debug */ |
393 | hs_csr = cpci_get_hs_csr(slot); |
394 | dbg("%s - slot %s HS_CSR = %04x" , |
395 | __func__, slot_name(slot), hs_csr); |
396 | |
397 | if (!slot->extracting) { |
398 | slot->latch_status = 0; |
399 | slot->extracting = 1; |
400 | atomic_inc(v: &extracting); |
401 | } |
402 | extracted++; |
403 | } else if (slot->extracting) { |
404 | hs_csr = cpci_get_hs_csr(slot); |
405 | if (hs_csr == 0xffff) { |
406 | /* |
407 | * Hmmm, we're likely hosed at this point, should we |
408 | * bother trying to tell the driver or not? |
409 | */ |
410 | err("card in slot %s was improperly removed" , |
411 | slot_name(slot)); |
412 | slot->adapter_status = 0; |
413 | slot->extracting = 0; |
414 | atomic_dec(v: &extracting); |
415 | } |
416 | } |
417 | } |
418 | up_read(sem: &list_rwsem); |
419 | dbg("inserted=%d, extracted=%d, extracting=%d" , |
420 | inserted, extracted, atomic_read(&extracting)); |
421 | if (inserted || extracted) |
422 | return extracted; |
423 | else if (!atomic_read(v: &extracting)) { |
424 | err("cannot find ENUM# source, shutting down" ); |
425 | return -1; |
426 | } |
427 | return 0; |
428 | } |
429 | |
430 | /* This is the interrupt mode worker thread body */ |
431 | static int |
432 | event_thread(void *data) |
433 | { |
434 | int rc; |
435 | |
436 | dbg("%s - event thread started" , __func__); |
437 | while (1) { |
438 | dbg("event thread sleeping" ); |
439 | set_current_state(TASK_INTERRUPTIBLE); |
440 | schedule(); |
441 | if (kthread_should_stop()) |
442 | break; |
443 | do { |
444 | rc = check_slots(); |
445 | if (rc > 0) { |
446 | /* Give userspace a chance to handle extraction */ |
447 | msleep(msecs: 500); |
448 | } else if (rc < 0) { |
449 | dbg("%s - error checking slots" , __func__); |
450 | thread_finished = 1; |
451 | goto out; |
452 | } |
453 | } while (atomic_read(v: &extracting) && !kthread_should_stop()); |
454 | if (kthread_should_stop()) |
455 | break; |
456 | |
457 | /* Re-enable ENUM# interrupt */ |
458 | dbg("%s - re-enabling irq" , __func__); |
459 | controller->ops->enable_irq(); |
460 | } |
461 | out: |
462 | return 0; |
463 | } |
464 | |
465 | /* This is the polling mode worker thread body */ |
466 | static int |
467 | poll_thread(void *data) |
468 | { |
469 | int rc; |
470 | |
471 | while (1) { |
472 | if (kthread_should_stop() || signal_pending(current)) |
473 | break; |
474 | if (controller->ops->query_enum()) { |
475 | do { |
476 | rc = check_slots(); |
477 | if (rc > 0) { |
478 | /* Give userspace a chance to handle extraction */ |
479 | msleep(msecs: 500); |
480 | } else if (rc < 0) { |
481 | dbg("%s - error checking slots" , __func__); |
482 | thread_finished = 1; |
483 | goto out; |
484 | } |
485 | } while (atomic_read(v: &extracting) && !kthread_should_stop()); |
486 | } |
487 | msleep(msecs: 100); |
488 | } |
489 | out: |
490 | return 0; |
491 | } |
492 | |
493 | static int |
494 | cpci_start_thread(void) |
495 | { |
496 | if (controller->irq) |
497 | cpci_thread = kthread_run(event_thread, NULL, "cpci_hp_eventd" ); |
498 | else |
499 | cpci_thread = kthread_run(poll_thread, NULL, "cpci_hp_polld" ); |
500 | if (IS_ERR(ptr: cpci_thread)) { |
501 | err("Can't start up our thread" ); |
502 | return PTR_ERR(ptr: cpci_thread); |
503 | } |
504 | thread_finished = 0; |
505 | return 0; |
506 | } |
507 | |
508 | static void |
509 | cpci_stop_thread(void) |
510 | { |
511 | kthread_stop(k: cpci_thread); |
512 | thread_finished = 1; |
513 | } |
514 | |
515 | int |
516 | cpci_hp_register_controller(struct cpci_hp_controller *new_controller) |
517 | { |
518 | int status = 0; |
519 | |
520 | if (controller) |
521 | return -1; |
522 | if (!(new_controller && new_controller->ops)) |
523 | return -EINVAL; |
524 | if (new_controller->irq) { |
525 | if (!(new_controller->ops->enable_irq && |
526 | new_controller->ops->disable_irq)) |
527 | status = -EINVAL; |
528 | if (request_irq(irq: new_controller->irq, |
529 | handler: cpci_hp_intr, |
530 | flags: new_controller->irq_flags, |
531 | MY_NAME, |
532 | dev: new_controller->dev_id)) { |
533 | err("Can't get irq %d for the hotplug cPCI controller" , |
534 | new_controller->irq); |
535 | status = -ENODEV; |
536 | } |
537 | dbg("%s - acquired controller irq %d" , |
538 | __func__, new_controller->irq); |
539 | } |
540 | if (!status) |
541 | controller = new_controller; |
542 | return status; |
543 | } |
544 | EXPORT_SYMBOL_GPL(cpci_hp_register_controller); |
545 | |
546 | static void |
547 | cleanup_slots(void) |
548 | { |
549 | struct slot *slot; |
550 | struct slot *tmp; |
551 | |
552 | /* |
553 | * Unregister all of our slots with the pci_hotplug subsystem, |
554 | * and free up all memory that we had allocated. |
555 | */ |
556 | down_write(sem: &list_rwsem); |
557 | if (!slots) |
558 | goto cleanup_null; |
559 | list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) { |
560 | list_del(entry: &slot->slot_list); |
561 | pci_hp_deregister(slot: &slot->hotplug_slot); |
562 | release_slot(slot); |
563 | } |
564 | cleanup_null: |
565 | up_write(sem: &list_rwsem); |
566 | } |
567 | |
568 | int |
569 | cpci_hp_unregister_controller(struct cpci_hp_controller *old_controller) |
570 | { |
571 | int status = 0; |
572 | |
573 | if (controller) { |
574 | if (!thread_finished) |
575 | cpci_stop_thread(); |
576 | if (controller->irq) |
577 | free_irq(controller->irq, controller->dev_id); |
578 | controller = NULL; |
579 | cleanup_slots(); |
580 | } else |
581 | status = -ENODEV; |
582 | return status; |
583 | } |
584 | EXPORT_SYMBOL_GPL(cpci_hp_unregister_controller); |
585 | |
586 | int |
587 | cpci_hp_start(void) |
588 | { |
589 | static int first = 1; |
590 | int status; |
591 | |
592 | dbg("%s - enter" , __func__); |
593 | if (!controller) |
594 | return -ENODEV; |
595 | |
596 | down_read(sem: &list_rwsem); |
597 | if (list_empty(head: &slot_list)) { |
598 | up_read(sem: &list_rwsem); |
599 | return -ENODEV; |
600 | } |
601 | up_read(sem: &list_rwsem); |
602 | |
603 | status = init_slots(clear_ins: first); |
604 | if (first) |
605 | first = 0; |
606 | if (status) |
607 | return status; |
608 | |
609 | status = cpci_start_thread(); |
610 | if (status) |
611 | return status; |
612 | dbg("%s - thread started" , __func__); |
613 | |
614 | if (controller->irq) { |
615 | /* Start enum interrupt processing */ |
616 | dbg("%s - enabling irq" , __func__); |
617 | controller->ops->enable_irq(); |
618 | } |
619 | dbg("%s - exit" , __func__); |
620 | return 0; |
621 | } |
622 | EXPORT_SYMBOL_GPL(cpci_hp_start); |
623 | |
624 | int |
625 | cpci_hp_stop(void) |
626 | { |
627 | if (!controller) |
628 | return -ENODEV; |
629 | if (controller->irq) { |
630 | /* Stop enum interrupt processing */ |
631 | dbg("%s - disabling irq" , __func__); |
632 | controller->ops->disable_irq(); |
633 | } |
634 | cpci_stop_thread(); |
635 | return 0; |
636 | } |
637 | EXPORT_SYMBOL_GPL(cpci_hp_stop); |
638 | |
639 | int __init |
640 | cpci_hotplug_init(int debug) |
641 | { |
642 | cpci_debug = debug; |
643 | return 0; |
644 | } |
645 | |