--- /dev/null
+From c2be6f93b383c873a4f9d521afa49b1b67d06085 Mon Sep 17 00:00:00 2001
+From: Yijing Wang <wangyijing@huawei.com>
+Date: Fri, 11 Jan 2013 10:15:54 +0800
+Subject: PCI: pciehp: Use per-slot workqueues to avoid deadlock
+
+From: Yijing Wang <wangyijing@huawei.com>
+
+commit c2be6f93b383c873a4f9d521afa49b1b67d06085 upstream.
+
+When we have a hotplug-capable PCIe port with a second hotplug-capable
+PCIe port below it, removing the device below the upstream port causes
+a deadlock.
+
+The deadlock happens because we use the pciehp_wq workqueue to run
+pciehp_power_thread(), which uses pciehp_disable_slot() to remove devices
+below the upstream port. When we remove the downstream PCIe port, we call
+pciehp_remove(), the pciehp driver's .remove() method. That calls
+flush_workqueue(pciehp_wq), which deadlocks because the
+pciehp_power_thread() work item is still running.
+
+This patch avoids the deadlock by creating a workqueue for every PCIe port
+and removing the single shared workqueue.
+
+Here's the call path that leads to the deadlock:
+
+ pciehp_queue_pushbutton_work
+ queue_work(pciehp_wq) # queue pciehp_power_thread
+ ...
+
+ pciehp_power_thread
+ pciehp_disable_slot
+ remove_board
+ pciehp_unconfigure_device
+ pci_stop_and_remove_bus_device
+ ...
+ pciehp_remove # pciehp driver .remove method
+ pciehp_release_ctrl
+ pcie_cleanup_slot
+ flush_workqueue(pciehp_wq)
+
+This is fairly urgent because it can be caused by simply unplugging a
+Thunderbolt adapter, as reported by Daniel below.
+
+[bhelgaas: changelog]
+Reference: http://lkml.kernel.org/r/CAMVG2ssiRgcTD1bej2tkUUfsWmpL5eNtPcNif9va2-Gzb2u8nQ@mail.gmail.com
+Reported-and-tested-by: Daniel J Blueman <daniel@quora.org>
+Reviewed-by: Kenji Kaneshige <kaneshige.kenji@jp.fujitsu.com>
+Signed-off-by: Yijing Wang <wangyijing@huawei.com>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ drivers/pci/hotplug/pciehp.h | 2 +-
+ drivers/pci/hotplug/pciehp_core.c | 11 ++---------
+ drivers/pci/hotplug/pciehp_ctrl.c | 8 ++++----
+ drivers/pci/hotplug/pciehp_hpc.c | 11 ++++++++++-
+ 4 files changed, 17 insertions(+), 15 deletions(-)
+
+--- a/drivers/pci/hotplug/pciehp.h
++++ b/drivers/pci/hotplug/pciehp.h
+@@ -44,7 +44,6 @@ extern bool pciehp_poll_mode;
+ extern int pciehp_poll_time;
+ extern bool pciehp_debug;
+ extern bool pciehp_force;
+-extern struct workqueue_struct *pciehp_wq;
+
+ #define dbg(format, arg...) \
+ do { \
+@@ -78,6 +77,7 @@ struct slot {
+ struct hotplug_slot *hotplug_slot;
+ struct delayed_work work; /* work for button event */
+ struct mutex lock;
++ struct workqueue_struct *wq;
+ };
+
+ struct event_info {
+--- a/drivers/pci/hotplug/pciehp_core.c
++++ b/drivers/pci/hotplug/pciehp_core.c
+@@ -42,7 +42,6 @@ bool pciehp_debug;
+ bool pciehp_poll_mode;
+ int pciehp_poll_time;
+ bool pciehp_force;
+-struct workqueue_struct *pciehp_wq;
+
+ #define DRIVER_VERSION "0.4"
+ #define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>, Dely Sy <dely.l.sy@intel.com>"
+@@ -340,18 +339,13 @@ static int __init pcied_init(void)
+ {
+ int retval = 0;
+
+- pciehp_wq = alloc_workqueue("pciehp", 0, 0);
+- if (!pciehp_wq)
+- return -ENOMEM;
+-
+ pciehp_firmware_init();
+ retval = pcie_port_service_register(&hpdriver_portdrv);
+ dbg("pcie_port_service_register = %d\n", retval);
+ info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
+- if (retval) {
+- destroy_workqueue(pciehp_wq);
++ if (retval)
+ dbg("Failure to register service\n");
+- }
++
+ return retval;
+ }
+
+@@ -359,7 +353,6 @@ static void __exit pcied_cleanup(void)
+ {
+ dbg("unload_pciehpd()\n");
+ pcie_port_service_unregister(&hpdriver_portdrv);
+- destroy_workqueue(pciehp_wq);
+ info(DRIVER_DESC " version: " DRIVER_VERSION " unloaded\n");
+ }
+
+--- a/drivers/pci/hotplug/pciehp_ctrl.c
++++ b/drivers/pci/hotplug/pciehp_ctrl.c
+@@ -49,7 +49,7 @@ static int queue_interrupt_event(struct
+ info->p_slot = p_slot;
+ INIT_WORK(&info->work, interrupt_event_handler);
+
+- queue_work(pciehp_wq, &info->work);
++ queue_work(p_slot->wq, &info->work);
+
+ return 0;
+ }
+@@ -344,7 +344,7 @@ void pciehp_queue_pushbutton_work(struct
+ kfree(info);
+ goto out;
+ }
+- queue_work(pciehp_wq, &info->work);
++ queue_work(p_slot->wq, &info->work);
+ out:
+ mutex_unlock(&p_slot->lock);
+ }
+@@ -377,7 +377,7 @@ static void handle_button_press_event(st
+ if (ATTN_LED(ctrl))
+ pciehp_set_attention_status(p_slot, 0);
+
+- queue_delayed_work(pciehp_wq, &p_slot->work, 5*HZ);
++ queue_delayed_work(p_slot->wq, &p_slot->work, 5*HZ);
+ break;
+ case BLINKINGOFF_STATE:
+ case BLINKINGON_STATE:
+@@ -439,7 +439,7 @@ static void handle_surprise_event(struct
+ else
+ p_slot->state = POWERON_STATE;
+
+- queue_work(pciehp_wq, &info->work);
++ queue_work(p_slot->wq, &info->work);
+ }
+
+ static void interrupt_event_handler(struct work_struct *work)
+--- a/drivers/pci/hotplug/pciehp_hpc.c
++++ b/drivers/pci/hotplug/pciehp_hpc.c
+@@ -874,23 +874,32 @@ static void pcie_shutdown_notification(s
+ static int pcie_init_slot(struct controller *ctrl)
+ {
+ struct slot *slot;
++ char name[32];
+
+ slot = kzalloc(sizeof(*slot), GFP_KERNEL);
+ if (!slot)
+ return -ENOMEM;
+
++ snprintf(name, sizeof(name), "pciehp-%u", PSN(ctrl));
++ slot->wq = alloc_workqueue(name, 0, 0);
++ if (!slot->wq)
++ goto abort;
++
+ slot->ctrl = ctrl;
+ mutex_init(&slot->lock);
+ INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work);
+ ctrl->slot = slot;
+ return 0;
++abort:
++ kfree(slot);
++ return -ENOMEM;
+ }
+
+ static void pcie_cleanup_slot(struct controller *ctrl)
+ {
+ struct slot *slot = ctrl->slot;
+ cancel_delayed_work(&slot->work);
+- flush_workqueue(pciehp_wq);
++ destroy_workqueue(slot->wq);
+ kfree(slot);
+ }
+
--- /dev/null
+From d347e75847c1fb299c97736638f45e6ea39702d4 Mon Sep 17 00:00:00 2001
+From: Bjorn Helgaas <bhelgaas@google.com>
+Date: Fri, 11 Jan 2013 12:07:22 -0700
+Subject: PCI: shpchp: Handle push button event asynchronously
+
+From: Bjorn Helgaas <bhelgaas@google.com>
+
+commit d347e75847c1fb299c97736638f45e6ea39702d4 upstream.
+
+Use non-ordered workqueue for attention button events.
+
+Attention button events on each slot can be handled asynchronously. So
+we should use non-ordered workqueue. This patch also removes ordered
+workqueue in shpchp as a result.
+
+486b10b9f4 ("PCI: pciehp: Handle push button event asynchronously") made
+the same change to pciehp. I split this out from a patch by Yijing Wang
+<wangyijing@huawei.com> so we fix one thing at a time and to make the
+shpchp history correspond more closely with the pciehp history.
+
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+CC: Kenji Kaneshige <kaneshige.kenji@jp.fujitsu.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ drivers/pci/hotplug/shpchp.h | 1 -
+ drivers/pci/hotplug/shpchp_core.c | 10 ----------
+ drivers/pci/hotplug/shpchp_ctrl.c | 2 +-
+ 3 files changed, 1 insertion(+), 12 deletions(-)
+
+--- a/drivers/pci/hotplug/shpchp.h
++++ b/drivers/pci/hotplug/shpchp.h
+@@ -47,7 +47,6 @@ extern bool shpchp_poll_mode;
+ extern int shpchp_poll_time;
+ extern bool shpchp_debug;
+ extern struct workqueue_struct *shpchp_wq;
+-extern struct workqueue_struct *shpchp_ordered_wq;
+
+ #define dbg(format, arg...) \
+ do { \
+--- a/drivers/pci/hotplug/shpchp_core.c
++++ b/drivers/pci/hotplug/shpchp_core.c
+@@ -40,7 +40,6 @@ bool shpchp_debug;
+ bool shpchp_poll_mode;
+ int shpchp_poll_time;
+ struct workqueue_struct *shpchp_wq;
+-struct workqueue_struct *shpchp_ordered_wq;
+
+ #define DRIVER_VERSION "0.4"
+ #define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>, Dely Sy <dely.l.sy@intel.com>"
+@@ -175,7 +174,6 @@ void cleanup_slots(struct controller *ct
+ list_del(&slot->slot_list);
+ cancel_delayed_work(&slot->work);
+ flush_workqueue(shpchp_wq);
+- flush_workqueue(shpchp_ordered_wq);
+ pci_hp_deregister(slot->hotplug_slot);
+ }
+ }
+@@ -364,17 +362,10 @@ static int __init shpcd_init(void)
+ if (!shpchp_wq)
+ return -ENOMEM;
+
+- shpchp_ordered_wq = alloc_ordered_workqueue("shpchp_ordered", 0);
+- if (!shpchp_ordered_wq) {
+- destroy_workqueue(shpchp_wq);
+- return -ENOMEM;
+- }
+-
+ retval = pci_register_driver(&shpc_driver);
+ dbg("%s: pci_register_driver = %d\n", __func__, retval);
+ info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
+ if (retval) {
+- destroy_workqueue(shpchp_ordered_wq);
+ destroy_workqueue(shpchp_wq);
+ }
+ return retval;
+@@ -384,7 +375,6 @@ static void __exit shpcd_cleanup(void)
+ {
+ dbg("unload_shpchpd()\n");
+ pci_unregister_driver(&shpc_driver);
+- destroy_workqueue(shpchp_ordered_wq);
+ destroy_workqueue(shpchp_wq);
+ info(DRIVER_DESC " version: " DRIVER_VERSION " unloaded\n");
+ }
+--- a/drivers/pci/hotplug/shpchp_ctrl.c
++++ b/drivers/pci/hotplug/shpchp_ctrl.c
+@@ -456,7 +456,7 @@ void shpchp_queue_pushbutton_work(struct
+ kfree(info);
+ goto out;
+ }
+- queue_work(shpchp_ordered_wq, &info->work);
++ queue_work(shpchp_wq, &info->work);
+ out:
+ mutex_unlock(&p_slot->lock);
+ }