nvme/pci: Use async_schedule for initial reset work
PATCH 1/1 nvme/pci: Use async_schedule for initial reset work
This patch schedules the initial controller reset in an async_domain so that it can be synchronized from wait_for_device_probe(). This way the kernel waits for the first nvme controller initialization to complete for all devices before proceeding with the boot sequence, which may have nvme dependencies.
--- drivers/nvme/host/pci.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)
drivers/nvme/host/pci.c +8 -1
@@ -13,6 +13,7 @@*/ #include <linux/aer.h> +#include <linux/async.h> #include <linux/blkdev.h> #include <linux/blk-mq.h> #include <linux/blk-mq-pci.h>
@@ -2489,6 +2490,12 @@ static unsigned long check_vendor_combination_bug(struct pci_dev *pdev)return 0 ; } +static void nvme_async_probe (void *data,async_cookie_t cookie) +{ +struct nvme_dev *dev = data; +nvme_reset_ctrl_sync (&dev->ctrl); +} +static int nvme_probe (struct pci_dev *pdev,const struct pci_device_id *id) {int node, result = -ENOMEM ;
@@ -2533,7 +2540,7 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)dev_info (dev->ctrl.device,"pci function %s\n" ,dev_name (&pdev->dev)); -nvme_reset_ctrl (&dev->ctrl); +async_schedule (nvme_async_probe, dev);return 0 ;
Signature · 1 line
2.14.3
PATCH 1/1 nvme/pci: Use async_schedule for initial reset work
Quoted 38 lines · Keith Busch
This patch schedules the initial controller reset in an
async_domainso that it can be synchronized fromwait_for_device_probe(). This way the kernel waits for the first nvme controller initialization to complete for all devices before proceeding with the boot sequence, which may have nvme dependencies.Reported-by Mikulas Patocka <mpatocka at redhat.com>Signed-off-by Keith Busch <keith.busch at intel.com>--- drivers/nvme/host/pci.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)drivers/nvme/host/pci.c +7
-0@@ -13,6 +13,7 @@*/ #include <linux/aer.h> +#include <linux/async.h> #include <linux/blkdev.h> #include <linux/blk-mq.h> #include <linux/blk-mq-pci.h> @@ -2489,6 +2490,12 @@ static unsigned long check_vendor_combination_bug(struct pci_dev *pdev)return 0 ; } +static void nvme_async_probe (void *data,async_cookie_t cookie) +{ +struct nvme_dev *dev = data; +nvme_reset_ctrl_sync (&dev->ctrl); +} +static int nvme_probe (struct pci_dev *pdev,const struct pci_device_id *id) {int node, result = -ENOMEM ;
drivers/nvme/host/pci.c static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) · line 2545
Quoted 7 lines
drivers/nvme/host/pci.c +1
-1@@ -2533,7 +2540,7 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)dev_info (dev->ctrl.device,"pci function %s\n" ,dev_name (&pdev->dev)); -nvme_reset_ctrl (&dev->ctrl); +async_schedule (nvme_async_probe, dev);return 0 ;
Looks fine,
Signature · 1 line
Ming Lei
PATCH 1/1 nvme/pci: Use async_schedule for initial reset work
Quoted 3 lines · Ming Lei
Looks fine,
Reviewed-by Ming Lei <ming.lei at redhat.com>
Thanks, Ming.
Mikulas, would you be able to test this and confirm it works for you? This appears successful in my testing, but want to hear from the source if possible.
Thanks,
Keith
PATCH 1/1 nvme/pci: Use async_schedule for initial reset work
Quoted 5 lines · Keith Busch
Quoted 3 lines · Ming Lei
Looks fine,
Reviewed-by Ming Lei <ming.lei at redhat.com>
Quoted 8 lines
Thanks, Ming.
Mikulas, would you be able to test this and confirm it works for you? This appears successful in my testing, but want to hear from the source if possible.
Thanks,
Keith
The patch is not correct - scan_work is still called from a workqueue and if it's too slow, the nvme device is not found when mounting root.
You can add msleep(10000) at the beginning of nvme_scan_work to test for the race condition on your system.
Here I submit the corrected patch - I added flush_work(&dev->ctrl.scan_work) to nvme_async_probe
Mikulas
This patch schedules the initial controller reset in an async_domain so that it can be synchronized from wait_for_device_probe(). This way the kernel waits for the first nvme controller initialization to complete for all devices before proceeding with the boot sequence, which may have nvme dependencies.
--- drivers/nvme/host/pci.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-)
drivers/nvme/host/pci.c +9 -1
@@ -13,6 +13,7 @@*/ #include <linux/aer.h> +#include <linux/async.h> #include <linux/blkdev.h> #include <linux/blk-mq.h> #include <linux/blk-mq-pci.h>
@@ -2471,6 +2472,13 @@ static unsigned long check_vendor_combinreturn 0 ; } +static void nvme_async_probe (void *data,async_cookie_t cookie) +{ +struct nvme_dev *dev = data; +nvme_reset_ctrl_sync (&dev->ctrl); +flush_work (&dev->ctrl.scan_work); +} +static int nvme_probe (struct pci_dev *pdev,const struct pci_device_id *id) {int node, result = -ENOMEM ;
@@ -2515,7 +2523,7 @@ static int nvme_probe(struct pci_dev *pddev_info (dev->ctrl.device,"pci function %s\n" ,dev_name (&pdev->dev)); -nvme_reset_ctrl (&dev->ctrl); +async_schedule (nvme_async_probe, dev);return 0 ;
PATCH 1/1 nvme/pci: Use async_schedule for initial reset work
Quoted 16 lines · Mikulas Patocka
Quoted 5 lines · Keith Busch
Quoted 3 lines · Ming Lei
Looks fine,
Reviewed-by Ming Lei <ming.lei at redhat.com>Quoted 8 lines
Thanks, Ming.
Mikulas, would you be able to test this and confirm it works for you? This appears successful in my testing, but want to hear from the source if possible.
Thanks,
Keith
Quoted 8 lines
The patch is not correct -
scan_workis still called from a workqueue and if it's too slow, the nvme device is not found when mounting root.You can add msleep(10000) at the beginning of
nvme_scan_workto test for the race condition on your system.Here I submit the corrected patch - I added
flush_work(&dev->ctrl.scan_work)tonvme_async_probe
Roger that. Will incorporate your adjustment and add your Tested-by. Thanks for the confirmation.
PATCH 1/1 nvme/pci: Use async_schedule for initial reset work
Quoted 20 lines · Keith Busch
Quoted 19 lines · Mikulas Patocka
Quoted 5 lines · Keith Busch
Quoted 3 lines · Ming Lei
Looks fine,
Reviewed-by Ming Lei <ming.lei at redhat.com>Quoted 8 lines
Thanks, Ming.
Mikulas, would you be able to test this and confirm it works for you? This appears successful in my testing, but want to hear from the source if possible.
Thanks,
KeithThe patch is not correct -
scan_workis still called from a workqueue and if it's too slow, the nvme device is not found when mounting root.
Quoted 8 lines
Quoted 5 lines
You can add msleep(10000) at the beginning of
nvme_scan_workto test for the race condition on your system.Here I submit the corrected patch - I added
flush_work(&dev->ctrl.scan_work)tonvme_async_probeRoger that. Will incorporate your adjustment and add your Tested-by. Thanks for the confirmation.
I think there is still one more bug:
If nvme_probe is called, it schedules the asynchronous work using async_schedule - now suppose that the pci system calls the "remove", "shutdown" or "suspend" method - this method will race with nvme_async_probe running in the async domain - that will cause misbehavior.
Or - does the PCI subsystem flush the async queues before calling these methods? I'm not sure, but it doesn't seem so.
I think, you need to save the cookie returned by async_schedule and wait for this cookie with async_synchronize_cookie in the other methods.
Mikulas
This patch schedules the initial controller reset in an async_domain so that it can be synchronized from wait_for_device_probe(). This way the kernel waits for the first nvme controller initialization to complete for all devices before proceeding with the boot sequence, which may have nvme dependencies.
--- drivers/nvme/host/pci.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-)
drivers/nvme/host/pci.c +20 -1
@@ -13,6 +13,7 @@*/ #include <linux/aer.h> +#include <linux/async.h> #include <linux/blkdev.h> #include <linux/blk-mq.h> #include <linux/blk-mq-pci.h>
@@ -111,6 +112,8 @@ struct nvme_dev {dma_addr_t host_mem_descs_dma;struct nvme_host_mem_buf_desc *host_mem_descs;void **host_mem_desc_bufs; + +async_cookie_t probe_cookie; };static int io_queue_depth_set (const char *val,const struct kernel_param *kp)
@@ -2471,6 +2474,13 @@ static unsigned long check_vendor_combinreturn 0 ; } +static void nvme_async_probe (void *data,async_cookie_t cookie) +{ +struct nvme_dev *dev = data; +nvme_reset_ctrl_sync (&dev->ctrl); +flush_work (&dev->ctrl.scan_work); +} +static int nvme_probe (struct pci_dev *pdev,const struct pci_device_id *id) {int node, result = -ENOMEM ;
@@ -2515,7 +2525,7 @@ static int nvme_probe(struct pci_dev *pddev_info (dev->ctrl.device,"pci function %s\n" ,dev_name (&pdev->dev)); -nvme_reset_ctrl (&dev->ctrl); + dev->probe_cookie =async_schedule (nvme_async_probe, dev);return 0 ;
@@ -2546,6 +2556,9 @@ static void nvme_reset_done(struct pci_dstatic void nvme_shutdown (struct pci_dev *pdev) {struct nvme_dev *dev =pci_get_drvdata (pdev); + +async_synchronize_cookie (dev->probe_cookie); +nvme_dev_disable (dev, true); }
@@ -2558,6 +2571,8 @@ static void nvme_remove(struct pci_dev * {struct nvme_dev *dev =pci_get_drvdata (pdev); +async_synchronize_cookie (dev->probe_cookie); +nvme_change_ctrl_state (&dev->ctrl,NVME_CTRL_DELETING );cancel_work_sync (&dev->ctrl.reset_work);
@@ -2605,6 +2620,8 @@ static int nvme_suspend(struct device *dstruct pci_dev *pdev =to_pci_dev (dev);struct nvme_dev *ndev =pci_get_drvdata (pdev); +async_synchronize_cookie (dev->probe_cookie); +nvme_dev_disable (ndev, true);return 0 ; }
@@ -2614,6 +2631,8 @@ static int nvme_resume(struct device *destruct pci_dev *pdev =to_pci_dev (dev);struct nvme_dev *ndev =pci_get_drvdata (pdev); +async_synchronize_cookie (dev->probe_cookie); +nvme_reset_ctrl (&ndev->ctrl);return 0 ; }
PATCH 1/1 nvme/pci: Use async_schedule for initial reset work
Quoted 8 lines · Mikulas Patocka
I think there is still one more bug:
If
nvme_probeis called, it schedules the asynchronous work usingasync_schedule- now suppose that the pci system calls the "remove", "shutdown" or "suspend" method - this method will race withnvme_async_proberunning in the async domain - that will cause misbehavior.
Quoted 5 lines
Or - does the PCI subsystem flush the async queues before calling these methods? I'm not sure, but it doesn't seem so.
I think, you need to save the cookie returned by
async_scheduleand wait for this cookie withasync_synchronize_cookiein the other methods.
I think we're fine as-is without syncing the cookie.
The remove path should be fine since we already sync with the necessary work queues.
The shutdown, suspend and reset paths will just cause the initial reset work to end early, the same result as what previously would happen.
PATCH 1/1 nvme/pci: Use async_schedule for initial reset work
Quoted 15 lines · Keith Busch
Quoted 8 lines · Mikulas Patocka
I think there is still one more bug:
If
nvme_probeis called, it schedules the asynchronous work usingasync_schedule- now suppose that the pci system calls the "remove", "shutdown" or "suspend" method - this method will race withnvme_async_proberunning in the async domain - that will cause misbehavior.Quoted 5 lines
Or - does the PCI subsystem flush the async queues before calling these methods? I'm not sure, but it doesn't seem so.
I think, you need to save the cookie returned by
async_scheduleand wait for this cookie withasync_synchronize_cookiein the other methods.
Quoted 7 lines
I think we're fine as-is without syncing the cookie.
The remove path should be fine since we already sync with the necessary work queues.
The shutdown, suspend and reset paths will just cause the initial reset work to end early, the same result as what previously would happen.
Suppose this:
task 1: nvme_probe
task 1: calls async_schedule(nvme_async_probe), that queues the work for
task 2
task 1: exits (so the device is active from pci subsystem's point of view) task 3: the pci subsystem calls nvme_remove
task 3: calls nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_DELETING); task 3: cancel_work_sync(&dev->ctrl.reset_work); (does nothing because the
work item hasn't started yet)
task 3: nvme_remove does all the remaining work
task 3: frees the device
task 3: exists nvme_remove
task 2: (in the async domain) runs nvme_async_probe
task 2: calls nvme_reset_ctrl_sync
task 2: nvme_reset_ctrl
task 2: calls nvme_change_ctrl_state and queue_work - on a structure that
was already freed by nvme_remove
This bug is rare - but it may happen if the user too quickly activates and deactivates the device by writing to sysfs.
Mikulas
PATCH 1/1 nvme/pci: Use async_schedule for initial reset work
Quoted 18 lines · Mikulas Patocka
Suppose this:
task 1:nvme_probe
task 1: callsasync_schedule(nvme_async_probe), that queues the work for
task 2
task 1: exits (so the device is active from pci subsystem's point of view) task 3: the pci subsystem callsnvme_remove
task 3: callsnvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_DELETING); task 3:cancel_work_sync(&dev->ctrl.reset_work); (does nothing because the
work item hasn't started yet)
task 3:nvme_removedoes all the remaining work
task 3: frees the device
task 3: existsnvme_remove
task 2: (in the async domain) runsnvme_async_probe
task 2: callsnvme_reset_ctrl_sync
task 2:nvme_reset_ctrl
task 2: callsnvme_change_ctrl_stateandqueue_work- on a structure that
was already freed bynvme_remove
Quoted 2 lines
This bug is rare - but it may happen if the user too quickly activates and deactivates the device by writing to sysfs.
Okay, I think I see your point. Pairing a nvme_get_ctrl with a nvme_put_ctrl should fix that.