linux-block.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCHv2 0/2] block: blk-rq-qos: replace static key with atomic bitop
@ 2025-08-05 17:17 Nilay Shroff
  2025-08-05 17:17 ` [PATCHv2 1/2] block: avoid cpu_hotplug_lock depedency on freeze_lock Nilay Shroff
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Nilay Shroff @ 2025-08-05 17:17 UTC (permalink / raw)
  To: linux-block
  Cc: ming.lei, yukuai1, axboe, hch, kch, shinichiro.kawasaki, gjoyce

Hi,

This patchset replaces the use of a static key in the I/O path (rq_qos_
xxx()) with an atomic queue flag (QUEUE_FLAG_QOS_ENABLED). This change
is made to eliminate a potential deadlock introduced by the use of static
keys in the blk-rq-qos infrastructure, as reported by lockdep during
blktests block/005[1].

The original static key approach was introduced to avoid unnecessary
dereferencing of q->rq_qos when no blk-rq-qos module (e.g., blk-wbt or
blk-iolatency) is configured. While efficient, enabling a static key at
runtime requires taking cpu_hotplug_lock and jump_label_mutex, which
becomes problematic if the queue is already frozen — causing a reverse
dependency on ->freeze_lock. This results in a lockdep splat indicating
a potential deadlock.

To resolve this, we now gate q->rq_qos access with a q->queue_flags
bitop (QUEUE_FLAG_QOS_ENABLED), avoiding the static key and the associated
locking altogether.

I compared both static key and atomic bitop implementations using ftrace
function graph tracer over ~50 invocations of rq_qos_issue() while ensuring
blk-wbt/blk-iolatency were disabled (i.e., no QoS functionality). For
easy comparision, I made rq_qos_issue() noinline. The comparision was
made on PowerPC machine.

Static Key disabled (QoS is not configured):
5d0: 00 00 00 60     nop    # patched in by static key framework
5d4: 20 00 80 4e     blr    # return (branch to link register)

Only a nop and blr (branch to link register) are executed — very lightweight.

atomic bitop (QoS is not configured):
5d0: 20 00 23 e9     ld      r9,32(r3)     # load q->queue_flags
5d4: 00 80 29 71     andi.   r9,r9,32768   # check QUEUE_FLAG_QOS_ENABLED (bit 15)
5d8: 20 00 82 4d     beqlr                 # return if bit not set

This performs an ld and andi. before returning. Slightly more work,
but q->queue_flags is typically hot in cache during I/O submission.

With Static Key (disabled):
Duration (us): min=0.668 max=0.816 avg≈0.750

With atomic bitop QUEUE_FLAG_QOS_ENABLED (bit not set):
Duration (us): min=0.684 max=0.834 avg≈0.759

As expected, both versions are almost similar in cost. The added latency
from an extra ld and andi. is in the range of ~9ns.

There're two patches in the series. The first patch replaces static key
with QUEUE_FLAG_QOS_ENABLED. The second patch ensures that we disable
the QUEUE_FLAG_QOS_ENABLED when the queue no longer has any associated
rq_qos policies.

As usual, feedback and review comments are welcome!

[1] https://lore.kernel.org/linux-block/4fdm37so3o4xricdgfosgmohn63aa7wj3ua4e5vpihoamwg3ui@fq42f5q5t5ic/


Changes from v1:
  - For debugging I made rq_qos_issue() noinline in my local workspace,
    but then inadvertently it slipped through the patchset upstream. So
    reverted it and made rq_qos_issue() inline again as earlier.
  - Added Reported-by and Closes tags in the first patch, which I
    obviously missed to add in the first version.

Nilay Shroff (2):
  block: avoid cpu_hotplug_lock depedency on freeze_lock
  block: clear QUEUE_FLAG_QOS_ENABLED in rq_qos_del()

 block/blk-mq-debugfs.c |  1 +
 block/blk-rq-qos.c     |  8 ++++----
 block/blk-rq-qos.h     | 43 +++++++++++++++++++++++++-----------------
 include/linux/blkdev.h |  1 +
 4 files changed, 32 insertions(+), 21 deletions(-)

-- 
2.50.1


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCHv2 1/2] block: avoid cpu_hotplug_lock depedency on freeze_lock
  2025-08-05 17:17 [PATCHv2 0/2] block: blk-rq-qos: replace static key with atomic bitop Nilay Shroff
@ 2025-08-05 17:17 ` Nilay Shroff
  2025-08-06  1:18   ` Yu Kuai
  2025-08-05 17:17 ` [PATCHv2 2/2] block: clear QUEUE_FLAG_QOS_ENABLED in rq_qos_del() Nilay Shroff
  2025-08-08  7:49 ` [PATCHv2 0/2] block: blk-rq-qos: replace static key with atomic bitop Shinichiro Kawasaki
  2 siblings, 1 reply; 8+ messages in thread
From: Nilay Shroff @ 2025-08-05 17:17 UTC (permalink / raw)
  To: linux-block
  Cc: ming.lei, yukuai1, axboe, hch, kch, shinichiro.kawasaki, gjoyce

A recent lockdep[1] splat observed while running blktest block/005
reveals a potential deadlock caused by the cpu_hotplug_lock dependency
on ->freeze_lock. This dependency was introduced by commit 033b667a823e
("block: blk-rq-qos: guard rq-qos helpers by static key").

That change added a static key to avoid fetching q->rq_qos when neither
blk-wbt nor blk-iolatency is configured. The static key dynamically
patches kernel text to a NOP when disabled, eliminating overhead of
fetching q->rq_qos in the I/O hot path. However, enabling a static key
at runtime requires acquiring both cpu_hotplug_lock and jump_label_mutex.
When this happens after the queue has already been frozen (i.e., while
holding ->freeze_lock), it creates a locking dependency from cpu_hotplug_
lock to ->freeze_lock, which leads to a potential deadlock reported by
lockdep [1].

To resolve this, replace the static key mechanism with q->queue_flags:
QUEUE_FLAG_QOS_ENABLED. This flag is evaluated in the fast path before
accessing q->rq_qos. If the flag is set, we proceed to fetch q->rq_qos;
otherwise, the access is skipped.

Since q->queue_flags is commonly accessed in IO hotpath and resides in
the first cacheline of struct request_queue, checking it imposes minimal
overhead while eliminating the deadlock risk.

This change avoids the lockdep splat without introducing performance
regressions.

[1] https://lore.kernel.org/linux-block/4fdm37so3o4xricdgfosgmohn63aa7wj3ua4e5vpihoamwg3ui@fq42f5q5t5ic/

Reported-by: Shinichiro Kawasaki <shinichiro.kawasaki@wdc.com>
Closes: https://lore.kernel.org/linux-block/4fdm37so3o4xricdgfosgmohn63aa7wj3ua4e5vpihoamwg3ui@fq42f5q5t5ic/
Fixes: 033b667a823e ("block: blk-rq-qos: guard rq-qos helpers by static key")
Signed-off-by: Nilay Shroff <nilay@linux.ibm.com>
---
 block/blk-mq-debugfs.c |  1 +
 block/blk-rq-qos.c     |  6 ++----
 block/blk-rq-qos.h     | 43 +++++++++++++++++++++++++-----------------
 include/linux/blkdev.h |  1 +
 4 files changed, 30 insertions(+), 21 deletions(-)

diff --git a/block/blk-mq-debugfs.c b/block/blk-mq-debugfs.c
index 7ed3e71f2fc0..32c65efdda46 100644
--- a/block/blk-mq-debugfs.c
+++ b/block/blk-mq-debugfs.c
@@ -95,6 +95,7 @@ static const char *const blk_queue_flag_name[] = {
 	QUEUE_FLAG_NAME(SQ_SCHED),
 	QUEUE_FLAG_NAME(DISABLE_WBT_DEF),
 	QUEUE_FLAG_NAME(NO_ELV_SWITCH),
+	QUEUE_FLAG_NAME(QOS_ENABLED),
 };
 #undef QUEUE_FLAG_NAME
 
diff --git a/block/blk-rq-qos.c b/block/blk-rq-qos.c
index 848591fb3c57..460c04715321 100644
--- a/block/blk-rq-qos.c
+++ b/block/blk-rq-qos.c
@@ -2,8 +2,6 @@
 
 #include "blk-rq-qos.h"
 
-__read_mostly DEFINE_STATIC_KEY_FALSE(block_rq_qos);
-
 /*
  * Increment 'v', if 'v' is below 'below'. Returns true if we succeeded,
  * false if 'v' + 1 would be bigger than 'below'.
@@ -319,8 +317,8 @@ void rq_qos_exit(struct request_queue *q)
 		struct rq_qos *rqos = q->rq_qos;
 		q->rq_qos = rqos->next;
 		rqos->ops->exit(rqos);
-		static_branch_dec(&block_rq_qos);
 	}
+	blk_queue_flag_clear(QUEUE_FLAG_QOS_ENABLED, q);
 	mutex_unlock(&q->rq_qos_mutex);
 }
 
@@ -346,7 +344,7 @@ int rq_qos_add(struct rq_qos *rqos, struct gendisk *disk, enum rq_qos_id id,
 		goto ebusy;
 	rqos->next = q->rq_qos;
 	q->rq_qos = rqos;
-	static_branch_inc(&block_rq_qos);
+	blk_queue_flag_set(QUEUE_FLAG_QOS_ENABLED, q);
 
 	blk_mq_unfreeze_queue(q, memflags);
 
diff --git a/block/blk-rq-qos.h b/block/blk-rq-qos.h
index 39749f4066fb..c4242508fa5e 100644
--- a/block/blk-rq-qos.h
+++ b/block/blk-rq-qos.h
@@ -12,7 +12,6 @@
 #include "blk-mq-debugfs.h"
 
 struct blk_mq_debugfs_attr;
-extern struct static_key_false block_rq_qos;
 
 enum rq_qos_id {
 	RQ_QOS_WBT,
@@ -113,43 +112,50 @@ void __rq_qos_queue_depth_changed(struct rq_qos *rqos);
 
 static inline void rq_qos_cleanup(struct request_queue *q, struct bio *bio)
 {
-	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos)
+	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
+			q->rq_qos)
 		__rq_qos_cleanup(q->rq_qos, bio);
 }
 
 static inline void rq_qos_done(struct request_queue *q, struct request *rq)
 {
-	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos &&
-	    !blk_rq_is_passthrough(rq))
+	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
+			q->rq_qos && !blk_rq_is_passthrough(rq))
 		__rq_qos_done(q->rq_qos, rq);
 }
 
 static inline void rq_qos_issue(struct request_queue *q, struct request *rq)
 {
-	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos)
+	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
+			q->rq_qos)
 		__rq_qos_issue(q->rq_qos, rq);
 }
 
 static inline void rq_qos_requeue(struct request_queue *q, struct request *rq)
 {
-	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos)
+	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
+			q->rq_qos)
 		__rq_qos_requeue(q->rq_qos, rq);
 }
 
 static inline void rq_qos_done_bio(struct bio *bio)
 {
-	if (static_branch_unlikely(&block_rq_qos) &&
-	    bio->bi_bdev && (bio_flagged(bio, BIO_QOS_THROTTLED) ||
-			     bio_flagged(bio, BIO_QOS_MERGED))) {
-		struct request_queue *q = bdev_get_queue(bio->bi_bdev);
-		if (q->rq_qos)
-			__rq_qos_done_bio(q->rq_qos, bio);
-	}
+	struct request_queue *q;
+
+	if (!bio->bi_bdev || (!bio_flagged(bio, BIO_QOS_THROTTLED) &&
+			     !bio_flagged(bio, BIO_QOS_MERGED)))
+		return;
+
+	q = bdev_get_queue(bio->bi_bdev);
+	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
+			q->rq_qos)
+		__rq_qos_done_bio(q->rq_qos, bio);
 }
 
 static inline void rq_qos_throttle(struct request_queue *q, struct bio *bio)
 {
-	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos) {
+	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
+			q->rq_qos) {
 		bio_set_flag(bio, BIO_QOS_THROTTLED);
 		__rq_qos_throttle(q->rq_qos, bio);
 	}
@@ -158,14 +164,16 @@ static inline void rq_qos_throttle(struct request_queue *q, struct bio *bio)
 static inline void rq_qos_track(struct request_queue *q, struct request *rq,
 				struct bio *bio)
 {
-	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos)
+	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
+			q->rq_qos)
 		__rq_qos_track(q->rq_qos, rq, bio);
 }
 
 static inline void rq_qos_merge(struct request_queue *q, struct request *rq,
 				struct bio *bio)
 {
-	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos) {
+	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
+			q->rq_qos) {
 		bio_set_flag(bio, BIO_QOS_MERGED);
 		__rq_qos_merge(q->rq_qos, rq, bio);
 	}
@@ -173,7 +181,8 @@ static inline void rq_qos_merge(struct request_queue *q, struct request *rq,
 
 static inline void rq_qos_queue_depth_changed(struct request_queue *q)
 {
-	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos)
+	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
+			q->rq_qos)
 		__rq_qos_queue_depth_changed(q->rq_qos);
 }
 
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 95886b404b16..fe1797bbec42 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -656,6 +656,7 @@ enum {
 	QUEUE_FLAG_SQ_SCHED,		/* single queue style io dispatch */
 	QUEUE_FLAG_DISABLE_WBT_DEF,	/* for sched to disable/enable wbt */
 	QUEUE_FLAG_NO_ELV_SWITCH,	/* can't switch elevator any more */
+	QUEUE_FLAG_QOS_ENABLED,		/* qos is enabled */
 	QUEUE_FLAG_MAX
 };
 
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCHv2 2/2] block: clear QUEUE_FLAG_QOS_ENABLED in rq_qos_del()
  2025-08-05 17:17 [PATCHv2 0/2] block: blk-rq-qos: replace static key with atomic bitop Nilay Shroff
  2025-08-05 17:17 ` [PATCHv2 1/2] block: avoid cpu_hotplug_lock depedency on freeze_lock Nilay Shroff
