Linux RAID subsystem development
 help / color / mirror / Atom feed
* [PATCH v4 00/25] md: improve lockless bitmap reshape support
@ 2026-08-01 17:24 Yu Kuai
  2026-08-01 17:24 ` [PATCH v4 01/25] md/md-llbitmap: clear flush state after daemon flush Yu Kuai
                   ` (24 more replies)
  0 siblings, 25 replies; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:24 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

This series fixes several lockless bitmap accounting issues and adds the
missing llbitmap support for online reshape in RAID10 and RAID5.

llbitmap has different 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. Without exact
old/new geometry tracking and reshape-boundary splitting, write accounting can
use the wrong bitmap mapping or merge state across ranges that should be
checkpointed separately.

The first patches fix independent llbitmap and RAID5 correctness issues that
should be safe before the reshape work. The rest of the series adds exact
bitmap mapping hooks, tracks llbitmap reshape geometry, grows the page cache
for reshape, remaps checkpointed state as reshape advances, and wires RAID10
and RAID5 to split reshape-crossing bios before bitmap accounting and to mark
llbitmap reshape checkpoints.

Changes since v3:

  - Add patch 1 to clear the sticky LLPageFlush state after daemon flush.
  - Add patch 2 to use GFP_NOIO for llbitmap cache and control allocations.
  - Add patch 3 to only checkpoint fully completed llbitmap sync chunks.
  - Add patch 4 to reject zero-sector RAID5 reshape chunks.
  - Fix patch 8 to snapshot mddev->reshape_position with READ_ONCE() while
    splitting reshape-crossing bios.
  - Fix patch 10 to keep the independently allocated page controls under the
    GFP_NOIO allocation policy added by patch 2.
  - Fix patch 11 to quiesce the array while llbitmap grows page controls
    during resize or reshape.
  - Fix patch 15 to use READ_ONCE(mddev->reshape_position) in llbitmap reshape
    range mapping helpers instead of plain lockless reads.
  - Fix patch 17 to serialize reshape checkpoint remapping against normal
    write/discard bitmap state updates with a rwlock, and copy overlapping
    remap ranges backwards when needed.
  - Fix patch 21 to end RAID10 write accounting if reshape bio splitting
    fails.
  - Fix patch 25 to resubmit the RAID5 front split bio after waiting for
    reshape instead of retrying the already-submitted remainder.

Changes since v2:

  - Fix RAID5 bitmap stripe rounding for non-power-of-two stripe widths in
    patch 1.
  - Fix partial discard chunks being marked unwritten in patch 11.
  - Fix RAID5 writes past component size being skipped by llbitmap accounting
    in patch 11.
  - Fix stopped reshape reassembly losing target llbitmap geometry in patch 9.
  - Fix the page-cache-grow patch using target-geometry fields before they are
    introduced in patch 7.

Changes since v1:

  - Add Reviewed-by tags from Su Yue.
  - Rename llbitmap_resize_chunks() to llbitmap_calculate_chunks().
  - Use an unsigned index in llbitmap_expand_pages() error cleanup.
  - Rebase on mdraid/md-7.2, including the mddev_bio_split_at_reshape_offset()
    declaration needed by the RAID5 build reported by kernel test robot.

Previous versions:
  v3: https://lore.kernel.org/linux-raid/cover.1785206690.git.yukuai@fygo.io/
  v2: https://lore.kernel.org/linux-raid/cover.1782282042.git.yukuai@kernel.org/
  v1: https://lore.kernel.org/linux-raid/20260605091527.2463539-1-yukuai@kernel.org/

Yu Kuai (25):
  md/md-llbitmap: clear flush state after daemon flush
  md/md-llbitmap: use GFP_NOIO for cache allocations
  md/md-llbitmap: only end fully synced chunks
  md/raid5: reject zero-sector reshape chunks
  md/raid5: round bitmap stripes with sector division
  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   |   9 +
 drivers/md/md-llbitmap.c | 717 +++++++++++++++++++++++++++++++++++----
 drivers/md/md.c          |  64 +++-
 drivers/md/md.h          |   8 +
 drivers/md/raid10.c      |  53 +++
 drivers/md/raid5.c       | 139 ++++++--
 7 files changed, 912 insertions(+), 86 deletions(-)


base-commit: 55b77337bdd088c77461588e5ec094421b89911b
-- 
2.51.0

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

