NVMe: Fix hot cpu notification dead lock
authorKeith Busch <keith.busch@intel.com>
Wed, 11 Jun 2014 17:51:35 +0000 (11:51 -0600)
committerMatthew Wilcox <matthew.r.wilcox@intel.com>
Fri, 13 Jun 2014 14:43:34 +0000 (10:43 -0400)
There is a potential dead lock if a cpu event occurs during nvme probe
since it registered with hot cpu notification. This fixes the race by
having the module register with notification outside of probe rather
than have each device register.

The actual work is done in a scheduled work queue instead of in the
notifier since assigning IO queues has the potential to block if the
driver creates additional queues.

Signed-off-by: Keith Busch <keith.busch@intel.com>
Signed-off-by: Matthew Wilcox <matthew.r.wilcox@intel.com>
drivers/block/nvme-core.c
include/linux/nvme.h

index 2af079e571fc72e8df1d534b6675a2f8ec05f983..e0ac1210fe3111edaf45c0072d0e5f41a84ab3f8 100644 (file)
@@ -73,6 +73,7 @@ static LIST_HEAD(dev_list);
 static struct task_struct *nvme_thread;
 static struct workqueue_struct *nvme_workq;
 static wait_queue_head_t nvme_kthread_wait;
+static struct notifier_block nvme_nb;
 
 static void nvme_reset_failed_dev(struct work_struct *ws);
 
@@ -2115,14 +2116,25 @@ static size_t db_bar_size(struct nvme_dev *dev, unsigned nr_io_queues)
        return 4096 + ((nr_io_queues + 1) * 8 * dev->db_stride);
 }
 
+static void nvme_cpu_workfn(struct work_struct *work)
+{
+       struct nvme_dev *dev = container_of(work, struct nvme_dev, cpu_work);
+       if (dev->initialized)
+               nvme_assign_io_queues(dev);
+}
+
 static int nvme_cpu_notify(struct notifier_block *self,
                                unsigned long action, void *hcpu)
 {
-       struct nvme_dev *dev = container_of(self, struct nvme_dev, nb);
+       struct nvme_dev *dev;
+
        switch (action) {
        case CPU_ONLINE:
        case CPU_DEAD:
-               nvme_assign_io_queues(dev);
+               spin_lock(&dev_list_lock);
+               list_for_each_entry(dev, &dev_list, node)
+                       schedule_work(&dev->cpu_work);
+               spin_unlock(&dev_list_lock);
                break;
        }
        return NOTIFY_OK;
@@ -2191,11 +2203,6 @@ static int nvme_setup_io_queues(struct nvme_dev *dev)
        nvme_free_queues(dev, nr_io_queues + 1);
        nvme_assign_io_queues(dev);
 
-       dev->nb.notifier_call = &nvme_cpu_notify;
-       result = register_hotcpu_notifier(&dev->nb);
-       if (result)
-               goto free_queues;
-
        return 0;
 
  free_queues:
@@ -2495,8 +2502,6 @@ static void nvme_dev_shutdown(struct nvme_dev *dev)
        int i;
 
        dev->initialized = 0;
-       unregister_hotcpu_notifier(&dev->nb);
-
        nvme_dev_list_remove(dev);
 
        if (!dev->bar || (dev->bar && readl(&dev->bar->csts) == -1)) {
@@ -2767,6 +2772,7 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)
        INIT_LIST_HEAD(&dev->namespaces);
        dev->reset_workfn = nvme_reset_failed_dev;
        INIT_WORK(&dev->reset_work, nvme_reset_workfn);
+       INIT_WORK(&dev->cpu_work, nvme_cpu_workfn);
        dev->pci_dev = pdev;
        pci_set_drvdata(pdev, dev);
        result = nvme_set_instance(dev);
@@ -2836,6 +2842,7 @@ static void nvme_remove(struct pci_dev *pdev)
 
        pci_set_drvdata(pdev, NULL);
        flush_work(&dev->reset_work);
+       flush_work(&dev->cpu_work);
        misc_deregister(&dev->miscdev);
        nvme_dev_remove(dev);
        nvme_dev_shutdown(dev);
@@ -2923,11 +2930,18 @@ static int __init nvme_init(void)
        else if (result > 0)
                nvme_major = result;
 
-       result = pci_register_driver(&nvme_driver);
+       nvme_nb.notifier_call = &nvme_cpu_notify;
+       result = register_hotcpu_notifier(&nvme_nb);
        if (result)
                goto unregister_blkdev;
+
+       result = pci_register_driver(&nvme_driver);
+       if (result)
+               goto unregister_hotcpu;
        return 0;
 
+ unregister_hotcpu:
+       unregister_hotcpu_notifier(&nvme_nb);
  unregister_blkdev:
        unregister_blkdev(nvme_major, "nvme");
  kill_workq:
@@ -2938,6 +2952,7 @@ static int __init nvme_init(void)
 static void __exit nvme_exit(void)
 {
        pci_unregister_driver(&nvme_driver);
+       unregister_hotcpu_notifier(&nvme_nb);
        unregister_blkdev(nvme_major, "nvme");
        destroy_workqueue(nvme_workq);
        BUG_ON(nvme_thread && !IS_ERR(nvme_thread));
index 8541dd920bb701ca5627abfa1140a180a6d8e7f0..2bf403195c09fd5470cab096409c8ee7c260285f 100644 (file)
@@ -90,7 +90,7 @@ struct nvme_dev {
        struct miscdevice miscdev;
        work_func_t reset_workfn;
        struct work_struct reset_work;
-       struct notifier_block nb;
+       struct work_struct cpu_work;
        char name[12];
        char serial[20];
        char model[40];