diff options
author | Bjorn Helgaas <bhelgaas@google.com> | 2013-01-11 14:21:15 -0500 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2013-01-14 12:23:22 -0500 |
commit | f652e7d2916fe2fcf9e7d709aa5b7476b431e2dd (patch) | |
tree | 3de39a91ba274a307e11face7f5bbb8a3f7244b5 /drivers | |
parent | d347e75847c1fb299c97736638f45e6ea39702d4 (diff) |
PCI: shpchp: Use per-slot workqueues to avoid deadlock
When we have an SHPC-capable bridge with a second SHPC-capable bridge
below it, pushing the upstream bridge's attention button causes a
deadlock.
The deadlock happens because we use the shpchp_wq workqueue to run
shpchp_pushbutton_thread(), which uses shpchp_disable_slot() to remove
devices below the upstream bridge. When we remove the downstream bridge,
we call shpc_remove(), the shpchp driver's .remove() method. That calls
flush_workqueue(shpchp_wq), which deadlocks because the
shpchp_pushbutton_thread() work item is still running.
This patch avoids the deadlock by creating a workqueue for every slot
and removing the single shared workqueue.
Here's the call path that leads to the deadlock:
shpchp_queue_pushbutton_work
queue_work(shpchp_wq) # shpchp_pushbutton_thread
...
shpchp_pushbutton_thread
shpchp_disable_slot
remove_board
shpchp_unconfigure_device
pci_stop_and_remove_bus_device
...
shpc_remove # shpchp driver .remove method
hpc_release_ctlr
cleanup_slots
flush_workqueue(shpchp_wq)
This change is based on code inspection, since we don't have hardware
with this topology.
Based-on-patch-by: Yijing Wang <wangyijing@huawei.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
CC: stable@vger.kernel.org
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/pci/hotplug/shpchp.h | 2 | ||||
-rw-r--r-- | drivers/pci/hotplug/shpchp_core.c | 26 | ||||
-rw-r--r-- | drivers/pci/hotplug/shpchp_ctrl.c | 6 |
3 files changed, 18 insertions, 16 deletions
diff --git a/drivers/pci/hotplug/shpchp.h b/drivers/pci/hotplug/shpchp.h index 1b69d955a31f..b849f995075a 100644 --- a/drivers/pci/hotplug/shpchp.h +++ b/drivers/pci/hotplug/shpchp.h | |||
@@ -46,7 +46,6 @@ | |||
46 | extern bool shpchp_poll_mode; | 46 | extern bool shpchp_poll_mode; |
47 | extern int shpchp_poll_time; | 47 | extern int shpchp_poll_time; |
48 | extern bool shpchp_debug; | 48 | extern bool shpchp_debug; |
49 | extern struct workqueue_struct *shpchp_wq; | ||
50 | 49 | ||
51 | #define dbg(format, arg...) \ | 50 | #define dbg(format, arg...) \ |
52 | do { \ | 51 | do { \ |
@@ -90,6 +89,7 @@ struct slot { | |||
90 | struct list_head slot_list; | 89 | struct list_head slot_list; |
91 | struct delayed_work work; /* work for button event */ | 90 | struct delayed_work work; /* work for button event */ |
92 | struct mutex lock; | 91 | struct mutex lock; |
92 | struct workqueue_struct *wq; | ||
93 | u8 hp_slot; | 93 | u8 hp_slot; |
94 | }; | 94 | }; |
95 | 95 | ||
diff --git a/drivers/pci/hotplug/shpchp_core.c b/drivers/pci/hotplug/shpchp_core.c index 3774e0d5506e..3100c52c837c 100644 --- a/drivers/pci/hotplug/shpchp_core.c +++ b/drivers/pci/hotplug/shpchp_core.c | |||
@@ -39,7 +39,6 @@ | |||
39 | bool shpchp_debug; | 39 | bool shpchp_debug; |
40 | bool shpchp_poll_mode; | 40 | bool shpchp_poll_mode; |
41 | int shpchp_poll_time; | 41 | int shpchp_poll_time; |
42 | struct workqueue_struct *shpchp_wq; | ||
43 | 42 | ||
44 | #define DRIVER_VERSION "0.4" | 43 | #define DRIVER_VERSION "0.4" |
45 | #define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>, Dely Sy <dely.l.sy@intel.com>" | 44 | #define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>, Dely Sy <dely.l.sy@intel.com>" |
@@ -128,6 +127,14 @@ static int init_slots(struct controller *ctrl) | |||
128 | slot->device = ctrl->slot_device_offset + i; | 127 | slot->device = ctrl->slot_device_offset + i; |
129 | slot->hpc_ops = ctrl->hpc_ops; | 128 | slot->hpc_ops = ctrl->hpc_ops; |
130 | slot->number = ctrl->first_slot + (ctrl->slot_num_inc * i); | 129 | slot->number = ctrl->first_slot + (ctrl->slot_num_inc * i); |
130 | |||
131 | snprintf(name, sizeof(name), "shpchp-%d", slot->number); | ||
132 | slot->wq = alloc_workqueue(name, 0, 0); | ||
133 | if (!slot->wq) { | ||
134 | retval = -ENOMEM; | ||
135 | goto error_info; | ||
136 | } | ||
137 | |||
131 | mutex_init(&slot->lock); | 138 | mutex_init(&slot->lock); |
132 | INIT_DELAYED_WORK(&slot->work, shpchp_queue_pushbutton_work); | 139 | INIT_DELAYED_WORK(&slot->work, shpchp_queue_pushbutton_work); |
133 | 140 | ||
@@ -147,7 +154,7 @@ static int init_slots(struct controller *ctrl) | |||
147 | if (retval) { | 154 | if (retval) { |
148 | ctrl_err(ctrl, "pci_hp_register failed with error %d\n", | 155 | ctrl_err(ctrl, "pci_hp_register failed with error %d\n", |
149 | retval); | 156 | retval); |
150 | goto error_info; | 157 | goto error_slotwq; |
151 | } | 158 | } |
152 | 159 | ||
153 | get_power_status(hotplug_slot, &info->power_status); | 160 | get_power_status(hotplug_slot, &info->power_status); |
@@ -159,6 +166,8 @@ static int init_slots(struct controller *ctrl) | |||
159 | } | 166 | } |
160 | 167 | ||
161 | return 0; | 168 | return 0; |
169 | error_slotwq: | ||
170 | destroy_workqueue(slot->wq); | ||
162 | error_info: | 171 | error_info: |
163 | kfree(info); | 172 | kfree(info); |
164 | error_hpslot: | 173 | error_hpslot: |
@@ -179,7 +188,7 @@ void cleanup_slots(struct controller *ctrl) | |||
179 | slot = list_entry(tmp, struct slot, slot_list); | 188 | slot = list_entry(tmp, struct slot, slot_list); |
180 | list_del(&slot->slot_list); | 189 | list_del(&slot->slot_list); |
181 | cancel_delayed_work(&slot->work); | 190 | cancel_delayed_work(&slot->work); |
182 | flush_workqueue(shpchp_wq); | 191 | destroy_workqueue(slot->wq); |
183 | pci_hp_deregister(slot->hotplug_slot); | 192 | pci_hp_deregister(slot->hotplug_slot); |
184 | } | 193 | } |
185 | } | 194 | } |
@@ -362,18 +371,12 @@ static struct pci_driver shpc_driver = { | |||
362 | 371 | ||
363 | static int __init shpcd_init(void) | 372 | static int __init shpcd_init(void) |
364 | { | 373 | { |
365 | int retval = 0; | 374 | int retval; |
366 | |||
367 | shpchp_wq = alloc_workqueue("shpchp", 0, 0); | ||
368 | if (!shpchp_wq) | ||
369 | return -ENOMEM; | ||
370 | 375 | ||
371 | retval = pci_register_driver(&shpc_driver); | 376 | retval = pci_register_driver(&shpc_driver); |
372 | dbg("%s: pci_register_driver = %d\n", __func__, retval); | 377 | dbg("%s: pci_register_driver = %d\n", __func__, retval); |
373 | info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); | 378 | info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); |
374 | if (retval) { | 379 | |
375 | destroy_workqueue(shpchp_wq); | ||
376 | } | ||
377 | return retval; | 380 | return retval; |
378 | } | 381 | } |
379 | 382 | ||
@@ -381,7 +384,6 @@ static void __exit shpcd_cleanup(void) | |||
381 | { | 384 | { |
382 | dbg("unload_shpchpd()\n"); | 385 | dbg("unload_shpchpd()\n"); |
383 | pci_unregister_driver(&shpc_driver); | 386 | pci_unregister_driver(&shpc_driver); |
384 | destroy_workqueue(shpchp_wq); | ||
385 | info(DRIVER_DESC " version: " DRIVER_VERSION " unloaded\n"); | 387 | info(DRIVER_DESC " version: " DRIVER_VERSION " unloaded\n"); |
386 | } | 388 | } |
387 | 389 | ||
diff --git a/drivers/pci/hotplug/shpchp_ctrl.c b/drivers/pci/hotplug/shpchp_ctrl.c index fd2cae9eb6c2..58499277903a 100644 --- a/drivers/pci/hotplug/shpchp_ctrl.c +++ b/drivers/pci/hotplug/shpchp_ctrl.c | |||
@@ -51,7 +51,7 @@ static int queue_interrupt_event(struct slot *p_slot, u32 event_type) | |||
51 | info->p_slot = p_slot; | 51 | info->p_slot = p_slot; |
52 | INIT_WORK(&info->work, interrupt_event_handler); | 52 | INIT_WORK(&info->work, interrupt_event_handler); |
53 | 53 | ||
54 | queue_work(shpchp_wq, &info->work); | 54 | queue_work(p_slot->wq, &info->work); |
55 | 55 | ||
56 | return 0; | 56 | return 0; |
57 | } | 57 | } |
@@ -453,7 +453,7 @@ void shpchp_queue_pushbutton_work(struct work_struct *work) | |||
453 | kfree(info); | 453 | kfree(info); |
454 | goto out; | 454 | goto out; |
455 | } | 455 | } |
456 | queue_work(shpchp_wq, &info->work); | 456 | queue_work(p_slot->wq, &info->work); |
457 | out: | 457 | out: |
458 | mutex_unlock(&p_slot->lock); | 458 | mutex_unlock(&p_slot->lock); |
459 | } | 459 | } |
@@ -501,7 +501,7 @@ static void handle_button_press_event(struct slot *p_slot) | |||
501 | p_slot->hpc_ops->green_led_blink(p_slot); | 501 | p_slot->hpc_ops->green_led_blink(p_slot); |
502 | p_slot->hpc_ops->set_attention_status(p_slot, 0); | 502 | p_slot->hpc_ops->set_attention_status(p_slot, 0); |
503 | 503 | ||
504 | queue_delayed_work(shpchp_wq, &p_slot->work, 5*HZ); | 504 | queue_delayed_work(p_slot->wq, &p_slot->work, 5*HZ); |
505 | break; | 505 | break; |
506 | case BLINKINGOFF_STATE: | 506 | case BLINKINGOFF_STATE: |
507 | case BLINKINGON_STATE: | 507 | case BLINKINGON_STATE: |