* [PATCH v4 01/25] md/md-llbitmap: clear flush state after daemon flush
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
@ 2026-08-01 17:24 ` Yu Kuai
  2026-08-01 17:43   ` sashiko-bot
  2026-08-01 17:24 ` [PATCH v4 02/25] md/md-llbitmap: use GFP_NOIO for cache allocations Yu Kuai
                   ` (23 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:24 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

llbitmap_flush() sets LLPageFlush on each bitmap page before it queues the
daemon worker. The flag tells md_llbitmap_daemon_fn() to ignore the normal
barrier_idle expiry check and clean the page immediately.

The daemon only tested LLPageFlush. Once a page had been flushed explicitly,
the flag stayed set, so later dirty bits on that page also bypassed
barrier_idle and were cleaned the next time the daemon ran. That can make a
new write look clean much earlier than the configured idle window.

Consume LLPageFlush in md_llbitmap_daemon_fn() with test_and_clear_bit() and
use the returned value for the current expiry check. The explicit flush still
forces the current daemon pass, while later writes on the same page wait for
barrier_idle again.

This can be reproduced through normal sysfs operations:

  1. Create a small RAID1 with --bitmap=lockless and --assume-clean.
  2. Set llbitmap/daemon_sleep=1 and llbitmap/barrier_idle=10.
  3. Toggle md/array_state from active to readonly and back to active to call
     llbitmap_flush() without destroying the in-memory bitmap.
  4. Write one sector and read llbitmap/bits immediately, after 2 seconds,
     and after 12 seconds.

On the bad kernel the dirty bit is already clean after 2 seconds. With this
change it remains dirty until the barrier_idle window expires.

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

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 5a4e2abaa757..131582724e7e 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1066,14 +1066,14 @@ static void md_llbitmap_daemon_fn(struct work_struct *work)
 
 	for (idx = 0; idx < llbitmap->nr_pages; idx++) {
 		struct llbitmap_page_ctl *pctl = llbitmap->pctl[idx];
+		bool flush = test_and_clear_bit(LLPageFlush, &pctl->flags);
 
 		if (idx > 0) {
 			start = end + 1;
 			end = min(end + PAGE_SIZE, llbitmap->chunks - 1);
 		}
 
-		if (!test_bit(LLPageFlush, &pctl->flags) &&
-		    time_before(jiffies, pctl->expire)) {
+		if (!flush && time_before(jiffies, pctl->expire)) {
 			restart = true;
 			continue;
 		}
-- 
2.51.0


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

* [PATCH v4 02/25] md/md-llbitmap: use GFP_NOIO for cache allocations
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
  2026-08-01 17:24 ` [PATCH v4 01/25] md/md-llbitmap: clear flush state after daemon flush Yu Kuai
@ 2026-08-01 17:24 ` Yu Kuai
  2026-08-01 17:41   ` sashiko-bot
  2026-08-01 17:24 ` [PATCH v4 03/25] md/md-llbitmap: only end fully synced chunks Yu Kuai
                   ` (22 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:24 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

llbitmap allocates its in-memory page cache and page-control structures from
paths that can already be holding MD reconfiguration or bitmap state locks.
For example, component_size_store() takes mddev_lock(), update_size() calls
the personality resize method, and llbitmap_resize() can grow the page cache
through llbitmap_prepare_resize().

Using GFP_KERNEL in those paths allows direct reclaim to enter filesystem or
block I/O while MD resize state is locked. That can recurse back into the
same array and wait on state that cannot make progress until the resize path
finishes.

Use GFP_NOIO for the llbitmap object, cached bitmap pages, page controls,
page-control arrays, and percpu_ref initialization. Leave the explicit
metadata zeroout path unchanged because it is intentional bitmap I/O rather
than reclaim-driven allocation.

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

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 131582724e7e..6ab2188bba27 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -521,7 +521,7 @@ static struct page *llbitmap_read_page(struct llbitmap *llbitmap, int idx)
 	if (page)
 		return page;
 
-	page = alloc_page(GFP_KERNEL | __GFP_ZERO);
+	page = alloc_page(GFP_NOIO | __GFP_ZERO);
 	if (!page)
 		return ERR_PTR(-ENOMEM);
 
@@ -616,12 +616,12 @@ static int llbitmap_cache_pages(struct llbitmap *llbitmap)
 	int i;
 
 	llbitmap->pctl = kmalloc_array(nr_pages, sizeof(void *),
-				       GFP_KERNEL | __GFP_ZERO);
+				       GFP_NOIO | __GFP_ZERO);
 	if (!llbitmap->pctl)
 		return -ENOMEM;
 
 	size = round_up(size, cache_line_size());
-	pctl = kmalloc_array(nr_pages, size, GFP_KERNEL | __GFP_ZERO);
+	pctl = kmalloc_array(nr_pages, size, GFP_NOIO | __GFP_ZERO);
 	if (!pctl) {
 		kfree(llbitmap->pctl);
 		return -ENOMEM;
@@ -640,7 +640,7 @@ static int llbitmap_cache_pages(struct llbitmap *llbitmap)
 		}
 
 		if (percpu_ref_init(&pctl->active, active_release,
-				    PERCPU_REF_ALLOW_REINIT, GFP_KERNEL)) {
+				    PERCPU_REF_ALLOW_REINIT, GFP_NOIO)) {
 			__free_page(page);
 			llbitmap_free_pages(llbitmap);
 			return -ENOMEM;
@@ -1110,7 +1110,7 @@ static int llbitmap_create(struct mddev *mddev)
 	if (ret)
 		return ret;
 
-	llbitmap = kzalloc_obj(*llbitmap);
+	llbitmap = kzalloc_obj(*llbitmap, GFP_NOIO);
 	if (!llbitmap)
 		return -ENOMEM;
 
-- 
2.51.0


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

* [PATCH v4 03/25] md/md-llbitmap: only end fully synced chunks
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
  2026-08-01 17:24 ` [PATCH v4 01/25] md/md-llbitmap: clear flush state after daemon flush Yu Kuai
  2026-08-01 17:24 ` [PATCH v4 02/25] md/md-llbitmap: use GFP_NOIO for cache allocations Yu Kuai
@ 2026-08-01 17:24 ` Yu Kuai
  2026-08-01 17:42   ` sashiko-bot
  2026-08-01 17:24 ` [PATCH v4 04/25] md/raid5: reject zero-sector reshape chunks Yu Kuai
                   ` (21 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:24 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

llbitmap_cond_end_sync() is called with the sync thread's current sector.
That value is an exclusive progress boundary: sectors below it have
completed, but the llbitmap chunk containing it can still be in progress.

The old code converted that sector directly to the last bit passed to
BitmapActionEndsync. If resync had only advanced part-way into a large
llbitmap chunk, the in-progress chunk was marked synced and flushed before
the rest of the chunk was repaired. A later bitmap-assisted RAID1 resync
could then skip the remainder of that chunk and leave stale mirror data
behind.

This can be reproduced without editing bitmap metadata by creating a large
RAID1 with a lockless bitmap so llbitmap naturally selects a 524288-sector
chunk (with the default 128 KiB bitmap area, an array just over 16 TiB is
enough), making one mirror stale through the normal degraded write/re-add
path, and throttling resync so the daemon checkpoint runs while resync is
still inside the first chunk. On the bad kernel, bit 0 is ended early and a
stale sector later in the same chunk is skipped. With this fix, bit 0
remains Syncing until resync reaches the next chunk boundary.

Round the exclusive progress sector down to the nearest llbitmap chunk
boundary and end only chunks strictly below that boundary. Also honor the
force argument so callers that need an immediate checkpoint are not
suppressed by daemon_sleep.

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

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 6ab2188bba27..be580601d5b9 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1450,22 +1450,27 @@ static void llbitmap_cond_end_sync(struct mddev *mddev, sector_t sector,
 				   bool force)
 {
 	struct llbitmap *llbitmap = mddev->bitmap;
+	sector_t complete;
 
 	if (sector == 0) {
 		llbitmap->last_end_sync = jiffies;
 		return;
 	}
 
-	if (time_before(jiffies, llbitmap->last_end_sync +
-				 HZ * mddev->bitmap_info.daemon_sleep))
+	if (!force && time_before(jiffies, llbitmap->last_end_sync +
+				  HZ * mddev->bitmap_info.daemon_sleep))
 		return;
 
 	wait_event(mddev->recovery_wait, !atomic_read(&mddev->recovery_active));
 
 	mddev->curr_resync_completed = sector;
 	set_bit(MD_SB_CHANGE_CLEAN, &mddev->sb_flags);
-	llbitmap_state_machine(llbitmap, 0, sector >> llbitmap->chunkshift,
-			       BitmapActionEndsync);
+
+	complete = round_down(sector, llbitmap->chunksize);
+	if (complete)
+		llbitmap_state_machine(llbitmap, 0,
+				       (complete >> llbitmap->chunkshift) - 1,
+				       BitmapActionEndsync);
 	__llbitmap_flush(mddev);
 
 	llbitmap->last_end_sync = jiffies;
-- 
2.51.0


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

* [PATCH v4 04/25] md/raid5: reject zero-sector reshape chunks
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (2 preceding siblings ...)
  2026-08-01 17:24 ` [PATCH v4 03/25] md/md-llbitmap: only end fully synced chunks Yu Kuai
@ 2026-08-01 17:24 ` Yu Kuai
  2026-08-01 17:45   ` sashiko-bot
  2026-08-01 17:24 ` [PATCH v4 05/25] md/raid5: round bitmap stripes with sector division Yu Kuai
                   ` (20 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:24 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Sashiko reported that RAID5 can accept a reshape chunk size that becomes
zero sectors. chunk_size_store() stores the sysfs byte value as n >> 9, so
writing a value below 512 bytes sets mddev->new_chunk_sectors to zero.
RAID5 then accepted that pending reshape geometry and raid5_start_reshape()
installed it into conf->chunk_sectors, letting reshape code divide by zero.

Reject zero-sector chunks both in check_reshape(), where normal sysfs
requests are validated, and in raid5_start_reshape(), so assembly/resume
paths also cannot install zero chunk geometry.

Test script: in QEMU, create a plain three-disk RAID5 array with 64K
chunks, write/read back a small pattern, write 1 to
/sys/block/md0/md/chunk_size, add a fourth disk, and run mdadm --grow
--raid-devices=4 --backup-file=... . The script scans dmesg for divide
error/Oops/KASAN signatures.

Bad kernel, eb29914412c3:

  echo 1 > /sys/block/md0/md/chunk_size
  mdadm --grow /dev/md0 --raid-devices=4 --backup-file=/root/md0-grow.bak

  Oops: divide error: 0000 [#1] SMP KASAN NOPTI
  RIP: raid5_get_active_stripe+0x863/0xc10
  Call Trace:
   raid5_sync_request
   md_do_sync
   md_thread
  Kernel panic - not syncing: Fatal exception

Fixed kernel: echo 1 > /sys/block/md0/md/chunk_size bash: echo: write
error: Invalid argument chunk_write_rc=1 grow_rc=skipped RESULT:
REJECTED_ZERO_CHUNK_NO_OOPS

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

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 0c5c9fb0606e..4276b125f94c 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -8479,6 +8479,8 @@ static int check_reshape(struct mddev *mddev)
 		return 0; /* nothing to do */
 	if (has_failed(conf))
 		return -EINVAL;
+	if (!mddev->new_chunk_sectors)
+		return -EINVAL;
 	if (mddev->delta_disks < 0 && mddev->reshape_position == MaxSector) {
 		/* We might be able to shrink, but the devices must
 		 * be made bigger first.
@@ -8522,6 +8524,9 @@ static int raid5_start_reshape(struct mddev *mddev)
 	if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery))
 		return -EBUSY;
 
+	if (!mddev->new_chunk_sectors)
+		return -EINVAL;
+
 	if (!check_stripe_cache(mddev))
 		return -ENOSPC;
 
-- 
2.51.0


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

* [PATCH v4 05/25] md/raid5: round bitmap stripes with sector division
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (3 preceding siblings ...)
  2026-08-01 17:24 ` [PATCH v4 04/25] md/raid5: reject zero-sector reshape chunks Yu Kuai
@ 2026-08-01 17:24 ` Yu Kuai
  2026-08-01 17:39   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 06/25] md: add exact bitmap mapping and reshape hooks Yu Kuai
                   ` (19 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:24 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

raid5_bitmap_sector_map() aligns the array range to full RAID5 stripe
widths before converting it to component sectors. That width is
chunk_sectors multiplied by the number of data disks, and it is not
always a power of two.

Reproduce with a 4-disk RAID5, 1024-sector chunks, and three data disks.
The full-stripe width is 3072 sectors. For a one-sector write at array
sector 3072, correct rounding gives array range [3072, 6144), which maps
to component range [1024, 2048). The old round_down()/round_up() logic
instead gives [1024, 4096), which maps to [0, 1024).

Use sector_div() based arithmetic so the rounded range is aligned to the
actual RAID5 stripe width.

The deterministic mapper test now reports the fixed component range as
[1024, 2048), while the old mask-based range was [0, 1024).

Fixes: 9c89f604476c ("md/raid5: implement pers->bitmap_sector()")
Reported-by: Mykola Marzhan <mykola@meshstor.io>
Link: https://lore.kernel.org/all/20260726185916.2223460-1-mykola@meshstor.io/
Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/raid5.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 4276b125f94c..8b3483674398 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -5956,8 +5956,11 @@ static void raid5_bitmap_sector(struct mddev *mddev, sector_t *offset,
 
 	sectors_per_chunk = conf->chunk_sectors *
 		(conf->raid_disks - conf->max_degraded);
-	start = round_down(start, sectors_per_chunk);
-	end = round_up(end, sectors_per_chunk);
+	sector_div(start, sectors_per_chunk);
+	start *= sectors_per_chunk;
+	if (sector_div(end, sectors_per_chunk))
+		end++;
+	end *= sectors_per_chunk;
 
 	start = raid5_compute_sector(conf, start, 0, &dd_idx, NULL);
 	end = raid5_compute_sector(conf, end, 0, &dd_idx, NULL);
@@ -5975,8 +5978,10 @@ static void raid5_bitmap_sector(struct mddev *mddev, sector_t *offset,
 
 	sectors_per_chunk = conf->prev_chunk_sectors *
 		(conf->previous_raid_disks - conf->max_degraded);
-	prev_start = round_down(prev_start, sectors_per_chunk);
-	prev_end = round_down(prev_end, sectors_per_chunk);
+	sector_div(prev_start, sectors_per_chunk);
+	prev_start *= sectors_per_chunk;
+	sector_div(prev_end, sectors_per_chunk);
+	prev_end *= sectors_per_chunk;
 
 	prev_start = raid5_compute_sector(conf, prev_start, 1, &dd_idx, NULL);
 	prev_end = raid5_compute_sector(conf, prev_end, 1, &dd_idx, NULL);
-- 
2.51.0


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

* [PATCH v4 06/25] md: add exact bitmap mapping and reshape hooks
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (4 preceding siblings ...)
  2026-08-01 17:24 ` [PATCH v4 05/25] md/raid5: round bitmap stripes with sector division Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:25 ` [PATCH v4 07/25] md: skip bitmap accounting for empty write ranges Yu Kuai
                   ` (18 subsequent siblings)
  24 siblings, 0 replies; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

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 0f02e2956398..4c31807e6bcc 100644
--- a/drivers/md/md-bitmap.c
+++ b/drivers/md/md-bitmap.c
@@ -1730,6 +1730,13 @@ static void bitmap_start_write(struct mddev *mddev, sector_t offset,
 	}
 }
 
+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)
 {
@@ -3083,6 +3090,7 @@ static struct bitmap_operations bitmap_ops = {
 	.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,
 
diff --git a/drivers/md/md-bitmap.h b/drivers/md/md-bitmap.h
index f46674bdfeb9..26cd07737aab 100644
--- a/drivers/md/md-bitmap.h
+++ b/drivers/md/md-bitmap.h
@@ -93,6 +93,14 @@ struct bitmap_operations {
 	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);
 
diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index be580601d5b9..1fa2adf0ada1 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1188,6 +1188,13 @@ static void llbitmap_destroy(struct mddev *mddev)
 	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)
 {
@@ -1785,6 +1792,7 @@ static struct bitmap_operations llbitmap_ops = {
 	.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 d1465bcd86c8..3b20a57b8c7e 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -9361,6 +9361,12 @@ void md_submit_discard_bio(struct mddev *mddev, struct md_rdev *rdev,
 }
 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)
 {
@@ -9368,10 +9374,8 @@ static void md_bitmap_start(struct mddev *mddev,
 			   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);
 }
 
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
@@ -798,6 +798,10 @@ struct md_personality
 	/* 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 {
-- 
2.51.0


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

* [PATCH v4 07/25] md: skip bitmap accounting for empty write ranges
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (5 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 06/25] md: add exact bitmap mapping and reshape hooks Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 18:05   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 08/25] md: add helper to split bios at reshape offset Yu Kuai
                   ` (17 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

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 3b20a57b8c7e..0b59c676f7c0 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -9376,6 +9376,8 @@ static void md_bitmap_start(struct mddev *mddev,
 
 	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);
 }
 
@@ -9396,7 +9398,8 @@ static void md_end_clone_io(struct bio *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)
@@ -9423,10 +9426,12 @@ static void md_clone_bio(struct mddev *mddev, struct bio **bio)
 	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));
-- 
2.51.0


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

* [PATCH v4 08/25] md: add helper to split bios at reshape offset
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (6 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 07/25] md: skip bitmap accounting for empty write ranges Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:41   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 09/25] md/md-llbitmap: track bitmap sync_size explicitly Yu Kuai
                   ` (16 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

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 boundary
handling in multiple places.

The helper first applies the optional max_sectors limit. If reshape is
running and the bio crosses reshape_position, it further limits the front
bio to the current reshape boundary so callers can account and submit one
side of the reshape at a time.

Snapshot reshape_position with READ_ONCE(). RAID5 and RAID10 update this
field as reshape progresses, while the I/O path only needs one consistent
decision point for the current bio. Using an explicit single load avoids a
plain lockless access and prevents the compiler from refetching a different
boundary while deciding whether and where to split.

When a split is needed, bio_submit_split_bioset() submits the remainder and
returns the front bio. Callers must therefore continue processing the
returned bio, not the original pointer.

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 0b59c676f7c0..34e95ef9f2d4 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -9361,6 +9361,45 @@ void md_submit_discard_bio(struct mddev *mddev, struct md_rdev *rdev,
 }
 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 = READ_ONCE(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)
 {
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
@@ -925,6 +925,10 @@ 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);
-- 
2.51.0


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

* [PATCH v4 09/25] md/md-llbitmap: track bitmap sync_size explicitly
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (7 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 08/25] md: add helper to split bios at reshape offset Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:44   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 10/25] md/md-llbitmap: allocate page controls independently Yu Kuai
                   ` (15 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Track llbitmap's own sync_size instead of always using
mddev->resync_max_sectors directly.

This is the minimal bookkeeping needed before llbitmap can track old
and new reshape geometry independently.

Reviewed-by: Su Yue <glass.su@suse.com>
Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/md-llbitmap.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 1fa2adf0ada1..3417da288e99 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -287,6 +287,8 @@ struct llbitmap {
 	unsigned long chunksize;
 	/* total number of chunks */
 	unsigned long chunks;
+	/* total number of sectors tracked by current bitmap geometry */
+	sector_t sync_size;
 	unsigned long last_end_sync;
 	/*
 	 * time in seconds that dirty bits will be cleared if the page is not
@@ -918,6 +920,7 @@ static int llbitmap_init(struct llbitmap *llbitmap)
 	llbitmap->chunkshift = ffz(~chunksize);
 	llbitmap->chunksize = chunksize;
 	llbitmap->chunks = chunks;
+	llbitmap->sync_size = blocks;
 	mddev->bitmap_info.daemon_sleep = DEFAULT_DAEMON_SLEEP;
 
 	ret = llbitmap_cache_pages(llbitmap);
@@ -938,6 +941,7 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
 	unsigned long daemon_sleep;
 	unsigned long chunksize;
 	unsigned long events;
+	sector_t sync_size;
 	struct page *sb_page;
 	bitmap_super_t *sb;
 	int ret = -EINVAL;
@@ -987,6 +991,9 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
 		goto out_put_page;
 	}
 
+	sync_size = le64_to_cpu(sb->sync_size);
+	if (!sync_size)
+		sync_size = mddev->resync_max_sectors;
 	chunksize = le32_to_cpu(sb->chunksize);
 	if (!is_power_of_2(chunksize)) {
 		pr_err("md/llbitmap: %s: chunksize not a power of 2",
@@ -1022,8 +1029,9 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
 
 	llbitmap->barrier_idle = DEFAULT_BARRIER_IDLE;
 	llbitmap->chunksize = chunksize;
-	llbitmap->chunks = DIV_ROUND_UP_SECTOR_T(mddev->resync_max_sectors, chunksize);
+	llbitmap->chunks = DIV_ROUND_UP_SECTOR_T(sync_size, chunksize);
 	llbitmap->chunkshift = ffz(~chunksize);
+	llbitmap->sync_size = sync_size;
 	ret = llbitmap_cache_pages(llbitmap);
 
 out_put_page:
@@ -1153,6 +1161,7 @@ static int llbitmap_resize(struct mddev *mddev, sector_t blocks, int chunksize)
 	llbitmap->chunkshift = ffz(~chunksize);
 	llbitmap->chunksize = chunksize;
 	llbitmap->chunks = chunks;
+	llbitmap->sync_size = blocks;
 
 	return 0;
 }
@@ -1531,7 +1540,7 @@ static void llbitmap_update_sb(void *data)
 	sb->events = cpu_to_le64(mddev->events);
 	sb->state = cpu_to_le32(llbitmap->flags);
 	sb->chunksize = cpu_to_le32(llbitmap->chunksize);
-	sb->sync_size = cpu_to_le64(mddev->resync_max_sectors);
+	sb->sync_size = cpu_to_le64(llbitmap->sync_size);
 	sb->events_cleared = cpu_to_le64(llbitmap->events_cleared);
 	sb->sectors_reserved = cpu_to_le32(mddev->bitmap_info.space);
 	sb->daemon_sleep = cpu_to_le32(mddev->bitmap_info.daemon_sleep);
@@ -1549,6 +1558,7 @@ static int llbitmap_get_stats(void *data, struct md_bitmap_stats *stats)
 	stats->missing_pages = 0;
 	stats->pages = llbitmap->nr_pages;
 	stats->file_pages = llbitmap->nr_pages;
+	stats->sync_size = llbitmap->sync_size;
 
 	stats->behind_writes = atomic_read(&llbitmap->behind_writes);
 	stats->behind_wait = wq_has_sleeper(&llbitmap->behind_wait);
-- 
2.51.0


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

* [PATCH v4 10/25] md/md-llbitmap: allocate page controls independently
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (8 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 09/25] md/md-llbitmap: track bitmap sync_size explicitly Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:47   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 11/25] md/md-llbitmap: grow the page cache in place for reshape Yu Kuai
                   ` (14 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Allocate one llbitmap page-control object at a time and free each
object through the same model.

Let llbitmap_read_page() return a zeroed page without reading disk when
the page index is beyond the current bitmap size, so page-control
allocation no longer needs a separate read_existing flag.

This keeps the llbitmap page-control lifetime self-consistent and
prepares the page-cache code for later in-place growth.

Reviewed-by: Su Yue <glass.su@suse.com>
Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/md-llbitmap.c | 99 +++++++++++++++++++++++++---------------
 1 file changed, 62 insertions(+), 37 deletions(-)

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 3417da288e99..d12b3dba6ae1 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -512,13 +512,19 @@ static void llbitmap_write(struct llbitmap *llbitmap, enum llbitmap_state state,
 		llbitmap_set_page_dirty(llbitmap, idx, bit, false);
 }
 
+static unsigned int llbitmap_used_pages(struct llbitmap *llbitmap,
+					unsigned long chunks)
+{
+	return DIV_ROUND_UP(chunks + BITMAP_DATA_OFFSET, PAGE_SIZE);
+}
+
 static struct page *llbitmap_read_page(struct llbitmap *llbitmap, int idx)
 {
 	struct mddev *mddev = llbitmap->mddev;
 	struct page *page = NULL;
 	struct md_rdev *rdev;
 
-	if (llbitmap->pctl && llbitmap->pctl[idx])
+	if (llbitmap->pctl && idx < llbitmap->nr_pages && llbitmap->pctl[idx])
 		page = llbitmap->pctl[idx]->page;
 	if (page)
 		return page;
@@ -526,6 +532,8 @@ static struct page *llbitmap_read_page(struct llbitmap *llbitmap, int idx)
 	page = alloc_page(GFP_NOIO | __GFP_ZERO);
 	if (!page)
 		return ERR_PTR(-ENOMEM);
+	if (idx >= llbitmap_used_pages(llbitmap, llbitmap->chunks))
+		return page;
 
 	rdev_for_each(rdev, mddev) {
 		sector_t sector;
@@ -596,61 +604,78 @@ static void llbitmap_free_pages(struct llbitmap *llbitmap)
 	for (i = 0; i < llbitmap->nr_pages; i++) {
 		struct llbitmap_page_ctl *pctl = llbitmap->pctl[i];
 
-		if (!pctl || !pctl->page)
-			break;
-
-		__free_page(pctl->page);
+		if (!pctl)
+			continue;
+		if (pctl->page)
+			__free_page(pctl->page);
 		percpu_ref_exit(&pctl->active);
+		kfree(pctl);
 	}
 
-	kfree(llbitmap->pctl[0]);
 	kfree(llbitmap->pctl);
 	llbitmap->pctl = NULL;
 }
 
-static int llbitmap_cache_pages(struct llbitmap *llbitmap)
+static struct llbitmap_page_ctl *
+llbitmap_alloc_page_ctl(struct llbitmap *llbitmap, int idx)
 {
 	struct llbitmap_page_ctl *pctl;
-	unsigned int nr_pages = DIV_ROUND_UP(llbitmap->chunks +
-					     BITMAP_DATA_OFFSET, PAGE_SIZE);
+	struct page *page;
 	unsigned int size = struct_size(pctl, dirty, BITS_TO_LONGS(
 						llbitmap->blocks_per_page));
-	int i;
-
-	llbitmap->pctl = kmalloc_array(nr_pages, sizeof(void *),
-				       GFP_NOIO | __GFP_ZERO);
-	if (!llbitmap->pctl)
-		return -ENOMEM;
 
 	size = round_up(size, cache_line_size());
-	pctl = kmalloc_array(nr_pages, size, GFP_NOIO | __GFP_ZERO);
-	if (!pctl) {
-		kfree(llbitmap->pctl);
-		return -ENOMEM;
+	pctl = kzalloc(size, GFP_NOIO);
+	if (!pctl)
+		return ERR_PTR(-ENOMEM);
+
+	page = llbitmap_read_page(llbitmap, idx);
+
+	if (IS_ERR(page)) {
+		kfree(pctl);
+		return ERR_CAST(page);
 	}
 
-	llbitmap->nr_pages = nr_pages;
+	if (percpu_ref_init(&pctl->active, active_release,
+			    PERCPU_REF_ALLOW_REINIT, GFP_NOIO)) {
+		__free_page(page);
+		kfree(pctl);
+		return ERR_PTR(-ENOMEM);
+	}
 
-	for (i = 0; i < nr_pages; i++, pctl = (void *)pctl + size) {
-		struct page *page = llbitmap_read_page(llbitmap, i);
+	pctl->page = page;
+	pctl->state = page_address(page);
+	init_waitqueue_head(&pctl->wait);
+	return pctl;
+}
 
-		llbitmap->pctl[i] = pctl;
+static unsigned int llbitmap_reserved_pages(struct llbitmap *llbitmap)
+{
+	return DIV_ROUND_UP(llbitmap->mddev->bitmap_info.space << SECTOR_SHIFT,
+			    PAGE_SIZE);
+}
 
-		if (IS_ERR(page)) {
-			llbitmap_free_pages(llbitmap);
-			return PTR_ERR(page);
-		}
+static int llbitmap_alloc_pages(struct llbitmap *llbitmap)
+{
+	unsigned int used_pages = llbitmap_used_pages(llbitmap, llbitmap->chunks);
+	unsigned int nr_pages = max(used_pages, llbitmap_reserved_pages(llbitmap));
+	int i;
+
+	llbitmap->pctl = kcalloc(nr_pages, sizeof(*llbitmap->pctl), GFP_NOIO);
+	if (!llbitmap->pctl)
+		return -ENOMEM;
 
-		if (percpu_ref_init(&pctl->active, active_release,
-				    PERCPU_REF_ALLOW_REINIT, GFP_NOIO)) {
-			__free_page(page);
+	llbitmap->nr_pages = nr_pages;
+
+	for (i = 0; i < nr_pages; i++) {
+		llbitmap->pctl[i] = llbitmap_alloc_page_ctl(llbitmap, i);
+		if (IS_ERR(llbitmap->pctl[i])) {
+			int ret = PTR_ERR(llbitmap->pctl[i]);
+
+			llbitmap->pctl[i] = NULL;
 			llbitmap_free_pages(llbitmap);
-			return -ENOMEM;
+			return ret;
 		}
-
-		pctl->page = page;
-		pctl->state = page_address(page);
-		init_waitqueue_head(&pctl->wait);
 	}
 
 	return 0;
@@ -923,7 +948,7 @@ static int llbitmap_init(struct llbitmap *llbitmap)
 	llbitmap->sync_size = blocks;
 	mddev->bitmap_info.daemon_sleep = DEFAULT_DAEMON_SLEEP;
 
-	ret = llbitmap_cache_pages(llbitmap);
+	ret = llbitmap_alloc_pages(llbitmap);
 	if (ret)
 		return ret;
 
@@ -1032,7 +1057,7 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
 	llbitmap->chunks = DIV_ROUND_UP_SECTOR_T(sync_size, chunksize);
 	llbitmap->chunkshift = ffz(~chunksize);
 	llbitmap->sync_size = sync_size;
-	ret = llbitmap_cache_pages(llbitmap);
+	ret = llbitmap_alloc_pages(llbitmap);
 
 out_put_page:
 	__free_page(sb_page);
-- 
2.51.0


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

* [PATCH v4 11/25] md/md-llbitmap: grow the page cache in place for reshape
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (9 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 10/25] md/md-llbitmap: allocate page controls independently Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 18:03   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 12/25] md/md-llbitmap: track target reshape geometry fields Yu Kuai
                   ` (13 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Use the page-control helpers to grow llbitmap's cached pages in place
for resize and later reshape preparation, instead of rebuilding the
whole cache.

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

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index d12b3dba6ae1..cf522fae78f8 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -416,6 +416,19 @@ static char state_machine[BitStateCount][BitmapActionCount] = {
 };
 
 static void __llbitmap_flush(struct mddev *mddev);
+static void llbitmap_flush(struct mddev *mddev);
+static void llbitmap_update_sb(void *data);
+
+static void llbitmap_calculate_chunks(struct mddev *mddev, sector_t blocks,
+				      unsigned long *chunksize,
+				      unsigned long *chunks)
+{
+	*chunks = DIV_ROUND_UP_SECTOR_T(blocks, *chunksize);
+	while (*chunks > mddev->bitmap_info.space << SECTOR_SHIFT) {
+		*chunksize = *chunksize << 1;
+		*chunks = DIV_ROUND_UP_SECTOR_T(blocks, *chunksize);
+	}
+}
 
 static enum llbitmap_state llbitmap_read(struct llbitmap *llbitmap, loff_t pos)
 {
@@ -655,6 +668,48 @@ static unsigned int llbitmap_reserved_pages(struct llbitmap *llbitmap)
 			    PAGE_SIZE);
 }
 
+static int llbitmap_expand_pages(struct llbitmap *llbitmap,
+				 unsigned long chunks)
+{
+	struct llbitmap_page_ctl **pctl;
+	unsigned int old_nr_pages = llbitmap->nr_pages;
+	unsigned int nr_pages = llbitmap_used_pages(llbitmap, chunks);
+	unsigned int i;
+	int ret;
+
+	if (nr_pages <= old_nr_pages)
+		return 0;
+
+	pctl = kcalloc(nr_pages, sizeof(*pctl), GFP_KERNEL);
+	if (!pctl)
+		return -ENOMEM;
+
+	if (llbitmap->pctl)
+		memcpy(pctl, llbitmap->pctl,
+		       array_size(old_nr_pages, sizeof(*pctl)));
+
+	for (i = old_nr_pages; i < nr_pages; i++) {
+		pctl[i] = llbitmap_alloc_page_ctl(llbitmap, i);
+		if (IS_ERR(pctl[i]))
+			goto err_alloc_ptr;
+	}
+
+	kfree(llbitmap->pctl);
+	llbitmap->pctl = pctl;
+	llbitmap->nr_pages = nr_pages;
+	return 0;
+
+err_alloc_ptr:
+	ret = PTR_ERR(pctl[i]);
+	while (i-- > old_nr_pages) {
+		__free_page(pctl[i]->page);
+		percpu_ref_exit(&pctl[i]->active);
+		kfree(pctl[i]);
+	}
+	kfree(pctl);
+	return ret;
+}
+
 static int llbitmap_alloc_pages(struct llbitmap *llbitmap)
 {
 	unsigned int used_pages = llbitmap_used_pages(llbitmap, llbitmap->chunks);
@@ -730,6 +785,34 @@ static bool llbitmap_zero_all_disks(struct llbitmap *llbitmap)
 	return true;
 }
 
+static void llbitmap_mark_range(struct llbitmap *llbitmap,
+				unsigned long start,
+				unsigned long end,
+				enum llbitmap_state state)
+{
+	while (start <= end) {
+		llbitmap_write(llbitmap, state, start);
+		start++;
+	}
+}
+
+static int llbitmap_prepare_resize(struct llbitmap *llbitmap,
+				   unsigned long old_chunks,
+				   unsigned long new_chunks,
+				   unsigned long cache_chunks)
+{
+	int ret;
+
+	llbitmap_flush(llbitmap->mddev);
+	ret = llbitmap_expand_pages(llbitmap, cache_chunks);
+	if (ret)
+		return ret;
+	if (new_chunks > old_chunks)
+		llbitmap_mark_range(llbitmap, old_chunks, new_chunks - 1,
+				    BitUnwritten);
+	return 0;
+}
+
 static void llbitmap_init_state(struct llbitmap *llbitmap)
 {
 	struct mddev *mddev = llbitmap->mddev;
@@ -1026,10 +1109,10 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
 		goto out_put_page;
 	}
 
-	if (chunksize < DIV_ROUND_UP_SECTOR_T(mddev->resync_max_sectors,
+	if (chunksize < DIV_ROUND_UP_SECTOR_T(sync_size,
 					      mddev->bitmap_info.space << SECTOR_SHIFT)) {
 		pr_err("md/llbitmap: %s: chunksize too small %lu < %llu / %lu",
-		       mdname(mddev), chunksize, mddev->resync_max_sectors,
+		       mdname(mddev), chunksize, sync_size,
 		       mddev->bitmap_info.space);
 		goto out_put_page;
 	}
@@ -1171,24 +1254,62 @@ static int llbitmap_create(struct mddev *mddev)
 static int llbitmap_resize(struct mddev *mddev, sector_t blocks, int chunksize)
 {
 	struct llbitmap *llbitmap = mddev->bitmap;
+	sector_t old_blocks = llbitmap->sync_size;
+	unsigned long old_chunks = llbitmap->chunks;
 	unsigned long chunks;
+	unsigned long cache_chunks;
+	int ret = 0;
+	unsigned long bitmap_chunksize;
+	bool reshape;
+	bool quiesced = false;
 
 	if (chunksize == 0)
 		chunksize = llbitmap->chunksize;
 
-	/* If there is enough space, leave the chunksize unchanged. */
-	chunks = DIV_ROUND_UP_SECTOR_T(blocks, chunksize);
-	while (chunks > mddev->bitmap_info.space << SECTOR_SHIFT) {
-		chunksize = chunksize << 1;
-		chunks = DIV_ROUND_UP_SECTOR_T(blocks, chunksize);
-	}
+	bitmap_chunksize = chunksize;
+	llbitmap_calculate_chunks(mddev, blocks, &bitmap_chunksize, &chunks);
 
-	llbitmap->chunkshift = ffz(~chunksize);
-	llbitmap->chunksize = chunksize;
-	llbitmap->chunks = chunks;
-	llbitmap->sync_size = blocks;
+	reshape = mddev->delta_disks || mddev->new_level != mddev->level ||
+		mddev->new_layout != mddev->layout ||
+		mddev->new_chunk_sectors != mddev->chunk_sectors;
+	if (!reshape && bitmap_chunksize != llbitmap->chunksize)
+		return -EOPNOTSUPP;
+	if (blocks == old_blocks && chunks == llbitmap->chunks)
+		return 0;
 
+	if (mddev->pers->quiesce) {
+		mddev->pers->quiesce(mddev, 1);
+		quiesced = true;
+	}
+
+	mutex_lock(&mddev->bitmap_info.mutex);
+	cache_chunks = reshape ? max(old_chunks, chunks) : chunks;
+	ret = llbitmap_prepare_resize(llbitmap, old_chunks, chunks, cache_chunks);
+	if (ret)
+		goto out;
+
+	if (reshape) {
+		llbitmap->chunks = max(old_chunks, chunks);
+	} else {
+		if (blocks < old_blocks && chunks < old_chunks)
+			llbitmap_mark_range(llbitmap, chunks, old_chunks - 1,
+					    BitUnwritten);
+		mddev->bitmap_info.chunksize = bitmap_chunksize;
+		llbitmap->chunks = chunks;
+		llbitmap->sync_size = blocks;
+		llbitmap_update_sb(llbitmap);
+	}
+	__llbitmap_flush(mddev);
+	mutex_unlock(&mddev->bitmap_info.mutex);
+	if (quiesced)
+		mddev->pers->quiesce(mddev, 0);
 	return 0;
+
+out:
+	mutex_unlock(&mddev->bitmap_info.mutex);
+	if (quiesced)
+		mddev->pers->quiesce(mddev, 0);
+	return ret;
 }
 
 static int llbitmap_load(struct mddev *mddev)
-- 
2.51.0


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

* [PATCH v4 12/25] md/md-llbitmap: track target reshape geometry fields
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (10 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 11/25] md/md-llbitmap: grow the page cache in place for reshape Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:51   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 13/25] md/md-llbitmap: finish reshape geometry Yu Kuai
                   ` (12 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Track llbitmap bookkeeping for the target reshape geometry while keeping
a single live bitmap instance.

Add the reshape geometry fields, refresh helper, and update the load and
resize paths to keep the target geometry in sync.

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

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index cf522fae78f8..37d1312e3eab 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -289,6 +289,9 @@ struct llbitmap {
 	unsigned long chunks;
 	/* total number of sectors tracked by current bitmap geometry */
 	sector_t sync_size;
+	unsigned long reshape_chunksize;
+	unsigned long reshape_chunks;
+	sector_t reshape_sync_size;
 	unsigned long last_end_sync;
 	/*
 	 * time in seconds that dirty bits will be cleared if the page is not
@@ -430,6 +433,39 @@ static void llbitmap_calculate_chunks(struct mddev *mddev, sector_t blocks,
 	}
 }
 
+static bool llbitmap_reshaping(struct llbitmap *llbitmap)
+{
+	return llbitmap->mddev->reshape_position != MaxSector;
+}
+
+static sector_t llbitmap_personality_sync_size(struct llbitmap *llbitmap,
+					       bool previous)
+{
+	struct mddev *mddev = llbitmap->mddev;
+
+	if (!llbitmap_reshaping(llbitmap) || !mddev->private || !mddev->pers ||
+	    !mddev->pers->bitmap_sync_size)
+		return llbitmap->sync_size;
+	return mddev->pers->bitmap_sync_size(mddev, previous);
+}
+
+static void llbitmap_refresh_reshape(struct llbitmap *llbitmap)
+{
+	unsigned long old_chunks = DIV_ROUND_UP_SECTOR_T(llbitmap->sync_size,
+						 llbitmap->chunksize);
+	sector_t blocks = llbitmap_personality_sync_size(llbitmap, false);
+	unsigned long chunksize = llbitmap->chunksize;
+	unsigned long chunks = DIV_ROUND_UP_SECTOR_T(blocks, chunksize);
+
+	llbitmap->reshape_sync_size = blocks;
+	llbitmap->reshape_chunksize = chunksize;
+	llbitmap->reshape_chunks = chunks;
+	llbitmap_calculate_chunks(llbitmap->mddev, blocks,
+				  &llbitmap->reshape_chunksize,
+				  &llbitmap->reshape_chunks);
+	llbitmap->chunks = max(old_chunks, llbitmap->reshape_chunks);
+}
+
 static enum llbitmap_state llbitmap_read(struct llbitmap *llbitmap, loff_t pos)
 {
 	unsigned int idx;
@@ -1029,6 +1065,7 @@ static int llbitmap_init(struct llbitmap *llbitmap)
 	llbitmap->chunksize = chunksize;
 	llbitmap->chunks = chunks;
 	llbitmap->sync_size = blocks;
+	llbitmap_refresh_reshape(llbitmap);
 	mddev->bitmap_info.daemon_sleep = DEFAULT_DAEMON_SLEEP;
 
 	ret = llbitmap_alloc_pages(llbitmap);
@@ -1140,6 +1177,7 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
 	llbitmap->chunks = DIV_ROUND_UP_SECTOR_T(sync_size, chunksize);
 	llbitmap->chunkshift = ffz(~chunksize);
 	llbitmap->sync_size = sync_size;
+	llbitmap_refresh_reshape(llbitmap);
 	ret = llbitmap_alloc_pages(llbitmap);
 
 out_put_page:
@@ -1289,6 +1327,9 @@ static int llbitmap_resize(struct mddev *mddev, sector_t blocks, int chunksize)
 		goto out;
 
 	if (reshape) {
+		llbitmap->reshape_sync_size = blocks;
+		llbitmap->reshape_chunksize = bitmap_chunksize;
+		llbitmap->reshape_chunks = chunks;
 		llbitmap->chunks = max(old_chunks, chunks);
 	} else {
 		if (blocks < old_blocks && chunks < old_chunks)
@@ -1297,6 +1338,7 @@ static int llbitmap_resize(struct mddev *mddev, sector_t blocks, int chunksize)
 		mddev->bitmap_info.chunksize = bitmap_chunksize;
 		llbitmap->chunks = chunks;
 		llbitmap->sync_size = blocks;
+		llbitmap_refresh_reshape(llbitmap);
 		llbitmap_update_sb(llbitmap);
 	}
 	__llbitmap_flush(mddev);
-- 
2.51.0


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

* [PATCH v4 13/25] md/md-llbitmap: finish reshape geometry
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (11 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 12/25] md/md-llbitmap: track target reshape geometry fields Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:59   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 14/25] md/md-llbitmap: refuse reshape while llbitmap still needs sync Yu Kuai
                   ` (11 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Commit the staged llbitmap geometry when reshape finishes.

When assembling a stopped reshape, md_run() creates the bitmap before
publishing mddev->pers. llbitmap_read_sb() can therefore only initialize
the reshape fields from the old on-disk sync size. Refresh the staged
reshape geometry again from llbitmap_load(), after mddev->pers is
available, and expand the in-memory page controls before replaying bitmap
state.

Reproduce on the old kernel by creating a RAID10 llbitmap with four
active disks and two spares, growing it to six disks, then stopping and
assembling while reshape is still running. The llbitmap chunk count was
32704 before grow, 49056 during reshape, then rolled back to 32704 after
reassemble.

The fixed kernel kept the target geometry across the same stop/reassemble
flow: 65440 chunks before grow, 98160 during reshape, and 98160 after
reassemble.

Reported-by: Mykola Marzhan <mykola@meshstor.io>
Link: https://lore.kernel.org/all/20260726185916.2223460-1-mykola@meshstor.io/
Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/md-llbitmap.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 37d1312e3eab..f41f9ba2063b 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1358,11 +1358,20 @@ static int llbitmap_load(struct mddev *mddev)
 {
 	enum llbitmap_action action = BitmapActionReload;
 	struct llbitmap *llbitmap = mddev->bitmap;
+	int ret;
 
 	if (test_and_clear_bit(BITMAP_STALE, &llbitmap->flags))
 		action = BitmapActionStale;
 
+	mutex_lock(&mddev->bitmap_info.mutex);
+	llbitmap_refresh_reshape(llbitmap);
+	ret = llbitmap_expand_pages(llbitmap, llbitmap->chunks);
+	if (ret) {
+		mutex_unlock(&mddev->bitmap_info.mutex);
+		return ret;
+	}
 	llbitmap_state_machine(llbitmap, 0, llbitmap->chunks - 1, action);
+	mutex_unlock(&mddev->bitmap_info.mutex);
 	return 0;
 }
 
@@ -1694,6 +1703,30 @@ static void llbitmap_dirty_bits(struct mddev *mddev, unsigned long s,
 	llbitmap_state_machine(mddev->bitmap, s, e, BitmapActionStartwrite);
 }
 
+static void llbitmap_reshape_finish(struct mddev *mddev)
+{
+	struct llbitmap *llbitmap = mddev->bitmap;
+
+	if (mddev->pers->quiesce)
+		mddev->pers->quiesce(mddev, 1);
+
+	mutex_lock(&mddev->bitmap_info.mutex);
+	llbitmap_flush(mddev);
+
+	llbitmap->chunksize = llbitmap->reshape_chunksize;
+	llbitmap->chunkshift = ffz(~llbitmap->chunksize);
+	llbitmap->chunks = llbitmap->reshape_chunks;
+	llbitmap->sync_size = llbitmap->reshape_sync_size;
+	llbitmap_refresh_reshape(llbitmap);
+	mddev->bitmap_info.chunksize = llbitmap->chunksize;
+	llbitmap_update_sb(llbitmap);
+	__llbitmap_flush(mddev);
+	mutex_unlock(&mddev->bitmap_info.mutex);
+
+	if (mddev->pers->quiesce)
+		mddev->pers->quiesce(mddev, 0);
+}
+
 static void llbitmap_write_sb(struct llbitmap *llbitmap)
 {
 	int nr_blocks = DIV_ROUND_UP(BITMAP_DATA_OFFSET, llbitmap->io_size);
@@ -1991,6 +2024,7 @@ static struct bitmap_operations llbitmap_ops = {
 	.get_stats		= llbitmap_get_stats,
 	.dirty_bits		= llbitmap_dirty_bits,
 	.prepare_range		= llbitmap_prepare_range,
+	.reshape_finish		= llbitmap_reshape_finish,
 	.write_all		= llbitmap_write_all,
 
 	.groups			= md_llbitmap_groups,
-- 
2.51.0


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

* [PATCH v4 14/25] md/md-llbitmap: refuse reshape while llbitmap still needs sync
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (12 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 13/25] md/md-llbitmap: finish reshape geometry Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:50   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 15/25] md/md-llbitmap: add reshape range mapping helpers Yu Kuai
                   ` (10 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Reject reshape when llbitmap still contains NeedSync or Syncing bits.

This keeps reshape from starting until the current llbitmap state has
been reconciled.

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

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index f41f9ba2063b..3b121b372cd8 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1703,6 +1703,29 @@ static void llbitmap_dirty_bits(struct mddev *mddev, unsigned long s,
 	llbitmap_state_machine(mddev->bitmap, s, e, BitmapActionStartwrite);
 }
 
+static int llbitmap_reshape_can_start(struct mddev *mddev)
+{
+	struct llbitmap *llbitmap = mddev->bitmap;
+	unsigned long chunk;
+	int ret = 0;
+
+	if (!llbitmap)
+		return 0;
+
+	mutex_lock(&mddev->bitmap_info.mutex);
+	for (chunk = 0; chunk < llbitmap->chunks; chunk++) {
+		enum llbitmap_state state = llbitmap_read(llbitmap, chunk);
+
+		if (state == BitNeedSync || state == BitSyncing) {
+			ret = -EBUSY;
+			break;
+		}
+	}
+	mutex_unlock(&mddev->bitmap_info.mutex);
+
+	return ret;
+}
+
 static void llbitmap_reshape_finish(struct mddev *mddev)
 {
 	struct llbitmap *llbitmap = mddev->bitmap;
@@ -2025,6 +2048,7 @@ static struct bitmap_operations llbitmap_ops = {
 	.dirty_bits		= llbitmap_dirty_bits,
 	.prepare_range		= llbitmap_prepare_range,
 	.reshape_finish		= llbitmap_reshape_finish,
+	.reshape_can_start	= llbitmap_reshape_can_start,
 	.write_all		= llbitmap_write_all,
 
 	.groups			= md_llbitmap_groups,
-- 
2.51.0


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

* [PATCH v4 15/25] md/md-llbitmap: add reshape range mapping helpers
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (13 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 14/25] md/md-llbitmap: refuse reshape while llbitmap still needs sync Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:47   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 16/25] md/md-llbitmap: don't skip reshape ranges from bitmap state Yu Kuai
                   ` (9 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Teach llbitmap to choose old versus new geometry during reshape and to
encode exact bitmap ranges for the active geometry. This is the mapping
groundwork for checkpoint remapping.

Range preparation now distinguishes writes from discards. Normal writes
must cover every touched bitmap chunk, while discards may only mark fully
covered chunks unwritten. Without this distinction, a discard that starts
or ends inside a chunk can make live data look unwritten after the range
has been mapped and floored.

Reproduce that with a RAID1 llbitmap using 128-sector chunks. A discard
starting halfway into chunk 8 with a 128-sector length changed clean bits
from 16352 to 16350 and unwritten bits from 0 to 2, even though no chunk
was fully discarded. With discard-specific range encoding, both counts
stay unchanged for the same test.

Range preparation also clamps the pre-map range in the same coordinate
space as the incoming IO. RAID5 receives array-sector offsets but tracks
llbitmap sync size in component sectors, so steady-state RAID5 must use
bitmap_array_sectors() before mapping and keep the existing sync-size
clamp after mapping.

Reproduce that with a 4-disk RAID5 llbitmap created --assume-clean. A
write below dev_sectors changed dirty bits from 0 to 512, but a write at
seek=2094080 left the count at 512. With the array-sector pre-map limit,
writing at seek=component_size + 65536 increased dirty bits from 512 to
1024.

Reported-by: Mykola Marzhan <mykola@meshstor.io>
Link: https://lore.kernel.org/all/20260726185916.2223460-1-mykola@meshstor.io/
Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
 drivers/md/md-bitmap.c   |   2 +-
 drivers/md/md-bitmap.h   |   3 +-
 drivers/md/md-llbitmap.c | 137 +++++++++++++++++++++++++++++++++++----
 drivers/md/md.c          |  10 +--
 4 files changed, 134 insertions(+), 18 deletions(-)

diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c
index 4c31807e6bcc..52dfbca28b64 100644
--- a/drivers/md/md-bitmap.c
+++ b/drivers/md/md-bitmap.c
@@ -1731,7 +1731,7 @@ static void bitmap_start_write(struct mddev *mddev, sector_t offset,
 }
 
 static void bitmap_prepare_range(struct mddev *mddev, sector_t *offset,
-				 unsigned long *sectors)
+				 unsigned long *sectors, bool discard)
 {
 	if (mddev->pers->bitmap_sector)
 		mddev->pers->bitmap_sector(mddev, offset, sectors);
diff --git a/drivers/md/md-bitmap.h b/drivers/md/md-bitmap.h
index 26cd07737aab..97cdd7cea5dd 100644
--- a/drivers/md/md-bitmap.h
+++ b/drivers/md/md-bitmap.h
@@ -96,7 +96,8 @@ struct bitmap_operations {
 	/* Prepare a range for this bitmap implementation. */
 	void (*prepare_range)(struct mddev *mddev,
 			      sector_t *offset,
-			      unsigned long *sectors);
+			      unsigned long *sectors,
+			      bool discard);
 	void (*reshape_finish)(struct mddev *mddev);
 	int (*reshape_can_start)(struct mddev *mddev);
 	void (*reshape_mark)(struct mddev *mddev, sector_t old_pos,
diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 3b121b372cd8..eed3e248b730 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -9,6 +9,7 @@
 #include <linux/sched.h>
 #include <linux/list.h>
 #include <linux/file.h>
+#include <linux/math64.h>
 #include <linux/seq_file.h>
 #include <trace/events/block.h>
 
@@ -433,22 +434,28 @@ static void llbitmap_calculate_chunks(struct mddev *mddev, sector_t blocks,
 	}
 }
 
-static bool llbitmap_reshaping(struct llbitmap *llbitmap)
-{
-	return llbitmap->mddev->reshape_position != MaxSector;
-}
-
 static sector_t llbitmap_personality_sync_size(struct llbitmap *llbitmap,
 					       bool previous)
 {
 	struct mddev *mddev = llbitmap->mddev;
 
-	if (!llbitmap_reshaping(llbitmap) || !mddev->private || !mddev->pers ||
+	if (READ_ONCE(mddev->reshape_position) == MaxSector ||
+	    !mddev->private || !mddev->pers ||
 	    !mddev->pers->bitmap_sync_size)
 		return llbitmap->sync_size;
 	return mddev->pers->bitmap_sync_size(mddev, previous);
 }
 
+static sector_t llbitmap_logical_size(struct llbitmap *llbitmap, bool previous)
+{
+	struct mddev *mddev = llbitmap->mddev;
+
+	if (!mddev->private || !mddev->pers ||
+	    !mddev->pers->bitmap_array_sectors)
+		return llbitmap_personality_sync_size(llbitmap, previous);
+	return mddev->pers->bitmap_array_sectors(mddev, previous);
+}
+
 static void llbitmap_refresh_reshape(struct llbitmap *llbitmap)
 {
 	unsigned long old_chunks = DIV_ROUND_UP_SECTOR_T(llbitmap->sync_size,
@@ -466,6 +473,80 @@ static void llbitmap_refresh_reshape(struct llbitmap *llbitmap)
 	llbitmap->chunks = max(old_chunks, llbitmap->reshape_chunks);
 }
 
+static void llbitmap_map_layout(struct llbitmap *llbitmap, sector_t *offset,
+				unsigned long *sectors, bool previous)
+{
+	sector_t limit = llbitmap_logical_size(llbitmap, previous);
+	sector_t start = *offset;
+	sector_t end = start + *sectors;
+
+	if (start >= limit) {
+		*sectors = 0;
+		return;
+	}
+	if (end > limit)
+		end = limit;
+
+	*offset = start;
+	*sectors = end - start;
+	if (!*sectors)
+		return;
+
+	if (llbitmap->mddev->pers->bitmap_sector_map)
+		llbitmap->mddev->pers->bitmap_sector_map(llbitmap->mddev, offset,
+							 sectors, previous);
+	else if (!previous && llbitmap->mddev->pers->bitmap_sector)
+		llbitmap->mddev->pers->bitmap_sector(llbitmap->mddev, offset,
+							 sectors);
+}
+
+static void llbitmap_encode_range(struct llbitmap *llbitmap, sector_t *offset,
+				  unsigned long *sectors, bool previous)
+{
+	unsigned long chunksize = previous ? llbitmap->chunksize :
+				      llbitmap->reshape_chunksize;
+	u64 start;
+	u64 end;
+
+	if (!*sectors) {
+		*offset = 0;
+		return;
+	}
+
+	start = div64_u64(*offset, chunksize);
+	end = div64_u64(*offset + *sectors - 1, chunksize);
+	*offset = (sector_t)start << llbitmap->chunkshift;
+	*sectors = (end - start + 1) << llbitmap->chunkshift;
+}
+
+static void llbitmap_encode_discard_range(struct llbitmap *llbitmap,
+					  sector_t *offset,
+					  unsigned long *sectors,
+					  bool previous)
+{
+	unsigned long chunksize = previous ? llbitmap->chunksize :
+					      llbitmap->reshape_chunksize;
+	sector_t end = *offset + *sectors;
+	u64 start;
+	u64 last;
+
+	if (!*sectors) {
+		*offset = 0;
+		return;
+	}
+
+	start = DIV_ROUND_UP_SECTOR_T(*offset, chunksize);
+	last = div64_u64(end, chunksize);
+	if (start >= last) {
+		*offset = 0;
+		*sectors = 0;
+		return;
+	}
+
+	*offset = (sector_t)start << llbitmap->chunkshift;
+	*sectors = (last - start) << llbitmap->chunkshift;
+}
+
 static enum llbitmap_state llbitmap_read(struct llbitmap *llbitmap, loff_t pos)
 {
 	unsigned int idx;
@@ -1394,11 +1475,35 @@ static void llbitmap_destroy(struct mddev *mddev)
 	mutex_unlock(&mddev->bitmap_info.mutex);
 }
 
+static bool llbitmap_map_previous(struct llbitmap *llbitmap, sector_t offset,
+				  unsigned long sectors)
+{
+	struct mddev *mddev = llbitmap->mddev;
+	sector_t boundary = READ_ONCE(mddev->reshape_position);
+
+	if (boundary == MaxSector)
+		return false;
+
+	WARN_ON_ONCE(sectors && offset < boundary && offset + sectors > boundary);
+
+	return mddev->reshape_backwards ? offset < boundary : offset >= boundary;
+}
+
 static void llbitmap_prepare_range(struct mddev *mddev, sector_t *offset,
-				   unsigned long *sectors)
+				   unsigned long *sectors, bool discard)
 {
-	if (mddev->pers->bitmap_sector)
-		mddev->pers->bitmap_sector(mddev, offset, sectors);
+	struct llbitmap *llbitmap = mddev->bitmap;
+	bool previous;
+
+	if (!llbitmap)
+		return;
+
+	previous = llbitmap_map_previous(llbitmap, *offset, *sectors);
+	llbitmap_map_layout(llbitmap, offset, sectors, previous);
+	if (discard)
+		llbitmap_encode_discard_range(llbitmap, offset, sectors, previous);
+	else
+		llbitmap_encode_range(llbitmap, offset, sectors, previous);
 }
 
 static void llbitmap_start_write(struct mddev *mddev, sector_t offset,
@@ -1567,7 +1672,11 @@ static bool llbitmap_blocks_synced(struct mddev *mddev, sector_t offset)
 {
 	struct llbitmap *llbitmap = mddev->bitmap;
 	unsigned long p = offset >> llbitmap->chunkshift;
-	enum llbitmap_state c = llbitmap_read(llbitmap, p);
+	enum llbitmap_state c;
+
+	if (p >= llbitmap->chunks)
+		return false;
+	c = llbitmap_read(llbitmap, p);
 
 	return c == BitClean || c == BitDirty || c == BitCleanUnwritten;
 }
@@ -1577,7 +1686,11 @@ static sector_t llbitmap_skip_sync_blocks(struct mddev *mddev, sector_t offset)
 	struct llbitmap *llbitmap = mddev->bitmap;
 	unsigned long p = offset >> llbitmap->chunkshift;
 	int blocks = llbitmap->chunksize - (offset & (llbitmap->chunksize - 1));
-	enum llbitmap_state c = llbitmap_read(llbitmap, p);
+	enum llbitmap_state c;
+
+	if (p >= llbitmap->chunks)
+		return 0;
+	c = llbitmap_read(llbitmap, p);
 
 	/* always skip unwritten blocks */
 	if (c == BitUnwritten)
@@ -1622,6 +1735,8 @@ static bool llbitmap_start_sync(struct mddev *mddev, sector_t offset,
 	 * if md_do_sync() loop more times.
 	 */
 	*blocks = llbitmap->chunksize - (offset & (llbitmap->chunksize - 1));
+	if (p >= llbitmap->chunks)
+		return false;
 	state = llbitmap_state_machine(llbitmap, p, p, BitmapActionStartsync);
 	return state == BitSyncing || state == BitSyncingUnwritten;
 }
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 34e95ef9f2d4..30d9f9c02e73 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -9401,20 +9401,20 @@ struct bio *mddev_bio_split_at_reshape_offset(struct mddev *mddev,
 EXPORT_SYMBOL_GPL(mddev_bio_split_at_reshape_offset);
 
 static void md_bitmap_prepare_range(struct mddev *mddev, sector_t *offset,
-				    unsigned long *sectors)
+				    unsigned long *sectors, bool discard)
 {
-	mddev->bitmap_ops->prepare_range(mddev, offset, sectors);
+	mddev->bitmap_ops->prepare_range(mddev, offset, sectors, discard);
 }
 
 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 :
+	bool discard = md_io_clone->rw == STAT_DISCARD;
+	md_bitmap_fn *fn = discard ? mddev->bitmap_ops->start_discard :
 			   mddev->bitmap_ops->start_write;
 
 	md_bitmap_prepare_range(mddev, &md_io_clone->offset,
-				&md_io_clone->sectors);
+				&md_io_clone->sectors, discard);
 	if (!md_io_clone->sectors)
 		return;
 	fn(mddev, md_io_clone->offset, md_io_clone->sectors);
-- 
2.51.0


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

* [PATCH v4 16/25] md/md-llbitmap: don't skip reshape ranges from bitmap state
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (14 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 15/25] md/md-llbitmap: add reshape range mapping helpers Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:41   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 17/25] md/md-llbitmap: remap checkpointed bits as reshape progresses Yu Kuai
                   ` (8 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Reshape progress is tracked by array metadata rather than llbitmap.
Do not let llbitmap skip_sync_blocks() suppress reshape ranges based on
stale bitmap state before the corresponding checkpoint is persisted.

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

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index eed3e248b730..5597b1afcb58 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1692,6 +1692,14 @@ static sector_t llbitmap_skip_sync_blocks(struct mddev *mddev, sector_t offset)
 		return 0;
 	c = llbitmap_read(llbitmap, p);
 
+	/*
+	 * Reshape progress is tracked by array metadata rather than llbitmap.
+	 * Skipping reshape ranges from stale bitmap state can lose data after a
+	 * restart before the corresponding bits are checkpointed to disk.
+	 */
+	if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery))
+		return 0;
+
 	/* always skip unwritten blocks */
 	if (c == BitUnwritten)
 		return blocks;
-- 
2.51.0


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

* [PATCH v4 17/25] md/md-llbitmap: remap checkpointed bits as reshape progresses
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (15 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 16/25] md/md-llbitmap: don't skip reshape ranges from bitmap state Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 18:12   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 18/25] md/md-llbitmap: clamp state-machine walks to tracked bits Yu Kuai
                   ` (7 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Merge checkpointed old llbitmap state forward as reshape_position advances
and record the checkpoint remap through reshape_mark().

Normal write accounting can run while the reshape thread checkpoints a new
reshape position. llbitmap_reshape_mark() reads old state bytes, merges them
into destination bits, and writes the result back. If llbitmap_start_write()
or llbitmap_start_discard() updates the same state bytes at the same time,
the two read/modify/write paths can overwrite each other and lose the state
from one side.

Serialize only this state-byte race with a rwlock. Normal I/O takes the read
side around llbitmap_state_machine(), after page active references are raised,
so concurrent normal I/O updates still run in parallel. Reshape checkpointing
takes the write side only while merging the checkpointed range, avoiding page
suspension and avoiding a sleeping mutex in the I/O accounting path.

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

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 5597b1afcb58..c3f6e807f739 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -302,6 +302,11 @@ struct llbitmap {
 	/* fires on first BitDirty state */
 	struct timer_list pending_timer;
 	struct work_struct daemon_work;
+	/*
+	 * Serialize reshape checkpoint remapping against normal I/O bitmap
+	 * updates without blocking concurrent I/O updates on each other.
+	 */
+	rwlock_t reshape_lock;
 
 	unsigned long flags;
 	__u64	events_cleared;
@@ -498,6 +503,14 @@ static void llbitmap_map_layout(struct llbitmap *llbitmap, sector_t *offset,
 	else if (!previous && llbitmap->mddev->pers->bitmap_sector)
 		llbitmap->mddev->pers->bitmap_sector(llbitmap->mddev, offset,
 							 sectors);
+
+	limit = llbitmap_personality_sync_size(llbitmap, previous);
+	start = *offset;
+	end = start + *sectors;
+	if (start >= limit)
+		*sectors = 0;
+	else if (end > limit)
+		*sectors = limit - start;
 }
 
 static void llbitmap_encode_range(struct llbitmap *llbitmap, sector_t *offset,
@@ -930,6 +943,33 @@ static int llbitmap_prepare_resize(struct llbitmap *llbitmap,
 	return 0;
 }
 
+static enum llbitmap_state
+llbitmap_rmerge_state(struct llbitmap *llbitmap,
+		      enum llbitmap_state dst,
+		      enum llbitmap_state src)
+{
+	bool level_456 = raid_is_456(llbitmap->mddev);
+
+	if (dst == BitNeedSync || dst == BitSyncing ||
+	    src == BitNeedSync || src == BitSyncing)
+		return BitNeedSync;
+
+	if (dst == BitDirty || src == BitDirty)
+		return BitDirty;
+
+	/*
+	 * Reshape generates valid target parity/data for both already-written
+	 * and not-yet-written regions in the checkpointed range, so a mix of
+	 * clean and unwritten still results in a clean destination bit.
+	 */
+	if (level_456 && ((dst == BitClean && src == BitUnwritten) ||
+			  (src == BitClean && dst == BitUnwritten)))
+		return BitClean;
+	if (dst == BitClean || src == BitClean)
+		return BitClean;
+	return BitUnwritten;
+}
+
 static void llbitmap_init_state(struct llbitmap *llbitmap)
 {
 	struct mddev *mddev = llbitmap->mddev;
@@ -1294,6 +1334,7 @@ static void md_llbitmap_daemon_fn(struct work_struct *work)
 
 	if (llbitmap->mddev->degraded)
 		return;
+
 retry:
 	start = 0;
 	end = min(llbitmap->chunks, PAGE_SIZE - BITMAP_DATA_OFFSET) - 1;
@@ -1355,6 +1396,7 @@ static int llbitmap_create(struct mddev *mddev)
 
 	timer_setup(&llbitmap->pending_timer, llbitmap_pending_timer_fn, 0);
 	INIT_WORK(&llbitmap->daemon_work, md_llbitmap_daemon_fn);
+	rwlock_init(&llbitmap->reshape_lock);
 	atomic_set(&llbitmap->behind_writes, 0);
 	init_waitqueue_head(&llbitmap->behind_wait);
 
@@ -1520,7 +1562,9 @@ static void llbitmap_start_write(struct mddev *mddev, sector_t offset,
 		page_start++;
 	}
 
+	read_lock(&llbitmap->reshape_lock);
 	llbitmap_state_machine(llbitmap, start, end, BitmapActionStartwrite);
+	read_unlock(&llbitmap->reshape_lock);
 }
 
 static void llbitmap_end_write(struct mddev *mddev, sector_t offset,
@@ -1552,7 +1596,9 @@ static void llbitmap_start_discard(struct mddev *mddev, sector_t offset,
 		page_start++;
 	}
 
+	read_lock(&llbitmap->reshape_lock);
 	llbitmap_state_machine(llbitmap, start, end, BitmapActionDiscard);
+	read_unlock(&llbitmap->reshape_lock);
 }
 
 static void llbitmap_end_discard(struct mddev *mddev, sector_t offset,
@@ -1849,6 +1895,136 @@ static int llbitmap_reshape_can_start(struct mddev *mddev)
 	return ret;
 }
 
+struct llbitmap_reshape_range {
+	sector_t offset;
+	unsigned long sectors;
+	sector_t start;
+	sector_t end;
+};
+
+static enum llbitmap_state
+llbitmap_reshape_init_dst(struct llbitmap *llbitmap, unsigned long dst,
+			  const struct llbitmap_reshape_range *new)
+{
+	u64 bit_start = (u64)dst * llbitmap->reshape_chunksize;
+	u64 bit_end = bit_start + llbitmap->reshape_chunksize;
+
+	if (!llbitmap->mddev->reshape_backwards)
+		return bit_start < new->offset ? llbitmap_read(llbitmap, dst) :
+		       BitUnwritten;
+	return bit_end > new->end ? llbitmap_read(llbitmap, dst) : BitUnwritten;
+}
+
+static void llbitmap_reshape_dst_range(struct llbitmap *llbitmap,
+				       unsigned long dst,
+				       const struct llbitmap_reshape_range *new,
+				       struct llbitmap_reshape_range *dst_range)
+{
+	sector_t dst_bit_start = (sector_t)dst * llbitmap->reshape_chunksize;
+
+	dst_range->start = max(dst_bit_start, new->offset);
+	dst_range->end = min(dst_bit_start + llbitmap->reshape_chunksize,
+			     new->end);
+	dst_range->offset = dst_range->start;
+	dst_range->sectors = dst_range->end - dst_range->start;
+}
+
+static void llbitmap_reshape_map_range(struct llbitmap *llbitmap,
+				       sector_t lo, sector_t hi,
+				       bool previous,
+				       struct llbitmap_reshape_range *range)
+{
+	range->offset = lo;
+	range->sectors = hi - lo;
+	llbitmap_map_layout(llbitmap, &range->offset, &range->sectors, previous);
+	range->start = range->offset;
+	range->end = range->offset + range->sectors;
+}
+
+static bool llbitmap_reshape_src_range(const struct llbitmap_reshape_range *old,
+				       const struct llbitmap_reshape_range *new,
+				       const struct llbitmap_reshape_range *dst,
+				       struct llbitmap_reshape_range *src)
+{
+	if (!old->sectors)
+		return false;
+
+	src->start = old->offset +
+		mul_u64_u64_div_u64(dst->start - new->offset,
+				    old->sectors, new->sectors);
+	src->end = old->offset +
+		mul_u64_u64_div_u64_roundup(dst->end - new->offset,
+					    old->sectors, new->sectors);
+	if (src->end > old->end)
+		src->end = old->end;
+	src->offset = src->start;
+	src->sectors = src->end - src->start;
+
+	return src->sectors;
+}
+
+static enum llbitmap_state llbitmap_rmerge_src(struct llbitmap *llbitmap,
+					       enum llbitmap_state state,
+					       const struct llbitmap_reshape_range *src)
+{
+	unsigned long bit = div64_u64(src->start, llbitmap->chunksize);
+	unsigned long end = div64_u64(src->end - 1, llbitmap->chunksize);
+
+	while (bit <= end) {
+		enum llbitmap_state src_state = llbitmap_read(llbitmap, bit);
+
+		state = llbitmap_rmerge_state(llbitmap, state, src_state);
+		bit++;
+	}
+
+	return state;
+}
+
+static void llbitmap_reshape_merge(struct llbitmap *llbitmap,
+				   const struct llbitmap_reshape_range *old,
+				   const struct llbitmap_reshape_range *new)
+{
+	unsigned long dst_start;
+	unsigned long dst_end;
+	unsigned long dst;
+	bool backwards = false;
+
+	if (!new->sectors)
+		return;
+
+	dst_start = div64_u64(new->offset, llbitmap->reshape_chunksize);
+	dst_end = div64_u64(new->end - 1, llbitmap->reshape_chunksize);
+	if (old->sectors) {
+		unsigned long src_start = div64_u64(old->offset,
+						    llbitmap->chunksize);
+		unsigned long src_end = div64_u64(old->end - 1,
+						  llbitmap->chunksize);
+
+		backwards = src_start < dst_start && src_end >= dst_start;
+	}
+
+	dst = backwards ? dst_end : dst_start;
+	while (true) {
+		struct llbitmap_reshape_range dst_range;
+		struct llbitmap_reshape_range src;
+		enum llbitmap_state state;
+
+		llbitmap_reshape_dst_range(llbitmap, dst, new, &dst_range);
+		state = llbitmap_reshape_init_dst(llbitmap, dst, new);
+		if (llbitmap_reshape_src_range(old, new, &dst_range, &src))
+			state = llbitmap_rmerge_src(llbitmap, state, &src);
+		else
+			state = llbitmap_rmerge_state(llbitmap, state, BitUnwritten);
+		llbitmap_write(llbitmap, state, dst);
+		if (dst == (backwards ? dst_start : dst_end))
+			break;
+		if (backwards)
+			dst--;
+		else
+			dst++;
+	}
+}
+
 static void llbitmap_reshape_finish(struct mddev *mddev)
 {
 	struct llbitmap *llbitmap = mddev->bitmap;
@@ -1873,6 +2049,33 @@ static void llbitmap_reshape_finish(struct mddev *mddev)
 		mddev->pers->quiesce(mddev, 0);
 }
 
+static void llbitmap_reshape_mark(struct mddev *mddev, sector_t old_pos,
+				  sector_t new_pos)
+{
+	struct llbitmap *llbitmap = mddev->bitmap;
+	sector_t lo;
+	sector_t hi;
+	struct llbitmap_reshape_range old;
+	struct llbitmap_reshape_range new;
+
+	if (!llbitmap || old_pos == new_pos)
+		return;
+
+	lo = min(old_pos, new_pos);
+	hi = max(old_pos, new_pos);
+	if (!hi)
+		return;
+
+	llbitmap_reshape_map_range(llbitmap, lo, hi, true, &old);
+	llbitmap_reshape_map_range(llbitmap, lo, hi, false, &new);
+	if (!new.sectors)
+		return;
+
+	write_lock(&llbitmap->reshape_lock);
+	llbitmap_reshape_merge(llbitmap, &old, &new);
+	write_unlock(&llbitmap->reshape_lock);
+}
+
 static void llbitmap_write_sb(struct llbitmap *llbitmap)
 {
 	int nr_blocks = DIV_ROUND_UP(BITMAP_DATA_OFFSET, llbitmap->io_size);
@@ -2172,6 +2375,7 @@ static struct bitmap_operations llbitmap_ops = {
 	.prepare_range		= llbitmap_prepare_range,
 	.reshape_finish		= llbitmap_reshape_finish,
 	.reshape_can_start	= llbitmap_reshape_can_start,
+	.reshape_mark		= llbitmap_reshape_mark,
 	.write_all		= llbitmap_write_all,
 
 	.groups			= md_llbitmap_groups,
-- 
2.51.0


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

* [PATCH v4 18/25] md/md-llbitmap: clamp state-machine walks to tracked bits
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (16 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 17/25] md/md-llbitmap: remap checkpointed bits as reshape progresses Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:55   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 19/25] md/raid10: reject llbitmap reshape when md chunk shrinks Yu Kuai
                   ` (6 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

llbitmap_state_machine() can be called with an end bit beyond
llbitmap->chunks. In particular, llbitmap_cond_end_sync() passes
sector >> chunkshift, and sector can reach the tracked boundary
exactly.

Clamp the state-machine range to llbitmap->chunks so it cannot walk
past the tracked bitmap.

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

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index c3f6e807f739..27418bc001c1 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1012,7 +1012,10 @@ static enum llbitmap_state llbitmap_state_machine(struct llbitmap *llbitmap,
 		llbitmap_init_state(llbitmap);
 		return BitNone;
 	}
-
+	if (start >= llbitmap->chunks)
+		return BitNone;
+	if (end >= llbitmap->chunks)
+		end = llbitmap->chunks - 1;
 	while (start <= end) {
 		enum llbitmap_state c = llbitmap_read(llbitmap, start);
 
-- 
2.51.0


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

* [PATCH v4 19/25] md/raid10: reject llbitmap reshape when md chunk shrinks
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (17 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 18/25] md/md-llbitmap: clamp state-machine walks to tracked bits Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:25 ` [PATCH v4 20/25] md/raid10: wire llbitmap reshape lifecycle Yu Kuai
                   ` (5 subsequent siblings)
  24 siblings, 0 replies; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

llbitmap reshape keeps one live bitmap and cannot safely make an
existing bitmap bit cover a smaller data range.

The llbitmap chunksize itself will not shrink when mddev->chunk_sectors
stays the same or grows. However, shrinking mddev->chunk_sectors can
shrink the effective data range covered by each bit for the RAID10
reshape geometry. Reject that reshape while llbitmap is active.

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

diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 0a3cfdd3f5df..1242b8d3bb6b 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -4284,6 +4284,10 @@ static int raid10_check_reshape(struct mddev *mddev)
 
 	if (conf->geo.far_copies != 1 && !conf->geo.far_offset)
 		return -EINVAL;
+	if (mddev->bitmap_id == ID_LLBITMAP &&
+	    mddev->new_chunk_sectors &&
+	    mddev->new_chunk_sectors < mddev->chunk_sectors)
+		return -EOPNOTSUPP;
 
 	if (setup_geo(&geo, mddev, geo_start) != conf->copies)
 		/* mustn't change number of copies */
-- 
2.51.0


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

* [PATCH v4 20/25] md/raid10: wire llbitmap reshape lifecycle
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (18 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 19/25] md/raid10: reject llbitmap reshape when md chunk shrinks Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 18:28   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 21/25] md/raid10: split reshape bios before bitmap accounting Yu Kuai
                   ` (4 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Prepare llbitmap before RAID10 starts growing, checkpoint the bitmap
before advancing reshape_position, finish the llbitmap geometry update
when reshape completes, and export the old and new tracked sizes.

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

diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 1242b8d3bb6b..e50e4adee389 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -4394,6 +4394,12 @@ static int raid10_start_reshape(struct mddev *mddev)
 
 	if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery))
 		return -EBUSY;
+	if (md_bitmap_enabled(mddev, false) &&
+	    mddev->bitmap_ops->reshape_can_start) {
+		ret = mddev->bitmap_ops->reshape_can_start(mddev);
+		if (ret)
+			return ret;
+	}
 
 	if (setup_geo(&new, mddev, geo_start) != conf->copies)
 		return -EINVAL;
@@ -4707,6 +4713,13 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr,
 	    time_after(jiffies, conf->reshape_checkpoint + 10*HZ)) {
 		/* Need to update reshape_position in metadata */
 		wait_barrier(conf, false);
+		if (md_bitmap_enabled(mddev, false) &&
+		    mddev->bitmap_ops->reshape_mark &&
+		    conf->reshape_safe != conf->reshape_progress) {
+			mddev->bitmap_ops->reshape_mark(mddev, conf->reshape_safe,
+						       conf->reshape_progress);
+			mddev->bitmap_ops->unplug(mddev, true);
+		}
 		mddev->reshape_position = conf->reshape_progress;
 		if (mddev->reshape_backwards)
 			mddev->curr_resync_completed = raid10_size(mddev, 0, 0)
@@ -4905,9 +4918,19 @@ static void reshape_request_write(struct mddev *mddev, struct r10bio *r10_bio)
 
 static void end_reshape(struct r10conf *conf)
 {
+	struct mddev *mddev = conf->mddev;
+
 	if (test_bit(MD_RECOVERY_INTR, &conf->mddev->recovery))
 		return;
 
+	if (md_bitmap_enabled(mddev, false) &&
+	    mddev->bitmap_ops->reshape_mark &&
+	    conf->reshape_safe != conf->reshape_progress) {
+		mddev->bitmap_ops->reshape_mark(mddev, conf->reshape_safe,
+					       conf->reshape_progress);
+		mddev->bitmap_ops->unplug(mddev, true);
+	}
+
 	spin_lock_irq(&conf->device_lock);
 	conf->prev = conf->geo;
 	md_finish_reshape(conf->mddev);
@@ -5039,10 +5062,15 @@ static void end_reshape_request(struct r10bio *r10_bio)
 static void raid10_finish_reshape(struct mddev *mddev)
 {
 	struct r10conf *conf = mddev->private;
+	bool llbitmap = mddev->bitmap_id == ID_LLBITMAP &&
+		md_bitmap_enabled(mddev, false);
 
 	if (test_bit(MD_RECOVERY_INTR, &mddev->recovery))
 		return;
 
+	if (llbitmap && mddev->bitmap_ops->reshape_finish)
+		mddev->bitmap_ops->reshape_finish(mddev);
+
 	if (mddev->delta_disks > 0) {
 		if (mddev->resync_offset > mddev->resync_max_sectors) {
 			mddev->resync_offset = mddev->resync_max_sectors;
@@ -5069,6 +5097,15 @@ static void raid10_finish_reshape(struct mddev *mddev)
 	mddev->reshape_backwards = 0;
 }
 
+static sector_t raid10_bitmap_sync_size(struct mddev *mddev, bool previous)
+{
+	struct r10conf *conf = mddev->private;
+
+	if (previous)
+		return raid10_size(mddev, 0, 0);
+	return raid10_size(mddev, 0, conf->geo.raid_disks);
+}
+
 static struct md_personality raid10_personality =
 {
 	.head = {
@@ -5095,6 +5132,8 @@ static struct md_personality raid10_personality =
 	.start_reshape	= raid10_start_reshape,
 	.finish_reshape	= raid10_finish_reshape,
 	.update_reshape_pos = raid10_update_reshape_pos,
+	.bitmap_sync_size = raid10_bitmap_sync_size,
+	.bitmap_array_sectors = raid10_bitmap_sync_size,
 };
 
 static int __init raid10_init(void)
-- 
2.51.0


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

* [PATCH v4 21/25] md/raid10: split reshape bios before bitmap accounting
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (19 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 20/25] md/raid10: wire llbitmap reshape lifecycle Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:59   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 22/25] md/raid5: add exact old and new llbitmap mapping helpers Yu Kuai
                   ` (3 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Use the shared mddev_bio_split_at_reshape_offset() helper so RAID10
submits only one-side bios to llbitmap during reshape.

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

diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index e50e4adee389..61900ace190f 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -1886,6 +1886,7 @@ static bool raid10_make_request(struct mddev *mddev, struct bio *bio)
 {
 	struct r10conf *conf = mddev->private;
 	sector_t chunk_mask = (conf->geo.chunk_mask & conf->prev.chunk_mask);
+	const int rw = bio_data_dir(bio);
 	int chunk_sects = chunk_mask + 1;
 	int sectors = bio_sectors(bio);
 
@@ -1911,6 +1912,15 @@ static bool raid10_make_request(struct mddev *mddev, struct bio *bio)
 		sectors = chunk_sects -
 			(bio->bi_iter.bi_sector &
 			 (chunk_sects - 1));
+
+	bio = mddev_bio_split_at_reshape_offset(mddev, bio, &sectors,
+						&conf->bio_split);
+	if (!bio) {
+		if (rw == WRITE)
+			md_write_end(mddev);
+		return true;
+	}
+
 	if (!__make_request(mddev, bio, sectors))
 		md_write_end(mddev);
 
-- 
2.51.0


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

* [PATCH v4 22/25] md/raid5: add exact old and new llbitmap mapping helpers
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (20 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 21/25] md/raid10: split reshape bios before bitmap accounting Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 18:05   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 23/25] md/raid5: reject llbitmap reshape when md chunk shrinks Yu Kuai
                   ` (2 subsequent siblings)
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Teach RAID5 to export exact old and new llbitmap mappings and the
corresponding sync and array sizes for reshape-aware bitmap users.

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

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 8b3483674398..af7775278af6 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -5942,28 +5942,46 @@ static enum reshape_loc get_reshape_loc(struct mddev *mddev,
 	return LOC_BEHIND_RESHAPE;
 }
 
-static void raid5_bitmap_sector(struct mddev *mddev, sector_t *offset,
-				unsigned long *sectors)
+static void raid5_bitmap_sector_map(struct mddev *mddev, sector_t *offset,
+				    unsigned long *sectors,
+				    bool previous)
 {
 	struct r5conf *conf = mddev->private;
 	sector_t start = *offset;
 	sector_t end = start + *sectors;
-	sector_t prev_start = start;
-	sector_t prev_end = end;
 	int sectors_per_chunk;
-	enum reshape_loc loc;
 	int dd_idx;
 
-	sectors_per_chunk = conf->chunk_sectors *
-		(conf->raid_disks - conf->max_degraded);
+	if (previous)
+		sectors_per_chunk = conf->prev_chunk_sectors *
+			(conf->previous_raid_disks - conf->max_degraded);
+	else
+		sectors_per_chunk = conf->chunk_sectors *
+			(conf->raid_disks - conf->max_degraded);
 	sector_div(start, sectors_per_chunk);
 	start *= sectors_per_chunk;
 	if (sector_div(end, sectors_per_chunk))
 		end++;
 	end *= sectors_per_chunk;
 
-	start = raid5_compute_sector(conf, start, 0, &dd_idx, NULL);
-	end = raid5_compute_sector(conf, end, 0, &dd_idx, NULL);
+	start = raid5_compute_sector(conf, start, previous, &dd_idx, NULL);
+	end = raid5_compute_sector(conf, end, previous, &dd_idx, NULL);
+	*offset = start;
+	*sectors = end - start;
+}
+
+static void raid5_bitmap_sector(struct mddev *mddev, sector_t *offset,
+				unsigned long *sectors)
+{
+	struct r5conf *conf = mddev->private;
+	sector_t start = *offset;
+	sector_t end = start + *sectors;
+	sector_t prev_start = start;
+	unsigned long prev_sectors = end - start;
+	enum reshape_loc loc;
+
+	raid5_bitmap_sector_map(mddev, &start, sectors, false);
+	end = start + *sectors;
 
 	/*
 	 * For LOC_INSIDE_RESHAPE, this IO will wait for reshape to make
@@ -5972,19 +5990,10 @@ static void raid5_bitmap_sector(struct mddev *mddev, sector_t *offset,
 	loc = get_reshape_loc(mddev, conf, prev_start);
 	if (likely(loc != LOC_AHEAD_OF_RESHAPE)) {
 		*offset = start;
-		*sectors = end - start;
 		return;
 	}
 
-	sectors_per_chunk = conf->prev_chunk_sectors *
-		(conf->previous_raid_disks - conf->max_degraded);
-	sector_div(prev_start, sectors_per_chunk);
-	prev_start *= sectors_per_chunk;
-	sector_div(prev_end, sectors_per_chunk);
-	prev_end *= sectors_per_chunk;
-
-	prev_start = raid5_compute_sector(conf, prev_start, 1, &dd_idx, NULL);
-	prev_end = raid5_compute_sector(conf, prev_end, 1, &dd_idx, NULL);
+	raid5_bitmap_sector_map(mddev, &prev_start, &prev_sectors, true);
 
 	/*
 	 * for LOC_AHEAD_OF_RESHAPE, reshape can make progress before this IO
@@ -5992,7 +6001,7 @@ static void raid5_bitmap_sector(struct mddev *mddev, sector_t *offset,
 	 * we set bits for both.
 	 */
 	*offset = min(start, prev_start);
-	*sectors = max(end, prev_end) - *offset;
+	*sectors = max(end, prev_start + prev_sectors) - *offset;
 }
 
 static enum stripe_result make_stripe_request(struct mddev *mddev,
@@ -9059,6 +9068,20 @@ static void raid5_prepare_suspend(struct mddev *mddev)
 	wake_up(&conf->wait_for_reshape);
 }
 
+static sector_t raid5_bitmap_sync_size(struct mddev *mddev, bool previous)
+{
+	return mddev->dev_sectors;
+}
+
+static sector_t raid5_bitmap_array_sectors(struct mddev *mddev, bool previous)
+{
+	struct r5conf *conf = mddev->private;
+
+	if (previous)
+		return raid5_size(mddev, 0, 0);
+	return raid5_size(mddev, mddev->dev_sectors, conf->raid_disks);
+}
+
 static struct md_personality raid6_personality =
 {
 	.head = {
@@ -9088,6 +9111,9 @@ static struct md_personality raid6_personality =
 	.change_consistency_policy = raid5_change_consistency_policy,
 	.prepare_suspend = raid5_prepare_suspend,
 	.bitmap_sector	= raid5_bitmap_sector,
+	.bitmap_sector_map = raid5_bitmap_sector_map,
+	.bitmap_sync_size = raid5_bitmap_sync_size,
+	.bitmap_array_sectors = raid5_bitmap_array_sectors,
 };
 static struct md_personality raid5_personality =
 {
@@ -9118,6 +9144,9 @@ static struct md_personality raid5_personality =
 	.change_consistency_policy = raid5_change_consistency_policy,
 	.prepare_suspend = raid5_prepare_suspend,
 	.bitmap_sector	= raid5_bitmap_sector,
+	.bitmap_sector_map = raid5_bitmap_sector_map,
+	.bitmap_sync_size = raid5_bitmap_sync_size,
+	.bitmap_array_sectors = raid5_bitmap_array_sectors,
 };
 
 static struct md_personality raid4_personality =
@@ -9149,6 +9178,9 @@ static struct md_personality raid4_personality =
 	.change_consistency_policy = raid5_change_consistency_policy,
 	.prepare_suspend = raid5_prepare_suspend,
 	.bitmap_sector	= raid5_bitmap_sector,
+	.bitmap_sector_map = raid5_bitmap_sector_map,
+	.bitmap_sync_size = raid5_bitmap_sync_size,
+	.bitmap_array_sectors = raid5_bitmap_array_sectors,
 };
 
 static int __init raid5_init(void)
-- 
2.51.0


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

* [PATCH v4 23/25] md/raid5: reject llbitmap reshape when md chunk shrinks
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (21 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 22/25] md/raid5: add exact old and new llbitmap mapping helpers Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 17:25 ` [PATCH v4 24/25] md/raid5: wire llbitmap reshape lifecycle Yu Kuai
  2026-08-01 17:25 ` [PATCH v4 25/25] md/raid5: split reshape bios before bitmap accounting Yu Kuai
  24 siblings, 0 replies; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

llbitmap reshape keeps one live bitmap and cannot safely make an
existing bitmap bit cover a smaller data range.

The llbitmap chunksize itself will not shrink when mddev->chunk_sectors
stays the same or grows. However, shrinking mddev->chunk_sectors shrinks
sectors_per_chunk used by raid5_bitmap_sector_map(). That can shrink the
effective data range covered by each bit across the old and new RAID5
geometry. Reject that reshape while llbitmap is active.

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

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index af7775278af6..5697570120da 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -8511,6 +8511,9 @@ static int check_reshape(struct mddev *mddev)
 	if (!check_stripe_cache(mddev))
 		return -ENOSPC;
 
+	if (mddev->bitmap_id == ID_LLBITMAP &&
+	    mddev->new_chunk_sectors < mddev->chunk_sectors)
+		return -EOPNOTSUPP;
 	if (mddev->new_chunk_sectors > mddev->chunk_sectors ||
 	    mddev->delta_disks > 0)
 		if (resize_chunks(conf,
-- 
2.51.0


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

* [PATCH v4 24/25] md/raid5: wire llbitmap reshape lifecycle
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (22 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 23/25] md/raid5: reject llbitmap reshape when md chunk shrinks Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 18:12   ` sashiko-bot
  2026-08-01 17:25 ` [PATCH v4 25/25] md/raid5: split reshape bios before bitmap accounting Yu Kuai
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

Prepare llbitmap before RAID5 reshape starts, checkpoint the bitmap
before advancing reshape_position, and finish the llbitmap geometry
update when reshape completes.

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

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 5697570120da..896aa522f446 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -6433,6 +6433,13 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk
 			   || test_bit(MD_RECOVERY_INTR, &mddev->recovery));
 		if (atomic_read(&conf->reshape_stripes) != 0)
 			return 0;
+		if (md_bitmap_enabled(mddev, false) &&
+		    mddev->bitmap_ops->reshape_mark &&
+		    conf->reshape_safe != conf->reshape_progress) {
+			mddev->bitmap_ops->reshape_mark(mddev, conf->reshape_safe,
+						       conf->reshape_progress);
+			mddev->bitmap_ops->unplug(mddev, true);
+		}
 		mddev->reshape_position = conf->reshape_progress;
 		mddev->curr_resync_completed = sector_nr;
 		if (!mddev->reshape_backwards)
@@ -6542,6 +6549,13 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk
 			   || test_bit(MD_RECOVERY_INTR, &mddev->recovery));
 		if (atomic_read(&conf->reshape_stripes) != 0)
 			goto ret;
+		if (md_bitmap_enabled(mddev, false) &&
+		    mddev->bitmap_ops->reshape_mark &&
+		    conf->reshape_safe != conf->reshape_progress) {
+			mddev->bitmap_ops->reshape_mark(mddev, conf->reshape_safe,
+						       conf->reshape_progress);
+			mddev->bitmap_ops->unplug(mddev, true);
+		}
 		mddev->reshape_position = conf->reshape_progress;
 		mddev->curr_resync_completed = sector_nr;
 		if (!mddev->reshape_backwards)
@@ -8579,6 +8593,12 @@ static int raid5_start_reshape(struct mddev *mddev)
 			mdname(mddev));
 		return -EINVAL;
 	}
+	if (md_bitmap_enabled(mddev, false) &&
+	    mddev->bitmap_id == ID_LLBITMAP) {
+		i = mddev->bitmap_ops->resize(mddev, mddev->dev_sectors, 0);
+		if (i)
+			return i;
+	}
 
 	atomic_set(&conf->reshape_stripes, 0);
 	spin_lock_irq(&conf->device_lock);
@@ -8663,10 +8683,19 @@ static int raid5_start_reshape(struct mddev *mddev)
  */
 static void end_reshape(struct r5conf *conf)
 {
+	struct mddev *mddev = conf->mddev;
 
 	if (!test_bit(MD_RECOVERY_INTR, &conf->mddev->recovery)) {
 		struct md_rdev *rdev;
 
+		if (md_bitmap_enabled(mddev, false) &&
+		    mddev->bitmap_ops->reshape_mark &&
+		    conf->reshape_safe != conf->reshape_progress) {
+			mddev->bitmap_ops->reshape_mark(mddev, conf->reshape_safe,
+						       conf->reshape_progress);
+			mddev->bitmap_ops->unplug(mddev, true);
+		}
+
 		spin_lock_irq(&conf->device_lock);
 		conf->previous_raid_disks = conf->raid_disks;
 		md_finish_reshape(conf->mddev);
@@ -8693,8 +8722,16 @@ static void raid5_finish_reshape(struct mddev *mddev)
 {
 	struct r5conf *conf = mddev->private;
 	struct md_rdev *rdev;
+	bool llbitmap = mddev->bitmap_id == ID_LLBITMAP &&
+		md_bitmap_enabled(mddev, false);
 
 	if (!test_bit(MD_RECOVERY_INTR, &mddev->recovery)) {
+		if (llbitmap && mddev->bitmap_ops->reshape_finish)
+			mddev->bitmap_ops->reshape_finish(mddev);
+		if (llbitmap) {
+			mddev->resync_offset = 0;
+			mddev->resync_max_sectors = mddev->dev_sectors;
+		}
 
 		if (mddev->delta_disks <= 0) {
 			int d;
-- 
2.51.0


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

* [PATCH v4 25/25] md/raid5: split reshape bios before bitmap accounting
  2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
                   ` (23 preceding siblings ...)
  2026-08-01 17:25 ` [PATCH v4 24/25] md/raid5: wire llbitmap reshape lifecycle Yu Kuai
@ 2026-08-01 17:25 ` Yu Kuai
  2026-08-01 18:43   ` sashiko-bot
  24 siblings, 1 reply; 48+ messages in thread
From: Yu Kuai @ 2026-08-01 17:25 UTC (permalink / raw)
  To: Song Liu
  Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel

From: Yu Kuai <yukuai@fygo.io>

RAID5 maps array sectors through different geometries before and after the
reshape position. During llbitmap reshape, md core cannot account one bio
against both geometries as a single bitmap range, because the old and new
bitmap mappings can cover different chunks.

Split bios that cross reshape_position before md_account_bio(), so the
bitmap only sees ranges that belong to one side of the reshape boundary.

mddev_bio_split_at_reshape_offset() uses bio_submit_split_bioset(), which
submits the remainder immediately and returns the front split bio. If that
front bio later has to wait for reshape, md_handle_request() must not retry
the original bio pointer, because after the split that pointer is the
already-submitted remainder. Track whether the split happened, clear the
temporary BLK_STS_RESOURCE status after the internal clone completion, and
resubmit the front bio directly after the reshape wait. Keep the old
return-false retry path for unsplit bios, where md_handle_request() still
owns the same bio.

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

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 896aa522f446..9b47d9a277f9 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -6148,9 +6148,11 @@ static bool raid5_make_request(struct mddev *mddev, struct bio * bi)
 	struct r5conf *conf = mddev->private;
 	const int rw = bio_data_dir(bi);
 	struct stripe_request_ctx *ctx;
+	struct bio *front_bio;
 	sector_t logical_sector;
 	enum stripe_result res;
 	int s, stripe_cnt;
+	bool split = false;
 	bool on_wq;
 
 	if (unlikely(bi->bi_opf & REQ_PREFLUSH)) {
@@ -6184,6 +6186,18 @@ static bool raid5_make_request(struct mddev *mddev, struct bio * bi)
 		return true;
 	}
 
+	front_bio = bi;
+	bi = mddev_bio_split_at_reshape_offset(mddev, bi, NULL,
+					       &conf->bio_split);
+	if (!bi) {
+		if (rw == WRITE)
+			md_write_end(mddev);
+		return true;
+	}
+	if (bi != front_bio)
+		split = true;
+	front_bio = bi;
+
 	logical_sector = bi->bi_iter.bi_sector & ~((sector_t)RAID5_STRIPE_SECTORS(conf)-1);
 	bi->bi_next = NULL;
 
@@ -6284,6 +6298,11 @@ static bool raid5_make_request(struct mddev *mddev, struct bio * bi)
 		bio_endio(bi);
 
 		wait_for_completion(&done);
+		front_bio->bi_status = BLK_STS_OK;
+		if (split) {
+			submit_bio_noacct(front_bio);
+			return true;
+		}
 		return false;
 	}
 
-- 
2.51.0


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

* Re: [PATCH v4 05/25] md/raid5: round bitmap stripes with sector division
  2026-08-01 17:24 ` [PATCH v4 05/25] md/raid5: round bitmap stripes with sector division Yu Kuai
@ 2026-08-01 17:39   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:39 UTC (permalink / raw)
  To: Yu Kuai; +Cc: yukuai, linux-raid

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] The IO boundary `prev_end` is incorrectly rounded down instead of up to the nearest stripe boundary, causing bitmap bits for the last chunk of an IO to be missed during reshape.
--

--- Patch [5]: [PATCH v4 05/25] md/raid5: round bitmap stripes with sector division ---
commit 804a4bf19f3825714694494e86aceb7594f387a3
Author: Yu Kuai <yukuai@fygo.io>

md/raid5: round bitmap stripes with sector division

This commit fixes alignment of array ranges to full RAID5 stripe widths when
the width is not a power of two. It replaces the old mask-based round_down()
and round_up() logic with sector_div() arithmetic.

Link: https://lore.kernel.org/all/20260726185916.2223460-1-mykola@meshstor.io/

> diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
> index 4276b125f94cb..8b3483674398d 100644
> --- a/drivers/md/raid5.c
> +++ b/drivers/md/raid5.c

[ ... ]

> @@ -5975,8 +5978,10 @@ static void raid5_bitmap_sector(struct mddev *mddev, sector_t *offset,
>  
>  	sectors_per_chunk = conf->prev_chunk_sectors *
>  		(conf->previous_raid_disks - conf->max_degraded);
> -	prev_start = round_down(prev_start, sectors_per_chunk);
> -	prev_end = round_down(prev_end, sectors_per_chunk);
> +	sector_div(prev_start, sectors_per_chunk);
> +	prev_start *= sectors_per_chunk;
> +	sector_div(prev_end, sectors_per_chunk);
> +	prev_end *= sectors_per_chunk;

[Severity: High]
This is a pre-existing issue, but does prev_end need to be rounded up here
instead of rounded down?

The original code incorrectly used round_down(prev_end, ...) for the exclusive
end boundary. By preserving that logic here with sector_div() and
multiplication, we truncate the boundary instead of rounding it up.

If a write occurs ahead of the reshape position (LOC_AHEAD_OF_RESHAPE) and
ends inside a partial stripe, could shrinking the exclusive prev_end boundary
cause us to miss bitmap bits for the trailing sectors?

If a crash happens during reshape, would the subsequent resync silently skip
these trailing sectors because they were omitted from the bitmap?

Should this include the same remainder check that was applied to the end
variable earlier in the file to properly round up?

	if (sector_div(prev_end, sectors_per_chunk))
		prev_end++;

>  
>  	prev_start = raid5_compute_sector(conf, prev_start, 1, &dd_idx, NULL);
>  	prev_end = raid5_compute_sector(conf, prev_end, 1, &dd_idx, NULL);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=5

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

* Re: [PATCH v4 16/25] md/md-llbitmap: don't skip reshape ranges from bitmap state
  2026-08-01 17:25 ` [PATCH v4 16/25] md/md-llbitmap: don't skip reshape ranges from bitmap state Yu Kuai
@ 2026-08-01 17:41   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:41 UTC (permalink / raw)
  To: Yu Kuai; +Cc: yukuai, linux-raid

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Integer overflow in `blocks` calculation can cause `md_do_sync` to skip the entire array.
--

--- Patch [16]: [PATCH v4 16/25] md/md-llbitmap: don't skip reshape ranges from bitmap state ---
commit 20fdc8d04a69beb6ba6a356b102dd6e6920204f1
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: don't skip reshape ranges from bitmap state

Reshape progress is tracked by array metadata rather than llbitmap.
Do not let llbitmap skip_sync_blocks() suppress reshape ranges based on
stale bitmap state before the corresponding checkpoint is persisted.

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index eed3e248b730d..5597b1afcb580 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c
> @@ -1686,12 +1686,20 @@ static sector_t llbitmap_skip_sync_blocks(struct mddev *mddev, sector_t offset)
>  {
>  	struct llbitmap *llbitmap = mddev->bitmap;
>  	unsigned long p = offset >> llbitmap->chunkshift;
>  	int blocks = llbitmap->chunksize - (offset & (llbitmap->chunksize - 1));
        ^^^^

[Severity: High]
This isn't a bug introduced by this patch, but can this integer subtraction
overflow?

If llbitmap->chunksize is dynamically calculated up to 2^31 sectors for
extremely large arrays, would 'blocks' overflow to a negative value since it
is a 32-bit signed int?

When returned as a sector_t (unsigned 64-bit), C sign-extends it to a massive
value (0xffffffff80000000). Could the caller md_do_sync() add this to its
position tracker, exceeding max_sectors and prematurely terminating the resync
process, which would leave the array degraded?

>  	enum llbitmap_state c;
>  
>  	if (p >= llbitmap->chunks)
>  		return 0;
>  	c = llbitmap_read(llbitmap, p);
>  
> +	/*
> +	 * Reshape progress is tracked by array metadata rather than llbitmap.
> +	 * Skipping reshape ranges from stale bitmap state can lose data after a
> +	 * restart before the corresponding bits are checkpointed to disk.
> +	 */
> +	if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery))
> +		return 0;
> +
>  	/* always skip unwritten blocks */
>  	if (c == BitUnwritten)
>  		return blocks;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=16

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

* Re: [PATCH v4 08/25] md: add helper to split bios at reshape offset
  2026-08-01 17:25 ` [PATCH v4 08/25] md: add helper to split bios at reshape offset Yu Kuai
@ 2026-08-01 17:41   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:41 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] READ_ONCE() on the 64-bit `mddev->reshape_position` can result in a torn read on 32-bit architectures, leading to incorrect bio splitting and silent data corruption.
--

--- Patch [8]: [PATCH v4 08/25] md: add helper to split bios at reshape offset ---
commit 71f7d05880dacfb8a18a94d6924ba3faac7d7965
Author: Yu Kuai <yukuai@fygo.io>

md: add helper to split bios at reshape offset

This commit adds mddev_bio_split_at_reshape_offset() to allow RAID
personalities to share reshape-offset bio splitting. It introduces a
lockless read of mddev->reshape_position using READ_ONCE().

> diff --git a/drivers/md/md.c b/drivers/md/md.c
> --- a/drivers/md/md.c
> +++ b/drivers/md/md.c
[ ... ]
> +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 = READ_ONCE(mddev->reshape_position);
> +	start = bio->bi_iter.bi_sector;
> +	end = bio_end_sector(bio);
> +	if (start >= boundary || end <= boundary)
> +		goto split;

[Severity: Critical]
Is it possible for the READ_ONCE() above to result in a torn read on 32-bit
architectures?

Since mddev->reshape_position is a sector_t (64-bit value), compiling a
READ_ONCE() on a 32-bit architecture will result in two separate 32-bit loads.
If the reshape thread concurrently updates this value without a lock, a read
could happen exactly when crossing a 32-bit boundary, producing a corrupted
boundary value.

If the torn read produces an oversized boundary value, will this bounds check
evaluate to true and incorrectly skip splitting the bio?

Passing an unsplit bio that spans both old and new geometry layouts to the
RAID personality might result in writes to incorrect disks and offsets. Before
this helper was introduced, did personalities like RAID5 use a lock (like
conf->device_lock) to prevent this 64-bit read race?

> +
> +	if (boundary - start < split_sectors)
> +		split_sectors = boundary - start;
> +
> +split:
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=8

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

* Re: [PATCH v4 02/25] md/md-llbitmap: use GFP_NOIO for cache allocations
  2026-08-01 17:24 ` [PATCH v4 02/25] md/md-llbitmap: use GFP_NOIO for cache allocations Yu Kuai
@ 2026-08-01 17:41   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:41 UTC (permalink / raw)
  To: Yu Kuai; +Cc: yukuai, linux-raid

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] Large contiguous memory allocation failure using `kmalloc_array` and `GFP_NOIO`.

Pre-existing issues:
- [Critical] Unprotected read-modify-write race condition corrupts lockless bitmap state.
- [High] Out-of-bounds array access due to missing page cache reallocation during array resize.
- [High] Use-After-Free of `llbitmap` in the `llbitmap_create` error path.
--

--- Patch [2]: [PATCH v4 02/25] md/md-llbitmap: use GFP_NOIO for cache allocations ---
commit e93c6e98fd387cb6b6c743dfb60b192e52bca4d1
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: use GFP_NOIO for cache allocations

This commit changes the allocation context from GFP_KERNEL to GFP_NOIO
for several llbitmap cache and object structures. This is done to prevent
direct reclaim from entering filesystem or block I/O while MD resize state
is locked, avoiding recursive deadlocks.

> update_size() calls the personality resize method, and llbitmap_resize()
> can grow the page cache through llbitmap_prepare_resize().

[Severity: High]
This isn't a bug introduced by this patch, but the commit message mentions
llbitmap_prepare_resize(), which appears to be entirely missing from the
code.

In llbitmap_resize(), llbitmap->chunks is increased, but the underlying
page cache arrays (pctl) are never actually reallocated to match the new
capacity:

drivers/md/md-llbitmap.c:llbitmap_resize() {
    ...
	llbitmap->chunkshift = ffz(~chunksize);
	llbitmap->chunksize = chunksize;
	llbitmap->chunks = chunks;
    ...
}

Will subsequent I/O to the newly resized space calculate a page_start
index exceeding nr_pages and cause an out-of-bounds access?

drivers/md/md-llbitmap.c:llbitmap_start_write() {
    ...
	int page_start = (start + BITMAP_DATA_OFFSET) >> PAGE_SHIFT;
    ...
	while (page_start <= page_end) {
		llbitmap_raise_barrier(llbitmap, page_start);
    ...
}


diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 131582724e7e9..6ab2188bba279 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c

[ ... ]

> @@ -616,12 +616,12 @@ static int llbitmap_cache_pages(struct llbitmap *llbitmap)
>  	int i;
>  
>  	llbitmap->pctl = kmalloc_array(nr_pages, sizeof(void *),
> -				       GFP_KERNEL | __GFP_ZERO);
> +				       GFP_NOIO | __GFP_ZERO);
>  	if (!llbitmap->pctl)
>  		return -ENOMEM;
>  
>  	size = round_up(size, cache_line_size());
> -	pctl = kmalloc_array(nr_pages, size, GFP_KERNEL | __GFP_ZERO);
> +	pctl = kmalloc_array(nr_pages, size, GFP_NOIO | __GFP_ZERO);

[Severity: High]
Will this strict GFP_NOIO constraint cause large contiguous memory
allocations to deterministically fail due to physical memory fragmentation?

For large MD arrays, allocating the pctl array scales linearly with disk
size. This allocation can reach several megabytes, which will exceed
KMALLOC_MAX_SIZE or frequently fail because GFP_NOIO prevents direct
filesystem and I/O reclaim.

Should this allocation be migrated to kvmalloc_array() to handle larger
sizes gracefully?

>  	if (!pctl) {
>  		kfree(llbitmap->pctl);
>  		return -ENOMEM;

[ ... ]

> @@ -1110,7 +1110,7 @@ static int llbitmap_create(struct mddev *mddev)
>  	if (ret)
>  		return ret;
>  
> -	llbitmap = kzalloc_obj(*llbitmap);
> +	llbitmap = kzalloc_obj(*llbitmap, GFP_NOIO);
>  	if (!llbitmap)
>  		return -ENOMEM;

[ ... ]

>  	mutex_unlock(&mddev->bitmap_info.mutex);
>  	if (ret) {
>  		kfree(llbitmap);
>  		mddev->bitmap = NULL;
>  	}

[Severity: High]
This is a pre-existing issue, but does dropping the mutex before clearing
mddev->bitmap open a race window for concurrent sysfs readers?

If llbitmap_read_sb() fails during llbitmap_create(), the code drops
mddev->bitmap_info.mutex, calls kfree(llbitmap), and only then sets
mddev->bitmap to NULL.

Could a concurrent sysfs read (such as metadata_show()) acquire the lock
while mddev->bitmap is still non-NULL and read from the freed memory?

[Severity: Critical]
This is a pre-existing issue, but can the lockless bitmap state be
corrupted by a read-modify-write race condition here?

When multiple threads write to the same sector concurrently,
llbitmap_infect_dirty_bits() iterates over the chunks and performs an
unlocked read-modify-write on the byte array:

drivers/md/md-llbitmap.c:llbitmap_infect_dirty_bits() {
    ...
		switch (pctl->state[pos]) {
		case BitUnwritten:
			pctl->state[pos] = level_456 ? BitNeedSync : BitDirty;
    ...
}

Simultaneously, another thread may execute llbitmap_write() and assign a
new state directly:

drivers/md/md-llbitmap.c:llbitmap_write() {
    ...
	llbitmap->pctl[idx]->state[bit] = state;
    ...
}

Could the modifying thread in llbitmap_infect_dirty_bits() overwrite the
direct assignment in llbitmap_write() with a stale value, permanently
losing state updates and leading to silent data corruption (e.g., dirty
regions failing to resync after a crash)?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=2

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

* Re: [PATCH v4 03/25] md/md-llbitmap: only end fully synced chunks
  2026-08-01 17:24 ` [PATCH v4 03/25] md/md-llbitmap: only end fully synced chunks Yu Kuai
@ 2026-08-01 17:42   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:42 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Mutually re-arming timer and workqueue are not safely torn down in llbitmap_destroy(), leading to a Use-After-Free.
- [High] Missing wait for behind writes in llbitmap_destroy() leads to a NULL pointer dereference or UAF when inflight behind writes complete.
--

--- Patch [3]: [PATCH v4 03/25] md/md-llbitmap: only end fully synced chunks ---
commit e6e3673364dedea74baaa7ed43c3fb7a3c397e90
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: only end fully synced chunks
    
llbitmap_cond_end_sync() is called with the sync thread's current sector,
which is an exclusive progress boundary where the chunk containing it can
still be in progress. The old code converted that sector directly to the last
bit, which allowed in-progress chunks to be mistakenly marked as synced. This
patch fixes the issue by rounding the sector down to the nearest chunk
boundary and ending only fully synced chunks.

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index 6ab2188bba279..be580601d5b91 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c

[ ... ]

[Severity: High]
This isn't a bug introduced by this patch, but I noticed a preexisting
issue where the mutually re-arming timer and workqueue are not safely torn
down in llbitmap_destroy().

drivers/md/md-llbitmap.c:llbitmap_destroy() {
...
	timer_delete_sync(&llbitmap->pending_timer);
	flush_workqueue(md_llbitmap_io_wq);
...
}

If md_llbitmap_daemon_fn() is executing concurrently, it finishes its loop,
sets restart to true, and unconditionally re-arms the timer without checking
if the bitmap is being destroyed:

drivers/md/md-llbitmap.c:md_llbitmap_daemon_fn() {
...
	if (restart)
		mod_timer(&llbitmap->pending_timer,
			  jiffies + llbitmap->mddev->bitmap_info.daemon_sleep * HZ);
...
}

Can this lead to a Use-After-Free when flush_workqueue() completes and the
llbitmap struct is freed, leaving an active timer pointing to freed memory?


[Severity: High]
This is a pre-existing issue, but I also noticed missing synchronization for
inflight behind writes before destroying the bitmap.

drivers/md/md-llbitmap.c:llbitmap_destroy() {
	struct llbitmap *llbitmap = mddev->bitmap;
...
}

The destroy function does not wait for inflight behind writes to complete
(atomic_read(&llbitmap->behind_writes) > 0). The caller then sets
mddev->bitmap_ops to NULL.

If an inflight behind write later completes in raid1.c:

drivers/md/raid1.c:close_write() {
...
	if (test_bit(R1BIO_BehindIO, &r1_bio->state))
		mddev->bitmap_ops->end_behind_write(mddev);
...
}

Does this cause a NULL pointer dereference (or UAF) since mddev->bitmap_ops
has been cleared concurrently?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=3

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

* Re: [PATCH v4 01/25] md/md-llbitmap: clear flush state after daemon flush
  2026-08-01 17:24 ` [PATCH v4 01/25] md/md-llbitmap: clear flush state after daemon flush Yu Kuai
@ 2026-08-01 17:43   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:43 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Consuming LLPageFlush in the daemon introduces a race condition that re-arms the timer during array shutdown, leading to a Use-After-Free of the llbitmap struct.
--

--- Patch [1]: [PATCH v4 01/25] md/md-llbitmap: clear flush state after daemon flush ---
commit abba2aa11371c8630f75f9bd27a43851e99339eb
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: clear flush state after daemon flush

This commit clears the LLPageFlush bit in md_llbitmap_daemon_fn() rather
than leaving it set. This prevents previously flushed pages from bypassing
the barrier_idle expiry check on subsequent daemon runs, making new writes
wait for the full idle window.

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index 5a4e2abaa7577..131582724e7e9 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c
> @@ -1066,14 +1066,14 @@ static void md_llbitmap_daemon_fn(struct work_struct *work)
>  
>  	for (idx = 0; idx < llbitmap->nr_pages; idx++) {
>  		struct llbitmap_page_ctl *pctl = llbitmap->pctl[idx];
> +		bool flush = test_and_clear_bit(LLPageFlush, &pctl->flags);
>  
>  		if (idx > 0) {
>  			start = end + 1;
>  			end = min(end + PAGE_SIZE, llbitmap->chunks - 1);
>  		}
>  
> -		if (!test_bit(LLPageFlush, &pctl->flags) &&
> -		    time_before(jiffies, pctl->expire)) {
> +		if (!flush && time_before(jiffies, pctl->expire)) {

[Severity: High]
Could clearing the flush bit here introduce a race condition during array
shutdown that leads to a use-after-free of the llbitmap struct?

If llbitmap/daemon_sleep is configured to a small value, stopping the array
invokes llbitmap_flush(), which sets LLPageFlush and queues the daemon:

llbitmap_flush()
    ...
    queue_work(md_llbitmap_io_wq, &llbitmap->daemon_work);
    flush_work(&llbitmap->daemon_work);

If the daemon is concurrently running, it can consume the newly set
LLPageFlush bits via test_and_clear_bit(). The explicitly queued daemon run
would then see flush == false, evaluate the unexpired pages, set restart =
true, and re-arm the timer.

If disk flushing takes longer than the sleep interval, the timer can fire
and queue the work again.

Later, llbitmap_destroy() calls:

llbitmap_destroy()
    ...
    timer_delete_sync(&llbitmap->pending_timer);
    flush_workqueue(md_llbitmap_io_wq);

Because the timer already fired, timer_delete_sync() does nothing. Then
flush_workqueue() executes the queued daemon work, which incorrectly re-arms
the timer after the deletion point. The struct is then freed, and the timer
subsequently fires on freed memory.

>  			restart = true;
>  			continue;
>  		}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=1

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

* Re: [PATCH v4 09/25] md/md-llbitmap: track bitmap sync_size explicitly
  2026-08-01 17:25 ` [PATCH v4 09/25] md/md-llbitmap: track bitmap sync_size explicitly Yu Kuai
@ 2026-08-01 17:44   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:44 UTC (permalink / raw)
  To: Yu Kuai; +Cc: yukuai, linux-raid

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing bounds check on `sync_size` causes out-of-bounds memory accesses and disk overwrites.
--

--- Patch [9]: [PATCH v4 09/25] md/md-llbitmap: track bitmap sync_size explicitly ---
commit b06a020ec2776bd2bc0418543c90f7ea1a1e754b
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: track bitmap sync_size explicitly

Track llbitmap's own sync_size instead of always using
mddev->resync_max_sectors directly. This is the minimal bookkeeping needed
before llbitmap can track old and new reshape geometry independently.

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index 1fa2adf0ada1c..3417da288e994 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c

[ ... ]

> @@ -987,6 +991,9 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
>  		goto out_put_page;
>  	}
>  
> +	sync_size = le64_to_cpu(sb->sync_size);
> +	if (!sync_size)
> +		sync_size = mddev->resync_max_sectors;

[Severity: High]
Does this code bypass capacity validation if the superblock is corrupted or
tampered?

If an array is assembled with a bitmap superblock where sb->sync_size is
artificially large or small, this untrusted sync_size is read here.

However, it appears the bounds check later in llbitmap_read_sb() still
validates against mddev->resync_max_sectors instead of the new sync_size:

	if (chunksize < DIV_ROUND_UP_SECTOR_T(mddev->resync_max_sectors,
					      mddev->bitmap_info.space << SECTOR_SHIFT)) {

Could this allow maliciously large or small chunk requirements to bypass
capacity validation?

>  	chunksize = le32_to_cpu(sb->chunksize);
>  	if (!is_power_of_2(chunksize)) {
>  		pr_err("md/llbitmap: %s: chunksize not a power of 2",

[ ... ]

> @@ -1022,8 +1029,9 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
>  
>  	llbitmap->barrier_idle = DEFAULT_BARRIER_IDLE;
>  	llbitmap->chunksize = chunksize;
> -	llbitmap->chunks = DIV_ROUND_UP_SECTOR_T(mddev->resync_max_sectors, chunksize);
> +	llbitmap->chunks = DIV_ROUND_UP_SECTOR_T(sync_size, chunksize);

[Severity: High]
Is it safe to compute llbitmap->chunks using the unvalidated sync_size here?

Since the validation check above still uses mddev->resync_max_sectors, an
artificially large sync_size will cause llbitmap->chunks to exceed the
allocated metadata space. This breaks the capacity validation invariant.

Could this cause __llbitmap_flush() and llbitmap_write_page() to write bitmap
data out-of-bounds on the underlying disk, overwriting adjacent partitions?

Additionally, if sync_size is artificially small, the llbitmap->pctl array
allocation (nr_pages) will be undersized. When IO arrives at valid offsets
bounded by the block layer capacity, fast paths like llbitmap_start_write()
could calculate a page_start index that exceeds nr_pages:

drivers/md/md-llbitmap.c:llbitmap_start_write() {
    ...
	int page_start = (start + BITMAP_DATA_OFFSET) >> PAGE_SHIFT;
	int page_end = (end + BITMAP_DATA_OFFSET) >> PAGE_SHIFT;

	while (page_start <= page_end) {
		llbitmap_raise_barrier(llbitmap, page_start);
		page_start++;
	}
    ...
}

Could this lead to an out-of-bounds memory access on the pctl array when
calling llbitmap_raise_barrier()?

>  	llbitmap->chunkshift = ffz(~chunksize);
> +	llbitmap->sync_size = sync_size;
>  	ret = llbitmap_cache_pages(llbitmap);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=9

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

* Re: [PATCH v4 04/25] md/raid5: reject zero-sector reshape chunks
  2026-08-01 17:24 ` [PATCH v4 04/25] md/raid5: reject zero-sector reshape chunks Yu Kuai
@ 2026-08-01 17:45   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:45 UTC (permalink / raw)
  To: Yu Kuai; +Cc: yukuai, linux-raid

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Writing large unsigned values to the `chunk_size` or `layout` sysfs attributes causes integer truncation to negative values, completely bypassing validation and leading to arbitrary geometry corruption or kernel BUGs.
--

--- Patch [4]: [PATCH v4 04/25] md/raid5: reject zero-sector reshape chunks ---
commit 6759d75e360239d90630b8d047daa1d3cb528597
Author: Yu Kuai <yukuai@fygo.io>

md/raid5: reject zero-sector reshape chunks

Sashiko reported that RAID5 can accept a reshape chunk size that becomes
zero sectors. chunk_size_store() stores the sysfs byte value as n >> 9, so
writing a value below 512 bytes sets mddev->new_chunk_sectors to zero.

> diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
> index 0c5c9fb0606ee..4276b125f94cb 100644
> --- a/drivers/md/raid5.c
> +++ b/drivers/md/raid5.c
> @@ -8479,6 +8479,8 @@ static int check_reshape(struct mddev *mddev)
>  		return 0; /* nothing to do */
>  	if (has_failed(conf))
>  		return -EINVAL;
> +	if (!mddev->new_chunk_sectors)
> +		return -EINVAL;

[Severity: High]
This is a pre-existing issue, but does this check adequately protect against
negative values?

In drivers/md/md.c:chunk_size_store(), user-supplied unsigned values are
shifted and truncated into the signed int field new_chunk_sectors without
overflow checks:

    mddev->new_chunk_sectors = n >> 9;

If a large value is written, new_chunk_sectors can become negative. This
would bypass the !mddev->new_chunk_sectors check introduced here.

Additionally, in raid5_check_reshape(), validation is skipped for negative
values:

    if (new_chunk > 0) {
        ...
    }

Would it be better for this check to reject all values <= 0, to prevent
negative chunk sizes from corrupting I/O geometry?

A similar truncation issue exists for new_layout in
drivers/md/md.c:layout_store(). A negative layout value bypasses validation
in raid5_check_reshape() and eventually triggers a BUG() in
raid5_compute_sector() if it hits the default case.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=4

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

* Re: [PATCH v4 15/25] md/md-llbitmap: add reshape range mapping helpers
  2026-08-01 17:25 ` [PATCH v4 15/25] md/md-llbitmap: add reshape range mapping helpers Yu Kuai
@ 2026-08-01 17:47   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:47 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] Missing post-mapping clamp in llbitmap_map_layout causes out-of-bounds array access.
- [High] Missing RAID5 personality callbacks for exact bitmap mapping cause array-sector I/Os to be improperly clamped and silently dropped.
- [High] Bios are not split at the reshape boundary before bitmap processing, causing I/Os to span the boundary and corrupt sync state.

Pre-existing issues:
- [High] Timer teardown race in llbitmap_destroy allows the daemon workqueue to re-arm the timer on a freed structure.
--

--- Patch [15]: [PATCH v4 15/25] md/md-llbitmap: add reshape range mapping helpers ---
commit 9c9574c9c91f5e10866a7c4d06542de767878cc2
Author: Yu Kuai <yukuai@fygo.io>
Subject: md/md-llbitmap: add reshape range mapping helpers

This patch updates llbitmap to choose between old and new geometry during a
reshape and to encode exact bitmap ranges. It also distinguishes writes from
discards for range preparation and clamps pre-map ranges to array-sector
limits.

Link: https://lore.kernel.org/all/20260726185916.2223460-1-mykola@meshstor.io/

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c
[ ... ]
> +static sector_t llbitmap_logical_size(struct llbitmap *llbitmap, bool previous)
> +{
> +	struct mddev *mddev = llbitmap->mddev;
> +
> +	if (!mddev->private || !mddev->pers ||
> +	    !mddev->pers->bitmap_array_sectors)
> +		return llbitmap_personality_sync_size(llbitmap, previous);
> +	return mddev->pers->bitmap_array_sectors(mddev, previous);
> +}

[Severity: High]
Does this fallback to llbitmap_personality_sync_size() hide the fact that
the raid5 personality doesn't implement bitmap_array_sectors or
bitmap_sector_map?

If an I/O is issued beyond the component size (for instance, past 1/N of
the array size), won't the pre-map clamp in llbitmap_map_layout() see
start >= limit and force sectors = 0? 

This would silently drop the bitmap update, leading to permanent data
corruption if the system crashes and fails to resync those unrecorded
regions.

[ ... ]
> +static void llbitmap_map_layout(struct llbitmap *llbitmap, sector_t *offset,
> +				unsigned long *sectors, bool previous)
> +{
[ ... ]
> +	if (llbitmap->mddev->pers->bitmap_sector_map)
> +		llbitmap->mddev->pers->bitmap_sector_map(llbitmap->mddev, offset,
> +							 sectors, previous);
> +	else if (!previous && llbitmap->mddev->pers->bitmap_sector)
> +		llbitmap->mddev->pers->bitmap_sector(llbitmap->mddev, offset,
> +							 sectors);
> +}

[Severity: High]
Is there a missing post-mapping clamp against the component sync_size here?

When a write near the end of the array is passed to llbitmap_start_write(),
the pre-map clamp correctly limits offset and sectors to the array size.
However, bitmap_sector() translates only the offset to component space
without scaling down the sectors value.

If offset + sectors exceeds the component size, couldn't this cause the
calculated page_end index in llbitmap_start_write() to exceed
llbitmap->nr_pages? This would result in an out-of-bounds read beyond the
llbitmap->pctl pointer array and memory corruption when accessing the active
percpu_ref.

[ ... ]
> @@ -1394,11 +1475,35 @@ static void llbitmap_destroy(struct mddev *mddev)
[ ... ]
>  	mutex_unlock(&mddev->bitmap_info.mutex);
>  }

[Severity: High]
This is a pre-existing issue, but does this teardown sequence have a
use-after-free race with the daemon workqueue?

If the timer fires right before timer_delete_sync(), it queues daemon_work.
flush_workqueue() then blocks until md_llbitmap_daemon_fn() finishes.
However, md_llbitmap_daemon_fn() can unconditionally re-arm the timer:

	if (restart)
		mod_timer(&llbitmap->pending_timer, ...);

If the timer is re-armed during the flush, won't it fire again after
kfree(llbitmap) executes, dereferencing the freed structure?

> +static bool llbitmap_map_previous(struct llbitmap *llbitmap, sector_t offset,
> +				  unsigned long sectors)
> +{
> +	struct mddev *mddev = llbitmap->mddev;
> +	sector_t boundary = READ_ONCE(mddev->reshape_position);
> +
> +	if (boundary == MaxSector)
> +		return false;
> +
> +	WARN_ON_ONCE(sectors && offset < boundary && offset + sectors > boundary);

[Severity: High]
Can bios span across the reshape boundary and trigger this warning?

It appears mddev_bio_split_at_reshape_offset() is exported in md.c but is
never called before md_account_bio(). Because the bio isn't split,
md_bitmap_start() will receive the original, un-split bio from the block
layer.

If a spanning bio triggers this warning, llbitmap_map_previous() forces the
entire bio to be mapped to a single geometry. Won't this fail to mark the
necessary bits in the other geometry and corrupt the sync state?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=15

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

* Re: [PATCH v4 10/25] md/md-llbitmap: allocate page controls independently
  2026-08-01 17:25 ` [PATCH v4 10/25] md/md-llbitmap: allocate page controls independently Yu Kuai
@ 2026-08-01 17:47   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:47 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [Critical] Unflushed zeroed pages create a memory-disk inconsistency, leading to silent parity corruption on crash/reboot after an array resize.

Pre-existing issues:
- [High] Incomplete fix for out-of-bounds array access on `pctl` during maximum in-place array growth.
--

--- Patch [10]: [PATCH v4 10/25] md/md-llbitmap: allocate page controls independently ---
commit 325d92bcc68f880073f4c6b96714394c0bafe835
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: allocate page controls independently

This patch allocates one llbitmap page-control object at a time and frees
each object through the same model. It also updates llbitmap_read_page() to
return a zeroed page without reading from disk when the page index is beyond
the current bitmap size.

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index 3417da288e994..d12b3dba6ae15 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c
[ ... ]
> @@ -526,6 +532,8 @@ static struct page *llbitmap_read_page(struct llbitmap *llbitmap, int idx)
>  	page = alloc_page(GFP_NOIO | __GFP_ZERO);
>  	if (!page)
>  		return ERR_PTR(-ENOMEM);
> +	if (idx >= llbitmap_used_pages(llbitmap, llbitmap->chunks))
> +		return page;

[Severity: Critical]
Does this new bypass create a memory-disk inconsistency?

By returning a zeroed page in memory without reading or flushing to disk, the
physical disk is left containing uninitialized garbage. If the array is
reshaped and expands into this pre-allocated region, a crash before the new
region is fully written could expose the disk garbage to the state machine on
the subsequent boot. If the garbage matches states like BitClean, resync
might be incorrectly skipped for the new region.

>  
>  	rdev_for_each(rdev, mddev) {
[ ... ]
> +static unsigned int llbitmap_reserved_pages(struct llbitmap *llbitmap)
> +{
> +	return DIV_ROUND_UP(llbitmap->mddev->bitmap_info.space << SECTOR_SHIFT,
> +			    PAGE_SIZE);
> +}

[Severity: High]
This isn't a bug introduced by this patch, but does this calculation
under-allocate the array by one page for the maximum allowed chunk size?

This calculation omits BITMAP_DATA_OFFSET. Because llbitmap_resize() allows
chunks to grow up to space << SECTOR_SHIFT, the maximum used_pages (which
correctly includes the offset) can exceed reserved_pages by 1.

When pos approaches the maximum allowed chunks in llbitmap_write():

    pos += BITMAP_DATA_OFFSET;
    idx = pos >> PAGE_SHIFT;

the offset causes idx to exceed the pre-allocated bounds of the pctl array,
which could lead to out-of-bounds memory corruption.

> +
> +static int llbitmap_alloc_pages(struct llbitmap *llbitmap)
> +{
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=10

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

* Re: [PATCH v4 14/25] md/md-llbitmap: refuse reshape while llbitmap still needs sync
  2026-08-01 17:25 ` [PATCH v4 14/25] md/md-llbitmap: refuse reshape while llbitmap still needs sync Yu Kuai
@ 2026-08-01 17:50   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:50 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [Medium] The newly added `llbitmap_reshape_can_start` callback is never executed because there are no callers for `mddev->bitmap_ops->reshape_can_start` anywhere in the md codebase.

Pre-existing issues:
- [High] Use-After-Free in the `llbitmap_create` error path where `mddev->bitmap` points to freed memory without holding the lock.
--

--- Patch [14]: [PATCH v4 14/25] md/md-llbitmap: refuse reshape while llbitmap still needs sync ---
commit a811c308c426ffe3f23803b0888eecbb420a8f6d
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: refuse reshape while llbitmap still needs sync

Reject reshape when llbitmap still contains NeedSync or Syncing bits. This
keeps reshape from starting until the current llbitmap state has been
reconciled.

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index f41f9ba2063b5..3b121b372cd8d 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c
[ ... ]
> @@ -2025,6 +2048,7 @@ static struct bitmap_operations llbitmap_ops = {
>  	.dirty_bits		= llbitmap_dirty_bits,
>  	.prepare_range		= llbitmap_prepare_range,
>  	.reshape_finish		= llbitmap_reshape_finish,
> +	.reshape_can_start	= llbitmap_reshape_can_start,
>  	.write_all		= llbitmap_write_all,

[Severity: Medium]
Is this callback executed by the md core?

Looking at the md codebase, there do not appear to be any callers for
mddev->bitmap_ops->reshape_can_start. Does this mean the validation logic
is bypassed and reshapes could still start prematurely?

[Severity: High]
This is a pre-existing issue, but can a concurrent reader access freed memory
during bitmap creation?

In llbitmap_create(), if an error occurs, the mutex is dropped before
mddev->bitmap is set to NULL:

    mutex_unlock(&mddev->bitmap_info.mutex);
    if (ret) {
        kfree(llbitmap);
        mddev->bitmap = NULL;
    }

A concurrent /proc/mdstat reader could acquire the mutex after it is dropped
but before mddev->bitmap is cleared, allowing it to dereference the freed
llbitmap pointer:

drivers/md/md.c:md_seq_show() {
    ...
    /* prevent bitmap to be freed after checking */
    mutex_lock(&mddev->bitmap_info.mutex);
    ...
        md_bitmap_status(seq, mddev);
    ...
}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=14

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

* Re: [PATCH v4 12/25] md/md-llbitmap: track target reshape geometry fields
  2026-08-01 17:25 ` [PATCH v4 12/25] md/md-llbitmap: track target reshape geometry fields Yu Kuai
@ 2026-08-01 17:51   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:51 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] Target reshape geometry is silently discarded across reboots due to NULL `mddev->pers` during array assembly.
- [High] Reshape geometry tracking fails completely because target personality callbacks are unimplemented.

Pre-existing issues:
- [Critical] Expanded array regions suffer silent data loss during I/O due to mismatched chunkshift indexing.
- [Critical] Use-After-Free (UAF) in timer callback during subsystem teardown due to inadequate synchronization.
--

--- Patch [12]: [PATCH v4 12/25] md/md-llbitmap: track target reshape geometry fields ---
commit fff2be87a121aeeb8a4cb9f28329398a1b07d67a
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: track target reshape geometry fields

Track llbitmap bookkeeping for the target reshape geometry while keeping
a single live bitmap instance. Add the reshape geometry fields, refresh
helper, and update the load and resize paths to keep the target geometry
in sync.

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index cf522fae78f8a..37d1312e3eab9 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c

[ ... ]

> @@ -430,6 +433,39 @@ static void llbitmap_calculate_chunks(struct mddev *mddev, sector_t blocks,
>  	}
>  }
>  
> +static bool llbitmap_reshaping(struct llbitmap *llbitmap)
> +{
> +	return llbitmap->mddev->reshape_position != MaxSector;
> +}
> +
> +static sector_t llbitmap_personality_sync_size(struct llbitmap *llbitmap,
> +					       bool previous)
> +{
> +	struct mddev *mddev = llbitmap->mddev;
> +
> +	if (!llbitmap_reshaping(llbitmap) || !mddev->private || !mddev->pers ||
> +	    !mddev->pers->bitmap_sync_size)
> +		return llbitmap->sync_size;

[Severity: High]
Does this code handle array assembly correctly across reboots?

During array assembly, md_run() invokes md_bitmap_create() to load the
bitmap before explicitly assigning mddev->pers. Because mddev->pers is still
NULL during this bitmap loading phase, this check will unconditionally
hit the fallback path and return the old pre-reshape llbitmap->sync_size.

Could this cause the target reshape geometry to be completely lost if the
system reboots during a reshape, leading to inconsistent tracking?

[Severity: High]
Are there any RAID personality drivers that actually implement the new
bitmap_sync_size callback?

It appears these callbacks are not yet implemented in any RAID personality
drivers (e.g., raid5, raid1). Consequently, the check for
!mddev->pers->bitmap_sync_size will always evaluate to true, forcing a silent
fallback to the old geometry sizes.

Will this render the newly introduced reshape tracking mechanisms unable
to track the target geometry?

> +	return mddev->pers->bitmap_sync_size(mddev, previous);
> +}
> +
> +static void llbitmap_refresh_reshape(struct llbitmap *llbitmap)
> +{

[ ... ]

> @@ -1289,6 +1327,9 @@ static int llbitmap_resize(struct mddev *mddev, sector_t blocks, int chunksize)
>  		goto out;
>  
>  	if (reshape) {
> +		llbitmap->reshape_sync_size = blocks;
> +		llbitmap->reshape_chunksize = bitmap_chunksize;
> +		llbitmap->reshape_chunks = chunks;
>  		llbitmap->chunks = max(old_chunks, chunks);

[Severity: High]
This is a pre-existing issue, but does this code scale the chunkshift to match
the updated chunksize?

When a reshape increases array capacity beyond the bitmap's physical limits,
bitmap_chunksize is scaled up. The code updates llbitmap->chunks but seems
to miss updating llbitmap->chunkshift to match the new chunk size.

Later, during I/O operations, llbitmap_start_write() calculates the chunk
index using the stale, smaller chunkshift:

    unsigned long start = offset >> llbitmap->chunkshift;

This produces artificially large chunk indices for data in the expanded region,
which llbitmap_state_machine() then detects as exceeding llbitmap->chunks and
silently drops:

    if (start >= llbitmap->chunks)
        return BitNone;

Could this lead to silent data corruption if writes to the expanded capacity
are never tracked in the bitmap?

>  	} else {
>  		if (blocks < old_blocks && chunks < old_chunks)

[ ... ]

[Severity: High]
This is a pre-existing issue, but is there adequate synchronization when
tearing down the bitmap?

During array destruction, llbitmap_destroy() initiates teardown by deleting
the timer and flushing the workqueue:

    timer_delete_sync(&llbitmap->pending_timer);
    flush_workqueue(md_llbitmap_io_wq);

However, the function doesn't seem to set any shutdown flag. If the background
work md_llbitmap_daemon_fn() is executing or queued when flush_workqueue()
is called, it runs to completion and unconditionally re-arms the timer:

    mod_timer(&llbitmap->pending_timer, ...);

Once the workqueue is flushed, llbitmap_destroy() proceeds to free the
llbitmap structure. The newly re-armed timer can subsequently fire and attempt
to access the freed memory inside llbitmap_pending_timer_fn().

Could this sequence result in a use-after-free?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=12

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

* Re: [PATCH v4 18/25] md/md-llbitmap: clamp state-machine walks to tracked bits
  2026-08-01 17:25 ` [PATCH v4 18/25] md/md-llbitmap: clamp state-machine walks to tracked bits Yu Kuai
@ 2026-08-01 17:55   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:55 UTC (permalink / raw)
  To: Yu Kuai; +Cc: yukuai, linux-raid

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [Critical] TOCTOU race in llbitmap_state_machine() allows chunk state to be blindly overwritten by concurrent actions, leading to data corruption.
--

--- Patch [18]: [PATCH v4 18/25] md/md-llbitmap: clamp state-machine walks to tracked bits ---
commit 5929c29d9f90d552df02364e1245faa32b6df618
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: clamp state-machine walks to tracked bits

This commit addresses an issue where llbitmap_state_machine() can be called
with an end bit beyond the tracked chunks. It clamps the start and end values
to ensure the state machine does not walk past the tracked bitmap boundaries.

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c
> @@ -1012,7 +1012,10 @@ static enum llbitmap_state llbitmap_state_machine(struct llbitmap *llbitmap,
>  		llbitmap_init_state(llbitmap);
>  		return BitNone;
>  	}
> -
> +	if (start >= llbitmap->chunks)
> +		return BitNone;
> +	if (end >= llbitmap->chunks)
> +		end = llbitmap->chunks - 1;
>  	while (start <= end) {
>  		enum llbitmap_state c = llbitmap_read(llbitmap, start);

[Severity: Critical]
This is a pre-existing issue, but does this lockless read-modify-write cycle
allow concurrent updates to overwrite each other?

When llbitmap_state_machine() calculates the new state and eventually calls
llbitmap_write(), it blindly overwrites the state without an atomic cmpxchg
loop or per-chunk locking.

Could this cause data corruption during concurrent execution? For example, if
llbitmap_start_sync() is running lockless with BitmapActionClearUnwritten
across all chunks, and llbitmap_start_write() concurrently processes the same
chunk with BitmapActionStartwrite:

Thread 1 (Sync):
    c = llbitmap_read(llbitmap, start);
    // c is BitCleanUnwritten
    state = state_machine[c][BitmapActionClearUnwritten];
    // state becomes BitUnwritten

Thread 2 (Write):
    c = llbitmap_read(llbitmap, start);
    // c is BitCleanUnwritten
    state = state_machine[c][BitmapActionStartwrite];
    // state becomes BitNeedSync
    llbitmap_write(llbitmap, state, start);

Thread 1 (Sync):
    llbitmap_write(llbitmap, state, start);
    // blindly overwrites the written chunk with BitUnwritten

This would leave the actively written chunk marked as BitUnwritten, causing
llbitmap_skip_sync_blocks() to skip syncing this chunk and leaving stale
parity on the recovering drive.

>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=18

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

* Re: [PATCH v4 13/25] md/md-llbitmap: finish reshape geometry
  2026-08-01 17:25 ` [PATCH v4 13/25] md/md-llbitmap: finish reshape geometry Yu Kuai
@ 2026-08-01 17:59   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:59 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [High] The `llbitmap_reshape_finish` callback is dead code and misses API integration, which would cause a sleep-in-atomic panic if properly integrated.

Pre-existing issues:
- [Critical] The pending timer can be re-armed during array teardown after `timer_delete_sync()`, leading to a Use-After-Free when the timer fires on the freed `llbitmap`.
--

--- Patch [13]: [PATCH v4 13/25] md/md-llbitmap: finish reshape geometry ---
commit 726da375aa50cd87c4a667a63fcb2d5924d50556
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: finish reshape geometry

Commit the staged llbitmap geometry when reshape finishes. When assembling a
stopped reshape, md_run() creates the bitmap before publishing mddev->pers.
Refresh the staged reshape geometry again from llbitmap_load().

Link: https://lore.kernel.org/all/20260726185916.2223460-1-mykola@meshstor.io/

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index 37d1312e3eab9..f41f9ba2063b5 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c

[ ... ]

> @@ -1694,6 +1703,30 @@ static void llbitmap_dirty_bits(struct mddev *mddev, unsigned long s,
>  	llbitmap_state_machine(mddev->bitmap, s, e, BitmapActionStartwrite);
>  }
>  
> +static void llbitmap_reshape_finish(struct mddev *mddev)
> +{
> +	struct llbitmap *llbitmap = mddev->bitmap;
> +
> +	if (mddev->pers->quiesce)
> +		mddev->pers->quiesce(mddev, 1);
> +
> +	mutex_lock(&mddev->bitmap_info.mutex);

[Severity: High]
Is this callback actually invoked anywhere by the MD core?

Looking at the MD core code, it seems md_finish_reshape() doesn't call
bitmap_ops->reshape_finish(). This means the staged geometry remains
uncommitted.

Additionally, if it were to be called from md_finish_reshape(), wouldn't
this cause a sleep-in-atomic panic? The callers of md_finish_reshape() hold
a spinlock, but this function acquires a mutex and performs a sleeping wait
via mddev->pers->quiesce().

> +	llbitmap_flush(mddev);
> +
> +	llbitmap->chunksize = llbitmap->reshape_chunksize;
> +	llbitmap->chunkshift = ffz(~llbitmap->chunksize);
> +	llbitmap->chunks = llbitmap->reshape_chunks;
> +	llbitmap->sync_size = llbitmap->reshape_sync_size;
> +	llbitmap_refresh_reshape(llbitmap);
> +	mddev->bitmap_info.chunksize = llbitmap->chunksize;
> +	llbitmap_update_sb(llbitmap);
> +	__llbitmap_flush(mddev);
> +	mutex_unlock(&mddev->bitmap_info.mutex);
> +
> +	if (mddev->pers->quiesce)
> +		mddev->pers->quiesce(mddev, 0);
> +}
> +

[ ... ]

> @@ -1991,6 +2024,7 @@ static struct bitmap_operations llbitmap_ops = {
>  	.get_stats		= llbitmap_get_stats,
>  	.dirty_bits		= llbitmap_dirty_bits,
>  	.prepare_range		= llbitmap_prepare_range,
> +	.reshape_finish		= llbitmap_reshape_finish,
>  	.write_all		= llbitmap_write_all,
>  
>  	.groups			= md_llbitmap_groups,

[Severity: Critical]
This is a pre-existing issue, but is there a use-after-free risk during
array teardown in llbitmap_destroy()?

Looking at llbitmap_destroy(), the pending timer is deleted before flushing
the workqueue:

    timer_delete_sync(&llbitmap->pending_timer);
    flush_workqueue(md_llbitmap_io_wq);

If the array is being destroyed on an error path where BITMAP_WRITE_ERROR is
set, __md_stop_writes() skips calling mddev->bitmap_ops->flush(mddev), so
the LLPageFlush bit is never set.

When the workqueue executes md_llbitmap_daemon_fn(), it can encounter
unexpired dirty pages without the flush bit, set restart = true, and call
mod_timer().

Could this re-arm the timer right before the llbitmap is freed, allowing
the timer to fire on the freed object?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=13

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

* Re: [PATCH v4 21/25] md/raid10: split reshape bios before bitmap accounting
  2026-08-01 17:25 ` [PATCH v4 21/25] md/raid10: split reshape bios before bitmap accounting Yu Kuai
@ 2026-08-01 17:59   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 17:59 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Nested double allocation from the same `conf->bio_split` mempool can cause a self-deadlock when a bio crosses a chunk or reshape boundary and encounters a bad block.
- [Low] Passing 'int *' to 'unsigned int *' triggers a compiler error with -Werror=incompatible-pointer-types.
--

--- Patch [21]: [PATCH v4 21/25] md/raid10: split reshape bios before bitmap accounting ---
commit ac17607a8057c3c9040bf0443e50aa65062e0d48
Author: Yu Kuai <yukuai@fygo.io>

md/raid10: split reshape bios before bitmap accounting

This commit updates raid10 to submit only one-side bios to llbitmap
during reshape. It accomplishes this by using the shared helper
mddev_bio_split_at_reshape_offset() to split bios at reshape boundaries.

> diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
> index e50e4adee389f..61900ace190fd 100644
> --- a/drivers/md/raid10.c
> +++ b/drivers/md/raid10.c
> @@ -1886,6 +1886,7 @@ static bool raid10_make_request(struct mddev *mddev, struct bio *bio)
>  {
>  	struct r10conf *conf = mddev->private;
>  	sector_t chunk_mask = (conf->geo.chunk_mask & conf->prev.chunk_mask);
> +	const int rw = bio_data_dir(bio);
>  	int chunk_sects = chunk_mask + 1;
>  	int sectors = bio_sectors(bio);
>  
[ ... ]
> @@ -1911,6 +1912,15 @@ static bool raid10_make_request(struct mddev *mddev, struct bio *bio)
>  		sectors = chunk_sects -
>  			(bio->bi_iter.bi_sector &
>  			 (chunk_sects - 1));
> +
> +	bio = mddev_bio_split_at_reshape_offset(mddev, bio, &sectors,
> +						&conf->bio_split);

[Severity: Low]
Will this cause a build failure with -Werror=incompatible-pointer-types?

The sectors variable is declared as an int, but
mddev_bio_split_at_reshape_offset() expects an unsigned int pointer for
its third argument.

[Severity: High]
Could this introduce a mempool self-deadlock when encountering a bad block?

If the bio crosses a chunk or reshape boundary, it allocates a split bio from
conf->bio_split here. This newly allocated bio is then passed down the stack:

raid10_make_request() -> __make_request() -> raid10_write_request()

If a partial bad block is encountered in raid10_write_request():

drivers/md/raid10.c:raid10_write_request() {
    ...
    if (r10_bio->sectors < bio_sectors(bio)) {
        ...
        bio = bio_submit_split_bioset(bio, r10_bio->sectors, &conf->bio_split);
    ...
}

It attempts to split the bio a second time using the exact same
conf->bio_split mempool. Because the original bio is already holding an
element from conf->bio_split, nested allocations from the same pool by
concurrent threads can exhaust it and cause the I/O threads to hang.

> +	if (!bio) {
> +		if (rw == WRITE)
> +			md_write_end(mddev);
> +		return true;
> +	}
> +
>  	if (!__make_request(mddev, bio, sectors))
>  		md_write_end(mddev);
>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=21

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

* Re: [PATCH v4 11/25] md/md-llbitmap: grow the page cache in place for reshape
  2026-08-01 17:25 ` [PATCH v4 11/25] md/md-llbitmap: grow the page cache in place for reshape Yu Kuai
@ 2026-08-01 18:03   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 18:03 UTC (permalink / raw)
  To: Yu Kuai; +Cc: yukuai, linux-raid

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] Deadlock during memory allocation under quiesced MD array state
- [High] Incomplete reshape logic fails to update chunksize, causing out-of-bounds array access
- [High] Non-atomic replacement of `llbitmap->pctl` exposes concurrent lockless readers to use-after-free

Pre-existing issues:
- [Critical] Bitmap sizing logic omits superblock offset, causing out-of-bounds on-disk write
--

--- Patch [11]: [PATCH v4 11/25] md/md-llbitmap: grow the page cache in place for reshape ---
commit 4834d913bd6249e72f12baf6160c3e9e149b6525
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: grow the page cache in place for reshape

Use the page-control helpers to grow llbitmap's cached pages in place
for resize and later reshape preparation, instead of rebuilding the
whole cache.

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index d12b3dba6ae15..cf522fae78f8a 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c
[ ... ]
> @@ -416,6 +416,19 @@ static char state_machine[BitStateCount][BitmapActionCount] = {
>  };
>  
>  static void __llbitmap_flush(struct mddev *mddev);
> +static void llbitmap_flush(struct mddev *mddev);
> +static void llbitmap_update_sb(void *data);
> +
> +static void llbitmap_calculate_chunks(struct mddev *mddev, sector_t blocks,
> +				      unsigned long *chunksize,
> +				      unsigned long *chunks)
> +{
> +	*chunks = DIV_ROUND_UP_SECTOR_T(blocks, *chunksize);
> +	while (*chunks > mddev->bitmap_info.space << SECTOR_SHIFT) {

[Severity: Critical]
This isn't a bug introduced by this patch, but does this space validation
check omit the BITMAP_DATA_OFFSET?

Since the bitmap format includes a superblock at the start, it consumes
BITMAP_DATA_OFFSET bytes (typically 1024). If a geometry requires payload
bytes closely fitting the reserved space, the total footprint could exceed
the reserved sectors.

During a flush, could the bitmap write its pages to disk past the end of
the reserved space, potentially overwriting adjacent metadata or user data?

> +		*chunksize = *chunksize << 1;
> +		*chunks = DIV_ROUND_UP_SECTOR_T(blocks, *chunksize);
> +	}
> +}
[ ... ]
> @@ -655,6 +668,48 @@ static unsigned int llbitmap_reserved_pages(struct llbitmap *llbitmap)
>  			    PAGE_SIZE);
>  }
>  
> +static int llbitmap_expand_pages(struct llbitmap *llbitmap,
> +				 unsigned long chunks)
> +{
> +	struct llbitmap_page_ctl **pctl;
> +	unsigned int old_nr_pages = llbitmap->nr_pages;
> +	unsigned int nr_pages = llbitmap_used_pages(llbitmap, chunks);
> +	unsigned int i;
> +	int ret;
> +
> +	if (nr_pages <= old_nr_pages)
> +		return 0;
> +
> +	pctl = kcalloc(nr_pages, sizeof(*pctl), GFP_KERNEL);

[Severity: High]
Can this allocation cause a deadlock under memory pressure?

The GFP_KERNEL flag permits direct memory reclaim. If the VM attempts to
write dirty filesystem pages back to the block device to free memory, the
writeback could be directed at the MD array.

Since llbitmap_resize() suspends the array using
mddev->pers->quiesce(mddev, 1) before calling this function:

drivers/md/md-llbitmap.c:llbitmap_resize() {
    ...
    if (mddev->pers->quiesce) {
        mddev->pers->quiesce(mddev, 1);
    ...
    ret = llbitmap_prepare_resize(...)
}

Will the I/O block indefinitely waiting for the array to unquiesce,
deadlocking the thread that is holding the quiesce state?

> +	if (!pctl)
> +		return -ENOMEM;
> +
> +	if (llbitmap->pctl)
> +		memcpy(pctl, llbitmap->pctl,
> +		       array_size(old_nr_pages, sizeof(*pctl)));
> +
> +	for (i = old_nr_pages; i < nr_pages; i++) {
> +		pctl[i] = llbitmap_alloc_page_ctl(llbitmap, i);
> +		if (IS_ERR(pctl[i]))
> +			goto err_alloc_ptr;
> +	}
> +
> +	kfree(llbitmap->pctl);
> +	llbitmap->pctl = pctl;

[Severity: High]
Does freeing the old pointer array non-atomically without RCU protection
expose concurrent lockless readers to a use-after-free?

Although the array is quiesced here, background tasks such as
llbitmap_unplug_fn() (queued by blk_finish_plug() for tasks that submitted
I/O before the quiesce) might still execute concurrently.

If a lockless reader is iterating over llbitmap->nr_pages and reading from
llbitmap->pctl[i], could it be preempted while holding the old pointer and
dereference freed memory when this code executes?

> +	llbitmap->nr_pages = nr_pages;
> +	return 0;
[ ... ]
> @@ -1171,24 +1254,62 @@ static int llbitmap_create(struct mddev *mddev)
>  static int llbitmap_resize(struct mddev *mddev, sector_t blocks, int chunksize)
>  {
[ ... ]
> +	mutex_lock(&mddev->bitmap_info.mutex);
> +	cache_chunks = reshape ? max(old_chunks, chunks) : chunks;
> +	ret = llbitmap_prepare_resize(llbitmap, old_chunks, chunks, cache_chunks);
> +	if (ret)
> +		goto out;
> +
> +	if (reshape) {
> +		llbitmap->chunks = max(old_chunks, chunks);
> +	} else {

[Severity: High]
Is the reshape logic missing updates to the chunksize and chunkshift
variables?

When reshaping an array to a larger size, the required bitmap chunksize
may increase. While llbitmap->chunks is updated here, llbitmap->chunksize
and llbitmap->chunkshift appear to be left unchanged.

When new I/O arrives, llbitmap_start_write() would calculate the chunk
index using the old, smaller chunkshift.

Could this produce a required chunk index that exceeds the newly allocated
llbitmap->chunks limit, leading to an out-of-bounds access when writing the
state in llbitmap_write():

drivers/md/md-llbitmap.c:llbitmap_write() {
    ...
    idx = pos >> PAGE_SHIFT;
    bit = offset_in_page(pos);
    llbitmap->pctl[idx]->state[bit] = state;
    ...
}

> +		if (blocks < old_blocks && chunks < old_chunks)
> +			llbitmap_mark_range(llbitmap, chunks, old_chunks - 1,
> +					    BitUnwritten);
> +		mddev->bitmap_info.chunksize = bitmap_chunksize;
> +		llbitmap->chunks = chunks;
> +		llbitmap->sync_size = blocks;
> +		llbitmap_update_sb(llbitmap);
> +	}
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=11

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

* Re: [PATCH v4 07/25] md: skip bitmap accounting for empty write ranges
  2026-08-01 17:25 ` [PATCH v4 07/25] md: skip bitmap accounting for empty write ranges Yu Kuai
@ 2026-08-01 18:05   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 18:05 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Uninitialized `start_time` leads to corrupted IO accounting, and using `0` as a sentinel causes in-flight IO leaks when `jiffies` wraps to 0.
--

--- Patch [7]: [PATCH v4 07/25] md: skip bitmap accounting for empty write ranges ---
commit b61151db244d77a3d38d7fc791d0c00d7e6a2723
Author: Yu Kuai <yukuai@fygo.io>

md: skip bitmap accounting for empty write ranges

This commit prevents zero-sector flush/FUA bios from dirtying bitmap bits.
It skips bitmap accounting for empty write ranges by verifying the bio has
sectors in both md_bitmap_start() and md_clone_bio().

> diff --git a/drivers/md/md.c b/drivers/md/md.c
> index 3b20a57b8c7ed..0b59c676f7c03 100644
> --- a/drivers/md/md.c
> +++ b/drivers/md/md.c
[ ... ]
> @@ -9423,10 +9426,12 @@ static void md_clone_bio(struct mddev *mddev, struct bio **bio)
>  	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);

[Severity: High]
This is a pre-existing issue, but does md_clone_bio() leave start_time
uninitialized when IO stats are disabled?

Since md_io_clone is part of a bio allocation from a mempool, it might not
be zero-initialized. If blk_queue_io_stat() returns false, start_time could 
retain garbage memory. 

Later, in md_end_clone_io(), the completion path checks:

md_end_clone_io() {
        ...
        if (md_io_clone->start_time)
                bio_end_io_acct(orig_bio, md_io_clone->start_time);
}

If start_time contains garbage, couldn't this falsely trigger 
bio_end_io_acct() and decrement counters that were never incremented, 
corrupting the block layer metrics?

Additionally, if jiffies happens to be exactly 0 when bio_start_io_acct() 
is called, start_time becomes 0. The sentinel check in md_end_clone_io() 
would then evaluate to false, skipping bio_end_io_acct() and permanently 
leaking the in-flight IO counter. This leak prevents q_usage_counter from 
reaching zero, which would cause blk_mq_freeze_queue() to hang indefinitely 
during block device teardown.

Could start_time be explicitly initialized, or perhaps tracked with a 
separate flag instead of relying on 0 as a sentinel?

> -	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));

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=7

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

* Re: [PATCH v4 22/25] md/raid5: add exact old and new llbitmap mapping helpers
  2026-08-01 17:25 ` [PATCH v4 22/25] md/raid5: add exact old and new llbitmap mapping helpers Yu Kuai
@ 2026-08-01 18:05   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 18:05 UTC (permalink / raw)
  To: Yu Kuai; +Cc: yukuai, linux-raid

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [High] The newly introduced `raid5_bitmap_array_sectors` incorrectly calculates the previous array size when shrinking, returning the new smaller size instead of the exact old size.

Pre-existing issues:
- [Critical] Bitmap mapping logic uses the current `conf->max_degraded` to compute the previous layout's data disks, corrupting mapping during RAID level migration.
- [High] Integer overflow on 32-bit architectures in `raid5_bitmap_sector` when computing `*sectors` during reshape of large arrays.
--

--- Patch [22]: [PATCH v4 22/25] md/raid5: add exact old and new llbitmap mapping helpers ---
commit 2b6e9c9ff8dbd00ebcd0c4b00c57f4d4ef3e5964
Author: Yu Kuai <yukuai@fygo.io>

md/raid5: add exact old and new llbitmap mapping helpers
    
Teach RAID5 to export exact old and new llbitmap mappings and the
corresponding sync and array sizes for reshape-aware bitmap users.

> diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
> index 8b3483674398d..af7775278af6d 100644
> --- a/drivers/md/raid5.c
> +++ b/drivers/md/raid5.c
> @@ -5942,28 +5942,46 @@ static enum reshape_loc get_reshape_loc(struct mddev *mddev,
>  	return LOC_BEHIND_RESHAPE;
>  }
>  
> -static void raid5_bitmap_sector(struct mddev *mddev, sector_t *offset,
> -				unsigned long *sectors)
> +static void raid5_bitmap_sector_map(struct mddev *mddev, sector_t *offset,
> +				    unsigned long *sectors,
> +				    bool previous)
>  {
>  	struct r5conf *conf = mddev->private;
>  	sector_t start = *offset;
>  	sector_t end = start + *sectors;
> -	sector_t prev_start = start;
> -	sector_t prev_end = end;
>  	int sectors_per_chunk;
> -	enum reshape_loc loc;
>  	int dd_idx;
>  
> -	sectors_per_chunk = conf->chunk_sectors *
> -		(conf->raid_disks - conf->max_degraded);
> +	if (previous)
> +		sectors_per_chunk = conf->prev_chunk_sectors *
> +			(conf->previous_raid_disks - conf->max_degraded);

[Severity: Critical]
This is a pre-existing issue, but I noticed we are using the current layout's
max_degraded value to compute the previous layout's physical sectors here.

During a RAID5 to RAID6 migration, conf->max_degraded is updated to 2
immediately at the start of the reshape. However, the old RAID5 layout only
had 1 parity disk.

If we subtract 2 instead of 1, does this corrupt the logical-to-physical chunk
translation for the previous layout? It seems write operations ahead of the
reshape might dirty the wrong bits in the bitmap, potentially leading to silent
data corruption or skipped resyncs after a crash.

I also noticed raid5_compute_sector() applies the current max_degraded to the
previous layout's data_disks computation:

drivers/md/raid5.c:raid5_compute_sector() {
        ...
        int raid_disks = previous ? conf->previous_raid_disks
                                  : conf->raid_disks;
        int data_disks = raid_disks - conf->max_degraded;
        ...
}

Should we be tracking the previous layout's parity disk count separately?

> +	else
> +		sectors_per_chunk = conf->chunk_sectors *
> +			(conf->raid_disks - conf->max_degraded);
>  	sector_div(start, sectors_per_chunk);
>  	start *= sectors_per_chunk;

[ ... ]

> @@ -5992,7 +6001,7 @@ static void raid5_bitmap_sector(struct mddev *mddev, sector_t *offset,
>  	 * we set bits for both.
>  	 */
>  	*offset = min(start, prev_start);
> -	*sectors = max(end, prev_end) - *offset;
> +	*sectors = max(end, prev_start + prev_sectors) - *offset;

[Severity: High]
This is a pre-existing issue, but could this subtraction result in an integer
overflow on 32-bit architectures?

For large arrays (e.g., >24TB), the physical sector distance between the old
and new mappings for a single logical address can exceed ULONG_MAX.

Since *sectors is a 32-bit unsigned long pointer on 32-bit architectures, it
seems this subtraction could truncate the 64-bit sector_t value. If that
happens, might the bitmap logic only mark a small lower portion of the span
as dirty due to the integer wrap-around?

>  }
>  
>  static enum stripe_result make_stripe_request(struct mddev *mddev,

[ ... ]

> @@ -9059,6 +9068,20 @@ static void raid5_prepare_suspend(struct mddev *mddev)
>  	wake_up(&conf->wait_for_reshape);
>  }
>  
> +static sector_t raid5_bitmap_sync_size(struct mddev *mddev, bool previous)
> +{
> +	return mddev->dev_sectors;
> +}
> +
> +static sector_t raid5_bitmap_array_sectors(struct mddev *mddev, bool previous)
> +{
> +	struct r5conf *conf = mddev->private;
> +
> +	if (previous)
> +		return raid5_size(mddev, 0, 0);

[Severity: High]
When shrinking an array, the previous array size is larger than the new array
size.

By passing 0 for the raid_disks argument, raid5_size() calculates the size
using min(conf->raid_disks, conf->previous_raid_disks).

Will this evaluate to the new, smaller size instead of the exact previous
capacity? If reshape-aware bitmap users are tracking the tail of the array
during a backwards reshape, this might fail to export the exact original tail
boundaries and lead to missed bitmap synchronizations.

> +	return raid5_size(mddev, mddev->dev_sectors, conf->raid_disks);
> +}
> +

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=22

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

* Re: [PATCH v4 17/25] md/md-llbitmap: remap checkpointed bits as reshape progresses
  2026-08-01 17:25 ` [PATCH v4 17/25] md/md-llbitmap: remap checkpointed bits as reshape progresses Yu Kuai
@ 2026-08-01 18:12   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 18:12 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] Data race and potential bitmap state corruption due to missing `reshape_lock` acquisitions.
- [High] Incomplete feature implementation causing unreachable reshape checkpointing (dead code).
- [High] `llbitmap_rmerge_state` incorrectly drops 'Unwritten' sync state extensions, causing array inconsistency.

Pre-existing issues:
- [Critical] Pre-existing Use-After-Free in llbitmap teardown due to daemon_work re-arming pending_timer.
--

--- Patch [17]: [PATCH v4 17/25] md/md-llbitmap: remap checkpointed bits as reshape progresses ---
commit 51b23c29cf446cf378d49a002b1a356940be2ccd
Author: Yu Kuai <yukuai@fygo.io>

md/md-llbitmap: remap checkpointed bits as reshape progresses

This patch introduces logic to remap checkpointed old llbitmap state forward
as reshape_position advances. It records the checkpoint remap through
reshape_mark() and adds a reshape_lock to serialize reshape checkpoint
remapping against normal I/O bitmap updates.

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c

[ ... ]

> @@ -930,6 +943,33 @@ static int llbitmap_prepare_resize(struct llbitmap *llbitmap,
>  	return 0;
>  }
>  
> +static enum llbitmap_state
> +llbitmap_rmerge_state(struct llbitmap *llbitmap,
> +		      enum llbitmap_state dst,
> +		      enum llbitmap_state src)
> +{
> +	bool level_456 = raid_is_456(llbitmap->mddev);
> +
> +	if (dst == BitNeedSync || dst == BitSyncing ||
> +	    src == BitNeedSync || src == BitSyncing)
> +		return BitNeedSync;
> +
> +	if (dst == BitDirty || src == BitDirty)
> +		return BitDirty;
> +
> +	/*
> +	 * Reshape generates valid target parity/data for both already-written
> +	 * and not-yet-written regions in the checkpointed range, so a mix of
> +	 * clean and unwritten still results in a clean destination bit.
> +	 */
> +	if (level_456 && ((dst == BitClean && src == BitUnwritten) ||
> +			  (src == BitClean && dst == BitUnwritten)))
> +		return BitClean;
> +	if (dst == BitClean || src == BitClean)
> +		return BitClean;
> +	return BitUnwritten;
> +}

[Severity: High]
Does this switch logic drop the proactive sync extensions like
BitNeedSyncUnwritten, BitSyncingUnwritten, and BitCleanUnwritten?

If either dst or src holds one of these states, it fails the explicit equality
checks and falls through to the default BitUnwritten. Could this cause the
array to lose its proactive sync requirement or pre-built parity state during a
reshape checkpoint merge?

[ ... ]

> @@ -1294,6 +1334,7 @@ static void md_llbitmap_daemon_fn(struct work_struct *work)
>  
>  	if (llbitmap->mddev->degraded)
>  		return;
> +
>  retry:
>  	start = 0;
>  	end = min(llbitmap->chunks, PAGE_SIZE - BITMAP_DATA_OFFSET) - 1;

[Severity: Critical]
This is a pre-existing issue, but does the teardown path have a use-after-free
involving pending_timer?

In llbitmap_destroy(), timer_delete_sync() is called followed by
flush_workqueue(md_llbitmap_io_wq). If md_llbitmap_daemon_fn() is running,
flush_workqueue() will wait for it. However, md_llbitmap_daemon_fn() blindly
re-arms the timer at the end if there are unexpired dirty pages:

md_llbitmap_daemon_fn() {
    ...
    if (restart)
        mod_timer(&llbitmap->pending_timer, ...);
}

Could this cause the timer to fire after the llbitmap struct is freed, since
it re-arms after the teardown path has already deleted it?

[ ... ]

> @@ -1520,7 +1562,9 @@ static void llbitmap_start_write(struct mddev *mddev, sector_t offset,
>  		page_start++;
>  	}
>  
> +	read_lock(&llbitmap->reshape_lock);
>  	llbitmap_state_machine(llbitmap, start, end, BitmapActionStartwrite);
> +	read_unlock(&llbitmap->reshape_lock);
>  }

[Severity: High]
Does this lock need to be acquired in other paths that call
llbitmap_state_machine() as well?

The reshape_lock is taken here to serialize against llbitmap_reshape_mark(),
but paths like md_llbitmap_daemon_fn() and llbitmap_start_sync() appear to
modify the state bytes without acquiring reshape_lock.

Since llbitmap_reshape_mark() bypasses page suspension, could this allow
concurrent execution with the daemon or sync threads, leading to
read-modify-write data races on the bitmap state bytes?

[ ... ]

> @@ -1873,6 +2049,33 @@ static void llbitmap_reshape_finish(struct mddev *mddev)
>  		mddev->pers->quiesce(mddev, 0);
>  }
>  
> +static void llbitmap_reshape_mark(struct mddev *mddev, sector_t old_pos,
> +				  sector_t new_pos)
> +{

[Severity: High]
Is llbitmap_reshape_mark() ever invoked by the md subsystem during a reshape
operation?

A search through the tree doesn't show any callers invoking
mddev->bitmap_ops->reshape_mark(). If this callback is never executed, won't
the reshape checkpoint remapping fail to run and leave the bitmap in a stale
state?

> +	struct llbitmap *llbitmap = mddev->bitmap;
> +	sector_t lo;
> +	sector_t hi;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=17

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

* Re: [PATCH v4 24/25] md/raid5: wire llbitmap reshape lifecycle
  2026-08-01 17:25 ` [PATCH v4 24/25] md/raid5: wire llbitmap reshape lifecycle Yu Kuai
@ 2026-08-01 18:12   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 18:12 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [High] raid5_start_reshape fails to invoke the reshape_can_start bitmap operation before starting the reshape process.
- [High] raid5_finish_reshape unconditionally sets mddev->resync_offset = 0 when llbitmap is enabled, forcing an immediate full array parity rebuild.

Pre-existing issues:
- [High] The llbitmap daemon work and pending timer can mutually re-arm each other during llbitmap_destroy(), leading to a Use-After-Free.
--

--- Patch [24]: [PATCH v4 24/25] md/raid5: wire llbitmap reshape lifecycle ---
commit 5324dd2fc19c643561ddccf4575506b5ce251538
Author: Yu Kuai <yukuai@fygo.io>

md/raid5: wire llbitmap reshape lifecycle

Prepare llbitmap before RAID5 reshape starts, checkpoint the bitmap before
advancing reshape_position, and finish the llbitmap geometry update when
reshape completes.

> diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
> index 5697570120da9..896aa522f446c 100644
> --- a/drivers/md/raid5.c
> +++ b/drivers/md/raid5.c

[ ... ]

> @@ -8579,6 +8593,12 @@ static int raid5_start_reshape(struct mddev *mddev)
>  			mdname(mddev));
>  		return -EINVAL;
>  	}
> +	if (md_bitmap_enabled(mddev, false) &&
> +	    mddev->bitmap_id == ID_LLBITMAP) {
> +		i = mddev->bitmap_ops->resize(mddev, mddev->dev_sectors, 0);

[Severity: High]
Does this code need to call the reshape_can_start() bitmap operation before
proceeding with the resize?

If a reshape begins while the llbitmap is actively tracking out-of-sync
chunks (BitNeedSync or BitSyncing), couldn't the reshape incorrectly map the
sync state to the new geometry? 

This might lead to a loss of sync tracking and potential data regressions.

> +		if (i)
> +			return i;
> +	}
>  
>  	atomic_set(&conf->reshape_stripes, 0);

[ ... ]

> @@ -8693,8 +8722,16 @@ static void raid5_finish_reshape(struct mddev *mddev)
>  {
>  	struct r5conf *conf = mddev->private;
>  	struct md_rdev *rdev;
> +	bool llbitmap = mddev->bitmap_id == ID_LLBITMAP &&
> +		md_bitmap_enabled(mddev, false);
>  
>  	if (!test_bit(MD_RECOVERY_INTR, &mddev->recovery)) {
> +		if (llbitmap && mddev->bitmap_ops->reshape_finish)
> +			mddev->bitmap_ops->reshape_finish(mddev);
> +		if (llbitmap) {
> +			mddev->resync_offset = 0;

[Severity: High]
Does setting mddev->resync_offset to 0 here force an immediate full array
parity rebuild?

By unconditionally resetting the resync offset, the MD core might be
instructed that the entire array is out-of-sync upon reshape completion.

This would force a full resync of all sectors, despite the reshape correctly
remapping in-sync sectors, and cause performance regressions.

> +			mddev->resync_max_sectors = mddev->dev_sectors;
> +		}
>  
>  		if (mddev->delta_disks <= 0) {


As a related note regarding llbitmap teardown:

[Severity: High]
This isn't a bug introduced by this patch, but can the llbitmap daemon work
and pending timer mutually re-arm each other during llbitmap_destroy(),
leading to a use-after-free regression?

Looking at drivers/md/md-llbitmap.c:llbitmap_destroy():

    timer_delete_sync(&llbitmap->pending_timer);
    flush_workqueue(md_llbitmap_io_wq);

If the daemon_work is queued, the flush executes md_llbitmap_daemon_fn().

If there are unexpired dirty pages inside the work function, it will
unconditionally re-arm the timer via mod_timer().

Since there is no cancel_work_sync() or subsequent timer deletion, could the
newly armed timer fire after kfree(llbitmap) completes?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=24

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

* Re: [PATCH v4 20/25] md/raid10: wire llbitmap reshape lifecycle
  2026-08-01 17:25 ` [PATCH v4 20/25] md/raid10: wire llbitmap reshape lifecycle Yu Kuai
@ 2026-08-01 18:28   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 18:28 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Inconsistent abstraction usage in bitmap reshape lifecycle hooks, checking specific bitmap ID only in finish phase.
--

--- Patch [20]: [PATCH v4 20/25] md/raid10: wire llbitmap reshape lifecycle ---
commit 2fc96f9ae1ba07f2366bbf0c6db64db484bf74ad
Author: Yu Kuai <yukuai@fygo.io>

md/raid10: wire llbitmap reshape lifecycle

This commit prepares the llbitmap before RAID10 starts growing and finishes
the geometry update when reshape completes. It also exports the old and new
tracked sizes.

> --- a/drivers/md/raid10.c
> +++ b/drivers/md/raid10.c
> @@ -4394,6 +4394,12 @@ static int raid10_start_reshape(struct mddev *mddev)
>  
>  	if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery))
>  		return -EBUSY;
> +	if (md_bitmap_enabled(mddev, false) &&
> +	    mddev->bitmap_ops->reshape_can_start) {
> +		ret = mddev->bitmap_ops->reshape_can_start(mddev);
> +		if (ret)
> +			return ret;
> +	}

[ ... ]

> @@ -5039,10 +5062,15 @@ static void end_reshape_request(struct r10bio *r10_bio)
>  static void raid10_finish_reshape(struct mddev *mddev)
>  {
>  	struct r10conf *conf = mddev->private;
> +	bool llbitmap = mddev->bitmap_id == ID_LLBITMAP &&
> +		md_bitmap_enabled(mddev, false);
>  
>  	if (test_bit(MD_RECOVERY_INTR, &mddev->recovery))
>  		return;
>  
> +	if (llbitmap && mddev->bitmap_ops->reshape_finish)
> +		mddev->bitmap_ops->reshape_finish(mddev);

[Severity: Low]
This isn't a bug, but is it intentional to explicitly check for ID_LLBITMAP
here in raid10_finish_reshape()?

In earlier hooks like raid10_start_reshape() shown above, the code relies
entirely on the presence of the function pointer:

    if (md_bitmap_enabled(mddev, false) &&
        mddev->bitmap_ops->reshape_can_start)

If another bitmap provider implements reshape_finish() in the future,
would this hardcoded ID check silently prevent its hook from running?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=20

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

* Re: [PATCH v4 25/25] md/raid5: split reshape bios before bitmap accounting
  2026-08-01 17:25 ` [PATCH v4 25/25] md/raid5: split reshape bios before bitmap accounting Yu Kuai
@ 2026-08-01 18:43   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-08-01 18:43 UTC (permalink / raw)
  To: Yu Kuai; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Double accounting of cgroup I/O statistics and double throttling for resubmitted split bios.
- [Low] The error handling path for mddev_bio_split_at_reshape_offset() returning NULL is unreachable dead code.
--

--- Patch [25]: [PATCH v4 25/25] md/raid5: split reshape bios before bitmap accounting ---
commit d432335f5c2f577d3656123bc3166f0ab441619d
Author: Yu Kuai <yukuai@fygo.io>

md/raid5: split reshape bios before bitmap accounting

RAID5 maps array sectors through different geometries before and after the
reshape position. This commit splits bios that cross the reshape boundary
before md_account_bio() so the bitmap only sees ranges belonging to one
side. The split front bio is resubmitted directly after the reshape wait.

> diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
> --- a/drivers/md/raid5.c
> +++ b/drivers/md/raid5.c

[ ... ]

> @@ -6184,6 +6186,18 @@ static bool raid5_make_request(struct mddev *mddev, struct bio * bi)
>  		return true;
>  	}
>  
> +	front_bio = bi;
> +	bi = mddev_bio_split_at_reshape_offset(mddev, bi, NULL,
> +					       &conf->bio_split);
> +	if (!bi) {
> +		if (rw == WRITE)
> +			md_write_end(mddev);
> +		return true;
> +	}

[Severity: Low]
Is this error handling path reachable?

mddev_bio_split_at_reshape_offset() uses bio_submit_split_bioset() to split
the bio using the conf->bio_split bioset. Since this is a mempool-backed
allocation with GFP_NOIO that reuses the original bvecs, the allocation
cannot fail.

> +	if (bi != front_bio)
> +		split = true;
> +	front_bio = bi;
> +
>  	logical_sector = bi->bi_iter.bi_sector & ~((sector_t)RAID5_STRIPE_SECTORS(conf)-1);
>  	bi->bi_next = NULL;

[ ... ]

> @@ -6284,6 +6298,11 @@ static bool raid5_make_request(struct mddev *mddev, struct bio * bi)
>  		bio_endio(bi);
>  
>  		wait_for_completion(&done);
> +		front_bio->bi_status = BLK_STS_OK;
> +		if (split) {
> +			submit_bio_noacct(front_bio);

[Severity: Medium]
Will this cause the front split bio to be accounted and throttled twice?

When mddev_bio_split_at_reshape_offset() splits the bio, the newly allocated
clone (the front bio) does not inherit the BIO_CGROUP_ACCT or
BIO_BPS_THROTTLED flags from the original bio.

When make_stripe_request() returns STRIPE_WAIT_RESHAPE, this code resubmits
the split bio through submit_bio_noacct(). Because the flags are missing,
blk_cgroup_bio_start() and blk_throtl_bio() will account and throttle this
same I/O a second time.

> +			return true;
> +		}
>  		return false;
>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260801172519.2982121-1-yukuai@kernel.org?part=25

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

end of thread, other threads:[~2026-08-01 18:43 UTC | newest]

Thread overview: 48+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-01 17:24 [PATCH v4 00/25] md: improve lockless bitmap reshape support Yu Kuai
2026-08-01 17:24 ` [PATCH v4 01/25] md/md-llbitmap: clear flush state after daemon flush Yu Kuai
2026-08-01 17:43   ` sashiko-bot
2026-08-01 17:24 ` [PATCH v4 02/25] md/md-llbitmap: use GFP_NOIO for cache allocations Yu Kuai
2026-08-01 17:41   ` sashiko-bot
2026-08-01 17:24 ` [PATCH v4 03/25] md/md-llbitmap: only end fully synced chunks Yu Kuai
2026-08-01 17:42   ` sashiko-bot
2026-08-01 17:24 ` [PATCH v4 04/25] md/raid5: reject zero-sector reshape chunks Yu Kuai
2026-08-01 17:45   ` sashiko-bot
2026-08-01 17:24 ` [PATCH v4 05/25] md/raid5: round bitmap stripes with sector division Yu Kuai
2026-08-01 17:39   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 06/25] md: add exact bitmap mapping and reshape hooks Yu Kuai
2026-08-01 17:25 ` [PATCH v4 07/25] md: skip bitmap accounting for empty write ranges Yu Kuai
2026-08-01 18:05   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 08/25] md: add helper to split bios at reshape offset Yu Kuai
2026-08-01 17:41   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 09/25] md/md-llbitmap: track bitmap sync_size explicitly Yu Kuai
2026-08-01 17:44   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 10/25] md/md-llbitmap: allocate page controls independently Yu Kuai
2026-08-01 17:47   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 11/25] md/md-llbitmap: grow the page cache in place for reshape Yu Kuai
2026-08-01 18:03   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 12/25] md/md-llbitmap: track target reshape geometry fields Yu Kuai
2026-08-01 17:51   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 13/25] md/md-llbitmap: finish reshape geometry Yu Kuai
2026-08-01 17:59   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 14/25] md/md-llbitmap: refuse reshape while llbitmap still needs sync Yu Kuai
2026-08-01 17:50   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 15/25] md/md-llbitmap: add reshape range mapping helpers Yu Kuai
2026-08-01 17:47   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 16/25] md/md-llbitmap: don't skip reshape ranges from bitmap state Yu Kuai
2026-08-01 17:41   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 17/25] md/md-llbitmap: remap checkpointed bits as reshape progresses Yu Kuai
2026-08-01 18:12   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 18/25] md/md-llbitmap: clamp state-machine walks to tracked bits Yu Kuai
2026-08-01 17:55   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 19/25] md/raid10: reject llbitmap reshape when md chunk shrinks Yu Kuai
2026-08-01 17:25 ` [PATCH v4 20/25] md/raid10: wire llbitmap reshape lifecycle Yu Kuai
2026-08-01 18:28   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 21/25] md/raid10: split reshape bios before bitmap accounting Yu Kuai
2026-08-01 17:59   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 22/25] md/raid5: add exact old and new llbitmap mapping helpers Yu Kuai
2026-08-01 18:05   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 23/25] md/raid5: reject llbitmap reshape when md chunk shrinks Yu Kuai
2026-08-01 17:25 ` [PATCH v4 24/25] md/raid5: wire llbitmap reshape lifecycle Yu Kuai
2026-08-01 18:12   ` sashiko-bot
2026-08-01 17:25 ` [PATCH v4 25/25] md/raid5: split reshape bios before bitmap accounting Yu Kuai
2026-08-01 18:43   ` sashiko-bot

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