@ 2025-08-05 17:17 ` Nilay Shroff
  2025-08-06  1:25   ` Yu Kuai
  2025-08-08  7:49 ` [PATCHv2 0/2] block: blk-rq-qos: replace static key with atomic bitop Shinichiro Kawasaki
  2 siblings, 1 reply; 8+ messages in thread
From: Nilay Shroff @ 2025-08-05 17:17 UTC (permalink / raw)
  To: linux-block
  Cc: ming.lei, yukuai1, axboe, hch, kch, shinichiro.kawasaki, gjoyce

When a QoS function is removed via rq_qos_del(), and it happens to be the
last QoS function on the request queue, q->rq_qos becomes NULL. In this
case, the QUEUE_FLAG_QOS_ENABLED bit should also be cleared to reflect
that no QoS hooks remain active.

This patch ensures that the QUEUE_FLAG_QOS_ENABLED flag is cleared if the
queue no longer has any associated rq_qos policies. Failing to do so
could cause unnecessary dereferences of a now-null q->rq_qos pointer in
the I/O path.

Signed-off-by: Nilay Shroff <nilay@linux.ibm.com>
---
 block/blk-rq-qos.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/block/blk-rq-qos.c b/block/blk-rq-qos.c
index 460c04715321..654478dfbc20 100644
--- a/block/blk-rq-qos.c
+++ b/block/blk-rq-qos.c
@@ -375,6 +375,8 @@ void rq_qos_del(struct rq_qos *rqos)
 			break;
 		}
 	}
+	if (!q->rq_qos)
+		blk_queue_flag_clear(QUEUE_FLAG_QOS_ENABLED, q);
 	blk_mq_unfreeze_queue(q, memflags);
 
 	mutex_lock(&q->debugfs_mutex);
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCHv2 1/2] block: avoid cpu_hotplug_lock depedency on freeze_lock
  2025-08-05 17:17 ` [PATCHv2 1/2] block: avoid cpu_hotplug_lock depedency on freeze_lock Nilay Shroff
@ 2025-08-06  1:18   ` Yu Kuai
  2025-08-06  5:43     ` Nilay Shroff
  0 siblings, 1 reply; 8+ messages in thread
From: Yu Kuai @ 2025-08-06  1:18 UTC (permalink / raw)
  To: Nilay Shroff, linux-block
  Cc: ming.lei, yukuai1, axboe, hch, kch, shinichiro.kawasaki, gjoyce,
	yukuai (C)

Hi,

在 2025/08/06 1:17, Nilay Shroff 写道:
> A recent lockdep[1] splat observed while running blktest block/005
> reveals a potential deadlock caused by the cpu_hotplug_lock dependency
> on ->freeze_lock. This dependency was introduced by commit 033b667a823e
> ("block: blk-rq-qos: guard rq-qos helpers by static key").
> 
> That change added a static key to avoid fetching q->rq_qos when neither
> blk-wbt nor blk-iolatency is configured. The static key dynamically
> patches kernel text to a NOP when disabled, eliminating overhead of
> fetching q->rq_qos in the I/O hot path. However, enabling a static key
> at runtime requires acquiring both cpu_hotplug_lock and jump_label_mutex.
> When this happens after the queue has already been frozen (i.e., while
> holding ->freeze_lock), it creates a locking dependency from cpu_hotplug_
> lock to ->freeze_lock, which leads to a potential deadlock reported by
> lockdep [1].
> 
> To resolve this, replace the static key mechanism with q->queue_flags:
> QUEUE_FLAG_QOS_ENABLED. This flag is evaluated in the fast path before
> accessing q->rq_qos. If the flag is set, we proceed to fetch q->rq_qos;
> otherwise, the access is skipped.
> 
> Since q->queue_flags is commonly accessed in IO hotpath and resides in
> the first cacheline of struct request_queue, checking it imposes minimal
> overhead while eliminating the deadlock risk.
> 
> This change avoids the lockdep splat without introducing performance
> regressions.
> 
> [1] https://lore.kernel.org/linux-block/4fdm37so3o4xricdgfosgmohn63aa7wj3ua4e5vpihoamwg3ui@fq42f5q5t5ic/
> 
> Reported-by: Shinichiro Kawasaki <shinichiro.kawasaki@wdc.com>
> Closes: https://lore.kernel.org/linux-block/4fdm37so3o4xricdgfosgmohn63aa7wj3ua4e5vpihoamwg3ui@fq42f5q5t5ic/
> Fixes: 033b667a823e ("block: blk-rq-qos: guard rq-qos helpers by static key")
> Signed-off-by: Nilay Shroff <nilay@linux.ibm.com>
> ---
>   block/blk-mq-debugfs.c |  1 +
>   block/blk-rq-qos.c     |  6 ++----
>   block/blk-rq-qos.h     | 43 +++++++++++++++++++++++++-----------------
>   include/linux/blkdev.h |  1 +
>   4 files changed, 30 insertions(+), 21 deletions(-)
> 
This patch LGTM, just one nit below.

> diff --git a/block/blk-mq-debugfs.c b/block/blk-mq-debugfs.c
> index 7ed3e71f2fc0..32c65efdda46 100644
> --- a/block/blk-mq-debugfs.c
> +++ b/block/blk-mq-debugfs.c
> @@ -95,6 +95,7 @@ static const char *const blk_queue_flag_name[] = {
>   	QUEUE_FLAG_NAME(SQ_SCHED),
>   	QUEUE_FLAG_NAME(DISABLE_WBT_DEF),
>   	QUEUE_FLAG_NAME(NO_ELV_SWITCH),
> +	QUEUE_FLAG_NAME(QOS_ENABLED),
>   };
>   #undef QUEUE_FLAG_NAME
>   
> diff --git a/block/blk-rq-qos.c b/block/blk-rq-qos.c
> index 848591fb3c57..460c04715321 100644
> --- a/block/blk-rq-qos.c
> +++ b/block/blk-rq-qos.c
> @@ -2,8 +2,6 @@
>   
>   #include "blk-rq-qos.h"
>   
> -__read_mostly DEFINE_STATIC_KEY_FALSE(block_rq_qos);
> -
>   /*
>    * Increment 'v', if 'v' is below 'below'. Returns true if we succeeded,
>    * false if 'v' + 1 would be bigger than 'below'.
> @@ -319,8 +317,8 @@ void rq_qos_exit(struct request_queue *q)
>   		struct rq_qos *rqos = q->rq_qos;
>   		q->rq_qos = rqos->next;
>   		rqos->ops->exit(rqos);
> -		static_branch_dec(&block_rq_qos);
>   	}
> +	blk_queue_flag_clear(QUEUE_FLAG_QOS_ENABLED, q);
>   	mutex_unlock(&q->rq_qos_mutex);
>   }
>   
> @@ -346,7 +344,7 @@ int rq_qos_add(struct rq_qos *rqos, struct gendisk *disk, enum rq_qos_id id,
>   		goto ebusy;
>   	rqos->next = q->rq_qos;
>   	q->rq_qos = rqos;
> -	static_branch_inc(&block_rq_qos);
> +	blk_queue_flag_set(QUEUE_FLAG_QOS_ENABLED, q);
>   
>   	blk_mq_unfreeze_queue(q, memflags);
>   
> diff --git a/block/blk-rq-qos.h b/block/blk-rq-qos.h
> index 39749f4066fb..c4242508fa5e 100644
> --- a/block/blk-rq-qos.h
> +++ b/block/blk-rq-qos.h
> @@ -12,7 +12,6 @@
>   #include "blk-mq-debugfs.h"
>   
>   struct blk_mq_debugfs_attr;
> -extern struct static_key_false block_rq_qos;
>   
>   enum rq_qos_id {
>   	RQ_QOS_WBT,
> @@ -113,43 +112,50 @@ void __rq_qos_queue_depth_changed(struct rq_qos *rqos);
>   
>   static inline void rq_qos_cleanup(struct request_queue *q, struct bio *bio)
>   {
> -	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos)
> +	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
> +			q->rq_qos)
>   		__rq_qos_cleanup(q->rq_qos, bio);
>   }
>   
>   static inline void rq_qos_done(struct request_queue *q, struct request *rq)
>   {
> -	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos &&
> -	    !blk_rq_is_passthrough(rq))
> +	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
> +			q->rq_qos && !blk_rq_is_passthrough(rq))
>   		__rq_qos_done(q->rq_qos, rq);
>   }
>   
>   static inline void rq_qos_issue(struct request_queue *q, struct request *rq)
>   {
> -	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos)
> +	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
> +			q->rq_qos)
>   		__rq_qos_issue(q->rq_qos, rq);
>   }
>   
>   static inline void rq_qos_requeue(struct request_queue *q, struct request *rq)
>   {
> -	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos)
> +	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
> +			q->rq_qos)
>   		__rq_qos_requeue(q->rq_qos, rq);
>   }
>   
>   static inline void rq_qos_done_bio(struct bio *bio)
>   {
> -	if (static_branch_unlikely(&block_rq_qos) &&
> -	    bio->bi_bdev && (bio_flagged(bio, BIO_QOS_THROTTLED) ||
> -			     bio_flagged(bio, BIO_QOS_MERGED))) {
> -		struct request_queue *q = bdev_get_queue(bio->bi_bdev);
> -		if (q->rq_qos)
> -			__rq_qos_done_bio(q->rq_qos, bio);
> -	}
> +	struct request_queue *q;
> +
> +	if (!bio->bi_bdev || (!bio_flagged(bio, BIO_QOS_THROTTLED) &&
> +			     !bio_flagged(bio, BIO_QOS_MERGED)))
> +		return;
> +
> +	q = bdev_get_queue(bio->bi_bdev);
> +	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
> +			q->rq_qos)
This unlinkey doesn't make sense, BIO_QOS_THROTTLED or BIO_QOS_MERGED
already indicates rq_qos is enabled while issuing IO, and rq_qos should
still be valid until this IO is done.

Perhaps a prep cleanup patch to remove this checking?

Thanks,
Kuai

> +		__rq_qos_done_bio(q->rq_qos, bio);
>   }
>   
>   static inline void rq_qos_throttle(struct request_queue *q, struct bio *bio)
>   {
> -	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos) {
> +	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
> +			q->rq_qos) {
>   		bio_set_flag(bio, BIO_QOS_THROTTLED);
>   		__rq_qos_throttle(q->rq_qos, bio);
>   	}
> @@ -158,14 +164,16 @@ static inline void rq_qos_throttle(struct request_queue *q, struct bio *bio)
>   static inline void rq_qos_track(struct request_queue *q, struct request *rq,
>   				struct bio *bio)
>   {
> -	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos)
> +	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
> +			q->rq_qos)
>   		__rq_qos_track(q->rq_qos, rq, bio);
>   }
>   
>   static inline void rq_qos_merge(struct request_queue *q, struct request *rq,
>   				struct bio *bio)
>   {
> -	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos) {
> +	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
> +			q->rq_qos) {
>   		bio_set_flag(bio, BIO_QOS_MERGED);
>   		__rq_qos_merge(q->rq_qos, rq, bio);
>   	}
> @@ -173,7 +181,8 @@ static inline void rq_qos_merge(struct request_queue *q, struct request *rq,
>   
>   static inline void rq_qos_queue_depth_changed(struct request_queue *q)
>   {
> -	if (static_branch_unlikely(&block_rq_qos) && q->rq_qos)
> +	if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
> +			q->rq_qos)
>   		__rq_qos_queue_depth_changed(q->rq_qos);
>   }
>   
> diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
> index 95886b404b16..fe1797bbec42 100644
> --- a/include/linux/blkdev.h
> +++ b/include/linux/blkdev.h
> @@ -656,6 +656,7 @@ enum {
>   	QUEUE_FLAG_SQ_SCHED,		/* single queue style io dispatch */
>   	QUEUE_FLAG_DISABLE_WBT_DEF,	/* for sched to disable/enable wbt */
>   	QUEUE_FLAG_NO_ELV_SWITCH,	/* can't switch elevator any more */
> +	QUEUE_FLAG_QOS_ENABLED,		/* qos is enabled */
>   	QUEUE_FLAG_MAX
>   };
>   
> 


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCHv2 2/2] block: clear QUEUE_FLAG_QOS_ENABLED in rq_qos_del()
  2025-08-05 17:17 ` [PATCHv2 2/2] block: clear QUEUE_FLAG_QOS_ENABLED in rq_qos_del() Nilay Shroff
@ 2025-08-06  1:25   ` Yu Kuai
  2025-08-06  5:47     ` Nilay Shroff
  0 siblings, 1 reply; 8+ messages in thread
From: Yu Kuai @ 2025-08-06  1:25 UTC (permalink / raw)
  To: Nilay Shroff, linux-block
  Cc: ming.lei, yukuai1, axboe, hch, kch, shinichiro.kawasaki, gjoyce,
	yukuai (C)

Hi,

在 2025/08/06 1:17, Nilay Shroff 写道:
> When a QoS function is removed via rq_qos_del(), and it happens to be the
> last QoS function on the request queue, q->rq_qos becomes NULL. In this
> case, the QUEUE_FLAG_QOS_ENABLED bit should also be cleared to reflect
> that no QoS hooks remain active.
> 
> This patch ensures that the QUEUE_FLAG_QOS_ENABLED flag is cleared if the
> queue no longer has any associated rq_qos policies. Failing to do so
> could cause unnecessary dereferences of a now-null q->rq_qos pointer in
> the I/O path.
> 
> Signed-off-by: Nilay Shroff <nilay@linux.ibm.com>
> ---
>   block/blk-rq-qos.c | 2 ++
>   1 file changed, 2 insertions(+)
> 
There is no fixtag, and can be missing during backport easily.

I feel it's better to fix missing static_branch_dec() in rq_qos_del()
first, and then fix the deadlock problem.

Thanks,
Kuai

> diff --git a/block/blk-rq-qos.c b/block/blk-rq-qos.c
> index 460c04715321..654478dfbc20 100644
> --- a/block/blk-rq-qos.c
> +++ b/block/blk-rq-qos.c
> @@ -375,6 +375,8 @@ void rq_qos_del(struct rq_qos *rqos)
>   			break;
>   		}
>   	}
> +	if (!q->rq_qos)
> +		blk_queue_flag_clear(QUEUE_FLAG_QOS_ENABLED, q);
>   	blk_mq_unfreeze_queue(q, memflags);
>   
>   	mutex_lock(&q->debugfs_mutex);
> 


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCHv2 1/2] block: avoid cpu_hotplug_lock depedency on freeze_lock
  2025-08-06  1:18   ` Yu Kuai
@ 2025-08-06  5:43     ` Nilay Shroff
  0 siblings, 0 replies; 8+ messages in thread
From: Nilay Shroff @ 2025-08-06  5:43 UTC (permalink / raw)
  To: Yu Kuai, linux-block
  Cc: ming.lei, axboe, hch, kch, shinichiro.kawasaki, gjoyce,
	yukuai (C)



On 8/6/25 6:48 AM, Yu Kuai wrote:
> Hi,
> 
> 在 2025/08/06 1:17, Nilay Shroff 写道:
>>     static inline void rq_qos_done_bio(struct bio *bio)
>>   {
>> -    if (static_branch_unlikely(&block_rq_qos) &&
>> -        bio->bi_bdev && (bio_flagged(bio, BIO_QOS_THROTTLED) ||
>> -                 bio_flagged(bio, BIO_QOS_MERGED))) {
>> -        struct request_queue *q = bdev_get_queue(bio->bi_bdev);
>> -        if (q->rq_qos)
>> -            __rq_qos_done_bio(q->rq_qos, bio);
>> -    }
>> +    struct request_queue *q;
>> +
>> +    if (!bio->bi_bdev || (!bio_flagged(bio, BIO_QOS_THROTTLED) &&
>> +                 !bio_flagged(bio, BIO_QOS_MERGED)))
>> +        return;
>> +
>> +    q = bdev_get_queue(bio->bi_bdev);
>> +    if (unlikely(test_bit(QUEUE_FLAG_QOS_ENABLED, &q->queue_flags)) &&
>> +            q->rq_qos)
> This unlinkey doesn't make sense, BIO_QOS_THROTTLED or BIO_QOS_MERGED
> already indicates rq_qos is enabled while issuing IO, and rq_qos should
> still be valid until this IO is done.
> 
Yes good point! Agreed that having BIO_QOS_THROTTLED or BIO_QOS_MERGED set,
implicitly implies that q->rq_qos must not be NULL.

> Perhaps a prep cleanup patch to remove this checking?
Yes I'd do that but lets wait for Jens or Ming if in case they have any
further comment before I spin v3.

Thanks,
--Nilay

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCHv2 2/2] block: clear QUEUE_FLAG_QOS_ENABLED in rq_qos_del()
  2025-08-06  1:25   ` Yu Kuai
@ 2025-08-06  5:47     ` Nilay Shroff
  0 siblings, 0 replies; 8+ messages in thread
From: Nilay Shroff @ 2025-08-06  5:47 UTC (permalink / raw)
  To: Yu Kuai, linux-block
  Cc: ming.lei, axboe, hch, kch, shinichiro.kawasaki, gjoyce,
	yukuai (C)



On 8/6/25 6:55 AM, Yu Kuai wrote:
> Hi,
> 
> 在 2025/08/06 1:17, Nilay Shroff 写道:
>> When a QoS function is removed via rq_qos_del(), and it happens to be the
>> last QoS function on the request queue, q->rq_qos becomes NULL. In this
>> case, the QUEUE_FLAG_QOS_ENABLED bit should also be cleared to reflect
>> that no QoS hooks remain active.
>>
>> This patch ensures that the QUEUE_FLAG_QOS_ENABLED flag is cleared if the
>> queue no longer has any associated rq_qos policies. Failing to do so
>> could cause unnecessary dereferences of a now-null q->rq_qos pointer in
>> the I/O path.
>>
>> Signed-off-by: Nilay Shroff <nilay@linux.ibm.com>
>> ---
>>   block/blk-rq-qos.c | 2 ++
>>   1 file changed, 2 insertions(+)
>>
> There is no fixtag, and can be missing during backport easily.
> 
> I feel it's better to fix missing static_branch_dec() in rq_qos_del()
> first, and then fix the deadlock problem.
> 
Yes makes sense, will do it in v3. But again lets wait for Jens and Ming
in case they have any further comment.

Thanks,
--Nilay

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCHv2 0/2] block: blk-rq-qos: replace static key with atomic bitop
  2025-08-05 17:17 [PATCHv2 0/2] block: blk-rq-qos: replace static key with atomic bitop Nilay Shroff
  2025-08-05 17:17 ` [PATCHv2 1/2] block: avoid cpu_hotplug_lock depedency on freeze_lock Nilay Shroff
  2025-08-05 17:17 ` [PATCHv2 2/2] block: clear QUEUE_FLAG_QOS_ENABLED in rq_qos_del() Nilay Shroff
@ 2025-08-08  7:49 ` Shinichiro Kawasaki
  2 siblings, 0 replies; 8+ messages in thread
From: Shinichiro Kawasaki @ 2025-08-08  7:49 UTC (permalink / raw)
  To: Nilay Shroff
  Cc: linux-block@vger.kernel.org, ming.lei@redhat.com,
	yukuai1@huaweicloud.com, axboe@kernel.dk, hch, kch@nvidia.com,
	gjoyce@ibm.com

On Aug 05, 2025 / 22:47, Nilay Shroff wrote:
> Hi,
> 
> This patchset replaces the use of a static key in the I/O path (rq_qos_
> xxx()) with an atomic queue flag (QUEUE_FLAG_QOS_ENABLED). This change
> is made to eliminate a potential deadlock introduced by the use of static
> keys in the blk-rq-qos infrastructure, as reported by lockdep during
> blktests block/005[1].

...

> [1] https://lore.kernel.org/linux-block/4fdm37so3o4xricdgfosgmohn63aa7wj3ua4e5vpihoamwg3ui@fq42f5q5t5ic/

Thanks for this effort. I confirmed this series avoids the block/005 failure.
I'm not sure if this fix approach will be agreed upon, but if that is the case:

Tested-by: Shin'ichiro Kawasaki <shinichiro.kawasaki@wdc.com>

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2025-08-08  7:50 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-05 17:17 [PATCHv2 0/2] block: blk-rq-qos: replace static key with atomic bitop Nilay Shroff
2025-08-05 17:17 ` [PATCHv2 1/2] block: avoid cpu_hotplug_lock depedency on freeze_lock Nilay Shroff
2025-08-06  1:18   ` Yu Kuai
2025-08-06  5:43     ` Nilay Shroff
2025-08-05 17:17 ` [PATCHv2 2/2] block: clear QUEUE_FLAG_QOS_ENABLED in rq_qos_del() Nilay Shroff
2025-08-06  1:25   ` Yu Kuai
2025-08-06  5:47     ` Nilay Shroff
2025-08-08  7:49 ` [PATCHv2 0/2] block: blk-rq-qos: replace static key with atomic bitop Shinichiro Kawasaki

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).