* Re: [PATCH v3 04/10] block: introduce dma map backed bio type
From: Pavel Begunkov @ 2026-05-18 9:11 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Jens Axboe, Keith Busch, Sagi Grimberg, Alexander Viro,
Christian Brauner, Andrew Morton, Sumit Semwal,
Christian König, linux-block, linux-kernel, linux-nvme,
linux-fsdevel, io-uring, linux-media, dri-devel, linaro-mm-sig,
Nitesh Shetty, Kanchan Joshi, Anuj Gupta, Tushar Gohad,
William Power, Phil Cayton, Jason Gunthorpe
In-Reply-To: <20260513083937.GD6461@lst.de>
On 5/13/26 09:39, Christoph Hellwig wrote:
>> + union {
>> + struct bio_vec *bi_io_vec;
>> + /* Driver specific dma map, present only with BIO_DMABUF_MAP */
>> + struct io_dmabuf_map *dmabuf_map;
>> + };
>
> ... and please add the bi_ prefix we're using for all (well except for
> one oddity) fields in struct bio.
Ok, going to add
--
Pavel Begunkov
^ permalink raw reply
* Re: [PATCH v3 03/10] block: move bvec init into __bio_clone
From: Pavel Begunkov @ 2026-05-18 9:10 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Jens Axboe, Keith Busch, Sagi Grimberg, Alexander Viro,
Christian Brauner, Andrew Morton, Sumit Semwal,
Christian König, linux-block, linux-kernel, linux-nvme,
linux-fsdevel, io-uring, linux-media, dri-devel, linaro-mm-sig,
Nitesh Shetty, Kanchan Joshi, Anuj Gupta, Tushar Gohad,
William Power, Phil Cayton, Jason Gunthorpe
In-Reply-To: <20260513081238.GC5477@lst.de>
On 5/13/26 09:12, Christoph Hellwig wrote:
> On Wed, Apr 29, 2026 at 04:25:49PM +0100, Pavel Begunkov wrote:
>> To quote Cristoph: "Historically __bio_clone itself does not clone the
Sorry for late replies
> It's Christoph.
Oops, typo, sorry for that
--
Pavel Begunkov
^ permalink raw reply
* Re: [PATCH] block: fix pages array leak in bio_map_user_iov error path
From: 尹欣 @ 2026-05-18 8:31 UTC (permalink / raw)
To: Jens Axboe, dhowells; +Cc: linux-block, linux-kernel, stable
In-Reply-To: <20260509040119.1116544-1-yinxin.x@bytedance.com>
> From: "Xin Yin"<yinxin.x@bytedance.com>
> Date: Sat, May 9, 2026, 12:23
> Subject: [PATCH] block: fix pages array leak in bio_map_user_iov error path
> To: <axboe@kernel.dk>, <dhowells@redhat.com>
> Cc: <linux-block@vger.kernel.org>, <linux-kernel@vger.kernel.org>, "Xin Yin"<yinxin.x@bytedance.com>, <stable@vger.kernel.org>
> In bio_map_user_iov(), when iov_iter_extract_pages() is called with pages
> set to NULL (because nr_vecs > UIO_FASTIOV), want_pages_array() internally
> allocates a pages array via kvmalloc_array(). If iov_iter_extract_pages()
> subsequently returns bytes <= 0 (e.g., due to pin_user_pages_fast()
> failure), the code jumps to out_unmap without freeing the dynamically
> allocated pages array, causing a memory leak detectable by kmemleak.
>
> This can be triggered from userspace by issuing an SG_IO v4 ioctl on a
> bsg device with a large din_xfer_len and an invalid din_xferp (mapped
> PROT_NONE), which causes pin_user_pages_fast() to fail after the pages
> array has already been allocated by want_pages_array().
>
> The kmemleak backtrace looks like:
>
> unreferenced object 0xffff... (size 2048):
> backtrace (crc 0):
> kvmalloc_node+0x...
> want_pages_array+0x...
> iov_iter_extract_pages+0x...
> bio_map_user_iov+0x...
> blk_rq_map_user_iov+0x...
> blk_rq_map_user+0x...
> bsg_transport_sg_io_fn+0x...
>
> Fix this by freeing the dynamically allocated pages array (when it
> differs from the on-stack stack_pages) before jumping to the error path.
>
> Fixes: 403b6fb8dac1 ("block: convert bio_map_user_iov to use iov_iter_extract_pages")
> Cc: stable@vger.kernel.org # 6.5+
> Signed-off-by: Xin Yin <yinxin.x@bytedance.com>
> ---
> block/blk-map.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/block/blk-map.c b/block/blk-map.c
> index 0aadbaf7a9ddd..5b9f14caad4f9 100644
> --- a/block/blk-map.c
> +++ b/block/blk-map.c
> @@ -304,6 +304,8 @@ static int bio_map_user_iov(struct request *rq, struct iov_iter *iter,
> bytes = iov_iter_extract_pages(iter, &pages, LONG_MAX,
> nr_vecs, extraction_flags, &offs);
> if (unlikely(bytes <= 0)) {
> + if (pages != stack_pages)
> + kvfree(pages);
> ret = bytes ? bytes : -EFAULT;
> goto out_unmap;
> }
> --
> 2.20.1
>
Hi Jens
Gentle ping on this patch.
This patch fixes a memory leak in bio_map_user_iov() that only affects
stable kernels (6.5+) carrying commit 403b6fb8dac1. Mainline resolved
this through a later refactoring that removed the affected code path
entirely, so there is no upstream commit to backport.
Could this be picked up for 6.6.y?
Thanks,
Xin Yin
^ permalink raw reply
* Re: [PATCH v6 2/2] blk-mq: expose tag starvation counts via debugfs
From: John Garry @ 2026-05-18 8:14 UTC (permalink / raw)
To: Aaron Tomlin, axboe, rostedt, mhiramat, mathieu.desnoyers
Cc: bvanassche, johannes.thumshirn, kch, dlemoal, ritesh.list,
loberman, neelx, sean, mproche, chjohnst, linux-block,
linux-kernel, linux-trace-kernel
In-Reply-To: <20260517213614.350367-3-atomlin@atomlin.com>
On 17/05/2026 22:36, Aaron Tomlin wrote:
> In high-performance storage environments, particularly when utilising
> RAID controllers with shared tag sets (BLK_MQ_F_TAG_HCTX_SHARED), severe
> latency spikes can occur when fast devices are starved of available
> tags.
>
> This patch introduces two new debugfs attributes for each block
> hardware queue:
> - /sys/kernel/debug/block/[device]/hctxN/wait_on_hw_tag
> - /sys/kernel/debug/block/[device]/hctxN/wait_on_sched_tag
How would these counters be used? You are just saying that we may have
performance latency spikes and so here are two new counters.
>
> These files expose atomic counters that increment each time a submitting
> context is forced into an uninterruptible sleep via io_schedule() due to
> the complete exhaustion of physical driver tags or software scheduler
> tags, respectively.
>
> To ensure negligible performance overhead even in production
> environments where CONFIG_BLK_DEBUG_FS is actively enabled, this
> tracking logic utilises dynamically allocated per-CPU counters. When
> this configuration is disabled, the tracking logic compiles down to a
> safe no-op.
How does one normalise the values which are measured? I mean, during a
period of high contention, we may get a bunch of threads waiting for a
driver tag and the value in wait_on_hw_tag may jump considerably - how
do you normalize this value in wait_on_hw_tag for meaningful analysis?
>
> Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
> ---
> block/blk-mq-debugfs.c | 109 +++++++++++++++++++++++++++++++++++++++++
> block/blk-mq-debugfs.h | 19 +++++++
> block/blk-mq-tag.c | 4 ++
> block/blk-mq.c | 5 ++
> include/linux/blk-mq.h | 12 +++++
> 5 files changed, 149 insertions(+)
>
> diff --git a/block/blk-mq-debugfs.c b/block/blk-mq-debugfs.c
> index 047ec887456b..a94ffc2eacdf 100644
> --- a/block/blk-mq-debugfs.c
> +++ b/block/blk-mq-debugfs.c
> @@ -7,6 +7,7 @@
> #include <linux/blkdev.h>
> #include <linux/build_bug.h>
> #include <linux/debugfs.h>
> +#include <linux/percpu.h>
>
> #include "blk.h"
> #include "blk-mq.h"
> @@ -484,6 +485,54 @@ static int hctx_dispatch_busy_show(void *data, struct seq_file *m)
> return 0;
> }
>
> +/**
> + * hctx_wait_on_hw_tag_show - display hardware tag starvation count
> + * @data: generic pointer to the associated hardware context (hctx)
> + * @m: seq_file pointer for debugfs output formatting
> + *
> + * Prints the cumulative number of times a submitting context was forced
> + * to block due to the exhaustion of physical hardware driver tags.
> + *
> + * Return: 0 on success.
> + */
> +static int hctx_wait_on_hw_tag_show(void *data, struct seq_file *m)
> +{
> + struct blk_mq_hw_ctx *hctx = data;
> + unsigned long count = 0;
> + int cpu;
> +
> + if (hctx->wait_on_hw_tag) {
> + for_each_possible_cpu(cpu)
> + count += *per_cpu_ptr(hctx->wait_on_hw_tag, cpu);
> + }
> + seq_printf(m, "%lu\n", count);
> + return 0;
> +}
> +
> +/**
> + * hctx_wait_on_sched_tag_show - display scheduler tag starvation count
> + * @data: generic pointer to the associated hardware context (hctx)
> + * @m: seq_file pointer for debugfs output formatting
> + *
> + * Prints the cumulative number of times a submitting context was forced
> + * to block due to the exhaustion of software scheduler tags.
> + *
> + * Return: 0 on success.
> + */
> +static int hctx_wait_on_sched_tag_show(void *data, struct seq_file *m)
> +{
> + struct blk_mq_hw_ctx *hctx = data;
> + unsigned long count = 0;
> + int cpu;
> +
> + if (hctx->wait_on_sched_tag) {
> + for_each_possible_cpu(cpu)
> + count += *per_cpu_ptr(hctx->wait_on_sched_tag, cpu);
> + }
> + seq_printf(m, "%lu\n", count);
> + return 0;
> +}
> +
> #define CTX_RQ_SEQ_OPS(name, type) \
> static void *ctx_##name##_rq_list_start(struct seq_file *m, loff_t *pos) \
> __acquires(&ctx->lock) \
> @@ -599,6 +648,8 @@ static const struct blk_mq_debugfs_attr blk_mq_debugfs_hctx_attrs[] = {
> {"active", 0400, hctx_active_show},
> {"dispatch_busy", 0400, hctx_dispatch_busy_show},
> {"type", 0400, hctx_type_show},
> + {"wait_on_hw_tag", 0400, hctx_wait_on_hw_tag_show},
> + {"wait_on_sched_tag", 0400, hctx_wait_on_sched_tag_show},
> {},
> };
>
> @@ -815,3 +866,61 @@ void blk_mq_debugfs_unregister_sched_hctx(struct blk_mq_hw_ctx *hctx)
> debugfs_remove_recursive(hctx->sched_debugfs_dir);
> hctx->sched_debugfs_dir = NULL;
> }
> +
> +/**
> + * blk_mq_debugfs_alloc_hctx_stats - Allocate per-cpu starvation statistics
> + * @hctx: hardware context associated with the tag allocation
> + * @gfp: memory allocation flags
> + *
> + * Allocates the per-cpu memory for tracking hardware and scheduler tag
> + * starvation.
> + */
> +void blk_mq_debugfs_alloc_hctx_stats(struct blk_mq_hw_ctx *hctx, gfp_t gfp)
> +{
> + if (!hctx->wait_on_hw_tag)
> + hctx->wait_on_hw_tag = alloc_percpu_gfp(unsigned long,
> + gfp);
> + if (!hctx->wait_on_sched_tag)
> + hctx->wait_on_sched_tag = alloc_percpu_gfp(unsigned long,
> + gfp);
> +}
> +
> +/**
> + * blk_mq_debugfs_free_hctx_stats - Free per-cpu starvation statistics
> + * @hctx: hardware context associated with the tag allocation
> + *
> + * Frees the per-cpu memory used for tracking hardware and scheduler tag
> + * starvation. This must only be called during hardware queue teardown when
> + * the queue is safely frozen and no active I/O submissions can race to
> + * increment the statistics.
> + */
> +void blk_mq_debugfs_free_hctx_stats(struct blk_mq_hw_ctx *hctx)
> +{
> + free_percpu(hctx->wait_on_hw_tag);
> + hctx->wait_on_hw_tag = NULL;
> + free_percpu(hctx->wait_on_sched_tag);
> + hctx->wait_on_sched_tag = NULL;
> +}
> +
> +/**
> + * blk_mq_debugfs_inc_wait_tags - increment the tag starvation counters
> + * @hctx: hardware context associated with the tag allocation
> + * @is_sched: true if the starved pool is the software scheduler
> + *
> + * Evaluates the exhausted tag pool and safely increments the appropriate
> + * per-cpu debugfs starvation counter.
> + *
> + * Note: The per-cpu pointers are explicitly checked to prevent a NULL
> + * pointer dereference in the event that the system was under heavy memory
> + * pressure and the initial per-cpu allocation failed.
> + */
> +void blk_mq_debugfs_inc_wait_tags(struct blk_mq_hw_ctx *hctx,
> + bool is_sched)
> +{
> + unsigned long __percpu *tags = is_sched ?
> + READ_ONCE(hctx->wait_on_sched_tag) :
> + READ_ONCE(hctx->wait_on_hw_tag);
> +
> + if (likely(tags))
> + raw_cpu_inc(*tags);
> +}
> diff --git a/block/blk-mq-debugfs.h b/block/blk-mq-debugfs.h
> index 49bb1aaa83dc..7a7c0f376a2b 100644
> --- a/block/blk-mq-debugfs.h
> +++ b/block/blk-mq-debugfs.h
> @@ -17,6 +17,8 @@ struct blk_mq_debugfs_attr {
> const struct seq_operations *seq_ops;
> };
>
> +void blk_mq_debugfs_inc_wait_tags(struct blk_mq_hw_ctx *hctx,
> + bool is_sched);
> int __blk_mq_debugfs_rq_show(struct seq_file *m, struct request *rq);
> int blk_mq_debugfs_rq_show(struct seq_file *m, void *v);
>
> @@ -26,6 +28,9 @@ void blk_mq_debugfs_register_hctx(struct request_queue *q,
> void blk_mq_debugfs_unregister_hctx(struct blk_mq_hw_ctx *hctx);
> void blk_mq_debugfs_register_hctxs(struct request_queue *q);
> void blk_mq_debugfs_unregister_hctxs(struct request_queue *q);
> +void blk_mq_debugfs_alloc_hctx_stats(struct blk_mq_hw_ctx *hctx,
> + gfp_t gfp);
> +void blk_mq_debugfs_free_hctx_stats(struct blk_mq_hw_ctx *hctx);
>
> void blk_mq_debugfs_register_sched(struct request_queue *q);
> void blk_mq_debugfs_unregister_sched(struct request_queue *q);
> @@ -35,6 +40,11 @@ void blk_mq_debugfs_unregister_sched_hctx(struct blk_mq_hw_ctx *hctx);
>
> void blk_mq_debugfs_register_rq_qos(struct request_queue *q);
> #else
> +static inline void blk_mq_debugfs_inc_wait_tags(struct blk_mq_hw_ctx *hctx,
> + bool is_sched)
> +{
> +}
> +
> static inline void blk_mq_debugfs_register(struct request_queue *q)
> {
> }
> @@ -56,6 +66,15 @@ static inline void blk_mq_debugfs_unregister_hctxs(struct request_queue *q)
> {
> }
>
> +static inline void blk_mq_debugfs_alloc_hctx_stats(struct blk_mq_hw_ctx *hctx,
> + gfp_t gfp)
> +{
> +}
> +
> +static inline void blk_mq_debugfs_free_hctx_stats(struct blk_mq_hw_ctx *hctx)
> +{
> +}
> +
> static inline void blk_mq_debugfs_register_sched(struct request_queue *q)
> {
> }
> diff --git a/block/blk-mq-tag.c b/block/blk-mq-tag.c
> index 66138dd043d4..3cc6a97a87a0 100644
> --- a/block/blk-mq-tag.c
> +++ b/block/blk-mq-tag.c
> @@ -17,6 +17,7 @@
> #include "blk.h"
> #include "blk-mq.h"
> #include "blk-mq-sched.h"
> +#include "blk-mq-debugfs.h"
>
> /*
> * Recalculate wakeup batch when tag is shared by hctx.
> @@ -191,6 +192,9 @@ unsigned int blk_mq_get_tag(struct blk_mq_alloc_data *data)
> trace_block_rq_tag_wait(data->q, data->hctx,
> data->rq_flags & RQF_SCHED_TAGS);
>
> + blk_mq_debugfs_inc_wait_tags(data->hctx,
> + data->rq_flags & RQF_SCHED_TAGS);
> +
> bt_prev = bt;
> io_schedule();
>
> diff --git a/block/blk-mq.c b/block/blk-mq.c
> index 4c5c16cce4f8..cd52bf6f82ce 100644
> --- a/block/blk-mq.c
> +++ b/block/blk-mq.c
> @@ -3991,6 +3991,8 @@ static void blk_mq_exit_hctx(struct request_queue *q,
> blk_free_flush_queue_callback);
> hctx->fq = NULL;
>
> + blk_mq_debugfs_free_hctx_stats(hctx);
> +
> spin_lock(&q->unused_hctx_lock);
> list_add(&hctx->hctx_list, &q->unused_hctx_list);
> spin_unlock(&q->unused_hctx_lock);
> @@ -4016,6 +4018,8 @@ static int blk_mq_init_hctx(struct request_queue *q,
> {
> gfp_t gfp = GFP_NOIO | __GFP_NOWARN | __GFP_NORETRY;
>
> + blk_mq_debugfs_alloc_hctx_stats(hctx, gfp);
> +
> hctx->fq = blk_alloc_flush_queue(hctx->numa_node, set->cmd_size, gfp);
> if (!hctx->fq)
> goto fail;
> @@ -4041,6 +4045,7 @@ static int blk_mq_init_hctx(struct request_queue *q,
> blk_free_flush_queue(hctx->fq);
> hctx->fq = NULL;
> fail:
> + blk_mq_debugfs_free_hctx_stats(hctx);
> return -1;
> }
>
> diff --git a/include/linux/blk-mq.h b/include/linux/blk-mq.h
> index 18a2388ba581..41d61488d683 100644
> --- a/include/linux/blk-mq.h
> +++ b/include/linux/blk-mq.h
> @@ -453,6 +453,18 @@ struct blk_mq_hw_ctx {
> struct dentry *debugfs_dir;
> /** @sched_debugfs_dir: debugfs directory for the scheduler. */
> struct dentry *sched_debugfs_dir;
> + /**
> + * @wait_on_hw_tag: Cumulative per-cpu counter incremented each
> + * time a submitting context is forced to block due to physical
> + * hardware tag exhaustion.
> + */
> + unsigned long __percpu *wait_on_hw_tag;
> + /**
> + * @wait_on_sched_tag: Cumulative per-cpu counter incremented each
> + * time a submitting context is forced to block due to software
> + * scheduler tag exhaustion.
> + */
> + unsigned long __percpu *wait_on_sched_tag;
> #endif
>
> /**
^ permalink raw reply
* [PATCH] block: fix dio leak on integrity metadata mapping failure
From: Aaron Esau @ 2026-05-18 7:42 UTC (permalink / raw)
To: linux-block
Cc: Jens Axboe, Anuj Gupta, Kanchan Joshi, Christoph Hellwig,
Keith Busch, linux-kernel, stable, Aaron Esau
In __blkdev_direct_IO(), when bio_integrity_map_iter() fails on a bio
after earlier bios have already been submitted, the goto fail path frees
only the current bio and returns the error directly to the caller.
This is incorrect because the fail label does not decrement dio->ref for
the current bio, and does not return -EIOCBQUEUED. The in-flight bios
each hold a reference via dio->ref, which was incremented before their
submission. When they eventually complete, blkdev_bio_end_io() decrements
dio->ref but it will never reach zero since the failing bio did not
participate in the completion mechanism. This permanently leaks the
embedded dio/bio structure from blkdev_dio_pool.
The trigger is deterministic: a multi-segment direct IO with integrity
metadata where the user-provided metadata buffer is too small for all
segments causes bio_integrity_map_iter() to return -EINVAL on a later
bio after earlier bios are already in flight.
Fix this by handling the integrity mapping failure the same way
blkdev_iov_iter_get_pages() failure is handled: set bi_status and call
bio_endio() to enter the normal completion path, then break out of the
loop so the function returns -EIOCBQUEUED for async IO.
Fixes: 3d8b5a22d404 ("block: add support to pass user meta buffer")
Cc: stable@vger.kernel.org
Signed-off-by: Aaron Esau <aaron1esau@gmail.com>
---
block/fops.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/block/fops.c b/block/fops.c
index bb6642b459..39280d761c 100644
--- a/block/fops.c
+++ b/block/fops.c
@@ -239,8 +239,11 @@ static ssize_t __blkdev_direct_IO(struct kiocb *iocb, struct iov_iter *iter,
}
if (iocb->ki_flags & IOCB_HAS_METADATA) {
ret = bio_integrity_map_iter(bio, iocb->private);
- if (unlikely(ret))
- goto fail;
+ if (unlikely(ret)) {
+ bio->bi_status = BLK_STS_IOERR;
+ bio_endio(bio);
+ break;
+ }
}
if (is_read) {
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v6 4/4] block: enable RWF_DONTCACHE for block devices
From: Christoph Hellwig @ 2026-05-18 6:49 UTC (permalink / raw)
To: Tal Zussman
Cc: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Dave Chinner, Bart Van Assche, linux-block,
linux-kernel, linux-xfs, linux-fsdevel, linux-mm, Gao Xiang
In-Reply-To: <20260514-blk-dontcache-v6-4-782e2fa7477b@columbia.edu>
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply
* Re: [PATCH v6 3/4] buffer: add dropbehind writeback support
From: Christoph Hellwig @ 2026-05-18 6:49 UTC (permalink / raw)
To: Tal Zussman
Cc: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Dave Chinner, Bart Van Assche, linux-block,
linux-kernel, linux-xfs, linux-fsdevel, linux-mm, Gao Xiang
In-Reply-To: <20260514-blk-dontcache-v6-3-782e2fa7477b@columbia.edu>
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply
* Re: [PATCH v6 2/4] iomap: use BIO_COMPLETE_IN_TASK for dropbehind writeback
From: Christoph Hellwig @ 2026-05-18 6:48 UTC (permalink / raw)
To: Tal Zussman
Cc: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Dave Chinner, Bart Van Assche, linux-block,
linux-kernel, linux-xfs, linux-fsdevel, linux-mm, Gao Xiang
In-Reply-To: <20260514-blk-dontcache-v6-2-782e2fa7477b@columbia.edu>
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply
* Re: [PATCH v6 1/4] block: add task-context bio completion infrastructure
From: Christoph Hellwig @ 2026-05-18 6:48 UTC (permalink / raw)
To: Tal Zussman
Cc: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Dave Chinner, Bart Van Assche, linux-block,
linux-kernel, linux-xfs, linux-fsdevel, linux-mm, Gao Xiang
In-Reply-To: <20260514-blk-dontcache-v6-1-782e2fa7477b@columbia.edu>
On Thu, May 14, 2026 at 05:51:14PM -0400, Tal Zussman wrote:
> Some bio completion handlers need to run from preemptible task context,
> but bio_endio() may be called from IRQ context (e.g., buffer_head
> writeback). Callers need a way to ensure their callback eventually runs
> from a sleepable context. Add infrastructure for that, in two forms:
>
> 1. BIO_COMPLETE_IN_TASK, a bio flag the submitter sets when it knows
> in advance that its callback needs task context (e.g., dropbehind
> writeback). bio_endio() sees the flag and offloads completion to a
> worker automatically.
>
> 2. bio_complete_in_task(), a helper that completion callbacks can
> invoke from within bi_end_io() when the deferral decision is
> dynamic (e.g., fserror reporting).
Note that method 2 is unused as of this series. I do plan to add users
ASAP, and at one or two could even land through the block layer in this
merge window.
> Both share a per-CPU batch list drained by a delayed work item on a
> WQ_PERCPU workqueue. Producers push the bio onto the local CPU's batch
> and schedule the work item, which then dispatches each bio's bi_end_io()
> from task context. The delayed work item uses a 1-jiffie delay to allow
> batches of completions to accumulate before processing.
But this 1-jiffie delay also means we unconditionally increase
completion latency, which feels like a bad idea. Do you have any
measurements that show where it does benefit? Note that queing work
already often has very measurable latency on it's own. This also
directly contradics the erofs experience that even went to a RT
thread to reduce the latency.
> Both methods are gated on bio_in_atomic(), which returns true in any
> context where a sleeping bi_end_io() is unsafe, including
> non-preemptible task context. This logic is copied from commit
> c99fab6e80b7 ("erofs: fix atomic context detection when
> !CONFIG_DEBUG_LOCK_ALLOC").
Let's not copy it, but have a prep patch that moves the erofs logic
into the block layer under the new bio_in_atomic name.
> + while ((bio = bio_list_pop(&list)))
> + bio->bi_end_io(bio);
> +
> + if (need_resched()) {
> + bool is_empty;
> +
> + local_lock_irq(&bio_complete_batch.lock);
> + is_empty = bio_list_empty(&batch->list);
> + local_unlock_irq(&bio_complete_batch.lock);
> + if (!is_empty)
> + mod_delayed_work_on(batch->cpu,
> + bio_complete_wq,
> + &batch->work, 0);
> + break;
> + }
> + }
Ån all mainstream architetures we now default to lazy preempt, which
should remove the need for need_resched() calls. Do you have evidence
that we actually need this handling on recent kernels?
Otherwise this looks good to me.
^ permalink raw reply
* [PATCH 2/2] block: don't set BIO_QUIET for BLK_STS_AGAIN
From: Christoph Hellwig @ 2026-05-18 6:33 UTC (permalink / raw)
To: Jens Axboe; +Cc: Christian Brauner, Jan Kara, linux-fsdevel, linux-block
In-Reply-To: <20260518063336.507369-1-hch@lst.de>
Commit abb30460bda2 ("block: mark bio_wouldblock_error() bio with
BIO_QUIET") added this to suppress buffer_head warnings, but neither
when this commit was added nor now any buffer_head using code actually
ever sets REQ_NOWAIT which can lead to BLK_STS_AGAIN.
Remove the special handling for now. If we ever plan to use REQ_NOWAIT
for buffer_head based I/O we're better off handling BLK_STS_AGAIN in
the completion handler as it actually needs to retry the I/O as well.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
include/linux/bio.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/include/linux/bio.h b/include/linux/bio.h
index 85463981d0f5..7597ae4dc52b 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -379,7 +379,6 @@ static inline void bio_io_error(struct bio *bio)
static inline void bio_wouldblock_error(struct bio *bio)
{
- bio_set_flag(bio, BIO_QUIET);
bio->bi_status = BLK_STS_AGAIN;
bio_endio(bio);
}
--
2.53.0
^ permalink raw reply related
* [PATCH 1/2] direct-io: remove IOCB_NOWAIT support
From: Christoph Hellwig @ 2026-05-18 6:33 UTC (permalink / raw)
To: Jens Axboe; +Cc: Christian Brauner, Jan Kara, linux-fsdevel, linux-block
In-Reply-To: <20260518063336.507369-1-hch@lst.de>
None of the file systems using the legacy direct I/O code actually sets
FMODE_NOWAIT, and if they did this would not work, as the write locking
could not handle the retry. Remove this dead code.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/direct-io.c | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)
diff --git a/fs/direct-io.c b/fs/direct-io.c
index 2267f5ae7f77..9e4976716985 100644
--- a/fs/direct-io.c
+++ b/fs/direct-io.c
@@ -502,12 +502,8 @@ static blk_status_t dio_bio_complete(struct dio *dio, struct bio *bio)
const enum req_op dio_op = dio->opf & REQ_OP_MASK;
bool should_dirty = dio_op == REQ_OP_READ && dio->should_dirty;
- if (err) {
- if (err == BLK_STS_AGAIN && (bio->bi_opf & REQ_NOWAIT))
- dio->io_error = -EAGAIN;
- else
- dio->io_error = -EIO;
- }
+ if (err)
+ dio->io_error = -EIO;
if (dio->is_async && should_dirty) {
bio_check_pages_dirty(bio); /* transfers ownership */
@@ -1178,13 +1174,10 @@ ssize_t __blockdev_direct_IO(struct kiocb *iocb, struct inode *inode,
dio->is_async = true;
dio->inode = inode;
- if (iov_iter_rw(iter) == WRITE) {
+ if (iov_iter_rw(iter) == WRITE)
dio->opf = REQ_OP_WRITE | REQ_SYNC | REQ_IDLE;
- if (iocb->ki_flags & IOCB_NOWAIT)
- dio->opf |= REQ_NOWAIT;
- } else {
+ else
dio->opf = REQ_OP_READ;
- }
/*
* For AIO O_(D)SYNC writes we need to defer completions to a workqueue
--
2.53.0
^ permalink raw reply related
* REQ_NOAIT cleanups
From: Christoph Hellwig @ 2026-05-18 6:33 UTC (permalink / raw)
To: Jens Axboe; +Cc: Christian Brauner, Jan Kara, linux-fsdevel, linux-block
Hi all,
this series cleans up spurious code related to REQ_NOWAIT handling.
I have block layer work depending on this pending, so merging it through
the block tree would be helpful.
Diffstat:
fs/direct-io.c | 15 ++++-----------
include/linux/bio.h | 1 -
2 files changed, 4 insertions(+), 12 deletions(-)
^ permalink raw reply
* [PATCH] iomap: don't make REQ_POLLED imply REQ_NOWAIT
From: Christoph Hellwig @ 2026-05-18 6:29 UTC (permalink / raw)
To: brauner; +Cc: djwong, axboe, linux-xfs, linux-fsdevel, linux-block
As described in commit 2bc057692599 ("block: don't make REQ_POLLED imply
REQ_NOWAIT"), which fixed the same issue for the block device node, there
are valid cases to poll for I/O completion without REQ_NOWAIT.
Additionally, sing REQ_NOWAIT for file system writes is currently not
supported as file systems writes are not idempotent and would need a
retry of just the bio and not the entire operation to be fully supported.
Switch iomap to set REQ_POLLED and remove the now unused bio_set_polled
helper.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/iomap/direct-io.c | 2 +-
include/linux/bio.h | 14 --------------
2 files changed, 1 insertion(+), 15 deletions(-)
diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
index b0a6549b3848..db16f21446f7 100644
--- a/fs/iomap/direct-io.c
+++ b/fs/iomap/direct-io.c
@@ -69,7 +69,7 @@ static void iomap_dio_submit_bio(const struct iomap_iter *iter,
/* Sync dio can't be polled reliably */
if ((iocb->ki_flags & IOCB_HIPRI) && !is_sync_kiocb(iocb)) {
- bio_set_polled(bio, iocb);
+ bio->bi_opf |= REQ_POLLED;
WRITE_ONCE(iocb->private, bio);
}
diff --git a/include/linux/bio.h b/include/linux/bio.h
index 85463981d0f5..6b0ad28387b8 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -695,20 +695,6 @@ static inline bool bioset_initialized(struct bio_set *bs)
return bs->bio_slab != NULL;
}
-/*
- * Mark a bio as polled. Note that for async polled IO, the caller must
- * expect -EWOULDBLOCK if we cannot allocate a request (or other resources).
- * We cannot block waiting for requests on polled IO, as those completions
- * must be found by the caller. This is different than IRQ driven IO, where
- * it's safe to wait for IO to complete.
- */
-static inline void bio_set_polled(struct bio *bio, struct kiocb *kiocb)
-{
- bio->bi_opf |= REQ_POLLED;
- if (kiocb->ki_flags & IOCB_NOWAIT)
- bio->bi_opf |= REQ_NOWAIT;
-}
-
static inline void bio_clear_polled(struct bio *bio)
{
bio->bi_opf &= ~REQ_POLLED;
--
2.53.0
^ permalink raw reply related
* Re: [PATCH RFC 1/2] nvme: downgrade WARN in nvme_setup_rw to pr_debug
From: Christoph Hellwig @ 2026-05-18 5:56 UTC (permalink / raw)
To: Chao S
Cc: Christoph Hellwig, linux-nvme, linux-block, kbusch, sagi, axboe,
Sungwoo Kim, Dave Tian, Weidong Zhu
In-Reply-To: <CACd_6n3s+Y+xdsuQVja=t1RUEdro2yQRyJyjpqFcd9_pF98OzA@mail.gmail.com>
On Sat, May 16, 2026 at 11:54:49PM -0400, Chao S wrote:
> On Thu, May 07, 2026 at 07:48:09AM +0200, Christoph Hellwig wrote:
> > What is "semantic mutation of Identify Namespace responses" [...]
>
> The emulated controller returns a mutated Identify Namespace on
> rescan (flbas META_EXT, lbaf.ms != 0, dps == 0) -- the host-unaware
What does "mutated" mean here?
^ permalink raw reply
* Re: improve the swap_activate interface
From: Christoph Hellwig @ 2026-05-18 5:29 UTC (permalink / raw)
To: Chris Li
Cc: Christoph Hellwig, Andrew Morton, Kairui Song, Christian Brauner,
Darrick J . Wong, Jens Axboe, David Sterba, Theodore Ts'o,
Jaegeuk Kim, Chao Yu, Trond Myklebust, Anna Schumaker,
Namjae Jeon, Hyunchul Lee, Steve French, Paulo Alcantara,
Carlos Maiolino, Damien Le Moal, Naohiro Aota, linux-xfs,
linux-fsdevel, linux-doc, linux-mm, linux-block, linux-btrfs,
linux-ext4, linux-f2fs-devel, linux-nfs, linux-cifs
In-Reply-To: <CACePvbUj0-fAd-gjRjxFXYz22hGQaT9upFL85KUqD=W=SWX+0Q@mail.gmail.com>
On Fri, May 15, 2026 at 02:40:09PM -0700, Chris Li wrote:
> BTW, I just tried it, this series conflicts with Kairui's swap table
> phase IV series. Might need to coordinate the merge order with Kairui.
Yes. I think the swap table should be a priority. Next would be
swap_ops (see my take on that from Friday) and then this series. The
iomap-fuse work from Darrick seems to be a bit stalled on the fuse side,
so I hope he can wait a bit on this as well. Especially as swapfile
support might not be the highest priority feature for that.
^ permalink raw reply
* [PATCH] blk-cgroup: defer blkcg css_put until blkg is unlinked from queue
From: Zizhi Wo @ 2026-05-18 1:09 UTC (permalink / raw)
To: axboe, tj, josef, yukuai, linux-block
Cc: cgroups, yangerkun, chengzhihao1, wozizhi
From: Zizhi Wo <wozizhi@huawei.com>
[BUG]
Our fuzz testing triggered a blkcg use-after-free issue:
BUG: KASAN: slab-use-after-free in _raw_spin_lock+0x75/0xe0
Call Trace:
...
blkcg_deactivate_policy+0x244/0x4d0
ioc_rqos_exit+0x44/0xe0
rq_qos_exit+0xba/0x120
__del_gendisk+0x50b/0x800
del_gendisk+0xff/0x190
...
[CAUSE]
process1 process2
cgroup_rmdir
...
css_killed_work_fn
offline_css
...
blkcg_destroy_blkgs
...
__blkg_release
css_put(&blkg->blkcg->css)
blkg_free
INIT_WORK(xxx, blkg_free_workfn)
schedule_work
css_put
...
blkcg_css_free
kfree(blkcg)--------blkcg has been freed!!!
====================================schedule_work
blkg_free_workfn
__del_gendisk
rq_qos_exit
ioc_rqos_exit
blkcg_deactivate_policy
mutex_lock(&q->blkcg_mutex)
spin_lock_irq(&q->queue_lock)
list_for_each_entry(blkg, xxx)
blkcg = blkg->blkcg
spin_lock(&blkcg->lock)-------UAF!!!
mutex_lock(&q->blkcg_mutex)
spin_lock_irq(&q->queue_lock)
/* Only then is the blkg removed from the list */
list_del_init(&blkg->q_node)
As a result, a blkg can still be reachable through q->blkg_list while
its ->blkcg has already been freed.
[Fix]
Fix this by deferring the blkcg css_put() until after the blkg has been
unlinked from q->blkg_list in blkg_free_workfn(). This ensures that the
blkcg outlives every blkg still reachable through q->blkg_list, so any
iterator holding q->queue_lock is guaranteed to observe a valid
blkg->blkcg.
Fixes: f1c006f1c685 ("blk-cgroup: synchronize pd_free_fn() from blkg_free_workfn() and blkcg_deactivate_policy()")
Signed-off-by: Zizhi Wo <wozizhi@huawei.com>
---
block/blk-cgroup.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c
index 554c87bb4a86..7b7677d022d0 100644
--- a/block/blk-cgroup.c
+++ b/block/blk-cgroup.c
@@ -132,10 +132,15 @@ static void blkg_free_workfn(struct work_struct *work)
if (blkg->parent)
blkg_put(blkg->parent);
spin_lock_irq(&q->queue_lock);
list_del_init(&blkg->q_node);
spin_unlock_irq(&q->queue_lock);
+ /*
+ * Release blkcg css ref only after blkg is removed from q->blkg_list,
+ * so concurrent iterators won't see a blkg with a freed blkcg.
+ */
+ css_put(&blkg->blkcg->css);
mutex_unlock(&q->blkcg_mutex);
blk_put_queue(q);
free_percpu(blkg->iostat_cpu);
percpu_ref_exit(&blkg->refcnt);
@@ -177,12 +182,10 @@ static void __blkg_release(struct rcu_head *rcu)
* blkg_stat_lock is for serializing blkg stat update
*/
for_each_possible_cpu(cpu)
__blkcg_rstat_flush(blkcg, cpu);
- /* release the blkcg and parent blkg refs this blkg has been holding */
- css_put(&blkg->blkcg->css);
blkg_free(blkg);
}
/*
* A group is RCU protected, but having an rcu lock does not mean that one
--
2.52.0
^ permalink raw reply related
* Re: [PATCH RFC 1/2] nvme: downgrade WARN in nvme_setup_rw to pr_debug
From: Keith Busch @ 2026-05-17 22:42 UTC (permalink / raw)
To: Chao S
Cc: linux-nvme, linux-block, hch, sagi, axboe, Sungwoo Kim, Dave Tian,
Weidong Zhu
In-Reply-To: <ago7kyBnaWKU7l3E@kbusch-mbp>
On Sun, May 17, 2026 at 04:05:07PM -0600, Keith Busch wrote:
> On Sat, May 16, 2026 at 11:53:54PM -0400, Chao S wrote:
> > On Thu, May 07, 2026 at 07:12:26PM +0100, Keith Busch wrote:
> > > [...] how that was defeated [...]
> >
> > Hi Keith,
> >
> > Not the freeze. The WARN does not depend on q->limits, but on
> > ns->head->ms (read live at dispatch, set inside the freeze window) and
> > on REQ_INTEGRITY, never set for EXT_LBAS-non-PI. capacity==0 only
> > gates submission (bio_check_eod()), not dispatch: a writeback bio that
> > passed bio_check_eod() under the old capacity sits on the task plug
> > holding no q_usage_counter ref, so it does not block the freeze;
> > blk_finish_plug() flushes it after the update committed head->ms != 0
> > (dmesg: the capacity-change line prints before the WARN).
> >
> > So it is reachable -- the host-unaware geometry change you described,
> > unrelated to your block fix. The deeper fencing gap is the separate
> > TP-level issue; v2 does not attempt it, it only stops a
> > device-reachable, already-safely-rejected dispatch from being a WARN
> > (a panic under panic_on_warn).
>
> I think tHe WARN is serving it's intendeded purpose: the block layer
> shouldn't have submitted this request. You can't do generic read/write
> with extended metadatate as the DMA is going to corrupt memory with
> respect to what the block layer expects.
>
> This driver is depending on the capacity constraint to prevent this
> scenario, so I think The "end-of-device" check needs to happen within
> the entered queue context. If there's a scenario that escapes that
> check, then I think that's what needs fixing, not the driver.
Does this fix it? I don't necessarily like having yet another check in
the hotpath, but should be exactly the check that drivers expected to be
done, so should be cache hot in the normal case.
---
diff --git a/block/blk-core.c b/block/blk-core.c
index 17450058ea6d8..4b5fb32a7d6f8 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -553,7 +553,7 @@ ALLOW_ERROR_INJECTION(should_fail_bio, ERRNO);
* This may well happen - the kernel calls bread() without checking the size of
* the device, e.g., when mounting a file system.
*/
-static inline int bio_check_eod(struct bio *bio)
+int bio_check_eod(struct bio *bio)
{
sector_t maxsector = bdev_nr_sectors(bio->bi_bdev);
unsigned int nr_sectors = bio_sectors(bio);
diff --git a/block/blk-mq.c b/block/blk-mq.c
index 4c5c16cce4f8f..b75117ec5c988 100644
--- a/block/blk-mq.c
+++ b/block/blk-mq.c
@@ -3186,6 +3186,11 @@ void blk_mq_submit_bio(struct bio *bio)
goto queue_exit;
}
+ if (unlikely(bio_check_eod(bio))) {
+ bio_io_error(bio);
+ goto queue_exit;
+ }
+
if ((bio->bi_opf & REQ_POLLED) && !blk_mq_can_poll(q)) {
bio->bi_status = BLK_STS_NOTSUPP;
bio_endio(bio);
diff --git a/block/blk.h b/block/blk.h
index b998a7761faf3..84515bb75485d 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -54,6 +54,7 @@ bool blk_queue_start_drain(struct request_queue *q);
bool __blk_freeze_queue_start(struct request_queue *q,
struct task_struct *owner);
int __bio_queue_enter(struct request_queue *q, struct bio *bio);
+int bio_check_eod(struct bio *bio);
void submit_bio_noacct_nocheck(struct bio *bio, bool split);
int bio_submit_or_kill(struct bio *bio, unsigned int flags);
--
^ permalink raw reply related
* Re: [REGRESSION] block: virtio-blk + LVM raid1 spurious sector-0 read failures on libaio/threads submit since 5ff3f74e145a ("block: simplify direct io validity check")
From: Vjaceslavs Klimovs @ 2026-05-17 22:34 UTC (permalink / raw)
To: Thorsten Leemhuis
Cc: Jens Axboe, Keith Busch, Hannes Reinecke, Martin K. Petersen,
Christoph Hellwig, linux-block, linux-raid, dm-devel,
linux-kernel, regressions
In-Reply-To: <c3e576c8-f548-4ee4-87de-cb9151f8a061@leemhuis.info>
Yes, that was exactly it. The patch works for raid1 logical volumes
but, for obvious reasons (these are dm raid) this still oopses on
legacy mirror logical volumes:
[ 2.168054] device-mapper: raid1: Mirror read failed from 252:0.
Trying alternative device.
[ 2.169241] BUG: unable to handle page fault for address: fffff580045f4bc8
[ 2.170256] #PF: supervisor read access in kernel mode
[ 2.170997] #PF: error_code(0x0000) - not-present page
[ 2.171706] PGD 7ff9d067 P4D 7ff9d067 PUD 7ff9c067 PMD 0
[ 2.172433] Oops: Oops: 0000 [#1] SMP PTI
[ 2.173003] CPU: 0 UID: 0 PID: 11 Comm: kworker/0:1 Not tainted
6.18.29+ #19 PREEMPT(lazy)
[ 2.174118] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009),
BIOS 1.16.3-20250108_150619-localhost 04/01/2014
[ 2.175472] Workqueue: kmirrord do_mirror
[ 2.176040] RIP: 0010:bio_add_page+0x8c/0x340
[ 2.176676] Code: 07 4d 8b 48 08 41 f6 c1 01 0f 85 d6 01 00 00 0f
1f 44 00 00 4d 89 c1 49 8b 11 48 c1 ea 33 83 e2 07 83 fa 04 0f 84 bf
00 00 00 <48> 8b 56 08 4c 8d 4a ff f6 c2 01 75
08 0f 1f 44 00 00 49 89 f1 49
[ 2.179169] RSP: 0018:ffffcea500063bc8 EFLAGS: 00010293
[ 2.179933] RAX: 0000000000000001 RBX: ffff8d53149af400 RCX:
0000000000000580
[ 2.180947] RDX: 0000000000000001 RSI: fffff580045f4bc0 RDI:
ffff8d53149af488
[ 2.181969] RBP: 0000000000000000 R08: fffff580005f4c00 R09:
fffff580005f4c00
[ 2.182978] R10: ffffcea500063c14 R11: 0000000000000a80 R12:
ffff8d5303192a80
[ 2.183997] R13: ffffcea500063c20 R14: 0000000000000001 R15:
ffffcea500063cf8
[ 2.185022] FS: 0000000000000000(0000) GS:ffff8d53ed4d5000(0000)
knlGS:0000000000000000
[ 2.186180] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 2.187035] CR2: fffff580045f4bc8 CR3: 0000000002c44002 CR4:
0000000000372ef0
[ 2.188047] Call Trace:
[ 2.188417] <TASK>
[ 2.188756] do_region+0x21d/0x270
[ 2.189313] dispatch_io+0xf1/0x150
[ 2.189832] ? __pfx_bio_get_page+0x10/0x10
[ 2.190424] ? __pfx_bio_next_page+0x10/0x10
[ 2.191046] dm_io+0x136/0x240
[ 2.191503] ? __pfx_read_callback+0x10/0x10
[ 2.192108] ? __pfx_bio_get_page+0x10/0x10
[ 2.192708] ? __pfx_bio_next_page+0x10/0x10
[ 2.193319] do_reads+0x13e/0x210
[ 2.193807] ? __pfx_read_callback+0x10/0x10
[ 2.194411] do_mirror+0x117/0x2a0
[ 2.194912] process_one_work+0x18d/0x340
[ 2.195508] worker_thread+0x196/0x300
[ 2.196022] ? __pfx_worker_thread+0x10/0x10
[ 2.196617] kthread+0xfc/0x240
[ 2.197073] ? __pfx_kthread+0x10/0x10
[ 2.197606] ? __pfx_kthread+0x10/0x10
[ 2.198116] ret_from_fork+0x158/0x170
[ 2.198645] ? __pfx_kthread+0x10/0x10
[ 2.199161] ret_from_fork_asm+0x1a/0x30
[ 2.199736] </TASK>
[ 2.200053] Modules linked in:
[ 2.200493] CR2: fffff580045f4bc8
[ 2.200951] ---[ end trace 0000000000000000 ]---
[ 2.201599] RIP: 0010:bio_add_page+0x8c/0x340
[ 2.202193] Code: 07 4d 8b 48 08 41 f6 c1 01 0f 85 d6 01 00 00 0f
1f 44 00 00 4d 89 c1 49 8b 11 48 c1 ea 33 83 e2 07 83 fa 04 0f 84 bf
00 00 00 <48> 8b 56 08 4c 8d 4a ff f6 c2 01 75
08 0f 1f 44 00 00 49 89 f1 49
[ 2.204690] RSP: 0018:ffffcea500063bc8 EFLAGS: 00010293
[ 2.205390] RAX: 0000000000000001 RBX: ffff8d53149af400 RCX:
0000000000000580
[ 2.206368] RDX: 0000000000000001 RSI: fffff580045f4bc0 RDI:
ffff8d53149af488
[ 2.207333] RBP: 0000000000000000 R08: fffff580005f4c00 R09:
fffff580005f4c00
[ 2.208297] R10: ffffcea500063c14 R11: 0000000000000a80 R12:
ffff8d5303192a80
[ 2.209257] R13: ffffcea500063c20 R14: 0000000000000001 R15:
ffffcea500063cf8
[ 2.210265] FS: 0000000000000000(0000) GS:ffff8d53ed4d5000(0000)
knlGS:0000000000000000
[ 2.211391] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 2.212201] CR2: fffff580045f4bc8 CR3: 0000000002c44002 CR4:
0000000000372ef0
[ 2.213196] Kernel panic - not syncing: Fatal exception
[ 2.214313] Kernel Offset: 0xc200000 from 0xffffffff81000000
(relocation range: 0xffffffff80000000-0xffffffffbfffffff)
[ 2.215981] Rebooting in 10 seconds..
On Fri, May 15, 2026 at 10:10 PM Thorsten Leemhuis
<regressions@leemhuis.info> wrote:
>
> On 5/15/26 18:52, Vjaceslavs Klimovs wrote:
> > Summary
> > -------
> > On v6.18, starting a libvirt/QEMU guest with virtio-blk backed by an
> > LVM "--type raid1" LV (drivers/md/dm-raid.c stacked on
> > drivers/md/raid1.c) makes md/raid1 register read failures at LV
> > sector 0 within seconds of "virsh start" and mark rimage_0 Faulty
> > once max_corrected_read_errors (default 20) is exceeded. Reads
> > succeed via the redirect path so guests boot, but every guest disk
> > ends up degraded on every VM start. Same workload on legacy
> > "--type mirror" (drivers/md/dm-raid1.c) crashes the host: a
> > zero-length READ reaches the NVMe controller, is rejected with
> > "Invalid Field in Command", and the dm-mirror recovery path oopses.
>
> That sounds somewhat like
> https://lore.kernel.org/all/2982107.4sosBPzcNG@electra/
>
> Have you tried latest 7.1-rc? It contains a fix for the problem
> mentioned in said thread: f7b24c7b41f23b ("md/raid1,raid10: don't fail
> devices for invalid IO errors") [v7.1-rc2]
>
> Ciao, Thorsten
>
> > Symptom on dm-raid raid1 (post --type raid1)
> > --------------------------------------------
> > Per LV, at virsh start, in host dmesg:
> >
> > kernel: raid1_end_read_request: 95 callbacks suppressed
> > kernel: raid1_read_request: 95 callbacks suppressed
> > kernel: md/raid1:mdX: dm-58: rescheduling sector 0
> > kernel: md/raid1:mdX: redirecting sector 0 to other mirror: dm-58
> > kernel: md/raid1:mdX: dm-58: rescheduling sector 0
> > kernel: md/raid1:mdX: redirecting sector 0 to other mirror: dm-58
> > [... 10 rescheduling/redirecting pairs ...]
> > kernel: md/raid1:mdX: dm-58: Raid device exceeded read_error
> > threshold [cur 21:max 20]
> > kernel: md/raid1:mdX: dm-58: Failing raid device
> > kernel: md/raid1:mdX: Disk failure on dm-58, disabling device.
> > kernel: md/raid1:mdX: Operation continuing on 1 devices.
> >
> > dmeventd: WARNING: Device #0 of raid1 array, vg0-iris_boot, has failed.
> > dmeventd: WARNING: Waiting for resynchronization to finish before
> > initiating repair on RAID device vg0-iris_boot.
> > dmeventd: Use 'lvconvert --repair vg0/iris_boot' to replace failed device.
> >
> > Subsequent "lvs -a":
> >
> > WARNING: RaidLV vg0/iris_boot needs to be refreshed!
> > See character 'r' at position 9 in the RaidLV's attributes and its SubLV(s).
> >
> > dmesg | grep nvme is EMPTY on this path. The NVMe driver is not
> > involved in producing the error; the failure originates between the
> > virtio-blk bio submission and raid1_end_read_request().
> >
> > Symptom on legacy dm-mirror (pre-conversion --type mirror)
> > ----------------------------------------------------------
> > Same workload on drivers/md/dm-raid1.c reaches the NVMe controller
> > as a zero-length READ and panics the host through dm-mirror's
> > recovery path:
> >
> > kernel: operation not supported error, dev nvme1n1, sector 935446535
> > op 0x0:(READ) flags 0x0 phys_seg 0 prio class 2
> > kernel: nvme1n1: I/O Cmd(0x2) @ LBA 935446535, 0 blocks, I/O Error
> > (sct 0x0 / sc 0x2)
> > [... 10+ identical bursts at same timestamp ...]
> > dmeventd: Primary mirror device 252:58 read failed.
> > dmeventd: vg0-iris_boot is now in-sync.
> > [kernel oops in dm_mirror recovery path, full trace lost to console flash]
> >
> > The "phys_seg 0", "0 blocks", "sct 0x0/sc 0x2" trio (NVMe Generic,
> > Invalid Field in Command, NVMe spec 4.1.1.2) is unambiguous: a bio
> > with bi_iter.bi_size == 0 and bi_vcnt == 0 left the block layer and
> > hit the controller. dm-raid raid1 hides this by retrying on the
> > surviving leg, but the upstream-of-md trigger is identical.
> >
> > Bisect
> > ------
> > git bisect, v6.12..v6.18, 16 deterministic GOOD/BAD steps, no skips,
> > ~104 minutes:
> >
> > 5ff3f74e145adc79b49668adb8de276446acf6be is the first bad commit
> > block: simplify direct io validity check
> >
> > --- a/block/fops.c
> > +++ b/block/fops.c
> > @@ -38,8 +38,8 @@ static blk_opf_t dio_bio_write_op(struct kiocb *iocb)
> > static bool blkdev_dio_invalid(struct block_device *bdev, struct kiocb *iocb,
> > struct iov_iter *iter)
> > {
> > - return iocb->ki_pos & (bdev_logical_block_size(bdev) - 1) ||
> > - !bdev_iter_is_aligned(bdev, iter);
> > + return (iocb->ki_pos | iov_iter_count(iter)) &
> > + (bdev_logical_block_size(bdev) - 1);
> > }
> >
> > The dropped bdev_iter_is_aligned() used to walk the iov_iter and
> > reject per-segment misaligned/degenerate vectors at the blkdev fops
> > entry point. The replacement only validates ki_pos and total length
> > against the logical block size. Cases that now pass that no longer
> > get rejected:
> >
> > - iter with iov_iter_count(iter) == 0 (degenerate; total length is
> > "sector-aligned" since 0 % 512 == 0)
> > - iter where total length is sector-aligned but a segment isn't
> >
> > The commit message justifies the removal with "The block layer
> > checks all the segments for validity later". This is true for the
> > io_uring submit path (which enters __blkdev_direct_IO directly and
> > does its own validation) but not for the libaio aio_read/write_iter
> > or the worker-pool sync read/write_iter paths that enter via
> > blkdev_{read,write}_iter() -> blkdev_dio_invalid(). For those paths,
> > the segment check has no replacement.
> >
> > Reproducing
> > ----------------------------------------------------------
> >
> > The trigger requires QEMU virtio-blk's specific submission shape AND
> > a non-io_uring submit. Userspace libaio alone, userspace
> > preadv-in-a-thread alone, and QEMU's raw-driver open probes (which
> > qemu-img info exercises identically) are all insufficient. The
> > combination that hits the bug is "guest-driven I/O through
> > virtio-blk-pci with cache.direct=on and aio in {native, threads}".
> >
> > #regzbot introduced: 5ff3f74e145adc79b49668adb8de276446acf6be
> >
> > Thanks,
> > Vjaceslavs Klimovs
> >
>
^ permalink raw reply
* Re: [PATCH RFC 1/2] nvme: downgrade WARN in nvme_setup_rw to pr_debug
From: Keith Busch @ 2026-05-17 22:05 UTC (permalink / raw)
To: Chao S
Cc: linux-nvme, linux-block, hch, sagi, axboe, Sungwoo Kim, Dave Tian,
Weidong Zhu
In-Reply-To: <CACd_6n29MeACfWpqK5jaSPOVK+q5yop+eeuO_Wk0M0E5Wr7ujw@mail.gmail.com>
On Sat, May 16, 2026 at 11:53:54PM -0400, Chao S wrote:
> On Thu, May 07, 2026 at 07:12:26PM +0100, Keith Busch wrote:
> > [...] how that was defeated [...]
>
> Hi Keith,
>
> Not the freeze. The WARN does not depend on q->limits, but on
> ns->head->ms (read live at dispatch, set inside the freeze window) and
> on REQ_INTEGRITY, never set for EXT_LBAS-non-PI. capacity==0 only
> gates submission (bio_check_eod()), not dispatch: a writeback bio that
> passed bio_check_eod() under the old capacity sits on the task plug
> holding no q_usage_counter ref, so it does not block the freeze;
> blk_finish_plug() flushes it after the update committed head->ms != 0
> (dmesg: the capacity-change line prints before the WARN).
>
> So it is reachable -- the host-unaware geometry change you described,
> unrelated to your block fix. The deeper fencing gap is the separate
> TP-level issue; v2 does not attempt it, it only stops a
> device-reachable, already-safely-rejected dispatch from being a WARN
> (a panic under panic_on_warn).
I think tHe WARN is serving it's intendeded purpose: the block layer
shouldn't have submitted this request. You can't do generic read/write
with extended metadatate as the DMA is going to corrupt memory with
respect to what the block layer expects.
This driver is depending on the capacity constraint to prevent this
scenario, so I think The "end-of-device" check needs to happen within
the entered queue context. If there's a scenario that escapes that
check, then I think that's what needs fixing, not the driver.
^ permalink raw reply
* [PATCH v6 2/2] blk-mq: expose tag starvation counts via debugfs
From: Aaron Tomlin @ 2026-05-17 21:36 UTC (permalink / raw)
To: axboe, rostedt, mhiramat, mathieu.desnoyers
Cc: bvanassche, johannes.thumshirn, kch, dlemoal, ritesh.list,
loberman, neelx, sean, mproche, chjohnst, linux-block,
linux-kernel, linux-trace-kernel
In-Reply-To: <20260517213614.350367-1-atomlin@atomlin.com>
In high-performance storage environments, particularly when utilising
RAID controllers with shared tag sets (BLK_MQ_F_TAG_HCTX_SHARED), severe
latency spikes can occur when fast devices are starved of available
tags.
This patch introduces two new debugfs attributes for each block
hardware queue:
- /sys/kernel/debug/block/[device]/hctxN/wait_on_hw_tag
- /sys/kernel/debug/block/[device]/hctxN/wait_on_sched_tag
These files expose atomic counters that increment each time a submitting
context is forced into an uninterruptible sleep via io_schedule() due to
the complete exhaustion of physical driver tags or software scheduler
tags, respectively.
To ensure negligible performance overhead even in production
environments where CONFIG_BLK_DEBUG_FS is actively enabled, this
tracking logic utilises dynamically allocated per-CPU counters. When
this configuration is disabled, the tracking logic compiles down to a
safe no-op.
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
block/blk-mq-debugfs.c | 109 +++++++++++++++++++++++++++++++++++++++++
block/blk-mq-debugfs.h | 19 +++++++
block/blk-mq-tag.c | 4 ++
block/blk-mq.c | 5 ++
include/linux/blk-mq.h | 12 +++++
5 files changed, 149 insertions(+)
diff --git a/block/blk-mq-debugfs.c b/block/blk-mq-debugfs.c
index 047ec887456b..a94ffc2eacdf 100644
--- a/block/blk-mq-debugfs.c
+++ b/block/blk-mq-debugfs.c
@@ -7,6 +7,7 @@
#include <linux/blkdev.h>
#include <linux/build_bug.h>
#include <linux/debugfs.h>
+#include <linux/percpu.h>
#include "blk.h"
#include "blk-mq.h"
@@ -484,6 +485,54 @@ static int hctx_dispatch_busy_show(void *data, struct seq_file *m)
return 0;
}
+/**
+ * hctx_wait_on_hw_tag_show - display hardware tag starvation count
+ * @data: generic pointer to the associated hardware context (hctx)
+ * @m: seq_file pointer for debugfs output formatting
+ *
+ * Prints the cumulative number of times a submitting context was forced
+ * to block due to the exhaustion of physical hardware driver tags.
+ *
+ * Return: 0 on success.
+ */
+static int hctx_wait_on_hw_tag_show(void *data, struct seq_file *m)
+{
+ struct blk_mq_hw_ctx *hctx = data;
+ unsigned long count = 0;
+ int cpu;
+
+ if (hctx->wait_on_hw_tag) {
+ for_each_possible_cpu(cpu)
+ count += *per_cpu_ptr(hctx->wait_on_hw_tag, cpu);
+ }
+ seq_printf(m, "%lu\n", count);
+ return 0;
+}
+
+/**
+ * hctx_wait_on_sched_tag_show - display scheduler tag starvation count
+ * @data: generic pointer to the associated hardware context (hctx)
+ * @m: seq_file pointer for debugfs output formatting
+ *
+ * Prints the cumulative number of times a submitting context was forced
+ * to block due to the exhaustion of software scheduler tags.
+ *
+ * Return: 0 on success.
+ */
+static int hctx_wait_on_sched_tag_show(void *data, struct seq_file *m)
+{
+ struct blk_mq_hw_ctx *hctx = data;
+ unsigned long count = 0;
+ int cpu;
+
+ if (hctx->wait_on_sched_tag) {
+ for_each_possible_cpu(cpu)
+ count += *per_cpu_ptr(hctx->wait_on_sched_tag, cpu);
+ }
+ seq_printf(m, "%lu\n", count);
+ return 0;
+}
+
#define CTX_RQ_SEQ_OPS(name, type) \
static void *ctx_##name##_rq_list_start(struct seq_file *m, loff_t *pos) \
__acquires(&ctx->lock) \
@@ -599,6 +648,8 @@ static const struct blk_mq_debugfs_attr blk_mq_debugfs_hctx_attrs[] = {
{"active", 0400, hctx_active_show},
{"dispatch_busy", 0400, hctx_dispatch_busy_show},
{"type", 0400, hctx_type_show},
+ {"wait_on_hw_tag", 0400, hctx_wait_on_hw_tag_show},
+ {"wait_on_sched_tag", 0400, hctx_wait_on_sched_tag_show},
{},
};
@@ -815,3 +866,61 @@ void blk_mq_debugfs_unregister_sched_hctx(struct blk_mq_hw_ctx *hctx)
debugfs_remove_recursive(hctx->sched_debugfs_dir);
hctx->sched_debugfs_dir = NULL;
}
+
+/**
+ * blk_mq_debugfs_alloc_hctx_stats - Allocate per-cpu starvation statistics
+ * @hctx: hardware context associated with the tag allocation
+ * @gfp: memory allocation flags
+ *
+ * Allocates the per-cpu memory for tracking hardware and scheduler tag
+ * starvation.
+ */
+void blk_mq_debugfs_alloc_hctx_stats(struct blk_mq_hw_ctx *hctx, gfp_t gfp)
+{
+ if (!hctx->wait_on_hw_tag)
+ hctx->wait_on_hw_tag = alloc_percpu_gfp(unsigned long,
+ gfp);
+ if (!hctx->wait_on_sched_tag)
+ hctx->wait_on_sched_tag = alloc_percpu_gfp(unsigned long,
+ gfp);
+}
+
+/**
+ * blk_mq_debugfs_free_hctx_stats - Free per-cpu starvation statistics
+ * @hctx: hardware context associated with the tag allocation
+ *
+ * Frees the per-cpu memory used for tracking hardware and scheduler tag
+ * starvation. This must only be called during hardware queue teardown when
+ * the queue is safely frozen and no active I/O submissions can race to
+ * increment the statistics.
+ */
+void blk_mq_debugfs_free_hctx_stats(struct blk_mq_hw_ctx *hctx)
+{
+ free_percpu(hctx->wait_on_hw_tag);
+ hctx->wait_on_hw_tag = NULL;
+ free_percpu(hctx->wait_on_sched_tag);
+ hctx->wait_on_sched_tag = NULL;
+}
+
+/**
+ * blk_mq_debugfs_inc_wait_tags - increment the tag starvation counters
+ * @hctx: hardware context associated with the tag allocation
+ * @is_sched: true if the starved pool is the software scheduler
+ *
+ * Evaluates the exhausted tag pool and safely increments the appropriate
+ * per-cpu debugfs starvation counter.
+ *
+ * Note: The per-cpu pointers are explicitly checked to prevent a NULL
+ * pointer dereference in the event that the system was under heavy memory
+ * pressure and the initial per-cpu allocation failed.
+ */
+void blk_mq_debugfs_inc_wait_tags(struct blk_mq_hw_ctx *hctx,
+ bool is_sched)
+{
+ unsigned long __percpu *tags = is_sched ?
+ READ_ONCE(hctx->wait_on_sched_tag) :
+ READ_ONCE(hctx->wait_on_hw_tag);
+
+ if (likely(tags))
+ raw_cpu_inc(*tags);
+}
diff --git a/block/blk-mq-debugfs.h b/block/blk-mq-debugfs.h
index 49bb1aaa83dc..7a7c0f376a2b 100644
--- a/block/blk-mq-debugfs.h
+++ b/block/blk-mq-debugfs.h
@@ -17,6 +17,8 @@ struct blk_mq_debugfs_attr {
const struct seq_operations *seq_ops;
};
+void blk_mq_debugfs_inc_wait_tags(struct blk_mq_hw_ctx *hctx,
+ bool is_sched);
int __blk_mq_debugfs_rq_show(struct seq_file *m, struct request *rq);
int blk_mq_debugfs_rq_show(struct seq_file *m, void *v);
@@ -26,6 +28,9 @@ void blk_mq_debugfs_register_hctx(struct request_queue *q,
void blk_mq_debugfs_unregister_hctx(struct blk_mq_hw_ctx *hctx);
void blk_mq_debugfs_register_hctxs(struct request_queue *q);
void blk_mq_debugfs_unregister_hctxs(struct request_queue *q);
+void blk_mq_debugfs_alloc_hctx_stats(struct blk_mq_hw_ctx *hctx,
+ gfp_t gfp);
+void blk_mq_debugfs_free_hctx_stats(struct blk_mq_hw_ctx *hctx);
void blk_mq_debugfs_register_sched(struct request_queue *q);
void blk_mq_debugfs_unregister_sched(struct request_queue *q);
@@ -35,6 +40,11 @@ void blk_mq_debugfs_unregister_sched_hctx(struct blk_mq_hw_ctx *hctx);
void blk_mq_debugfs_register_rq_qos(struct request_queue *q);
#else
+static inline void blk_mq_debugfs_inc_wait_tags(struct blk_mq_hw_ctx *hctx,
+ bool is_sched)
+{
+}
+
static inline void blk_mq_debugfs_register(struct request_queue *q)
{
}
@@ -56,6 +66,15 @@ static inline void blk_mq_debugfs_unregister_hctxs(struct request_queue *q)
{
}
+static inline void blk_mq_debugfs_alloc_hctx_stats(struct blk_mq_hw_ctx *hctx,
+ gfp_t gfp)
+{
+}
+
+static inline void blk_mq_debugfs_free_hctx_stats(struct blk_mq_hw_ctx *hctx)
+{
+}
+
static inline void blk_mq_debugfs_register_sched(struct request_queue *q)
{
}
diff --git a/block/blk-mq-tag.c b/block/blk-mq-tag.c
index 66138dd043d4..3cc6a97a87a0 100644
--- a/block/blk-mq-tag.c
+++ b/block/blk-mq-tag.c
@@ -17,6 +17,7 @@
#include "blk.h"
#include "blk-mq.h"
#include "blk-mq-sched.h"
+#include "blk-mq-debugfs.h"
/*
* Recalculate wakeup batch when tag is shared by hctx.
@@ -191,6 +192,9 @@ unsigned int blk_mq_get_tag(struct blk_mq_alloc_data *data)
trace_block_rq_tag_wait(data->q, data->hctx,
data->rq_flags & RQF_SCHED_TAGS);
+ blk_mq_debugfs_inc_wait_tags(data->hctx,
+ data->rq_flags & RQF_SCHED_TAGS);
+
bt_prev = bt;
io_schedule();
diff --git a/block/blk-mq.c b/block/blk-mq.c
index 4c5c16cce4f8..cd52bf6f82ce 100644
--- a/block/blk-mq.c
+++ b/block/blk-mq.c
@@ -3991,6 +3991,8 @@ static void blk_mq_exit_hctx(struct request_queue *q,
blk_free_flush_queue_callback);
hctx->fq = NULL;
+ blk_mq_debugfs_free_hctx_stats(hctx);
+
spin_lock(&q->unused_hctx_lock);
list_add(&hctx->hctx_list, &q->unused_hctx_list);
spin_unlock(&q->unused_hctx_lock);
@@ -4016,6 +4018,8 @@ static int blk_mq_init_hctx(struct request_queue *q,
{
gfp_t gfp = GFP_NOIO | __GFP_NOWARN | __GFP_NORETRY;
+ blk_mq_debugfs_alloc_hctx_stats(hctx, gfp);
+
hctx->fq = blk_alloc_flush_queue(hctx->numa_node, set->cmd_size, gfp);
if (!hctx->fq)
goto fail;
@@ -4041,6 +4045,7 @@ static int blk_mq_init_hctx(struct request_queue *q,
blk_free_flush_queue(hctx->fq);
hctx->fq = NULL;
fail:
+ blk_mq_debugfs_free_hctx_stats(hctx);
return -1;
}
diff --git a/include/linux/blk-mq.h b/include/linux/blk-mq.h
index 18a2388ba581..41d61488d683 100644
--- a/include/linux/blk-mq.h
+++ b/include/linux/blk-mq.h
@@ -453,6 +453,18 @@ struct blk_mq_hw_ctx {
struct dentry *debugfs_dir;
/** @sched_debugfs_dir: debugfs directory for the scheduler. */
struct dentry *sched_debugfs_dir;
+ /**
+ * @wait_on_hw_tag: Cumulative per-cpu counter incremented each
+ * time a submitting context is forced to block due to physical
+ * hardware tag exhaustion.
+ */
+ unsigned long __percpu *wait_on_hw_tag;
+ /**
+ * @wait_on_sched_tag: Cumulative per-cpu counter incremented each
+ * time a submitting context is forced to block due to software
+ * scheduler tag exhaustion.
+ */
+ unsigned long __percpu *wait_on_sched_tag;
#endif
/**
--
2.51.0
^ permalink raw reply related
* [PATCH v6 1/2] blk-mq: add tracepoint block_rq_tag_wait
From: Aaron Tomlin @ 2026-05-17 21:36 UTC (permalink / raw)
To: axboe, rostedt, mhiramat, mathieu.desnoyers
Cc: bvanassche, johannes.thumshirn, kch, dlemoal, ritesh.list,
loberman, neelx, sean, mproche, chjohnst, linux-block,
linux-kernel, linux-trace-kernel
In-Reply-To: <20260517213614.350367-1-atomlin@atomlin.com>
In high-performance storage environments, particularly when utilising
RAID controllers with shared tag sets (BLK_MQ_F_TAG_HCTX_SHARED), severe
latency spikes can occur when fast devices (SSDs) are starved of hardware
tags when sharing the same blk_mq_tag_set.
Currently, diagnosing this specific hardware queue contention is
difficult. When a CPU thread exhausts the tag pool, blk_mq_get_tag()
forces the current thread to block uninterruptible via io_schedule().
While this can be inferred via sched:sched_switch or dynamically
traced by attaching a kprobe to blk_mq_mark_tag_wait(), there is no
dedicated, out-of-the-box observability for this event.
This patch introduces the block_rq_tag_wait trace point in the tag
allocation slow-path. It triggers immediately before the thread yields
the CPU, exposing the exact hardware context (hctx) that is starved, the
specific pool experiencing starvation (hardware or software scheduler),
and the total pool depth.
This provides storage engineers and performance monitoring agents
with a zero-configuration, low-overhead mechanism to definitively
identify shared-tag bottlenecks and tune I/O schedulers or cgroup
throttling accordingly.
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Chaitanya Kulkarni <kch@nvidia.com>
Reviewed-by: Laurence Oberman <loberman@redhat.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
block/blk-mq-tag.c | 4 ++++
include/trace/events/block.h | 43 ++++++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/block/blk-mq-tag.c b/block/blk-mq-tag.c
index 33946cdb5716..66138dd043d4 100644
--- a/block/blk-mq-tag.c
+++ b/block/blk-mq-tag.c
@@ -13,6 +13,7 @@
#include <linux/kmemleak.h>
#include <linux/delay.h>
+#include <trace/events/block.h>
#include "blk.h"
#include "blk-mq.h"
#include "blk-mq-sched.h"
@@ -187,6 +188,9 @@ unsigned int blk_mq_get_tag(struct blk_mq_alloc_data *data)
if (tag != BLK_MQ_NO_TAG)
break;
+ trace_block_rq_tag_wait(data->q, data->hctx,
+ data->rq_flags & RQF_SCHED_TAGS);
+
bt_prev = bt;
io_schedule();
diff --git a/include/trace/events/block.h b/include/trace/events/block.h
index 6aa79e2d799c..d6b9a6bc3c63 100644
--- a/include/trace/events/block.h
+++ b/include/trace/events/block.h
@@ -226,6 +226,49 @@ DECLARE_EVENT_CLASS(block_rq,
IOPRIO_PRIO_LEVEL(__entry->ioprio), __entry->comm)
);
+/**
+ * block_rq_tag_wait - triggered when a request is starved of a tag
+ * @q: request queue of the target device
+ * @hctx: hardware context of the request experiencing starvation
+ * @is_sched_tag: indicates whether the starved pool is the software scheduler
+ *
+ * Called immediately before the submitting context is forced to block due
+ * to the exhaustion of available tags (i.e., physical hardware driver tags
+ * or software scheduler tags). This trace point indicates that the context
+ * will be placed into an uninterruptible state via io_schedule() until an
+ * active request completes and relinquishes its assigned tag.
+ */
+TRACE_EVENT(block_rq_tag_wait,
+
+ TP_PROTO(struct request_queue *q, struct blk_mq_hw_ctx *hctx, bool is_sched_tag),
+
+ TP_ARGS(q, hctx, is_sched_tag),
+
+ TP_STRUCT__entry(
+ __field( dev_t, dev )
+ __field( u32, hctx_id )
+ __field( u32, nr_tags )
+ __field( bool, is_sched_tag )
+ ),
+
+ TP_fast_assign(
+ __entry->dev = q->disk ? disk_devt(q->disk) : 0;
+ __entry->hctx_id = hctx->queue_num;
+ __entry->is_sched_tag = is_sched_tag;
+
+ if (is_sched_tag)
+ __entry->nr_tags = hctx->sched_tags->nr_tags;
+ else
+ __entry->nr_tags = hctx->tags->nr_tags;
+ ),
+
+ TP_printk("%d,%d hctx=%u starved on %s tags (depth=%u)",
+ MAJOR(__entry->dev), MINOR(__entry->dev),
+ __entry->hctx_id,
+ __entry->is_sched_tag ? "scheduler" : "hardware",
+ __entry->nr_tags)
+);
+
/**
* block_rq_insert - insert block operation request into queue
* @rq: block IO operation request
--
2.51.0
^ permalink raw reply related
* [PATCH v6 0/2] blk-mq: introduce tag starvation observability
From: Aaron Tomlin @ 2026-05-17 21:36 UTC (permalink / raw)
To: axboe, rostedt, mhiramat, mathieu.desnoyers
Cc: bvanassche, johannes.thumshirn, kch, dlemoal, ritesh.list,
loberman, neelx, sean, mproche, chjohnst, linux-block,
linux-kernel, linux-trace-kernel
Hi Jens, Steve, Masami,
In high-performance storage environments, particularly when utilising RAID
controllers with shared tag sets (BLK_MQ_F_TAG_HCTX_SHARED), severe latency
spikes can occur when fast devices are starved of available tags.
Currently, diagnosing this specific queue contention requires deploying
dynamic kprobes or inferring sleep states, which lacks a simple,
out-of-the-box diagnostic path.
This short series introduces dedicated, low-overhead observability for tag
exhaustion events in the block layer:
- Patch 1 introduces the "block_rq_tag_wait" tracepoint in the tag
allocation slow-path to capture precise, event-based starvation.
- Patch 2 complements this by exposing "wait_on_hw_tag" and
"wait_on_sched_tag" per-CPU counters via debugfs for quick,
point-in-time cumulative polling.
Together, these provide storage engineers with zero-configuration
mechanisms to definitively identify shared-tag bottlenecks.
Please let me know your thoughts.
Changes since v5 [1]:
- Replaced this_cpu_inc() with raw_cpu_inc() within
blk_mq_debugfs_inc_wait_tags(). This resolves a preemption warning
triggered under CONFIG_DEBUG_PREEMPT=y, as the routine is invoked from a
preemptible context immediately prior to io_schedule(). This adjustment
deliberately prioritises the reduction of execution overhead over
absolute statistical precision for this diagnostic interface.
Changes since v4 [2]:
- Prevented a NULL pointer dereference in the tracepoint fast-assign for
disk-less request queues by safely checking q->disk before resolving the
dev_t
- Fixed a Use-After-Free (UAF) and permanent memory leak by decoupling
the per-CPU counter allocation from the volatile debugfs lifecycle and
tying it directly to the core hctx lifecycle (i.e., blk_mq_init_hctx()
and blk_mq_exit_hctx())
- Fixed a potential compiler double-fetch bug by wrapping the per-CPU
pointer evaluations with READ_ONCE() in blk_mq_debugfs_inc_wait_tags()
- Passed the appropriate gfp_t flags down to the allocation routines to
maintain the strict GFP_NOIO context
- Updated kernel-doc descriptions to clarify that the NULL pointer
checks guard against memory allocation failures under pressure, rather
than initialisation race conditions
Changes since v3 [3]:
- Transitioned tracking architecture from shared atomic_t variables to
dynamically allocated per-CPU counters to resolve cache line bouncing
(Bart Van Assche)
Changes since v2 [4]:
- Added "Reviewed-by:" and "Tested-by:" tags for patch 1
- Evaluate is_sched_tag directly within TP_fast_assign (Steven Rostedt)
- Introduced atomic counters via debugfs
Changes since v1 [5]:
- Improved the description of the trace point (Damien Le Moal)
- Removed the redundant "active requests" (Laurence Oberman)
- Introduced pool-specific starvation tracking
[1]: https://lore.kernel.org/lkml/20260427020142.358912-1-atomlin@atomlin.com/
[2]: https://lore.kernel.org/lkml/20260419023036.1419514-1-atomlin@atomlin.com/
[3]: https://lore.kernel.org/lkml/20260319221956.332770-1-atomlin@atomlin.com/
[4]: https://lore.kernel.org/lkml/20260319015300.287653-1-atomlin@atomlin.com/
[5]: https://lore.kernel.org/lkml/20260317182835.258183-1-atomlin@atomlin.com/
Aaron Tomlin (2):
blk-mq: add tracepoint block_rq_tag_wait
blk-mq: expose tag starvation counts via debugfs
block/blk-mq-debugfs.c | 109 +++++++++++++++++++++++++++++++++++
block/blk-mq-debugfs.h | 19 ++++++
block/blk-mq-tag.c | 8 +++
block/blk-mq.c | 5 ++
include/linux/blk-mq.h | 12 ++++
include/trace/events/block.h | 43 ++++++++++++++
6 files changed, 196 insertions(+)
--
2.51.0
^ permalink raw reply
* Re: [PATCH v5 2/2] blk-mq: expose tag starvation counts via debugfs
From: Aaron Tomlin @ 2026-05-17 21:11 UTC (permalink / raw)
To: axboe, rostedt, mhiramat, mathieu.desnoyers
Cc: bvanassche, johannes.thumshirn, kch, dlemoal, ritesh.list,
loberman, neelx, sean, mproche, chjohnst, linux-block,
linux-kernel, linux-trace-kernel
In-Reply-To: <20260427020142.358912-3-atomlin@atomlin.com>
On Sun, Apr 26, 2026 at 10:01:42PM -0400, Aaron Tomlin wrote:
> +/**
> + * blk_mq_debugfs_inc_wait_tags - increment the tag starvation counters
> + * @hctx: hardware context associated with the tag allocation
> + * @is_sched: true if the starved pool is the software scheduler
> + *
> + * Evaluates the exhausted tag pool and safely increments the appropriate
> + * per-cpu debugfs starvation counter.
> + *
> + * Note: The per-cpu pointers are explicitly checked to prevent a NULL
> + * pointer dereference in the event that the system was under heavy memory
> + * pressure and the initial per-cpu allocation failed.
> + */
> +void blk_mq_debugfs_inc_wait_tags(struct blk_mq_hw_ctx *hctx,
> + bool is_sched)
> +{
> + unsigned long __percpu *tags = is_sched ?
> + READ_ONCE(hctx->wait_on_sched_tag) :
> + READ_ONCE(hctx->wait_on_hw_tag);
> +
> + if (likely(tags))
> + this_cpu_inc(*tags);
> +}
Hi Jens,
I have realised that this particular code path, invoked from
blk_mq_get_tag() immediately prior to io_schedule(), is in fact, a
preemptible context. Consequently, utilising this_cpu_inc() here will
invariably trigger a warning when operating under a kernel with
CONFIG_DEBUG_PREEMPT=y enabled.
To rectify this, I intend to transition to the use of raw_cpu_inc(). Given
that this is solely for a debugfs interface, I believe it is far more
prudent to prioritise the mitigation of execution overhead over absolute
statistical precision, should a preemption race occur.
Kind regards,
--
Aaron Tomlin
^ permalink raw reply
* [PATCH RESEND] n64cart: use strscpy in n64cart_probe
From: Thorsten Blum @ 2026-05-17 17:26 UTC (permalink / raw)
To: Jens Axboe; +Cc: Thorsten Blum, linux-block, linux-kernel
strcpy() has been deprecated [1] because it performs no bounds checking
on the destination buffer, which can lead to buffer overflows. While the
current code works correctly, replace strcpy() with the safer strscpy()
to follow secure coding best practices.
[1] https://www.kernel.org/doc/html/latest/process/deprecated.html#strcpy
Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
---
drivers/block/n64cart.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/block/n64cart.c b/drivers/block/n64cart.c
index b9fdeff31caf..328da73b6f2c 100644
--- a/drivers/block/n64cart.c
+++ b/drivers/block/n64cart.c
@@ -12,6 +12,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/string.h>
enum {
PI_DRAM_REG = 0,
@@ -145,7 +146,7 @@ static int __init n64cart_probe(struct platform_device *pdev)
disk->flags = GENHD_FL_NO_PART;
disk->fops = &n64cart_fops;
disk->private_data = &pdev->dev;
- strcpy(disk->disk_name, "n64cart");
+ strscpy(disk->disk_name, "n64cart");
set_capacity(disk, size >> SECTOR_SHIFT);
set_disk_ro(disk, 1);
^ permalink raw reply related
* [PATCH v2] nvme: don't WARN on I/O to a namespace revalidated to unusable metadata
From: Chao Shi @ 2026-05-17 5:36 UTC (permalink / raw)
To: linux-nvme
Cc: linux-block, hch, kbusch, sagi, axboe, iam, daveti, weizhu,
Chao Shi
nvme_setup_rw() fires WARN_ON_ONCE(!nvme_ns_has_pi(ns->head)) for a
namespace with head->ms != 0 but no PI and no REQ_INTEGRITY. This
occurs when Identify Namespace reports flbas META_EXT, lbaf[].ms != 0
and dps == 0: on PCIe nvme_configure_metadata() sets EXT_LBAS without
METADATA_SUPPORTED, nvme_init_integrity() registers no profile, and
capacity is forced to 0. It is the host-unaware geometry change Keith
described -- an out-of-band format on a shared namespace, or a
non-compliant device seen on rescan -- not the host's own Format NVM,
which freezes first.
The freeze in nvme_update_ns_info_block() is not defeated; the WARN
just does not depend on q->limits. It depends on ns->head->ms (read
live at dispatch, set inside the freeze window) and on REQ_INTEGRITY,
never set for this geometry. capacity == 0 only gates submission
(bio_check_eod()), not dispatch: a writeback bio that passed
bio_check_eod() under the old capacity sits on the task plug holding
no q_usage_counter reference and is flushed by blk_finish_plug() after
the update committed head->ms != 0 (dmesg confirms: the capacity-change
line prints before the WARN).
The I/O is already rejected correctly (BLK_STS_NOTSUPP, capacity 0).
The assertion fires on a device-reachable, already-handled condition
-- a panic under panic_on_warn -- and its premise does not hold:
metadata without PI or a registrable profile is a legitimate, unusable
state. Add that explicit case and emit one dev_warn_once() instead.
Fully fencing already-submitted bios over a revalidation is larger,
TP-level work and is out of scope here.
Tested: built on linux-kcov-debug (6.19.0+, KASAN); boot-tested under
FEMU, 4x dd + 500 rescans, no splat; reject path verified by code
inspection.
Found by FuzzNvme (Syzkaller with FEMU fuzzing framework).
Link: https://lore.kernel.org/linux-nvme/20260427003457.1264511-1-coshi036@gmail.com/
Acked-by: Sungwoo Kim <iam@sung-woo.kim>
Acked-by: Dave Tian <daveti@purdue.edu>
Acked-by: Weidong Zhu <weizhu@fiu.edu>
Signed-off-by: Chao Shi <coshi036@gmail.com>
---
v2: drop 2/2 (faking an integrity profile); supersede 1/2 with an
explicit unusable-metadata case + one dev_warn_once() instead of
downgrading the WARN; rewrite the log with the requested mechanism.
RFC v1: https://lore.kernel.org/linux-nvme/20260427003457.1264511-1-coshi036@gmail.com/
drivers/nvme/host/core.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index d1711ef59fb8..32ccb56c4aaf 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -1039,8 +1039,23 @@ static inline blk_status_t nvme_setup_rw(struct nvme_ns *ns,
* namespace capacity to zero to prevent any I/O.
*/
if (!blk_integrity_rq(req)) {
- if (WARN_ON_ONCE(!nvme_ns_has_pi(ns->head)))
+ /*
+ * A namespace with metadata but neither PI nor a block
+ * layer integrity profile is unusable: nvme_init_integrity()
+ * registers no profile, blk_get_integrity() is NULL, no bio
+ * ever gets REQ_INTEGRITY, and the capacity is forced to 0.
+ * A bio that passed bio_check_eod() under the old capacity
+ * and was batched on a plug before the namespace revalidated
+ * can still be dispatched here afterwards. Reject it; this
+ * is the expected terminal handling of I/O to a namespace
+ * that revalidated to an unusable geometry, not a bug.
+ */
+ if (!nvme_ns_has_pi(ns->head)) {
+ dev_warn_once(ns->ctrl->device,
+ "%s: I/O to namespace with metadata but no usable integrity profile (ms=%u), rejecting\n",
+ ns->disk->disk_name, ns->head->ms);
return BLK_STS_NOTSUPP;
+ }
control |= NVME_RW_PRINFO_PRACT;
nvme_set_ref_tag(ns, cmnd, req);
}
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox