From 71ec3c7d6db80737b781d2739d1325b7e39758e1 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 28 Oct 2026 10:41:39 -0600 Subject: [PATCH] blk-mq: quiesce a queue that joins a quiesced tag set blk_mq_quiesce_tagset() only quiesces the queues on the list at that moment. A queue added before blk_mq_unquiesce_tagset() runs unquiesced, and then gets unquiesced without ever having been quiesced, which trips the warning in blk_mq_unquiesce_queue(). NVMe gets there when a controller fails while its namespaces are being scanned. Count the quiesce calls in the tag set and quiesce a joining queue that many times, under a spinlock that also serializes the two functions. It can't be tag_list_lock, see commit 59e25ef2b413. Fixes: 414dd48e882c ("blk-mq: add tagset quiesce interface") Signed-off-by: Josef Bacik Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20261028164415.1262013-37-axboe@kernel.dk Signed-off-by: Jens Axboe --- block/blk-mq.c | 22 ++++++++++++++++++++++ include/linux/blk-mq.h | 6 ++++++ 2 files changed, 28 insertions(+) diff --git a/block/blk-mq.c b/block/blk-mq.c index b7da2f7ac60a..fe7ff041bd6d 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -336,12 +336,15 @@ void blk_mq_quiesce_tagset(struct blk_mq_tag_set *set) { struct request_queue *q; + spin_lock(&set->quiesce_lock); + set->quiesce_depth++; rcu_read_lock(); list_for_each_entry_rcu(q, &set->tag_list, tag_set_list) { if (!blk_queue_skip_tagset_quiesce(q)) blk_mq_quiesce_queue_nowait(q); } rcu_read_unlock(); + spin_unlock(&set->quiesce_lock); blk_mq_wait_quiesce_done(set); } @@ -351,12 +354,16 @@ void blk_mq_unquiesce_tagset(struct blk_mq_tag_set *set) { struct request_queue *q; + spin_lock(&set->quiesce_lock); + if (set->quiesce_depth) + set->quiesce_depth--; rcu_read_lock(); list_for_each_entry_rcu(q, &set->tag_list, tag_set_list) { if (!blk_queue_skip_tagset_quiesce(q)) blk_mq_unquiesce_queue(q); } rcu_read_unlock(); + spin_unlock(&set->quiesce_lock); } EXPORT_SYMBOL_GPL(blk_mq_unquiesce_tagset); @@ -4345,6 +4352,8 @@ static void blk_mq_del_queue_tag_set(struct request_queue *q) static void blk_mq_add_queue_tag_set(struct blk_mq_tag_set *set, struct request_queue *q) { + unsigned int i; + mutex_lock(&set->tag_list_lock); /* @@ -4358,7 +4367,18 @@ static void blk_mq_add_queue_tag_set(struct blk_mq_tag_set *set, } if (set->flags & BLK_MQ_F_TAG_QUEUE_SHARED) queue_set_hctx_shared(q, true); + + /* + * A queue that joins a quiesced tag set is quiesced like the others, + * and blk_mq_unquiesce_tagset() finds what it is going to undo. + */ + spin_lock(&set->quiesce_lock); list_add_tail_rcu(&q->tag_set_list, &set->tag_list); + if (!blk_queue_skip_tagset_quiesce(q)) { + for (i = 0; i < set->quiesce_depth; i++) + blk_mq_quiesce_queue_nowait(q); + } + spin_unlock(&set->quiesce_lock); mutex_unlock(&set->tag_list_lock); } @@ -4927,6 +4947,8 @@ int blk_mq_alloc_tag_set(struct blk_mq_tag_set *set) mutex_init(&set->tag_list_lock); INIT_LIST_HEAD(&set->tag_list); + spin_lock_init(&set->quiesce_lock); + set->quiesce_depth = 0; return 0; diff --git a/include/linux/blk-mq.h b/include/linux/blk-mq.h index 5e130b282b42..aa3b35405edc 100644 --- a/include/linux/blk-mq.h +++ b/include/linux/blk-mq.h @@ -523,6 +523,10 @@ enum hctx_type { * @tag_list_lock: Serializes tag_list accesses. * @tag_list: List of the request queues that use this tag set. See also * request_queue.tag_set_list. + * @quiesce_lock: Serializes blk_mq_[un]quiesce_tagset() against each other + * and against a request queue being added to @tag_list. + * @quiesce_depth: How often blk_mq_quiesce_tagset() has been called without + * blk_mq_unquiesce_tagset(). * @srcu: Use as lock when type of the request queue is blocking * (BLK_MQ_F_BLOCKING). * @update_nr_hwq_lock: @@ -548,6 +552,8 @@ struct blk_mq_tag_set { struct mutex tag_list_lock; struct list_head tag_list; + spinlock_t quiesce_lock; + unsigned int quiesce_depth; struct srcu_struct *srcu; struct rw_semaphore update_nr_hwq_lock;