NVMe: add sysfs and ioctl controller reset
authorKeith Busch <keith.busch@intel.com>
Fri, 5 Jun 2015 16:30:08 +0000 (10:30 -0600)
committerJens Axboe <axboe@fb.com>
Fri, 5 Jun 2015 16:30:08 +0000 (10:30 -0600)
We need the ability to perform an nvme controller reset as discussed on
the mailing list thread:

  http://lists.infradead.org/pipermail/linux-nvme/2015-March/001585.html

This adds a sysfs entry that when written to will reset perform an NVMe
controller reset if the controller was successfully initialized in the
first place.

This also adds locking around resetting the device in the async probe
method so the driver can't schedule two resets.

Signed-off-by: Keith Busch <keith.busch@intel.com>
Cc: Brandon Schultz <brandon.schulz@hgst.com>
Cc: David Sariel <david.sariel@pmcs.com>
Updated by Jens to:

1) Merge this with the ioctl reset patch from David Sariel. The ioctl
   path now shares the reset code from the sysfs path.

2) Don't flush work if we fail issuing the reset.

Signed-off-by: Jens Axboe <axboe@fb.com>
drivers/block/nvme-core.c
include/uapi/linux/nvme.h

index 513908ff46c4b34129ba6f17f6eaddc1c23ccd06..9682e29b417146ab10b6a578d81d73dee12f3403 100644 (file)
@@ -80,6 +80,7 @@ static wait_queue_head_t nvme_kthread_wait;
 static struct class *nvme_class;
 
 static void nvme_reset_failed_dev(struct work_struct *ws);
+static int nvme_reset(struct nvme_dev *dev);
 static int nvme_process_cq(struct nvme_queue *nvmeq);
 
 struct async_cmd_info {
@@ -2689,6 +2690,9 @@ static long nvme_dev_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
                        return -ENOTTY;
                ns = list_first_entry(&dev->namespaces, struct nvme_ns, list);
                return nvme_user_cmd(dev, ns, (void __user *)arg);
+       case NVME_IOCTL_RESET:
+               dev_warn(dev->dev, "resetting controller\n");
+               return nvme_reset(dev);
        default:
                return -ENOTTY;
        }
@@ -2839,6 +2843,44 @@ static void nvme_reset_workfn(struct work_struct *work)
        dev->reset_workfn(work);
 }
 
+static int nvme_reset(struct nvme_dev *dev)
+{
+       int ret = -EBUSY;
+
+       if (!dev->admin_q || blk_queue_dying(dev->admin_q))
+               return -ENODEV;
+
+       spin_lock(&dev_list_lock);
+       if (!work_pending(&dev->reset_work)) {
+               dev->reset_workfn = nvme_reset_failed_dev;
+               queue_work(nvme_workq, &dev->reset_work);
+               ret = 0;
+       }
+       spin_unlock(&dev_list_lock);
+
+       if (!ret) {
+               flush_work(&dev->reset_work);
+               return 0;
+       }
+
+       return ret;
+}
+
+static ssize_t nvme_sysfs_reset(struct device *dev,
+                               struct device_attribute *attr, const char *buf,
+                               size_t count)
+{
+       struct nvme_dev *ndev = dev_get_drvdata(dev);
+       int ret;
+
+       ret = nvme_reset(ndev);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+static DEVICE_ATTR(reset_controller, S_IWUSR, NULL, nvme_sysfs_reset);
+
 static void nvme_async_probe(struct work_struct *work);
 static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 {
@@ -2883,12 +2925,20 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)
                goto release_pools;
        }
        get_device(dev->device);
+       dev_set_drvdata(dev->device, dev);
+
+       result = device_create_file(dev->device, &dev_attr_reset_controller);
+       if (result)
+               goto put_dev;
 
        INIT_LIST_HEAD(&dev->node);
        INIT_WORK(&dev->probe_work, nvme_async_probe);
        schedule_work(&dev->probe_work);
        return 0;
 
+ put_dev:
+       device_destroy(nvme_class, MKDEV(nvme_char_major, dev->instance));
+       put_device(dev->device);
  release_pools:
        nvme_release_prp_pools(dev);
  release:
@@ -2919,10 +2969,12 @@ static void nvme_async_probe(struct work_struct *work)
        nvme_set_irq_hints(dev);
        return;
  reset:
+       spin_lock(&dev_list_lock);
        if (!work_busy(&dev->reset_work)) {
                dev->reset_workfn = nvme_reset_failed_dev;
                queue_work(nvme_workq, &dev->reset_work);
        }
+       spin_unlock(&dev_list_lock);
 }
 
 static void nvme_reset_notify(struct pci_dev *pdev, bool prepare)
@@ -2952,6 +3004,7 @@ static void nvme_remove(struct pci_dev *pdev)
        pci_set_drvdata(pdev, NULL);
        flush_work(&dev->probe_work);
        flush_work(&dev->reset_work);
+       device_remove_file(dev->device, &dev_attr_reset_controller);
        nvme_dev_shutdown(dev);
        nvme_dev_remove(dev);
        nvme_dev_remove_admin(dev);
index aef9a81b2d75b9524000f3b1b1bf858464e575e2..b660dc2fadfba0393dd7e419f8beac34f13971e3 100644 (file)
@@ -579,5 +579,6 @@ struct nvme_passthru_cmd {
 #define NVME_IOCTL_ADMIN_CMD   _IOWR('N', 0x41, struct nvme_admin_cmd)
 #define NVME_IOCTL_SUBMIT_IO   _IOW('N', 0x42, struct nvme_user_io)
 #define NVME_IOCTL_IO_CMD      _IOWR('N', 0x43, struct nvme_passthru_cmd)
+#define NVME_IOCTL_RESET       _IO('N', 0x44)
 
 #endif /* _UAPI_LINUX_NVME_H */