SCSI: fix queue cleanup race before queue initialization is done

commit 8dc765d438 upstream.

c2856ae2f3 ("blk-mq: quiesce queue before freeing queue") has
already fixed this race, however the implied synchronize_rcu()
in blk_mq_quiesce_queue() can slow down LUN probe a lot, so caused
performance regression.

Then 1311326cf4 ("blk-mq: avoid to synchronize rcu inside blk_cleanup_queue()")
tried to quiesce queue for avoiding unnecessary synchronize_rcu()
only when queue initialization is done, because it is usual to see
lots of inexistent LUNs which need to be probed.

However, turns out it isn't safe to quiesce queue only when queue
initialization is done. Because when one SCSI command is completed,
the user of sending command can be waken up immediately, then the
scsi device may be removed, meantime the run queue in scsi_end_request()
is still in-progress, so kernel panic can be caused.

In Red Hat QE lab, there are several reports about this kind of kernel
panic triggered during kernel booting.

This patch tries to address the issue by grabing one queue usage
counter during freeing one request and the following run queue.

Fixes: 1311326cf4 ("blk-mq: avoid to synchronize rcu inside blk_cleanup_queue()")
Cc: Andrew Jones <drjones@redhat.com>
Cc: Bart Van Assche <bart.vanassche@wdc.com>
Cc: linux-scsi@vger.kernel.org
Cc: Martin K. Petersen <martin.petersen@oracle.com>
Cc: Christoph Hellwig <hch@lst.de>
Cc: James E.J. Bottomley <jejb@linux.vnet.ibm.com>
Cc: stable <stable@vger.kernel.org>
Cc: jianchao.wang <jianchao.w.wang@oracle.com>
Signed-off-by: Ming Lei <ming.lei@redhat.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Ming Lei 2018-11-14 16:25:51 +08:00 коммит произвёл Greg Kroah-Hartman
Родитель 2cbff556f2
Коммит a061395786
2 изменённых файлов: 10 добавлений и 3 удалений

Просмотреть файл

@ -671,9 +671,8 @@ void blk_cleanup_queue(struct request_queue *q)
* dispatch may still be in-progress since we dispatch requests * dispatch may still be in-progress since we dispatch requests
* from more than one contexts. * from more than one contexts.
* *
* No need to quiesce queue if it isn't initialized yet since * We rely on driver to deal with the race in case that queue
* blk_freeze_queue() should be enough for cases of passthrough * initialization isn't done.
* request.
*/ */
if (q->mq_ops && blk_queue_init_done(q)) if (q->mq_ops && blk_queue_init_done(q))
blk_mq_quiesce_queue(q); blk_mq_quiesce_queue(q);

Просмотреть файл

@ -683,6 +683,12 @@ static bool scsi_end_request(struct request *req, blk_status_t error,
*/ */
scsi_mq_uninit_cmd(cmd); scsi_mq_uninit_cmd(cmd);
/*
* queue is still alive, so grab the ref for preventing it
* from being cleaned up during running queue.
*/
percpu_ref_get(&q->q_usage_counter);
__blk_mq_end_request(req, error); __blk_mq_end_request(req, error);
if (scsi_target(sdev)->single_lun || if (scsi_target(sdev)->single_lun ||
@ -690,6 +696,8 @@ static bool scsi_end_request(struct request *req, blk_status_t error,
kblockd_schedule_work(&sdev->requeue_work); kblockd_schedule_work(&sdev->requeue_work);
else else
blk_mq_run_hw_queues(q, true); blk_mq_run_hw_queues(q, true);
percpu_ref_put(&q->q_usage_counter);
} else { } else {
unsigned long flags; unsigned long flags;