diff options
author | James Bottomley <JBottomley@Parallels.com> | 2013-07-02 09:05:26 -0400 |
---|---|---|
committer | James Bottomley <JBottomley@Parallels.com> | 2013-07-09 07:14:08 -0400 |
commit | e2eb7244bc9e4fd130fc8a961224968e22ba48ee (patch) | |
tree | a1ba31c44721a9a106967e16b6065d4b98c0abeb /drivers/scsi | |
parent | fec3c1b4575431e2020c5c6502d18b281925ca45 (diff) |
[SCSI] Fix race between starved list and device removal
scsi_run_queue() examines all SCSI devices that are present on
the starved list. Since scsi_run_queue() unlocks the SCSI host
lock a SCSI device can get removed after it has been removed
from the starved list and before its queue is run. Protect
against that race condition by holding a reference on the
queue while running it.
Reported-by: Chanho Min <chanho.min@lge.com>
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
Signed-off-by: James Bottomley <JBottomley@Parallels.com>
Diffstat (limited to 'drivers/scsi')
-rw-r--r-- | drivers/scsi/scsi_lib.c | 26 |
1 files changed, 21 insertions, 5 deletions
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 86d522004a20..df8bd5ab3c0b 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c | |||
@@ -434,6 +434,8 @@ static void scsi_run_queue(struct request_queue *q) | |||
434 | list_splice_init(&shost->starved_list, &starved_list); | 434 | list_splice_init(&shost->starved_list, &starved_list); |
435 | 435 | ||
436 | while (!list_empty(&starved_list)) { | 436 | while (!list_empty(&starved_list)) { |
437 | struct request_queue *slq; | ||
438 | |||
437 | /* | 439 | /* |
438 | * As long as shost is accepting commands and we have | 440 | * As long as shost is accepting commands and we have |
439 | * starved queues, call blk_run_queue. scsi_request_fn | 441 | * starved queues, call blk_run_queue. scsi_request_fn |
@@ -456,11 +458,25 @@ static void scsi_run_queue(struct request_queue *q) | |||
456 | continue; | 458 | continue; |
457 | } | 459 | } |
458 | 460 | ||
459 | spin_unlock(shost->host_lock); | 461 | /* |
460 | spin_lock(sdev->request_queue->queue_lock); | 462 | * Once we drop the host lock, a racing scsi_remove_device() |
461 | __blk_run_queue(sdev->request_queue); | 463 | * call may remove the sdev from the starved list and destroy |
462 | spin_unlock(sdev->request_queue->queue_lock); | 464 | * it and the queue. Mitigate by taking a reference to the |
463 | spin_lock(shost->host_lock); | 465 | * queue and never touching the sdev again after we drop the |
466 | * host lock. Note: if __scsi_remove_device() invokes | ||
467 | * blk_cleanup_queue() before the queue is run from this | ||
468 | * function then blk_run_queue() will return immediately since | ||
469 | * blk_cleanup_queue() marks the queue with QUEUE_FLAG_DYING. | ||
470 | */ | ||
471 | slq = sdev->request_queue; | ||
472 | if (!blk_get_queue(slq)) | ||
473 | continue; | ||
474 | spin_unlock_irqrestore(shost->host_lock, flags); | ||
475 | |||
476 | blk_run_queue(slq); | ||
477 | blk_put_queue(slq); | ||
478 | |||
479 | spin_lock_irqsave(shost->host_lock, flags); | ||
464 | } | 480 | } |
465 | /* put any unprocessed entries back */ | 481 | /* put any unprocessed entries back */ |
466 | list_splice(&starved_list, &shost->starved_list); | 482 | list_splice(&starved_list, &shost->starved_list); |