aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pci
diff options
context:
space:
mode:
authorIan Abbott <abbotti@mev.co.uk>2008-01-25 11:23:56 -0500
committerGreg Kroah-Hartman <gregkh@suse.de>2008-02-01 18:04:30 -0500
commit5c796ae7a7ebe56967ed9b9963d7c16d733635ff (patch)
tree6e110a095b52e42ef4eaae566de4702441c77313 /drivers/pci
parent4600c9d74e23b5696acf66a36ce5f2cfbcdecc6c (diff)
PCI: Fix fakephp deadlock
If the fakephp driver is used to emulate removal of a PCI device by writing text string "0" to the "power" sysfs attribute file, this causes its parent directory and its contents (including the "power" file) to be deleted before the write operation returns. Unfortunately, it ends up in a deadlock waiting for itself to complete. The deadlock is as follows: sysfs_write_file calls flush_write_buffer which calls sysfs_get_active_two before calling power_write_file in pci_hotplug_core.c via the sysfs store operation. The power_write_file function calls disable_slot in fakephp.c via the slot operation. The disable_slot function calls remove_slot which calls pci_hp_deregister (back in pci_hotplug_core.c) which calls fs_remove_slot which calls sysfs_remove_file to remove the "power" file. The sysfs_remove_file function calls sysfs_hash_and_remove which calls sysfs_addrm_finish which calls sysfs_deactivate. The sysfs_deactivate function sees that something has an active reference on the sysfs_dirent (from the previous call to sysfs_get_active_two back up the call stack somewhere) so waits for the active reference to go away, which is of course impossible. The problem has been present since 2.6.21. This patch breaks the deadlock by queuing work queue items on a single- threaded work queue to remove a slot from sysfs, and to rescan the PCI buses. There is also some protection against disabling a slot that is already being removed. Signed-off-by: Ian Abbott <abbotti@mev.co.uk> Cc: Kristen Accardi <kristen.c.accardi@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/pci')
-rw-r--r--drivers/pci/hotplug/fakephp.c39
1 files changed, 35 insertions, 4 deletions
diff --git a/drivers/pci/hotplug/fakephp.c b/drivers/pci/hotplug/fakephp.c
index d7a293e3faf5..94b640146d44 100644
--- a/drivers/pci/hotplug/fakephp.c
+++ b/drivers/pci/hotplug/fakephp.c
@@ -39,6 +39,7 @@
39#include <linux/init.h> 39#include <linux/init.h>
40#include <linux/string.h> 40#include <linux/string.h>
41#include <linux/slab.h> 41#include <linux/slab.h>
42#include <linux/workqueue.h>
42#include "../pci.h" 43#include "../pci.h"
43 44
44#if !defined(MODULE) 45#if !defined(MODULE)
@@ -63,10 +64,16 @@ struct dummy_slot {
63 struct list_head node; 64 struct list_head node;
64 struct hotplug_slot *slot; 65 struct hotplug_slot *slot;
65 struct pci_dev *dev; 66 struct pci_dev *dev;
67 struct work_struct remove_work;
68 unsigned long removed;
66}; 69};
67 70
68static int debug; 71static int debug;
69static LIST_HEAD(slot_list); 72static LIST_HEAD(slot_list);
73static struct workqueue_struct *dummyphp_wq;
74
75static void pci_rescan_worker(struct work_struct *work);
76static DECLARE_WORK(pci_rescan_work, pci_rescan_worker);
70 77
71static int enable_slot (struct hotplug_slot *slot); 78static int enable_slot (struct hotplug_slot *slot);
72static int disable_slot (struct hotplug_slot *slot); 79static int disable_slot (struct hotplug_slot *slot);
@@ -109,7 +116,7 @@ static int add_slot(struct pci_dev *dev)
109 slot->name = &dev->dev.bus_id[0]; 116 slot->name = &dev->dev.bus_id[0];
110 dbg("slot->name = %s\n", slot->name); 117 dbg("slot->name = %s\n", slot->name);
111 118
112 dslot = kmalloc(sizeof(struct dummy_slot), GFP_KERNEL); 119 dslot = kzalloc(sizeof(struct dummy_slot), GFP_KERNEL);
113 if (!dslot) 120 if (!dslot)
114 goto error_info; 121 goto error_info;
115 122
@@ -164,6 +171,14 @@ static void remove_slot(struct dummy_slot *dslot)
164 err("Problem unregistering a slot %s\n", dslot->slot->name); 171 err("Problem unregistering a slot %s\n", dslot->slot->name);
165} 172}
166 173
174/* called from the single-threaded workqueue handler to remove a slot */
175static void remove_slot_worker(struct work_struct *work)
176{
177 struct dummy_slot *dslot =
178 container_of(work, struct dummy_slot, remove_work);
179 remove_slot(dslot);
180}
181
167/** 182/**
168 * pci_rescan_slot - Rescan slot 183 * pci_rescan_slot - Rescan slot
169 * @temp: Device template. Should be set: bus and devfn. 184 * @temp: Device template. Should be set: bus and devfn.
@@ -267,11 +282,17 @@ static inline void pci_rescan(void) {
267 pci_rescan_buses(&pci_root_buses); 282 pci_rescan_buses(&pci_root_buses);
268} 283}
269 284
285/* called from the single-threaded workqueue handler to rescan all pci buses */
286static void pci_rescan_worker(struct work_struct *work)
287{
288 pci_rescan();
289}
270 290
271static int enable_slot(struct hotplug_slot *hotplug_slot) 291static int enable_slot(struct hotplug_slot *hotplug_slot)
272{ 292{
273 /* mis-use enable_slot for rescanning of the pci bus */ 293 /* mis-use enable_slot for rescanning of the pci bus */
274 pci_rescan(); 294 cancel_work_sync(&pci_rescan_work);
295 queue_work(dummyphp_wq, &pci_rescan_work);
275 return -ENODEV; 296 return -ENODEV;
276} 297}
277 298
@@ -306,6 +327,10 @@ static int disable_slot(struct hotplug_slot *slot)
306 err("Can't remove PCI devices with other PCI devices behind it yet.\n"); 327 err("Can't remove PCI devices with other PCI devices behind it yet.\n");
307 return -ENODEV; 328 return -ENODEV;
308 } 329 }
330 if (test_and_set_bit(0, &dslot->removed)) {
331 dbg("Slot already scheduled for removal\n");
332 return -ENODEV;
333 }
309 /* search for subfunctions and disable them first */ 334 /* search for subfunctions and disable them first */
310 if (!(dslot->dev->devfn & 7)) { 335 if (!(dslot->dev->devfn & 7)) {
311 for (func = 1; func < 8; func++) { 336 for (func = 1; func < 8; func++) {
@@ -328,8 +353,9 @@ static int disable_slot(struct hotplug_slot *slot)
328 /* remove the device from the pci core */ 353 /* remove the device from the pci core */
329 pci_remove_bus_device(dslot->dev); 354 pci_remove_bus_device(dslot->dev);
330 355
331 /* blow away this sysfs entry and other parts. */ 356 /* queue work item to blow away this sysfs entry and other parts. */
332 remove_slot(dslot); 357 INIT_WORK(&dslot->remove_work, remove_slot_worker);
358 queue_work(dummyphp_wq, &dslot->remove_work);
333 359
334 return 0; 360 return 0;
335} 361}
@@ -340,6 +366,7 @@ static void cleanup_slots (void)
340 struct list_head *next; 366 struct list_head *next;
341 struct dummy_slot *dslot; 367 struct dummy_slot *dslot;
342 368
369 destroy_workqueue(dummyphp_wq);
343 list_for_each_safe (tmp, next, &slot_list) { 370 list_for_each_safe (tmp, next, &slot_list) {
344 dslot = list_entry (tmp, struct dummy_slot, node); 371 dslot = list_entry (tmp, struct dummy_slot, node);
345 remove_slot(dslot); 372 remove_slot(dslot);
@@ -351,6 +378,10 @@ static int __init dummyphp_init(void)
351{ 378{
352 info(DRIVER_DESC "\n"); 379 info(DRIVER_DESC "\n");
353 380
381 dummyphp_wq = create_singlethread_workqueue(MY_NAME);
382 if (!dummyphp_wq)
383 return -ENOMEM;
384
354 return pci_scan_buses(); 385 return pci_scan_buses();
355} 386}
356 387