Fixes for SCSI device removal
Subsystems (2)
v8 cover Fixes for SCSI device removal
This is version eight of the SCSI device removal patch series. This version of this patch series has been tested by triggering a large number of removals of a SCSI device controlled by the ib_srp LLD and at the same time running an I/O integrity test with fio on a dm device on top of the SRP SCSI device.
Changes compared to v7:
- Made sure that __scsi_queue_insert() processes the request that has been passed to that function. Added a comment in that function too.
- Fixed a typo in the description of the first patch in this series and removed "CC: stable".
Changes compared to v6:
- Added a fix for a race in the block layer.
- Moved a BUG_ON(!sdev) statement up since it's a precondition check.
- Changed return type of scsi_queue_insert() from int to void.
- Added a cancel_work_sync(&sdev->requeue_work) call in __scsi_remove_device().
Changes compared to v5:
- Removed the function scsi_free_queue() and inlined that function in its callers.
- Added two additional patches.
PATCH v8 1/4 block: Fix race on request.end_io invocations
Some request.end_io implementations can be called safely without the queue lock held while several other implementations assume that the queue lock is held. So let's play it safe and make sure that the queue lock is held around all end_io invocations. Found this through source code review.
Note: blk_finish_request() already invokes request.end_io with the queue lock held.
--- block/blk-exec.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
block/blk-exec.c +1 -1
@@ -54,10 +54,10 @@ void blk_execute_rq_nowait(struct request_queue *q, struct gendisk *bd_disk,spin_lock_irq (q->queue_lock);if (unlikely (blk_queue_dead (q))) { -spin_unlock_irq (q->queue_lock); rq->errors = -ENXIO ;if (rq->end_io) rq->end_io (rq, rq->errors); +spin_unlock_irq (q->queue_lock);return ; }
Signature · 1 line
1.7.7
PATCH v8 2/4 scsi: Fix device removal NULL pointer dereference
In reply to Bart Van AsscheSince scsi_prep_fn() may be invoked concurrently with __scsi_remove_device(), keep the queuedata pointer in __scsi_remove_device(). This patch fixes a kernel oops that can be triggered by USB device removal. See also
http://www.spinics.net/lists/linux-scsi/msg56254.html.
--- drivers/scsi/hosts.c | 8 +++++++- drivers/scsi/scsi_lib.c | 35 ++++++++--------------------------- drivers/scsi/scsi_priv.h | 1 - drivers/scsi/scsi_sysfs.c | 5 +---- 4 files changed, 16 insertions(+), 33 deletions(-)
drivers/scsi/hosts.c +7 -1
@@ -299,9 +299,15 @@ static void scsi_host_dev_release(struct device *dev)destroy_workqueue (shost->work_q); q = shost->uspace_req_q;if (q) { +/* +* Note: freeing queuedata before invoking blk_cleanup_queue() +* is safe here because no request function is associated with +* uspace_req_q. See also the __scsi_alloc_queue() call in +* drivers/scsi/scsi_tgt_lib.c. +*/ kfree (q->queuedata); q->queuedata =NULL ; -scsi_free_queue (q); +blk_cleanup_queue (q); }scsi_destroy_command_freelist (shost);
drivers/scsi/scsi_lib.c +8 -27
@@ -406,10 +406,7 @@ static void scsi_run_queue(struct request_queue *q)LIST_HEAD (starved_list);unsigned long flags; -/* if the device is dead, sdev will be NULL, so no queue to run */ -if (!sdev) -return ; - +BUG_ON (!sdev); shost = sdev->host;if (scsi_target (sdev)->single_lun)scsi_single_lun_run (sdev);
@@ -1370,16 +1367,18 @@ static inline int scsi_host_queue_ready(struct request_queue *q,* may be changed after request stacking drivers call the function, * regardless of taking lock or not. * -* When scsi can't dispatch I/Os anymore and needs to kill I/Os -* (e.g. !sdev), scsi needs to return 'not busy'. -* Otherwise, request stacking drivers may hold requests forever. +* When scsi can't dispatch I/Os anymore and needs to kill I/Os scsi +* needs to return 'not busy'. Otherwise, request stacking drivers +* may hold requests forever. */ static int scsi_lld_busy (struct request_queue *q) {struct scsi_device *sdev = q->queuedata;struct Scsi_Host *shost; -if (!sdev) +BUG_ON (!sdev); + +if (blk_queue_dead (q))return 0 ; shost = sdev->host;
@@ -1490,11 +1489,7 @@ static void scsi_request_fn(struct request_queue *q)struct scsi_cmnd *cmd;struct request *req; -if (!sdev) { -while ((req =blk_peek_request (q)) !=NULL ) -scsi_kill_request (req, q); -return ; - } +BUG_ON (!sdev);if (!get_device (&sdev->sdev_gendev))/* We must be tearing the block queue down already */
@@ -1697,20 +1692,6 @@ struct request_queue *scsi_alloc_queue(struct scsi_device *sdev)return q; } -void scsi_free_queue (struct request_queue *q) -{ -unsigned long flags; - -WARN_ON (q->queuedata); - -/* cause scsi_request_fn() to kill all non-finished requests */ -spin_lock_irqsave (q->queue_lock, flags); - q->request_fn (q); -spin_unlock_irqrestore (q->queue_lock, flags); - -blk_cleanup_queue (q); -} -/* * Function: scsi_block_requests() *
drivers/scsi/scsi_priv.h +0 -1
@@ -84,7 +84,6 @@ extern void scsi_next_command(struct scsi_cmnd *cmd);extern void scsi_io_completion (struct scsi_cmnd *,unsigned int );extern void scsi_run_host_queues (struct Scsi_Host *shost);extern struct request_queue *scsi_alloc_queue (struct scsi_device *sdev); -extern void scsi_free_queue (struct request_queue *q);extern int scsi_init_queue (void );extern void scsi_exit_queue (void );struct request_queue ;
drivers/scsi/scsi_sysfs.c +1 -4
@@ -971,11 +971,8 @@ void __scsi_remove_device(struct scsi_device *sdev) sdev->host->hostt->slave_destroy (sdev);transport_destroy_device (dev); -/* cause the request function to reject all I/O requests */ - sdev->request_queue->queuedata =NULL ; -/* Freeing the queue signals to block that we're done */ -scsi_free_queue (sdev->request_queue); +blk_cleanup_queue (sdev->request_queue);put_device (dev); }
Signature · 1 line
1.7.7
PATCH v8 3/4 scsi: Change return type of scsi_queue_insert() into void
In reply to Bart Van AsscheThe return value of scsi_queue_insert() is ignored by all its callers, hence change the return type of this function into void.
--- drivers/scsi/scsi_lib.c | 8 +++----- drivers/scsi/scsi_priv.h | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-)
drivers/scsi/scsi_lib.c +3 -5
@@ -109,7 +109,7 @@ static void scsi_unprep_request(struct request *req)* for a requeue after completion, which should only occur in this * file. */ -static int __scsi_queue_insert (struct scsi_cmnd *cmd,int reason,int unbusy) +static void __scsi_queue_insert (struct scsi_cmnd *cmd,int reason,int unbusy) {struct Scsi_Host *host = cmd->device->host;struct scsi_device *device = cmd->device;
@@ -162,8 +162,6 @@ static int __scsi_queue_insert(struct scsi_cmnd *cmd, int reason, int unbusy)spin_unlock_irqrestore (q->queue_lock, flags);kblockd_schedule_work (q, &device->requeue_work); - -return 0 ; }/*
@@ -185,9 +183,9 @@ static int __scsi_queue_insert(struct scsi_cmnd *cmd, int reason, int unbusy)* Notes: This could be called either from an interrupt context or a * normal process context. */ -int scsi_queue_insert (struct scsi_cmnd *cmd,int reason) +void scsi_queue_insert (struct scsi_cmnd *cmd,int reason) { -return __scsi_queue_insert (cmd, reason,1 ); +__scsi_queue_insert (cmd, reason,1 ); }/** * scsi_execute - insert request and wait for the result
drivers/scsi/scsi_priv.h +1 -1
@@ -79,7 +79,7 @@ int scsi_noretry_cmd(struct scsi_cmnd *scmd);/* scsi_lib.c */ extern int scsi_maybe_unblock_host (struct scsi_device *sdev);extern void scsi_device_unbusy (struct scsi_device *sdev); -extern int scsi_queue_insert (struct scsi_cmnd *cmd,int reason); +extern void scsi_queue_insert (struct scsi_cmnd *cmd,int reason);extern void scsi_next_command (struct scsi_cmnd *cmd);extern void scsi_io_completion (struct scsi_cmnd *,unsigned int );extern void scsi_run_host_queues (struct Scsi_Host *shost);
PATCH v8 4/4 scsi: Stop accepting SCSI requests before removing a device
In reply to Bart Van AsscheAvoid that the code for requeueing SCSI requests triggers a crash by making sure that that code isn't scheduled anymore after a device has been removed.
Also, source code inspection of __scsi_remove_device() revealed a race condition in this function: no new SCSI requests must be accepted for a SCSI device after device removal started.
--- drivers/scsi/scsi_lib.c | 7 ++++--- drivers/scsi/scsi_sysfs.c | 11 +++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-)
drivers/scsi/scsi_lib.c +4 -3
@@ -155,13 +155,14 @@ static void __scsi_queue_insert(struct scsi_cmnd *cmd, int reason, int unbusy)/* * Requeue this command. It will go before all other commands -* that are already in the queue. +* that are already in the queue. Schedule requeue work under +* lock such that the kblockd_schedule_work() call happens +* before blk_cleanup_queue() finishes. */ spin_lock_irqsave (q->queue_lock, flags);blk_requeue_request (q, cmd->request); -spin_unlock_irqrestore (q->queue_lock, flags); -kblockd_schedule_work (q, &device->requeue_work); +spin_unlock_irqrestore (q->queue_lock, flags); }/*
drivers/scsi/scsi_sysfs.c +9 -2
@@ -966,13 +966,20 @@ void __scsi_remove_device(struct scsi_device *sdev)device_del (dev); }else put_device (&sdev->sdev_dev); + +/* +* Stop accepting new requests and wait until all queuecommand() and +* scsi_run_queue() invocations have finished before tearing down the +* device. +*/ scsi_device_set_state (sdev,SDEV_DEL ); +blk_cleanup_queue (sdev->request_queue); +cancel_work_sync (&sdev->requeue_work); +if (sdev->host->hostt->slave_destroy) sdev->host->hostt->slave_destroy (sdev);transport_destroy_device (dev); -/* Freeing the queue signals to block that we're done */ -blk_cleanup_queue (sdev->request_queue);put_device (dev); }
Quoted 6 lines · Bart Van Assche
This is version eight of the SCSI device removal patch series. This version of this patch series has been tested by triggering a large number of removals of a SCSI device controlled by the
ib_srpLLD and at the same time running an I/O integrity test with fio on a dm device on top of the SRP SCSI device.
Patches look ok to me.
I also tested them in a distro kernel and they fix a oops we were seeing there.