Commit 4cc06521 authored by Keith Busch's avatar Keith Busch Committed by Jens Axboe

NVMe: add sysfs and ioctl controller reset

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: default avatarKeith 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: default avatarJens Axboe <axboe@fb.com>
parent 8b70f45e
...@@ -80,6 +80,7 @@ static wait_queue_head_t nvme_kthread_wait; ...@@ -80,6 +80,7 @@ static wait_queue_head_t nvme_kthread_wait;
static struct class *nvme_class; static struct class *nvme_class;
static void nvme_reset_failed_dev(struct work_struct *ws); 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); static int nvme_process_cq(struct nvme_queue *nvmeq);
struct async_cmd_info { struct async_cmd_info {
...@@ -2689,6 +2690,9 @@ static long nvme_dev_ioctl(struct file *f, unsigned int cmd, unsigned long arg) ...@@ -2689,6 +2690,9 @@ static long nvme_dev_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
return -ENOTTY; return -ENOTTY;
ns = list_first_entry(&dev->namespaces, struct nvme_ns, list); ns = list_first_entry(&dev->namespaces, struct nvme_ns, list);
return nvme_user_cmd(dev, ns, (void __user *)arg); 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: default:
return -ENOTTY; return -ENOTTY;
} }
...@@ -2839,6 +2843,44 @@ static void nvme_reset_workfn(struct work_struct *work) ...@@ -2839,6 +2843,44 @@ static void nvme_reset_workfn(struct work_struct *work)
dev->reset_workfn(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 void nvme_async_probe(struct work_struct *work);
static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) 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) ...@@ -2883,12 +2925,20 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto release_pools; goto release_pools;
} }
get_device(dev->device); 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_LIST_HEAD(&dev->node);
INIT_WORK(&dev->probe_work, nvme_async_probe); INIT_WORK(&dev->probe_work, nvme_async_probe);
schedule_work(&dev->probe_work); schedule_work(&dev->probe_work);
return 0; return 0;
put_dev:
device_destroy(nvme_class, MKDEV(nvme_char_major, dev->instance));
put_device(dev->device);
release_pools: release_pools:
nvme_release_prp_pools(dev); nvme_release_prp_pools(dev);
release: release:
...@@ -2919,10 +2969,12 @@ static void nvme_async_probe(struct work_struct *work) ...@@ -2919,10 +2969,12 @@ static void nvme_async_probe(struct work_struct *work)
nvme_set_irq_hints(dev); nvme_set_irq_hints(dev);
return; return;
reset: reset:
spin_lock(&dev_list_lock);
if (!work_busy(&dev->reset_work)) { if (!work_busy(&dev->reset_work)) {
dev->reset_workfn = nvme_reset_failed_dev; dev->reset_workfn = nvme_reset_failed_dev;
queue_work(nvme_workq, &dev->reset_work); queue_work(nvme_workq, &dev->reset_work);
} }
spin_unlock(&dev_list_lock);
} }
static void nvme_reset_notify(struct pci_dev *pdev, bool prepare) static void nvme_reset_notify(struct pci_dev *pdev, bool prepare)
...@@ -2952,6 +3004,7 @@ static void nvme_remove(struct pci_dev *pdev) ...@@ -2952,6 +3004,7 @@ static void nvme_remove(struct pci_dev *pdev)
pci_set_drvdata(pdev, NULL); pci_set_drvdata(pdev, NULL);
flush_work(&dev->probe_work); flush_work(&dev->probe_work);
flush_work(&dev->reset_work); flush_work(&dev->reset_work);
device_remove_file(dev->device, &dev_attr_reset_controller);
nvme_dev_shutdown(dev); nvme_dev_shutdown(dev);
nvme_dev_remove(dev); nvme_dev_remove(dev);
nvme_dev_remove_admin(dev); nvme_dev_remove_admin(dev);
......
...@@ -579,5 +579,6 @@ struct nvme_passthru_cmd { ...@@ -579,5 +579,6 @@ struct nvme_passthru_cmd {
#define NVME_IOCTL_ADMIN_CMD _IOWR('N', 0x41, struct nvme_admin_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_SUBMIT_IO _IOW('N', 0x42, struct nvme_user_io)
#define NVME_IOCTL_IO_CMD _IOWR('N', 0x43, struct nvme_passthru_cmd) #define NVME_IOCTL_IO_CMD _IOWR('N', 0x43, struct nvme_passthru_cmd)
#define NVME_IOCTL_RESET _IO('N', 0x44)
#endif /* _UAPI_LINUX_NVME_H */ #endif /* _UAPI_LINUX_NVME_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment