Linux RAID subsystem development
 help / color / mirror / Atom feed
* [PATCH] md: add helper to split bios at reshape offset
From: Yu Kuai @ 2026-06-05  9:15 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel
In-Reply-To: <20260605091527.2463539-1-yukuai@kernel.org>

From: Yu Kuai <yukuai@fygo.io>

Add mddev_bio_split_at_reshape_offset() so personalities can share
reshape-offset bio splitting instead of open-coding the same helper in
multiple places.

Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/md.c | 39 +++++++++++++++++++++++++++++++++++++++
 drivers/md/md.h |  4 ++++
 2 files changed, 43 insertions(+)

diff --git a/drivers/md/md.c b/drivers/md/md.c
index ccc4180d2c1d..6685e4c53fd9 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -9359,10 +9359,49 @@ void md_submit_discard_bio(struct mddev *mddev, struct md_rdev *rdev,
 	mddev_trace_remap(mddev, discard_bio, bio->bi_iter.bi_sector);
 	submit_bio_noacct(discard_bio);
 }
 EXPORT_SYMBOL_GPL(md_submit_discard_bio);
 
+struct bio *mddev_bio_split_at_reshape_offset(struct mddev *mddev,
+					      struct bio *bio,
+					      unsigned int *max_sectors,
+					      struct bio_set *bs)
+{
+	sector_t boundary;
+	sector_t start;
+	sector_t end;
+	unsigned int split_sectors;
+
+	split_sectors = bio_sectors(bio);
+	if (max_sectors && *max_sectors && *max_sectors < split_sectors)
+		split_sectors = *max_sectors;
+
+	if (!test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery))
+		goto split;
+
+	boundary = mddev->reshape_position;
+	start = bio->bi_iter.bi_sector;
+	end = bio_end_sector(bio);
+	if (start >= boundary || end <= boundary)
+		goto split;
+
+	if (boundary - start < split_sectors)
+		split_sectors = boundary - start;
+
+split:
+	if (max_sectors)
+		*max_sectors = split_sectors;
+	if (split_sectors < bio_sectors(bio)) {
+		bio = bio_submit_split_bioset(bio, split_sectors, bs);
+		if (bio)
+			bio->bi_opf |= REQ_NOMERGE;
+	}
+
+	return bio;
+}
+EXPORT_SYMBOL_GPL(mddev_bio_split_at_reshape_offset);
+
 static void md_bitmap_prepare_range(struct mddev *mddev, sector_t *offset,
 				    unsigned long *sectors)
 {
 	mddev->bitmap_ops->prepare_range(mddev, offset, sectors);
 }
diff --git a/drivers/md/md.h b/drivers/md/md.h
index 110cf0f8b107..ebfc6da83161 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -923,10 +923,14 @@ extern void md_done_sync(struct mddev *mddev, int blocks);
 extern void md_sync_error(struct mddev *mddev);
 extern void md_error(struct mddev *mddev, struct md_rdev *rdev);
 extern void md_finish_reshape(struct mddev *mddev);
 void md_submit_discard_bio(struct mddev *mddev, struct md_rdev *rdev,
 			struct bio *bio, sector_t start, sector_t size);
+struct bio *mddev_bio_split_at_reshape_offset(struct mddev *mddev,
+					      struct bio *bio,
+					      unsigned int *max_sectors,
+					      struct bio_set *bs);
 void md_account_bio(struct mddev *mddev, struct bio **bio);
 
 extern bool __must_check md_flush_request(struct mddev *mddev, struct bio *bio);
 void md_write_metadata(struct mddev *mddev, struct md_rdev *rdev,
 		       sector_t sector, int size, struct page *page,
-- 
2.51.0


^ permalink raw reply related

* [PATCH] md: skip bitmap accounting for empty write ranges
From: Yu Kuai @ 2026-06-05  9:15 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel
In-Reply-To: <20260605091527.2463539-1-yukuai@kernel.org>

From: Yu Kuai <yukuai@fygo.io>

mkfs.ext4 can submit zero-sector flush/FUA bios. These bios are WRITE
bios for md_write_start() purposes, but they do not cover any data sector
and must not dirty bitmap bits.

md bitmap accounting currently passes such bios to bitmap start_write().
For llbitmap this reaches llbitmap_start_write() with sectors == 0,
which underflows the end chunk calculation.

The new bitmap prepare_range() hook can also turn a non-empty bio into an
empty bitmap range when the requested sectors are outside the active
bitmap geometry. Treat both cases as not started, so the completion path
will not call end_write() for an empty range.

Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/md.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/md/md.c b/drivers/md/md.c
index 08eabc7e5a71..ccc4180d2c1d 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -9374,10 +9374,12 @@ static void md_bitmap_start(struct mddev *mddev,
 			   mddev->bitmap_ops->start_discard :
 			   mddev->bitmap_ops->start_write;
 
 	md_bitmap_prepare_range(mddev, &md_io_clone->offset,
 				&md_io_clone->sectors);
+	if (!md_io_clone->sectors)
+		return;
 	fn(mddev, md_io_clone->offset, md_io_clone->sectors);
 }
 
 static void md_bitmap_end(struct mddev *mddev, struct md_io_clone *md_io_clone)
 {
@@ -9394,11 +9396,12 @@ static void md_end_clone_io(struct bio *bio)
 						       bio_clone);
 	struct bio *orig_bio = md_io_clone->orig_bio;
 	struct mddev *mddev = md_io_clone->mddev;
 	struct completion *reshape_completion = bio->bi_private;
 
-	if (bio_data_dir(orig_bio) == WRITE && md_bitmap_enabled(mddev, false))
+	if (bio_data_dir(orig_bio) == WRITE && md_io_clone->sectors &&
+	    md_bitmap_enabled(mddev, false))
 		md_bitmap_end(mddev, md_io_clone);
 
 	if (bio->bi_status && !orig_bio->bi_status)
 		orig_bio->bi_status = bio->bi_status;
 
@@ -9421,14 +9424,16 @@ static void md_clone_bio(struct mddev *mddev, struct bio **bio)
 		bio_alloc_clone(bdev, *bio, GFP_NOIO, &mddev->io_clone_set);
 
 	md_io_clone = container_of(clone, struct md_io_clone, bio_clone);
 	md_io_clone->orig_bio = *bio;
 	md_io_clone->mddev = mddev;
+	md_io_clone->sectors = 0;
 	if (blk_queue_io_stat(bdev->bd_disk->queue))
 		md_io_clone->start_time = bio_start_io_acct(*bio);
 
-	if (bio_data_dir(*bio) == WRITE && md_bitmap_enabled(mddev, false)) {
+	if (bio_data_dir(*bio) == WRITE && bio_sectors(*bio) &&
+	    md_bitmap_enabled(mddev, false)) {
 		md_io_clone->offset = (*bio)->bi_iter.bi_sector;
 		md_io_clone->sectors = bio_sectors(*bio);
 		md_io_clone->rw = op_stat_group(bio_op(*bio));
 		md_bitmap_start(mddev, md_io_clone);
 	}
-- 
2.51.0


^ permalink raw reply related

* [PATCH] md: add exact bitmap mapping and reshape hooks
From: Yu Kuai @ 2026-06-05  9:15 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel
In-Reply-To: <20260605091527.2463539-1-yukuai@kernel.org>

From: Yu Kuai <yukuai@fygo.io>

Add bitmap mapping and reshape hooks needed by llbitmap reshape
support without teaching md core to account a single bio against
multiple bitmap ranges.

This also adds the old/new bitmap geometry helpers used by
personalities to describe reshape mapping to llbitmap.

Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/md-bitmap.c   |  8 ++++++++
 drivers/md/md-bitmap.h   |  8 ++++++++
 drivers/md/md-llbitmap.c |  8 ++++++++
 drivers/md/md.c          | 12 ++++++++----
 drivers/md/md.h          |  4 ++++
 5 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c
index 028b9ca8ce52..e10296788cdd 100644
--- a/drivers/md/md-bitmap.c
+++ b/drivers/md/md-bitmap.c
@@ -1727,10 +1727,17 @@ static void bitmap_start_write(struct mddev *mddev, sector_t offset,
 		else
 			sectors = 0;
 	}
 }
 
+static void bitmap_prepare_range(struct mddev *mddev, sector_t *offset,
+				 unsigned long *sectors)
+{
+	if (mddev->pers->bitmap_sector)
+		mddev->pers->bitmap_sector(mddev, offset, sectors);
+}
+
 static void bitmap_end_write(struct mddev *mddev, sector_t offset,
 			     unsigned long sectors)
 {
 	struct bitmap *bitmap = mddev->bitmap;
 
@@ -3075,10 +3082,11 @@ static struct bitmap_operations bitmap_ops = {
 	.load			= bitmap_load,
 	.destroy		= bitmap_destroy,
 	.flush			= bitmap_flush,
 	.write_all		= bitmap_write_all,
 	.dirty_bits		= bitmap_dirty_bits,
+	.prepare_range		= bitmap_prepare_range,
 	.unplug			= bitmap_unplug,
 	.daemon_work		= bitmap_daemon_work,
 
 	.start_behind_write	= bitmap_start_behind_write,
 	.end_behind_write	= bitmap_end_behind_write,
diff --git a/drivers/md/md-bitmap.h b/drivers/md/md-bitmap.h
index 214f623c7e79..f0136fc02feb 100644
--- a/drivers/md/md-bitmap.h
+++ b/drivers/md/md-bitmap.h
@@ -91,10 +91,18 @@ struct bitmap_operations {
 	void (*destroy)(struct mddev *mddev);
 	void (*flush)(struct mddev *mddev);
 	void (*write_all)(struct mddev *mddev);
 	void (*dirty_bits)(struct mddev *mddev, unsigned long s,
 			   unsigned long e);
+	/* Prepare a range for this bitmap implementation. */
+	void (*prepare_range)(struct mddev *mddev,
+			      sector_t *offset,
+			      unsigned long *sectors);
+	void (*reshape_finish)(struct mddev *mddev);
+	int (*reshape_can_start)(struct mddev *mddev);
+	void (*reshape_mark)(struct mddev *mddev, sector_t old_pos,
+			     sector_t new_pos);
 	void (*unplug)(struct mddev *mddev, bool sync);
 	void (*daemon_work)(struct mddev *mddev);
 
 	void (*start_behind_write)(struct mddev *mddev);
 	void (*end_behind_write)(struct mddev *mddev);
diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 1adc5b117821..bcf34f0c9af6 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1186,10 +1186,17 @@ static void llbitmap_destroy(struct mddev *mddev)
 	llbitmap_free_pages(llbitmap);
 	kfree(llbitmap);
 	mutex_unlock(&mddev->bitmap_info.mutex);
 }
 
+static void llbitmap_prepare_range(struct mddev *mddev, sector_t *offset,
+				   unsigned long *sectors)
+{
+	if (mddev->pers->bitmap_sector)
+		mddev->pers->bitmap_sector(mddev, offset, sectors);
+}
+
 static void llbitmap_start_write(struct mddev *mddev, sector_t offset,
 				 unsigned long sectors)
 {
 	struct llbitmap *llbitmap = mddev->bitmap;
 	unsigned long start = offset >> llbitmap->chunkshift;
@@ -1775,10 +1782,11 @@ static struct bitmap_operations llbitmap_ops = {
 	.cond_end_sync		= llbitmap_cond_end_sync,
 
 	.update_sb		= llbitmap_update_sb,
 	.get_stats		= llbitmap_get_stats,
 	.dirty_bits		= llbitmap_dirty_bits,
+	.prepare_range		= llbitmap_prepare_range,
 	.write_all		= llbitmap_write_all,
 
 	.groups			= md_llbitmap_groups,
 };
 
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 096bb64e87bd..08eabc7e5a71 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -9359,21 +9359,25 @@ void md_submit_discard_bio(struct mddev *mddev, struct md_rdev *rdev,
 	mddev_trace_remap(mddev, discard_bio, bio->bi_iter.bi_sector);
 	submit_bio_noacct(discard_bio);
 }
 EXPORT_SYMBOL_GPL(md_submit_discard_bio);
 
+static void md_bitmap_prepare_range(struct mddev *mddev, sector_t *offset,
+				    unsigned long *sectors)
+{
+	mddev->bitmap_ops->prepare_range(mddev, offset, sectors);
+}
+
 static void md_bitmap_start(struct mddev *mddev,
 			    struct md_io_clone *md_io_clone)
 {
 	md_bitmap_fn *fn = unlikely(md_io_clone->rw == STAT_DISCARD) ?
 			   mddev->bitmap_ops->start_discard :
 			   mddev->bitmap_ops->start_write;
 
-	if (mddev->pers->bitmap_sector)
-		mddev->pers->bitmap_sector(mddev, &md_io_clone->offset,
-					   &md_io_clone->sectors);
-
+	md_bitmap_prepare_range(mddev, &md_io_clone->offset,
+				&md_io_clone->sectors);
 	fn(mddev, md_io_clone->offset, md_io_clone->sectors);
 }
 
 static void md_bitmap_end(struct mddev *mddev, struct md_io_clone *md_io_clone)
 {
diff --git a/drivers/md/md.h b/drivers/md/md.h
index d8daf0f75cbb..110cf0f8b107 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -796,10 +796,14 @@ struct md_personality
 	/* Changes the consistency policy of an active array. */
 	int (*change_consistency_policy)(struct mddev *mddev, const char *buf);
 	/* convert io ranges from array to bitmap */
 	void (*bitmap_sector)(struct mddev *mddev, sector_t *offset,
 			      unsigned long *sectors);
+	void (*bitmap_sector_map)(struct mddev *mddev, sector_t *offset,
+				  unsigned long *sectors, bool previous);
+	sector_t (*bitmap_sync_size)(struct mddev *mddev, bool previous);
+	sector_t (*bitmap_array_sectors)(struct mddev *mddev, bool previous);
 };
 
 struct md_sysfs_entry {
 	struct attribute attr;
 	ssize_t (*show)(struct mddev *, char *);
-- 
2.51.0


^ permalink raw reply related

* [PATCH 00/20] md/md-llbitmap: support reshape for RAID10 and RAID5
From: Yu Kuai @ 2026-06-05  9:15 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Hi,

This series adds llbitmap support for online reshape in RAID10 and RAID5.

llbitmap has a different set of constraints from the existing bitmap code:
there is one live bitmap instance, each bit state has richer semantics, and
reshape can change the mapping from logical array ranges to bitmap ranges.
The series therefore adds exact bitmap range mapping hooks, tracks old and
new llbitmap geometry during reshape, remaps checkpointed bits as reshape
progresses, and wires the reshape lifecycle into RAID10 and RAID5.

The main rules are:

1. split bios at the reshape position before bitmap accounting, so one bio
   is never accounted with mixed old/new geometry;
2. do not skip reshape ranges from stale llbitmap state, because reshape
   progress is checkpointed by array metadata;
3. remap llbitmap bits when reshape progress is checkpointed;
4. reject llbitmap reshape if mddev->chunk_sectors shrinks, because the
   effective data range represented by existing bitmap bits can shrink.

The first group of patches prepares generic bitmap and llbitmap
infrastructure.  The second group wires RAID10.  The last group wires
RAID5, including exact old/new stripe mapping.

Validation:
* RAID5 llbitmap test:
  - created 3-disk RAID5 with --bitmap=lockless
  - wrote 96 MiB of random data
  - reshaped to 4 disks
  - llbitmap bits changed from clean=1024 dirty=1024 to
    unwritten=448 clean=1600 dirty=0
  - all sync-related llbitmap counters were zero after reshape
  - data hash was unchanged after reshape
  - replaced one disk, waited for recovery, hash was unchanged
  - failed another old disk and verified degraded reads still matched
* RAID10 llbitmap test:
  - created 4-disk RAID10 n2 with --bitmap=lockless
  - wrote 128 MiB of random data
  - reshaped to 6 disks
  - llbitmap bits changed from clean=2048 dirty=2048 to
    unwritten=2048 clean=4096 dirty=0
  - all sync-related llbitmap counters were zero after reshape
  - data hash was unchanged after reshape
  - replaced one disk, waited for recovery, hash was unchanged
  - failed the rebuilt disk's mirror mate and verified degraded reads still
    matched

Yu Kuai (20):
  md: add exact bitmap mapping and reshape hooks
  md: skip bitmap accounting for empty write ranges
  md: add helper to split bios at reshape offset
  md/md-llbitmap: track bitmap sync_size explicitly
  md/md-llbitmap: allocate page controls independently
  md/md-llbitmap: grow the page cache in place for reshape
  md/md-llbitmap: track target reshape geometry fields
  md/md-llbitmap: finish reshape geometry
  md/md-llbitmap: refuse reshape while llbitmap still needs sync
  md/md-llbitmap: add reshape range mapping helpers
  md/md-llbitmap: don't skip reshape ranges from bitmap state
  md/md-llbitmap: remap checkpointed bits as reshape progresses
  md/md-llbitmap: clamp state-machine walks to tracked bits
  md/raid10: reject llbitmap reshape when md chunk shrinks
  md/raid10: wire llbitmap reshape lifecycle
  md/raid10: split reshape bios before bitmap accounting
  md/raid5: add exact old and new llbitmap mapping helpers
  md/raid5: reject llbitmap reshape when md chunk shrinks
  md/raid5: wire llbitmap reshape lifecycle
  md/raid5: split reshape bios before bitmap accounting

 drivers/md/md-bitmap.c   |   8 +
 drivers/md/md-bitmap.h   |   8 +
 drivers/md/md-llbitmap.c | 616 +++++++++++++++++++++++++++++++++++----
 drivers/md/md.c          |  60 +++-
 drivers/md/md.h          |   8 +
 drivers/md/raid10.c      |  50 +++-
 drivers/md/raid5.c       | 118 ++++++--
 7 files changed, 793 insertions(+), 75 deletions(-)

-- 
2.51.0

^ permalink raw reply

* [PATCH v2 3/3] md/raid5: always convert llbitmap bits for discard
From: Yu Kuai @ 2026-06-05  7:26 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel
In-Reply-To: <20260605072639.2434847-1-yukuai@kernel.org>

From: Yu Kuai <yukuai@fygo.io>

llbitmap discard is useful even when no underlying member device supports
it. The discard still converts the llbitmap range to unwritten, so later
reads and recovery do not rely on stale parity for that range.

Let llbitmap discard bypass the raid5 lower discard support check. If lower
discard is not safe or not supported, complete the accounted clone after
md_account_bio() so the llbitmap conversion callbacks run without member
discard bios.

Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/raid5.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 76e736ee48d3..180ff0660b6a 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -1136,10 +1136,13 @@ static void defer_issue_bios(struct r5conf *conf, sector_t sector,
 
 static bool raid5_discard_limits(struct mddev *mddev, struct bio *bi)
 {
 	struct r5conf *conf = mddev->private;
 
+	if (mddev->bitmap_id == ID_LLBITMAP)
+		return true;
+
 	if (!conf->raid5_discard_unsupported)
 		return true;
 
 	bi->bi_status = BLK_STS_NOTSUPP;
 	bio_endio(bi);
@@ -5738,10 +5741,16 @@ static void make_discard_request(struct mddev *mddev, struct bio *bi)
 	md_account_bio(mddev, &bi);
 	orig_bi->bi_iter = bi_iter;
 	bi->bi_iter = bi_iter;
 	bi->bi_next = NULL;
 
+	if (mddev->bitmap_id == ID_LLBITMAP &&
+	    conf->raid5_discard_unsupported) {
+		bio_endio(bi);
+		return;
+	}
+
 	logical_sector = first_stripe * conf->chunk_sectors;
 	last_sector = last_stripe * conf->chunk_sectors;
 
 	for (; logical_sector < last_sector;
 	     logical_sector += RAID5_STRIPE_SECTORS(conf)) {
-- 
2.51.0


^ permalink raw reply related

* [PATCH v2 2/3] md/raid5: validate discard support at request time
From: Yu Kuai @ 2026-06-05  7:26 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel
In-Reply-To: <20260605072639.2434847-1-yukuai@kernel.org>

From: Yu Kuai <yukuai@fygo.io>

Raid5 used to disable discard limits when devices_handle_discard_safely
was not set or when stacked member limits could not support a full-stripe
discard. That hides discard from userspace before raid5 can decide whether
a request can be handled safely.

Follow other virtual drivers and advertise a UINT_MAX discard limit for the
md device. Cache lower discard support in r5conf when setting queue limits,
and reject unsupported discard bios before queuing stripe work.

Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/raid5.c | 34 +++++++++++++++++++---------------
 drivers/md/raid5.h |  1 +
 2 files changed, 20 insertions(+), 15 deletions(-)

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index debf35342ae0..76e736ee48d3 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -1132,10 +1132,22 @@ static void defer_issue_bios(struct r5conf *conf, sector_t sector,
 	spin_unlock(&conf->pending_bios_lock);
 
 	dispatch_bio_list(&tmp);
 }
 
+static bool raid5_discard_limits(struct mddev *mddev, struct bio *bi)
+{
+	struct r5conf *conf = mddev->private;
+
+	if (!conf->raid5_discard_unsupported)
+		return true;
+
+	bi->bi_status = BLK_STS_NOTSUPP;
+	bio_endio(bi);
+	return false;
+}
+
 static void
 raid5_end_read_request(struct bio *bi);
 static void
 raid5_end_write_request(struct bio *bi);
 
@@ -5702,10 +5714,13 @@ static void make_discard_request(struct mddev *mddev, struct bio *bi)
 
 	if (mddev->reshape_position != MaxSector)
 		/* Skip discard while reshape is happening */
 		return;
 
+	if (!raid5_discard_limits(mddev, bi))
+		return;
+
 	stripe_sectors = conf->chunk_sectors *
 		(conf->raid_disks - conf->max_degraded);
 	first_stripe = DIV_ROUND_UP_SECTOR_T(bi->bi_iter.bi_sector,
 					     stripe_sectors);
 	last_stripe = bio_end_sector(bi);
@@ -7815,36 +7830,25 @@ static int raid5_set_limits(struct mddev *mddev)
 	mddev_stack_rdev_limits(mddev, &lim, 0);
 	rdev_for_each(rdev, mddev)
 		queue_limits_stack_bdev(&lim, rdev->bdev, rdev->new_data_offset,
 				mddev->gendisk->disk_name);
 
-	/*
-	 * Zeroing is required for discard, otherwise data could be lost.
-	 *
-	 * Consider a scenario: discard a stripe (the stripe could be
-	 * inconsistent if discard_zeroes_data is 0); write one disk of the
-	 * stripe (the stripe could be inconsistent again depending on which
-	 * disks are used to calculate parity); the disk is broken; The stripe
-	 * data of this disk is lost.
-	 *
-	 * We only allow DISCARD if the sysadmin has confirmed that only safe
-	 * devices are in use by setting a module parameter.  A better idea
-	 * might be to turn DISCARD into WRITE_ZEROES requests, as that is
-	 * required to be safe.
-	 */
 	if (!devices_handle_discard_safely ||
 	    lim.max_discard_sectors < (stripe >> 9) ||
 	    lim.discard_granularity < stripe)
-		lim.max_hw_discard_sectors = 0;
+		conf->raid5_discard_unsupported = true;
+	else
+		conf->raid5_discard_unsupported = false;
 
 	/*
 	 * Requests require having a bitmap for each stripe.
 	 * Limit the max sectors based on this.
 	 */
 	lim.max_hw_sectors = RAID5_MAX_REQ_STRIPES << RAID5_STRIPE_SHIFT(conf);
 	if ((lim.max_hw_sectors << 9) < lim.io_opt)
 		lim.max_hw_sectors = lim.io_opt >> 9;
+	lim.max_hw_discard_sectors = UINT_MAX;
 
 	/* No restrictions on the number of segments in the request */
 	lim.max_segments = USHRT_MAX;
 
 	return queue_limits_set(mddev->gendisk->queue, &lim);
diff --git a/drivers/md/raid5.h b/drivers/md/raid5.h
index 1c7b710fc9c1..ba06cf88aa24 100644
--- a/drivers/md/raid5.h
+++ b/drivers/md/raid5.h
@@ -687,10 +687,11 @@ struct r5conf {
 	struct r5pending_data	*pending_data;
 	struct list_head	free_list;
 	struct list_head	pending_list;
 	int			pending_data_cnt;
 	struct r5pending_data	*next_pending_data;
+	bool			raid5_discard_unsupported;
 
 	mempool_t		*ctx_pool;
 	int			ctx_size;
 };
 
-- 
2.51.0


^ permalink raw reply related

* [PATCH v2 1/3] md/raid5: account discard IO
From: Yu Kuai @ 2026-06-05  7:26 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel
In-Reply-To: <20260605072639.2434847-1-yukuai@kernel.org>

From: Yu Kuai <yukuai@fygo.io>

Raid5 handles discard bios internally through make_discard_request() and
never passes them through md_account_bio(). As a result, discard IO is
missing the md-device iostat accounting that normal raid5 IO and discard
IO in other raid levels get from md_account_bio().

Before accounting the bio, trim the request to the full data stripes that
raid5 will actually discard. The first full stripe is the ceiling of the
bio start divided by data-stripe sectors, and the last full stripe is the
floor of the bio end divided by data-stripe sectors. Account that exact
MD logical full-stripe range, then restore the original iterator so bio
completion and iostat still cover the original request.

Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/raid5.c | 33 +++++++++++++++++++++++----------
 1 file changed, 23 insertions(+), 10 deletions(-)

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 65ae7d8930fc..debf35342ae0 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -5688,34 +5688,47 @@ static void release_stripe_plug(struct mddev *mddev,
 
 static void make_discard_request(struct mddev *mddev, struct bio *bi)
 {
 	struct r5conf *conf = mddev->private;
 	sector_t logical_sector, last_sector;
+	sector_t first_stripe, last_stripe;
 	struct stripe_head *sh;
+	struct bvec_iter bi_iter;
+	struct bio *orig_bi = bi;
 	int stripe_sectors;
 
 	/* We need to handle this when io_uring supports discard/trim */
 	if (WARN_ON_ONCE(bi->bi_opf & REQ_NOWAIT))
 		return;
 
 	if (mddev->reshape_position != MaxSector)
 		/* Skip discard while reshape is happening */
 		return;
 
-	logical_sector = bi->bi_iter.bi_sector & ~((sector_t)RAID5_STRIPE_SECTORS(conf)-1);
-	last_sector = bio_end_sector(bi);
-
-	bi->bi_next = NULL;
-
 	stripe_sectors = conf->chunk_sectors *
 		(conf->raid_disks - conf->max_degraded);
-	logical_sector = DIV_ROUND_UP_SECTOR_T(logical_sector,
-					       stripe_sectors);
-	sector_div(last_sector, stripe_sectors);
+	first_stripe = DIV_ROUND_UP_SECTOR_T(bi->bi_iter.bi_sector,
+					     stripe_sectors);
+	last_stripe = bio_end_sector(bi);
+	sector_div(last_stripe, stripe_sectors);
+
+	if (first_stripe >= last_stripe) {
+		bio_endio(bi);
+		return;
+	}
+
+	bi_iter = bi->bi_iter;
+	bi->bi_iter.bi_sector = first_stripe * stripe_sectors;
+	bi->bi_iter.bi_size = ((last_stripe - first_stripe) *
+			       stripe_sectors) << 9;
+	md_account_bio(mddev, &bi);
+	orig_bi->bi_iter = bi_iter;
+	bi->bi_iter = bi_iter;
+	bi->bi_next = NULL;
 
-	logical_sector *= conf->chunk_sectors;
-	last_sector *= conf->chunk_sectors;
+	logical_sector = first_stripe * conf->chunk_sectors;
+	last_sector = last_stripe * conf->chunk_sectors;
 
 	for (; logical_sector < last_sector;
 	     logical_sector += RAID5_STRIPE_SECTORS(conf)) {
 		DEFINE_WAIT(w);
 		int d;
-- 
2.51.0


^ permalink raw reply related

* [PATCH v2 0/3] md/raid5: discard accounting and llbitmap support
From: Yu Kuai @ 2026-06-05  7:26 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Hi,

This series fixes raid5 discard IO accounting and lets llbitmap arrays
use discard to convert llbitmap bits even when lower member devices do
not support discard.

Patch 1 accounts the full-stripe range that raid5 handles before discard
is split.

Patch 2 advertises a UINT_MAX discard limit from raid5. Lower discard
support is cached in r5conf when raid5 sets queue limits, and unsupported
discard bios are rejected before stripe work is queued.

Patch 3 lets llbitmap discard bypass the lower-device support check. If
lower discard is not supported, raid5 completes the accounted clone after
md_account_bio(), so llbitmap conversion callbacks run without issuing
member discard bios.

Changes since v1:
- Account the exact full-stripe range handled by raid5 before converting
  llbitmap bits.
- Always advertise raid5 discard limits and move lower-device support
  validation to the request path.
- Cache unsupported lower discard state in r5conf as
  raid5_discard_unsupported.
- Keep llbitmap discard useful even when lower member devices cannot safely
  handle discard.

Validation:
- QEMU RAID5 discard test with devices_handle_discard_safely=N:
  llbitmap bit conversion matched full-stripe aligned and unaligned discard
  ranges, md discard iostat advanced for handled llbitmap discards, lower
  member discard counters stayed at zero, and normal RAID5 discarded no IO
  when lower discard was unsupported.

Thanks,
Kuai

Yu Kuai (3):
  md/raid5: account discard IO
  md/raid5: validate discard support at request time
  md/raid5: always convert llbitmap bits for discard

 drivers/md/raid5.c | 74 +++++++++++++++++++++++++++++++---------------
 drivers/md/raid5.h |  1 +
 2 files changed, 51 insertions(+), 24 deletions(-)


base-commit: 717359a168bb66ac95f6161715d17e491ee86ca7
-- 
2.51.0

^ permalink raw reply

* [PATCH] md/raid5: allow discard with llbitmap
From: Yu Kuai @ 2026-06-05  3:22 UTC (permalink / raw)
  To: Song Liu; +Cc: Yu Kuai, Li Nan, Xiao Ni, linux-raid, linux-kernel
In-Reply-To: <20260605032205.2376714-1-yukuai@kernel.org>

From: Yu Kuai <yukuai@fygo.io>

Raid5 disables discard unless devices_handle_discard_safely is set
because lower devices that do not return zeroes after discard can
leave a discarded stripe inconsistent. A later partial write can then
reconstruct bad data if a member fails.

The legacy bitmap needs this restriction because it only records
write-intent dirty ranges. It cannot distinguish discarded data from
valid data, so discard can make data inconsistent after later partial
writes and failures.

llbitmap records discarded ranges as unwritten. Raid5 already consults
llbitmap state to force RCW or lazy recovery before using parity for
unwritten data. Therefore non-zeroing discard is safe with llbitmap
while the existing full-stripe granularity and lower-device
discard-size checks still apply.

Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/raid5.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index debf35342ae0..4e9a758a8cc9 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -7829,11 +7829,12 @@ static int raid5_set_limits(struct mddev *mddev)
 	 * We only allow DISCARD if the sysadmin has confirmed that only safe
 	 * devices are in use by setting a module parameter.  A better idea
 	 * might be to turn DISCARD into WRITE_ZEROES requests, as that is
 	 * required to be safe.
 	 */
-	if (!devices_handle_discard_safely ||
+	if ((!devices_handle_discard_safely &&
+	     mddev->bitmap_id != ID_LLBITMAP) ||
 	    lim.max_discard_sectors < (stripe >> 9) ||
 	    lim.discard_granularity < stripe)
 		lim.max_hw_discard_sectors = 0;
 
 	/*
-- 
2.51.0


^ permalink raw reply related

* [PATCH] md/raid5: account discard IO
From: Yu Kuai @ 2026-06-05  3:22 UTC (permalink / raw)
  To: Song Liu; +Cc: Yu Kuai, Li Nan, Xiao Ni, linux-raid, linux-kernel
In-Reply-To: <20260605032205.2376714-1-yukuai@kernel.org>

From: Yu Kuai <yukuai@fygo.io>

Raid5 handles discard bios internally through make_discard_request() and
never passes them through md_account_bio(). As a result, discard IO is
missing the md-device iostat accounting that normal raid5 IO and discard
IO in other raid levels get from md_account_bio().

Before accounting the bio, trim the request to the full data stripes that
raid5 will actually discard. The first full stripe is the ceiling of the
bio start divided by data-stripe sectors, and the last full stripe is the
floor of the bio end divided by data-stripe sectors. Account that exact
MD logical full-stripe range, then restore the original iterator so bio
completion and iostat still cover the original request.

Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/raid5.c | 33 +++++++++++++++++++++++----------
 1 file changed, 23 insertions(+), 10 deletions(-)

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 65ae7d8930fc..debf35342ae0 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -5688,34 +5688,47 @@ static void release_stripe_plug(struct mddev *mddev,
 
 static void make_discard_request(struct mddev *mddev, struct bio *bi)
 {
 	struct r5conf *conf = mddev->private;
 	sector_t logical_sector, last_sector;
+	sector_t first_stripe, last_stripe;
 	struct stripe_head *sh;
+	struct bvec_iter bi_iter;
+	struct bio *orig_bi = bi;
 	int stripe_sectors;
 
 	/* We need to handle this when io_uring supports discard/trim */
 	if (WARN_ON_ONCE(bi->bi_opf & REQ_NOWAIT))
 		return;
 
 	if (mddev->reshape_position != MaxSector)
 		/* Skip discard while reshape is happening */
 		return;
 
-	logical_sector = bi->bi_iter.bi_sector & ~((sector_t)RAID5_STRIPE_SECTORS(conf)-1);
-	last_sector = bio_end_sector(bi);
-
-	bi->bi_next = NULL;
-
 	stripe_sectors = conf->chunk_sectors *
 		(conf->raid_disks - conf->max_degraded);
-	logical_sector = DIV_ROUND_UP_SECTOR_T(logical_sector,
-					       stripe_sectors);
-	sector_div(last_sector, stripe_sectors);
+	first_stripe = DIV_ROUND_UP_SECTOR_T(bi->bi_iter.bi_sector,
+					     stripe_sectors);
+	last_stripe = bio_end_sector(bi);
+	sector_div(last_stripe, stripe_sectors);
+
+	if (first_stripe >= last_stripe) {
+		bio_endio(bi);
+		return;
+	}
+
+	bi_iter = bi->bi_iter;
+	bi->bi_iter.bi_sector = first_stripe * stripe_sectors;
+	bi->bi_iter.bi_size = ((last_stripe - first_stripe) *
+			       stripe_sectors) << 9;
+	md_account_bio(mddev, &bi);
+	orig_bi->bi_iter = bi_iter;
+	bi->bi_iter = bi_iter;
+	bi->bi_next = NULL;
 
-	logical_sector *= conf->chunk_sectors;
-	last_sector *= conf->chunk_sectors;
+	logical_sector = first_stripe * conf->chunk_sectors;
+	last_sector = last_stripe * conf->chunk_sectors;
 
 	for (; logical_sector < last_sector;
 	     logical_sector += RAID5_STRIPE_SECTORS(conf)) {
 		DEFINE_WAIT(w);
 		int d;
-- 
2.51.0


^ permalink raw reply related

* [PATCH 0/2] md/raid5: account discard IO and allow llbitmap discard
From: Yu Kuai @ 2026-06-05  3:22 UTC (permalink / raw)
  To: Song Liu; +Cc: Yu Kuai, Li Nan, Xiao Ni, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Hi,

This series fixes RAID5 discard accounting and then allows RAID5
discard when llbitmap is enabled.

Patch 1 routes processed RAID5 discard bios through md_account_bio().
Since RAID5 only discards whole data stripes, it accounts the exact
full-stripe range that make_discard_request() will submit before
restoring the original bio iterator for completion.

Patch 2 allows discard without devices_handle_discard_safely when
llbitmap is enabled. Legacy bitmap cannot record discarded/unwritten
data and can make later partial writes plus member failure reconstruct
inconsistent data. llbitmap records discarded ranges as unwritten, and
RAID5 already uses that state to force safe recovery paths before
relying on parity.

Runtime validation was done in QEMU with a RAID5 array using a lockless
bitmap and discard-capable disks. Multiple aligned and unaligned
blkdiscard ranges were issued while tracing md_account_bio() and
llbitmap_start_discard(); the llbitmap unwritten-bit deltas matched the
traced bitmap ranges.

Yu Kuai (2):
  md/raid5: account discard IO
  md/raid5: allow discard with llbitmap

 drivers/md/raid5.c | 36 +++++++++++++++++++++++++-----------
 1 file changed, 25 insertions(+), 11 deletions(-)

-- 
2.51.0

^ permalink raw reply

* [PATCH] md/raid5: fix stripe_request_ctx bitmap sizing for unaligned bios
From: Chen Cheng @ 2026-06-03  9:04 UTC (permalink / raw)
  To: linux-raid, yukuai; +Cc: chencheng, linux-kernel

From: Chen Cheng <chencheng@fnnas.com>

stripe_request_ctx.sectors_to_do needs one extra bit for unaligned bios,
as documented by the existing comment.

raid5_make_request() rounds the bio start down to a stripe boundary, but
keeps ctx->last_sector at bio_end_sector(bio). As a result, an unaligned
bio with len == max_hw_sectors can span one more stripe than
max_hw_sectors >> RAID5_STRIPE_SHIFT(conf).

For example, if RAID5_STRIPE_SECTORS(conf) == 8 and max_hw_sectors ==
2048, a bio at sector 1 with len 2048 sectors yields stripe_cnt = 257.
bitmap_set(ctx->sectors_to_do, 0, 257) then touches the first bit beyond
a 256-bit allocation, matching the KASAN report:

  __bitmap_set
  bitmap_set
  raid5_make_request

Restore the missing extra bit in both ctx_pool sizing paths.

This bug is harder to reproduce from userspace on v7.1 because
lim.chunk_sectors = lim.io_opt >> 9 causes the block layer to split
large unaligned bios on full-stripe boundaries before they reach
raid5_make_request(). That masks the reproducer, but does not fix the
under-allocation.

Signed-off-by: Chen Cheng <chencheng@fnnas.com>
---
 drivers/md/raid5.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 65ae7d8930fc..9ece79e608f7 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -7756,15 +7756,15 @@ static int raid5_create_ctx_pool(struct r5conf *conf)
 {
 	struct stripe_request_ctx *ctx;
 	int size;
 
 	if (mddev_is_dm(conf->mddev))
-		size = BITS_TO_LONGS(RAID5_MAX_REQ_STRIPES);
+		size = BITS_TO_LONGS(RAID5_MAX_REQ_STRIPES + 1);
 	else
 		size = BITS_TO_LONGS(
-			queue_max_hw_sectors(conf->mddev->gendisk->queue) >>
-			RAID5_STRIPE_SHIFT(conf));
+			(queue_max_hw_sectors(conf->mddev->gendisk->queue) >>
+			 RAID5_STRIPE_SHIFT(conf)) + 1);
 
 	conf->ctx_size = struct_size(ctx, sectors_to_do, size);
 	conf->ctx_pool = mempool_create_kmalloc_pool(NR_RAID_BIOS,
 						     conf->ctx_size);
 
-- 
2.54.0

^ permalink raw reply related

* [PATCH v4 2/3] md/raid10: make r10bio_pool use fixed-size objects
From: Chen Cheng @ 2026-06-03  3:59 UTC (permalink / raw)
  To: linux-raid, yukuai; +Cc: chencheng, linux-kernel
In-Reply-To: <20260603035925.217847-1-chencheng@fnnas.com>

From: Chen Cheng <chencheng@fnnas.com>

raid10 currently sizes regular r10bio_pool objects from
conf->geo.raid_disks, which makes the mempool element width depend on
the current geometry.

That breaks across reshape. Regular r10bio objects are preallocated and
reused, so after a geometry change the pool may still contain objects
allocated for the old width. A later request under the new geometry can
then reuse an r10bio whose devs[] array is still sized for the previous
raid_disks value.

Fix this by backing r10bio_pool with a fixed-size kmalloc mempool sized
for the maximum width needed across the current reshape transition.
Apply the same sizing rule to standalone r10bio objects allocated from
r10buf_pool_alloc().

This removes the geometry-dependent allocation width from regular
r10bio_pool objects and prevents reshape from reusing pool entries that
are too small for the new layout.

Signed-off-by: Chen Cheng <chencheng@fnnas.com>
---
 drivers/md/raid10.c | 48 +++++++++++++++++++++++++++++++++------------
 drivers/md/raid10.h |  2 +-
 2 files changed, 36 insertions(+), 14 deletions(-)

diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index cee5a253a281..5eca34432e63 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -101,17 +101,32 @@ static void end_reshape(struct r10conf *conf);
 static inline struct r10bio *get_resync_r10bio(struct bio *bio)
 {
 	return get_resync_pages(bio)->raid_bio;
 }
 
-static void * r10bio_pool_alloc(gfp_t gfp_flags, void *data)
+static inline unsigned int calc_r10bio_pool_disks(struct mddev *mddev)
 {
-	struct r10conf *conf = data;
-	int size = offsetof(struct r10bio, devs[conf->geo.raid_disks]);
+	/* If delta_disks < 0, use bigger r10bio->devs[] is ok. */
+	return mddev->raid_disks + max(0, mddev->delta_disks);
+}
+
+static inline int calc_r10bio_size(struct mddev *mddev)
+{
+	return offsetof(struct r10bio, devs[calc_r10bio_pool_disks(mddev)]);
+}
+
+static mempool_t *create_r10bio_pool(struct mddev *mddev)
+{
+	int size = calc_r10bio_size(mddev);
+
+	return mempool_create_kmalloc_pool(NR_RAID_BIOS, size);
+}
+
+static struct r10bio *alloc_r10bio(struct mddev *mddev, gfp_t gfp_flags)
+{
+	int size = calc_r10bio_size(mddev);
 
-	/* allocate a r10bio with room for raid_disks entries in the
-	 * bios array */
 	return kzalloc(size, gfp_flags);
 }
 
 #define RESYNC_SECTORS (RESYNC_BLOCK_SIZE >> 9)
 /* amount of memory to reserve for resync requests */
@@ -135,11 +150,11 @@ static void * r10buf_pool_alloc(gfp_t gfp_flags, void *data)
 	struct bio *bio;
 	int j;
 	int nalloc, nalloc_rp;
 	struct resync_pages *rps;
 
-	r10_bio = r10bio_pool_alloc(gfp_flags, conf);
+	r10_bio = alloc_r10bio(conf->mddev, gfp_flags);
 	if (!r10_bio)
 		return NULL;
 
 	if (test_bit(MD_RECOVERY_SYNC, &conf->mddev->recovery) ||
 	    test_bit(MD_RECOVERY_RESHAPE, &conf->mddev->recovery))
@@ -275,11 +290,11 @@ static void put_all_bios(struct r10conf *conf, struct r10bio *r10_bio)
 static void free_r10bio(struct r10bio *r10_bio)
 {
 	struct r10conf *conf = r10_bio->mddev->private;
 
 	put_all_bios(conf, r10_bio);
-	mempool_free(r10_bio, &conf->r10bio_pool);
+	mempool_free(r10_bio, conf->r10bio_pool);
 }
 
 static void put_buf(struct r10bio *r10_bio)
 {
 	struct r10conf *conf = r10_bio->mddev->private;
@@ -1537,11 +1552,11 @@ static void raid10_write_request(struct mddev *mddev, struct bio *bio,
 static void __make_request(struct mddev *mddev, struct bio *bio, int sectors)
 {
 	struct r10conf *conf = mddev->private;
 	struct r10bio *r10_bio;
 
-	r10_bio = mempool_alloc(&conf->r10bio_pool, GFP_NOIO);
+	r10_bio = mempool_alloc(conf->r10bio_pool, GFP_NOIO);
 
 	r10_bio->master_bio = bio;
 	r10_bio->sectors = sectors;
 
 	r10_bio->mddev = mddev;
@@ -1729,11 +1744,11 @@ static int raid10_handle_discard(struct mddev *mddev, struct bio *bio)
 		last_stripe_index *= geo->far_copies;
 	end_disk_offset = (bio_end & geo->chunk_mask) +
 				(last_stripe_index << geo->chunk_shift);
 
 retry_discard:
-	r10_bio = mempool_alloc(&conf->r10bio_pool, GFP_NOIO);
+	r10_bio = mempool_alloc(conf->r10bio_pool, GFP_NOIO);
 	r10_bio->mddev = mddev;
 	r10_bio->state = 0;
 	r10_bio->sectors = 0;
 	r10_bio->read_slot = -1;
 	memset(r10_bio->devs, 0, sizeof(r10_bio->devs[0]) * geo->raid_disks);
@@ -3830,11 +3845,11 @@ static int setup_geo(struct geom *geo, struct mddev *mddev, enum geo_type new)
 static void raid10_free_conf(struct r10conf *conf)
 {
 	if (!conf)
 		return;
 
-	mempool_exit(&conf->r10bio_pool);
+	mempool_destroy(conf->r10bio_pool);
 	kfree(conf->mirrors);
 	kfree(conf->mirrors_old);
 	kfree(conf->mirrors_new);
 	safe_put_page(conf->tmppage);
 	bioset_exit(&conf->bio_split);
@@ -3877,13 +3892,12 @@ static struct r10conf *setup_conf(struct mddev *mddev)
 	if (!conf->tmppage)
 		goto out;
 
 	conf->geo = geo;
 	conf->copies = copies;
-	err = mempool_init(&conf->r10bio_pool, NR_RAID_BIOS, r10bio_pool_alloc,
-			   rbio_pool_free, conf);
-	if (err)
+	conf->r10bio_pool = create_r10bio_pool(mddev);
+	if (!conf->r10bio_pool)
 		goto out;
 
 	err = bioset_init(&conf->bio_split, BIO_POOL_SIZE, 0, 0);
 	if (err)
 		goto out;
@@ -4373,10 +4387,11 @@ static int raid10_start_reshape(struct mddev *mddev)
 	struct geom new;
 	struct r10conf *conf = mddev->private;
 	struct md_rdev *rdev;
 	int spares = 0;
 	int ret;
+	mempool_t *new_pool;
 
 	if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery))
 		return -EBUSY;
 
 	if (setup_geo(&new, mddev, geo_start) != conf->copies)
@@ -4409,10 +4424,17 @@ static int raid10_start_reshape(struct mddev *mddev)
 
 	if (spares < mddev->delta_disks)
 		return -EINVAL;
 
 	conf->offset_diff = min_offset_diff;
+	if (mddev->delta_disks > 0) {
+		new_pool = create_r10bio_pool(mddev);
+		if (!new_pool)
+			return -ENOMEM;
+		mempool_destroy(conf->r10bio_pool);
+		conf->r10bio_pool = new_pool;
+	}
 	spin_lock_irq(&conf->device_lock);
 	if (conf->mirrors_new) {
 		memcpy(conf->mirrors_new, conf->mirrors,
 		       sizeof(struct raid10_info)*conf->prev.raid_disks);
 		smp_mb();
diff --git a/drivers/md/raid10.h b/drivers/md/raid10.h
index ec79d87fb92f..b711626a5db7 100644
--- a/drivers/md/raid10.h
+++ b/drivers/md/raid10.h
@@ -85,11 +85,11 @@ struct r10conf {
 	int			have_replacement; /* There is at least one
 						   * replacement device.
 						   */
 	wait_queue_head_t	wait_barrier;
 
-	mempool_t		r10bio_pool;
+	mempool_t		*r10bio_pool;
 	mempool_t		r10buf_pool;
 	struct page		*tmppage;
 	struct bio_set		bio_split;
 
 	/* When taking over an array from a different personality, we store
-- 
2.54.0

^ permalink raw reply related

* [PATCH v4 0/3] md/raid10: fix r10bio width mismatches across reshape
From: Chen Cheng @ 2026-06-03  3:59 UTC (permalink / raw)
  To: linux-raid, yukuai; +Cc: chencheng, linux-kernel

From: Chen Cheng <chencheng@fnnas.com>

Hi,

This series fixes slab out-of-bounds accesses in raid10 when reshape changes
the number of raid disks while regular I/O is still reusing r10bio objects
allocated under the previous geometry.

The bug is reproducible with a simple 4-disk to 5-disk reshape under write
load, for example:

  mdadm -C /dev/md777 -l10 -n4 /dev/sda /dev/sdb /dev/sdc /dev/sdd
  mkfs.ext4 /dev/md777
  mount /dev/md777 /mnt/test
  fsstress -d /mnt/test -n 24000 -p 8 -l 24 &
  mdadm /dev/md777 --add /dev/sde
  mdadm --grow /dev/md777 --raid-devices=5 \
    --backup-file=/tmp/md-reshape-backup

Without these changes, an r10bio allocated under the old geometry can later be
reused, initialized, or freed after conf->geo.raid_disks has switched to the
new geometry. This creates width mismatches between the object and the current
devs[] walk/initialization width, which can trigger KASAN reports such as
slab-out-of-bounds in __make_request(), put_all_bios(), or find_bio_disk().

This series addresses the problem in three steps:

  1. ensure the sync_action=reshape caller suspends and locks before start_reshape

  2. make the regular r10bio pool fixed-size across reshape transitions, and
     move the pool rebuild into the freeze window before the live geometry
     switch;

  3. track the number of valid devs[] entries in each reused r10bio and use
     that recorded width when walking devs[] after reshape.


Changes in v4:
   - The sync_action=reshape path, caller now invokes
     mddev_suspend_and_lock() before calling start_reshape()
   - The md-cluster and dm-raid paths are unchanged, that is reach
     start_reshape() with the mddev locked but without suspended.


Changes in v3:
   - Replace freeze_array()/unfreeze_array() in raid10_start_reshape() with
     mddev_suspend_and_lock_nointr()/mddev_unlock_and_resume(). freeze_array()
     returns when nr_pending == nr_queued, which still allows retry-list items
     to hold pool objects; mddev_suspend() provides the correct upper-layer
     quiesce interface. (Suggested by Yu Kuai)


Changes in v2:
  - add this cover letter
  - convert r10bio_pool to a fixed-size kmalloc mempool
  - rebuild r10bio_pool inside the freeze window before switching live reshape
    geometry
  - switch raid10_quiesce() to freeze_array()/unfreeze_array()


Testing:
  - reproduced the original KASAN slab-out-of-bounds on 4-disk -> 5-disk
    raid10 reshape with fsstress
  - verified that this series fixes that reproducer
  - exercised the 5-disk -> 4-disk reshape direction as well

Thanks,
Chen Cheng



Chen Cheng (3):
  md: suspend array before raid10 reshape via sync_action
  md/raid10: make r10bio_pool use fixed-size objects
  md/raid10: bound reused r10bio devs[] walks by used_nr_devs

 drivers/md/md.c     | 22 ++++++++++++++----
 drivers/md/raid10.c | 56 +++++++++++++++++++++++++++++++++------------
 drivers/md/raid10.h |  4 +++-
 3 files changed, 61 insertions(+), 21 deletions(-)

-- 
2.54.0

^ permalink raw reply

* [PATCH v4 3/3] md/raid10: bound reused r10bio devs[] walks by used_nr_devs
From: Chen Cheng @ 2026-06-03  3:59 UTC (permalink / raw)
  To: linux-raid, yukuai; +Cc: chencheng, linux-kernel
In-Reply-To: <20260603035925.217847-1-chencheng@fnnas.com>

From: Chen Cheng <chencheng@fnnas.com>

After reshape changes raid_disks, an in-flight r10bio from the old geometry
can still be completed or freed later. In that case, using the current
geometry to walk r10_bio->devs[] is unsafe. A failure was reproduced with a
simple write workload while reshaping a raid10 array from 4 disks to 5 disks.
e.g.:

  mdadm -C /dev/md777 -l10 -n4 /dev/sda /dev/sdb /dev/sdc /dev/sdd
  mkfs.ext4 /dev/md777
  mount /dev/md777 /mnt/test
  fsstress -d /mnt/test -n 24000 -p 8 -l 24 &
  mdadm /dev/md777 --add /dev/sde
  mdadm --grow /dev/md777 --raid-devices=5 \
    --backup-file=/tmp/md-reshape-backup

the sequence above can trigger:

  BUG: KASAN: slab-out-of-bounds in free_r10bio+0x1c4/0x260 [raid10]
  Read of size 8 at addr ffff00008c2dfac8 by task ksoftirqd/0/15
  free_r10bio
  raid_end_bio_io
  one_write_done
  raid10_end_write_request

The buggy object was 200 bytes long, which matches an r10bio with space for
only four devs[] entries. However, put_all_bios() and find_bio_disk() walk
r10_bio->devs[] using the current conf->geo.raid_disks value. Once reshape
switches conf->geo.raid_disks from 4 to 5, an old 4-slot r10bio can be
completed or freed as if it had 5 slots, and the walk overruns devs[4]. The
same stale-width mismatch can also surface during a 5-disk to 4-disk reshape.

Track the number of valid devs[] entries in each reused r10bio with
used_nr_devs. Initialize it whenever an r10bio is prepared for regular I/O,
discard, or resync/recovery/reshape work, and use it to bound devs[] walks
in put_all_bios() and find_bio_disk().

Signed-off-by: Chen Cheng <chencheng@fnnas.com>
---
 drivers/md/raid10.c | 8 ++++++--
 drivers/md/raid10.h | 2 ++
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 5eca34432e63..f134b93fd593 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -273,11 +273,11 @@ static void r10buf_pool_free(void *__r10_bio, void *data)
 
 static void put_all_bios(struct r10conf *conf, struct r10bio *r10_bio)
 {
 	int i;
 
-	for (i = 0; i < conf->geo.raid_disks; i++) {
+	for (i = 0; i < r10_bio->used_nr_devs; i++) {
 		struct bio **bio = & r10_bio->devs[i].bio;
 		if (!BIO_SPECIAL(*bio))
 			bio_put(*bio);
 		*bio = NULL;
 		bio = &r10_bio->devs[i].repl_bio;
@@ -370,11 +370,11 @@ static int find_bio_disk(struct r10conf *conf, struct r10bio *r10_bio,
 			 struct bio *bio, int *slotp, int *replp)
 {
 	int slot;
 	int repl = 0;
 
-	for (slot = 0; slot < conf->geo.raid_disks; slot++) {
+	for (slot = 0; slot < r10_bio->used_nr_devs; slot++) {
 		if (r10_bio->devs[slot].bio == bio)
 			break;
 		if (r10_bio->devs[slot].repl_bio == bio) {
 			repl = 1;
 			break;
@@ -1561,10 +1561,11 @@ static void __make_request(struct mddev *mddev, struct bio *bio, int sectors)
 
 	r10_bio->mddev = mddev;
 	r10_bio->sector = bio->bi_iter.bi_sector;
 	r10_bio->state = 0;
 	r10_bio->read_slot = -1;
+	r10_bio->used_nr_devs = conf->geo.raid_disks;
 	memset(r10_bio->devs, 0, sizeof(r10_bio->devs[0]) *
 			conf->geo.raid_disks);
 
 	if (bio_data_dir(bio) == READ)
 		raid10_read_request(mddev, bio, r10_bio);
@@ -1749,10 +1750,11 @@ static int raid10_handle_discard(struct mddev *mddev, struct bio *bio)
 	r10_bio = mempool_alloc(conf->r10bio_pool, GFP_NOIO);
 	r10_bio->mddev = mddev;
 	r10_bio->state = 0;
 	r10_bio->sectors = 0;
 	r10_bio->read_slot = -1;
+	r10_bio->used_nr_devs = geo->raid_disks;
 	memset(r10_bio->devs, 0, sizeof(r10_bio->devs[0]) * geo->raid_disks);
 	wait_blocked_dev(mddev, r10_bio);
 
 	/*
 	 * For far layout it needs more than one r10bio to cover all regions.
@@ -3083,10 +3085,12 @@ static struct r10bio *raid10_alloc_init_r10buf(struct r10conf *conf)
 	    test_bit(MD_RECOVERY_RESHAPE, &conf->mddev->recovery))
 		nalloc = conf->copies; /* resync */
 	else
 		nalloc = 2; /* recovery */
 
+	r10bio->used_nr_devs = nalloc;
+
 	for (i = 0; i < nalloc; i++) {
 		bio = r10bio->devs[i].bio;
 		rp = bio->bi_private;
 		bio_reset(bio, NULL, 0);
 		bio->bi_private = rp;
diff --git a/drivers/md/raid10.h b/drivers/md/raid10.h
index b711626a5db7..4751119f9770 100644
--- a/drivers/md/raid10.h
+++ b/drivers/md/raid10.h
@@ -125,10 +125,12 @@ struct r10bio {
 	struct bio		*master_bio;
 	/*
 	 * if the IO is in READ direction, then this is where we read
 	 */
 	int			read_slot;
+	/* Used to bound devs[] walks when the object is reused. */
+	unsigned int		used_nr_devs;
 
 	struct list_head	retry_list;
 	/*
 	 * if the IO is in WRITE direction, then multiple bios are used,
 	 * one for each copy.
-- 
2.54.0

^ permalink raw reply related

* [PATCH v4 1/3] md: suspend array before raid10 reshape via sync_action
From: Chen Cheng @ 2026-06-03  3:59 UTC (permalink / raw)
  To: linux-raid, yukuai; +Cc: chencheng, linux-kernel
In-Reply-To: <20260603035925.217847-1-chencheng@fnnas.com>

From: Chen Cheng <chencheng@fnnas.com>

The sync_action=reshape path currently enters mddev_start_reshape() with
reconfig_mutex held but without suspending the array first. For raid10,
that means raid10_start_reshape() has to drop reconfig_mutex and reacquire
the array through mddev_suspend_and_lock_nointr() before it can safely
switch geometry-dependent state.

Use mddev_suspend_and_lock() for ACTION_RESHAPE in action_store(), so
the sysfs reshape path reaches mddev_start_reshape() with the array
already suspended and locked.

Other sync_action operations keep using mddev_lock() unchanged.

Signed-off-by: Chen Cheng <chencheng@fnnas.com>
---
 drivers/md/md.c | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/drivers/md/md.c b/drivers/md/md.c
index 096bb64e87bd..5bc937e149ac 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -5256,30 +5256,39 @@ static int mddev_start_reshape(struct mddev *mddev)
 
 static ssize_t
 action_store(struct mddev *mddev, const char *page, size_t len)
 {
 	int ret;
+	bool suspended = false;
 	enum sync_action action;
 
 	if (!mddev->pers || !mddev->pers->sync_request)
 		return -EINVAL;
 
+	action = md_sync_action_by_name(page);
 retry:
 	if (work_busy(&mddev->sync_work))
 		flush_work(&mddev->sync_work);
 
-	ret = mddev_lock(mddev);
+	if (action == ACTION_RESHAPE) {
+		ret = mddev_suspend_and_lock(mddev);
+		suspended = true;
+	} else {
+		ret = mddev_lock(mddev);
+		suspended = false;
+	}
 	if (ret)
 		return ret;
 
 	if (work_busy(&mddev->sync_work)) {
-		mddev_unlock(mddev);
+		if (suspended)
+			mddev_unlock_and_resume(mddev);
+		else
+			mddev_unlock(mddev);
 		goto retry;
 	}
 
-	action = md_sync_action_by_name(page);
-
 	/* TODO: mdadm rely on "idle" to start sync_thread. */
 	if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery)) {
 		switch (action) {
 		case ACTION_FROZEN:
 			md_frozen_sync_thread(mddev);
@@ -5344,11 +5353,14 @@ action_store(struct mddev *mddev, const char *page, size_t len)
 	md_wakeup_thread(mddev->thread);
 	sysfs_notify_dirent_safe(mddev->sysfs_action);
 	ret = len;
 
 out:
-	mddev_unlock(mddev);
+	if (suspended)
+		mddev_unlock_and_resume(mddev);
+	else
+		mddev_unlock(mddev);
 	return ret;
 }
 
 static struct md_sysfs_entry md_scan_mode =
 __ATTR_PREALLOC(sync_action, S_IRUGO|S_IWUSR, action_show, action_store);
-- 
2.54.0

^ permalink raw reply related

* Re: [GIT PULL] md-7.2-20260531
From: Coly Li @ 2026-06-02  3:40 UTC (permalink / raw)
  To: Jens Axboe; +Cc: Yu Kuai, linux-raid, linux-block, linux-bcache
In-Reply-To: <753dfdb4-7d68-4183-af3f-d51e92d4dd31@kernel.dk>

Hi Jens,

> 2026年6月2日 02:55,Jens Axboe <axboe@kernel.dk> 写道:
> 
> On 5/31/26 5:26 AM, Yu Kuai wrote:
>> Hi Jens,
>> 
>> Please consider pulling the following changes into your for-7.2/block
>> branch.
>> 
>> This pull request contains:
>> 
>> Bug Fixes:
>> - Only requeue dm-raid bios when dm is suspending. (Benjamin Marzinski)
>> - Reset raid10 read_slot when reusing r10bio for discard. (Chen Cheng)
>> - Fix raid1/raid10 deadlock in read error recovery path. (Abd-Alrhman Masalkhi)
>> - Fix raid1/raid10 error-path detection with md_cloned_bio(). (Abd-Alrhman Masalkhi)
>> - Fix raid1/raid10 bio accounting for split md cloned bios. (Abd-Alrhman Masalkhi)
>> - Fix raid1 nr_pending leak in REQ_ATOMIC bad-block path. (Abd-Alrhman Masalkhi)
>> 
>> Improvements:
>> - Skip redundant raid_disks updates when the value is unchanged. (Abd-Alrhman Masalkhi)
>> 
>> Cleanups:
>> - Update MAINTAINERS email addresses. (Yu Kuai, Li Nan)
>> - Clean up raid1 read error handling. (Christoph Hellwig)
>> - Move the exceed_read_errors condition out of fix_read_error(). (Christoph Hellwig)
>> - Use str_plural() in raid0 dump_zones(). (Thorsten Blum)
> 
> Pulled, thanks.
> 
> Side note - emails from you with the new email address all end up in
> spam. I just noticed some of them this morning. But I think you want to
> ensure that the DKIM etc part of your email is all kosher, gmail flags
> all of it.

Could you please check my patch to change my email address go fygo.io in your spam emails?
I guess similar situation may also happen on my patch sent from fygo.io.
And yes, we are looking into this DKIM issue now.

Thanks.

Coly Li

^ permalink raw reply

* Re: [GIT PULL] md-7.2-20260531
From: Yu Kuai @ 2026-06-02  3:07 UTC (permalink / raw)
  To: Jens Axboe
  Cc: linux-raid, linux-block, Abd-Alrhman Masalkhi, Benjamin Marzinski,
	Chen Cheng, Christoph Hellwig, Li Nan, Thorsten Blum, yukuai
In-Reply-To: <753dfdb4-7d68-4183-af3f-d51e92d4dd31@kernel.dk>

Hi,

在 2026/6/2 2:55, Jens Axboe 写道:
> On 5/31/26 5:26 AM, Yu Kuai wrote:
>> Hi Jens,
>>
>> Please consider pulling the following changes into your for-7.2/block
>> branch.
>>
>> This pull request contains:
>>
>> Bug Fixes:
>> - Only requeue dm-raid bios when dm is suspending. (Benjamin Marzinski)
>> - Reset raid10 read_slot when reusing r10bio for discard. (Chen Cheng)
>> - Fix raid1/raid10 deadlock in read error recovery path. (Abd-Alrhman Masalkhi)
>> - Fix raid1/raid10 error-path detection with md_cloned_bio(). (Abd-Alrhman Masalkhi)
>> - Fix raid1/raid10 bio accounting for split md cloned bios. (Abd-Alrhman Masalkhi)
>> - Fix raid1 nr_pending leak in REQ_ATOMIC bad-block path. (Abd-Alrhman Masalkhi)
>>
>> Improvements:
>> - Skip redundant raid_disks updates when the value is unchanged. (Abd-Alrhman Masalkhi)
>>
>> Cleanups:
>> - Update MAINTAINERS email addresses. (Yu Kuai, Li Nan)
>> - Clean up raid1 read error handling. (Christoph Hellwig)
>> - Move the exceed_read_errors condition out of fix_read_error(). (Christoph Hellwig)
>> - Use str_plural() in raid0 dump_zones(). (Thorsten Blum)
> Pulled, thanks.
>
> Side note - emails from you with the new email address all end up in
> spam. I just noticed some of them this morning. But I think you want to
> ensure that the DKIM etc part of your email is all kosher, gmail flags
> all of it.

Thanks a lot for the notice, we'll look into this.

>
-- 
Thansk,
Kuai

^ permalink raw reply

* Re: [GIT PULL] md-7.2-20260531
From: Jens Axboe @ 2026-06-01 18:55 UTC (permalink / raw)
  To: Yu Kuai
  Cc: linux-raid, linux-block, Abd-Alrhman Masalkhi, Benjamin Marzinski,
	Chen Cheng, Christoph Hellwig, Li Nan, Thorsten Blum
In-Reply-To: <20260531112618.160409-1-yukuai@fygo.io>

On 5/31/26 5:26 AM, Yu Kuai wrote:
> Hi Jens,
> 
> Please consider pulling the following changes into your for-7.2/block
> branch.
> 
> This pull request contains:
> 
> Bug Fixes:
> - Only requeue dm-raid bios when dm is suspending. (Benjamin Marzinski)
> - Reset raid10 read_slot when reusing r10bio for discard. (Chen Cheng)
> - Fix raid1/raid10 deadlock in read error recovery path. (Abd-Alrhman Masalkhi)
> - Fix raid1/raid10 error-path detection with md_cloned_bio(). (Abd-Alrhman Masalkhi)
> - Fix raid1/raid10 bio accounting for split md cloned bios. (Abd-Alrhman Masalkhi)
> - Fix raid1 nr_pending leak in REQ_ATOMIC bad-block path. (Abd-Alrhman Masalkhi)
> 
> Improvements:
> - Skip redundant raid_disks updates when the value is unchanged. (Abd-Alrhman Masalkhi)
> 
> Cleanups:
> - Update MAINTAINERS email addresses. (Yu Kuai, Li Nan)
> - Clean up raid1 read error handling. (Christoph Hellwig)
> - Move the exceed_read_errors condition out of fix_read_error(). (Christoph Hellwig)
> - Use str_plural() in raid0 dump_zones(). (Thorsten Blum)

Pulled, thanks.

Side note - emails from you with the new email address all end up in
spam. I just noticed some of them this morning. But I think you want to
ensure that the DKIM etc part of your email is all kosher, gmail flags
all of it.

-- 
Jens Axboe

^ permalink raw reply

* Re: [PATCH] raid1: fix nr_pending leak in REQ_ATOMIC bad-block error path
From: Abd-Alrhman Masalkhi @ 2026-06-01  9:13 UTC (permalink / raw)
  To: John Garry, song, yukuai, linan122, martin.petersen, axboe
  Cc: linux-raid, linux-kernel
In-Reply-To: <8f8ca66c-aaa6-4b51-bcd7-54203603ba8a@oracle.com>


hi,

On Mon, Jun 01, 2026 at 10:05 +0100, John Garry wrote:
> On 01/06/2026 10:03, Abd-Alrhman Masalkhi wrote:
>>>> +++ b/drivers/md/raid1.c
>>>> @@ -1580,8 +1580,10 @@ static void raid1_write_request(struct mddev *mddev, struct bio *bio,
>>>>    				 * complexity of supporting that is not worth
>>>>    				 * the benefit.
>>>>    				 */
>>>> -				if (bio->bi_opf & REQ_ATOMIC)
>>>> +				if (bio->bi_opf & REQ_ATOMIC) {
>>>> +					rdev_dec_pending(rdev, mddev);
>>> It's not so nice that we have 2x locations that does the
>>> rdev_dec_pending work
>>>
>> Are you suggesting deferring atomic_inc(&rdev->nr_pending) until after
>> the if (test_bit(WriteErrorSeen, &rdev->flags)) {..} block? The patch
>> is already in md-7.2; should I send a separate cleanup patch?
>
> I'm not suggesting any further change. I am just mentioning that it is 
> unfortunate that we have 2x locations which does the decrement, which 
> makes error handling harder to follow.

You are absolutely right. Having two decrement paths makes the error
handling harder to follow. Thanks for pointing that out.

-- 
Best Regards,
Abd-Alrhman

^ permalink raw reply

* Re: [PATCH] raid1: fix nr_pending leak in REQ_ATOMIC bad-block error path
From: John Garry @ 2026-06-01  9:05 UTC (permalink / raw)
  To: Abd-Alrhman Masalkhi, song, yukuai, linan122, martin.petersen,
	axboe
  Cc: linux-raid, linux-kernel
In-Reply-To: <m2h5nmljj6.fsf@gmail.com>

On 01/06/2026 10:03, Abd-Alrhman Masalkhi wrote:
>>> +++ b/drivers/md/raid1.c
>>> @@ -1580,8 +1580,10 @@ static void raid1_write_request(struct mddev *mddev, struct bio *bio,
>>>    				 * complexity of supporting that is not worth
>>>    				 * the benefit.
>>>    				 */
>>> -				if (bio->bi_opf & REQ_ATOMIC)
>>> +				if (bio->bi_opf & REQ_ATOMIC) {
>>> +					rdev_dec_pending(rdev, mddev);
>> It's not so nice that we have 2x locations that does the
>> rdev_dec_pending work
>>
> Are you suggesting deferring atomic_inc(&rdev->nr_pending) until after
> the if (test_bit(WriteErrorSeen, &rdev->flags)) {..} block? The patch
> is already in md-7.2; should I send a separate cleanup patch?

I'm not suggesting any further change. I am just mentioning that it is 
unfortunate that we have 2x locations which does the decrement, which 
makes error handling harder to follow.

^ permalink raw reply

* Re: [PATCH] raid1: fix nr_pending leak in REQ_ATOMIC bad-block error path
From: Abd-Alrhman Masalkhi @ 2026-06-01  9:03 UTC (permalink / raw)
  To: John Garry, song, yukuai, linan122, martin.petersen, axboe
  Cc: linux-raid, linux-kernel
In-Reply-To: <d9c769f5-19a3-4724-b805-3e2f30541c64@oracle.com>


hi,

Thank you for the feedback.

On Mon, Jun 01, 2026 at 09:43 +0100, John Garry wrote:
> On 30/05/2026 16:14, Abd-Alrhman Masalkhi wrote:
>> In raid1_write_request(), each per-mirror loop iteration begins by
>> incrementing rdev->nr_pending. If a REQ_ATOMIC write encounters a
>> badblock within the requested range, the code jumps to err_handle
>> without dropping the reference taken for the current mirror.
>> 
>> err_handle's cleanup loop will only decrements for k < i and
>> r1_bio->bios[k] is non-NULL. The current slot is therefore skipped,
>> leaving its nr_pending reference leaked permanently. The reference
>> prevents the rdev from ever being removed, since raid1_remove_conf()
>> refuses to remove an rdev with nr_pending > 0.
>> 
>> Fix this by calling rdev_dec_pending() before jumping to err_handle.
>> 
>> Fixes: f2a38abf5f1c ("md/raid1: Atomic write support")
>> Signed-off-by: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com>
>
> FWIW,
>
> Reviewed-by: John Garry <john.g.garry@oracle.com>
>
>> ---
>>   drivers/md/raid1.c | 4 +++-
>>   1 file changed, 3 insertions(+), 1 deletion(-)
>> 
>> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
>> index 181400e147c0..0084bbc24076 100644
>> --- a/drivers/md/raid1.c
>> +++ b/drivers/md/raid1.c
>> @@ -1580,8 +1580,10 @@ static void raid1_write_request(struct mddev *mddev, struct bio *bio,
>>   				 * complexity of supporting that is not worth
>>   				 * the benefit.
>>   				 */
>> -				if (bio->bi_opf & REQ_ATOMIC)
>> +				if (bio->bi_opf & REQ_ATOMIC) {
>> +					rdev_dec_pending(rdev, mddev);
>
> It's not so nice that we have 2x locations that does the 
> rdev_dec_pending work
>

Are you suggesting deferring atomic_inc(&rdev->nr_pending) until after
the if (test_bit(WriteErrorSeen, &rdev->flags)) {..} block? The patch
is already in md-7.2; should I send a separate cleanup patch?

>>   					goto err_handle;
>> +				}
>>   
>>   				good_sectors = first_bad - r1_bio->sector;
>>   				if (good_sectors < max_sectors)
>

-- 
Best Regards,
Abd-Alrhman

^ permalink raw reply

* Re: [PATCH] raid1: fix nr_pending leak in REQ_ATOMIC bad-block error path
From: John Garry @ 2026-06-01  8:43 UTC (permalink / raw)
  To: Abd-Alrhman Masalkhi, song, yukuai, linan122, martin.petersen,
	axboe
  Cc: linux-raid, linux-kernel
In-Reply-To: <20260530151411.4119-1-abd.masalkhi@gmail.com>

On 30/05/2026 16:14, Abd-Alrhman Masalkhi wrote:
> In raid1_write_request(), each per-mirror loop iteration begins by
> incrementing rdev->nr_pending. If a REQ_ATOMIC write encounters a
> badblock within the requested range, the code jumps to err_handle
> without dropping the reference taken for the current mirror.
> 
> err_handle's cleanup loop will only decrements for k < i and
> r1_bio->bios[k] is non-NULL. The current slot is therefore skipped,
> leaving its nr_pending reference leaked permanently. The reference
> prevents the rdev from ever being removed, since raid1_remove_conf()
> refuses to remove an rdev with nr_pending > 0.
> 
> Fix this by calling rdev_dec_pending() before jumping to err_handle.
> 
> Fixes: f2a38abf5f1c ("md/raid1: Atomic write support")
> Signed-off-by: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com>

FWIW,

Reviewed-by: John Garry <john.g.garry@oracle.com>

> ---
>   drivers/md/raid1.c | 4 +++-
>   1 file changed, 3 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
> index 181400e147c0..0084bbc24076 100644
> --- a/drivers/md/raid1.c
> +++ b/drivers/md/raid1.c
> @@ -1580,8 +1580,10 @@ static void raid1_write_request(struct mddev *mddev, struct bio *bio,
>   				 * complexity of supporting that is not worth
>   				 * the benefit.
>   				 */
> -				if (bio->bi_opf & REQ_ATOMIC)
> +				if (bio->bi_opf & REQ_ATOMIC) {
> +					rdev_dec_pending(rdev, mddev);

It's not so nice that we have 2x locations that does the 
rdev_dec_pending work

>   					goto err_handle;
> +				}
>   
>   				good_sectors = first_bad - r1_bio->sector;
>   				if (good_sectors < max_sectors)


^ permalink raw reply

* Re: [PATCH 2/2] md/raid1: move the exceed_read_errors condition out of fix_read_error
From: Xiao Ni @ 2026-06-01  5:53 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: Song Liu, Yu Kuai, Li Nan, Xiao Ni, linux-raid
In-Reply-To: <20260529054308.2720300-3-hch@lst.de>

On Fri, May 29, 2026 at 1:45 PM Christoph Hellwig <hch@lst.de> wrote:
>
> This condition much better fits into the only caller, limiting
> fix_read_error to actually fix up data devices after a read error.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---
>  drivers/md/raid1.c | 10 ++++------
>  1 file changed, 4 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
> index 8fad1692cf66..e510ad7eef32 100644
> --- a/drivers/md/raid1.c
> +++ b/drivers/md/raid1.c
> @@ -2411,11 +2411,6 @@ static void fix_read_error(struct r1conf *conf, struct r1bio *r1_bio)
>         struct mddev *mddev = conf->mddev;
>         struct md_rdev *rdev = conf->mirrors[read_disk].rdev;
>
> -       if (exceed_read_errors(mddev, rdev)) {
> -               r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED;
> -               return;
> -       }
> -
>         while(sectors) {
>                 int s = sectors;
>                 int d = read_disk;
> @@ -2652,7 +2647,10 @@ static void handle_read_error(struct r1conf *conf, struct r1bio *r1_bio)
>                 md_error(mddev, rdev);
>         } else {
>                 freeze_array(conf, 1);
> -               fix_read_error(conf, r1_bio);
> +               if (exceed_read_errors(mddev, rdev))
> +                       r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED;
> +               else
> +                       fix_read_error(conf, r1_bio);
>                 unfreeze_array(conf);
>         }
>
> --
> 2.53.0
>
>
This patch looks good to me.
Reviewed-by: Xiao Ni <xiao@kernel.org>


^ permalink raw reply

* Re: [PATCH 1/2] md/raid1: cleanup handle_read_error
From: Xiao Ni @ 2026-06-01  5:51 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: Song Liu, Yu Kuai, Li Nan, Xiao Ni, linux-raid
In-Reply-To: <20260529054308.2720300-2-hch@lst.de>

On Fri, May 29, 2026 at 1:46 PM Christoph Hellwig <hch@lst.de> wrote:
>
> Unwind the main conditional with duplicate conditions and initialize
> variables at initialization time where possible.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---
>  drivers/md/raid1.c | 34 ++++++++++++++++------------------
>  1 file changed, 16 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
> index 64d970e2ef50..8fad1692cf66 100644
> --- a/drivers/md/raid1.c
> +++ b/drivers/md/raid1.c
> @@ -2627,35 +2627,33 @@ static void handle_write_finished(struct r1conf *conf, struct r1bio *r1_bio)
>
>  static void handle_read_error(struct r1conf *conf, struct r1bio *r1_bio)
>  {
> +       struct md_rdev *rdev = conf->mirrors[r1_bio->read_disk].rdev;
> +       struct bio *bio = r1_bio->bios[r1_bio->read_disk];
>         struct mddev *mddev = conf->mddev;
> -       struct bio *bio;
> -       struct md_rdev *rdev;
>         sector_t sector;
>
>         clear_bit(R1BIO_ReadError, &r1_bio->state);
> -       /* we got a read error. Maybe the drive is bad.  Maybe just
> -        * the block and we can fix it.
> -        * We freeze all other IO, and try reading the block from
> -        * other devices.  When we find one, we re-write
> -        * and check it that fixes the read error.
> -        * This is all done synchronously while the array is
> -        * frozen
> -        */
>
> -       bio = r1_bio->bios[r1_bio->read_disk];
>         bio_put(bio);
>         r1_bio->bios[r1_bio->read_disk] = NULL;
>
> -       rdev = conf->mirrors[r1_bio->read_disk].rdev;
> -       if (mddev->ro == 0
> -           && !test_bit(FailFast, &rdev->flags)) {
> +       /*
> +        * We got a read error. Maybe the drive is bad.  Maybe just the block
> +        * and we can fix it.
> +        *
> +        * If allowed, freeze all other IO, and try reading the block from other
> +        * devices.  If we find one, we re-write and check it that fixes the
> +        * read error.  This is all done synchronously while the array is
> +        * frozen.
> +        */
> +       if (mddev->ro) {
> +               r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED;
> +       } else if (test_bit(FailFast, &rdev->flags)) {
> +               md_error(mddev, rdev);
> +       } else {
>                 freeze_array(conf, 1);
>                 fix_read_error(conf, r1_bio);
>                 unfreeze_array(conf);
> -       } else if (mddev->ro == 0 && test_bit(FailFast, &rdev->flags)) {
> -               md_error(mddev, rdev);
> -       } else {
> -               r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED;
>         }
>
>         rdev_dec_pending(rdev, conf->mddev);
> --
> 2.53.0
>
>

Yes, it's more readable. This patch is good to me.
Reviewed-by: Xiao Ni <xiao@kernel.org>


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox