* [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5
@ 2026-07-28 8:49 Yu Kuai
2026-07-28 8:49 ` [PATCH v3 01/21] md/raid5: round bitmap stripes with sector division Yu Kuai
` (21 more replies)
0 siblings, 22 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, linux-raid,
linux-kernel
From: Yu Kuai <yukuai@fygo.io>
Hi,
This series adds llbitmap support for online reshape in RAID10 and RAID5.
llbitmap has a different set of constraints from the existing bitmap code:
there is one live bitmap instance, each bit state has richer semantics, and
reshape can change the mapping from logical array ranges to bitmap ranges.
The series therefore adds exact bitmap range mapping hooks, tracks old and
new llbitmap geometry during reshape, remaps checkpointed bits as reshape
progresses, and wires the reshape lifecycle into RAID10 and RAID5.
The main rules are:
1. split bios at the reshape position before bitmap accounting, so one bio
is never accounted with mixed old/new geometry;
2. do not skip reshape ranges from stale llbitmap state, because reshape
progress is checkpointed by array metadata;
3. remap llbitmap bits when reshape progress is checkpointed;
4. reject llbitmap reshape if mddev->chunk_sectors shrinks, because the
effective data range represented by existing bitmap bits can shrink.
The first patch fixes RAID5 bitmap stripe rounding before the exact mapping
hooks are added, so the later RAID5 helper split starts from correct
sector_t division semantics. The rest of the series prepares generic bitmap
and llbitmap infrastructure, wires RAID10, and then wires RAID5 with exact
old/new stripe mapping.
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.
Yu Kuai (21):
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 | 659 ++++++++++++++++++++++++++++++++++++---
drivers/md/md.c | 64 +++-
drivers/md/md.h | 8 +
drivers/md/raid10.c | 49 +++
drivers/md/raid5.c | 123 ++++++--
7 files changed, 842 insertions(+), 78 deletions(-)
base-commit: 55b77337bdd088c77461588e5ec094421b89911b
--
2.51.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v3 01/21] md/raid5: round bitmap stripes with sector division
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 02/21] md: add exact bitmap mapping and reshape hooks Yu Kuai
` (20 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 0c5c9fb0606e..35910bc6683b 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 [flat|nested] 23+ messages in thread
* [PATCH v3 02/21] md: add exact bitmap mapping and reshape hooks
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
2026-07-28 8:49 ` [PATCH v3 01/21] md/raid5: round bitmap stripes with sector division Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 03/21] md: skip bitmap accounting for empty write ranges Yu Kuai
` (19 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 5a4e2abaa757..f0c20afa354e 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)
{
@@ -1780,6 +1787,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 [flat|nested] 23+ messages in thread
* [PATCH v3 03/21] md: skip bitmap accounting for empty write ranges
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
2026-07-28 8:49 ` [PATCH v3 01/21] md/raid5: round bitmap stripes with sector division Yu Kuai
2026-07-28 8:49 ` [PATCH v3 02/21] md: add exact bitmap mapping and reshape hooks Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 04/21] md: add helper to split bios at reshape offset Yu Kuai
` (18 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 [flat|nested] 23+ messages in thread
* [PATCH v3 04/21] md: add helper to split bios at reshape offset
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (2 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 03/21] md: skip bitmap accounting for empty write ranges Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 05/21] md/md-llbitmap: track bitmap sync_size explicitly Yu Kuai
` (17 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 helper in
multiple places.
Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
drivers/md/md.c | 39 +++++++++++++++++++++++++++++++++++++++
drivers/md/md.h | 4 ++++
2 files changed, 43 insertions(+)
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 0b59c676f7c0..c18a7f3c27e6 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 = 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 [flat|nested] 23+ messages in thread
* [PATCH v3 05/21] md/md-llbitmap: track bitmap sync_size explicitly
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (3 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 04/21] md: add helper to split bios at reshape offset Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 06/21] md/md-llbitmap: allocate page controls independently Yu Kuai
` (16 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 f0c20afa354e..e3500a1a52c0 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;
}
@@ -1526,7 +1535,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);
@@ -1544,6 +1553,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 [flat|nested] 23+ messages in thread
* [PATCH v3 06/21] md/md-llbitmap: allocate page controls independently
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (4 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 05/21] md/md-llbitmap: track bitmap sync_size explicitly Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 07/21] md/md-llbitmap: grow the page cache in place for reshape Yu Kuai
` (15 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 e3500a1a52c0..4c3fc4b35aff 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_KERNEL | __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_KERNEL | __GFP_ZERO);
- if (!llbitmap->pctl)
- return -ENOMEM;
size = round_up(size, cache_line_size());
- pctl = kmalloc_array(nr_pages, size, GFP_KERNEL | __GFP_ZERO);
- if (!pctl) {
- kfree(llbitmap->pctl);
- return -ENOMEM;
+ pctl = kzalloc(size, GFP_KERNEL);
+ 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_KERNEL)) {
+ __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_KERNEL);
+ if (!llbitmap->pctl)
+ return -ENOMEM;
- if (percpu_ref_init(&pctl->active, active_release,
- PERCPU_REF_ALLOW_REINIT, GFP_KERNEL)) {
- __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 [flat|nested] 23+ messages in thread
* [PATCH v3 07/21] md/md-llbitmap: grow the page cache in place for reshape
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (5 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 06/21] md/md-llbitmap: allocate page controls independently Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 08/21] md/md-llbitmap: track target reshape geometry fields Yu Kuai
` (14 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 | 136 +++++++++++++++++++++++++++++++++++----
1 file changed, 124 insertions(+), 12 deletions(-)
diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 4c3fc4b35aff..1ab9b1689fd8 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,53 @@ 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;
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;
+
+ 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);
return 0;
+
+out:
+ mutex_unlock(&mddev->bitmap_info.mutex);
+ return ret;
}
static int llbitmap_load(struct mddev *mddev)
--
2.51.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v3 08/21] md/md-llbitmap: track target reshape geometry fields
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (6 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 07/21] md/md-llbitmap: grow the page cache in place for reshape Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 09/21] md/md-llbitmap: finish reshape geometry Yu Kuai
` (13 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 1ab9b1689fd8..cdced2126e97 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:
@@ -1284,6 +1322,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)
@@ -1292,6 +1333,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 [flat|nested] 23+ messages in thread
* [PATCH v3 09/21] md/md-llbitmap: finish reshape geometry
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (7 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 08/21] md/md-llbitmap: track target reshape geometry fields Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 10/21] md/md-llbitmap: refuse reshape while llbitmap still needs sync Yu Kuai
` (12 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 cdced2126e97..55ab2a8a11e5 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1349,11 +1349,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;
}
@@ -1680,6 +1689,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);
@@ -1977,6 +2010,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 [flat|nested] 23+ messages in thread
* [PATCH v3 10/21] md/md-llbitmap: refuse reshape while llbitmap still needs sync
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (8 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 09/21] md/md-llbitmap: finish reshape geometry Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 11/21] md/md-llbitmap: add reshape range mapping helpers Yu Kuai
` (11 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 55ab2a8a11e5..f5df82adef64 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1689,6 +1689,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;
@@ -2011,6 +2034,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 [flat|nested] 23+ messages in thread
* [PATCH v3 11/21] md/md-llbitmap: add reshape range mapping helpers
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (9 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 10/21] md/md-llbitmap: refuse reshape while llbitmap still needs sync Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 12/21] md/md-llbitmap: don't skip reshape ranges from bitmap state Yu Kuai
` (10 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 | 129 +++++++++++++++++++++++++++++++++++++--
drivers/md/md.c | 10 +--
4 files changed, 132 insertions(+), 12 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 f5df82adef64..929c1f40ddec 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>
@@ -449,6 +450,16 @@ static sector_t llbitmap_personality_sync_size(struct llbitmap *llbitmap,
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 +477,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;
@@ -1385,11 +1470,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 = mddev->reshape_position;
+
+ if (!llbitmap_reshaping(llbitmap))
+ 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,
@@ -1558,7 +1667,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;
}
@@ -1568,7 +1681,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)
@@ -1613,6 +1730,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 c18a7f3c27e6..a00df52ab40b 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 [flat|nested] 23+ messages in thread
* [PATCH v3 12/21] md/md-llbitmap: don't skip reshape ranges from bitmap state
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (10 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 11/21] md/md-llbitmap: add reshape range mapping helpers Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 13/21] md/md-llbitmap: remap checkpointed bits as reshape progresses Yu Kuai
` (9 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 929c1f40ddec..bdc6ec8972f0 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1687,6 +1687,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 [flat|nested] 23+ messages in thread
* [PATCH v3 13/21] md/md-llbitmap: remap checkpointed bits as reshape progresses
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (11 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 12/21] md/md-llbitmap: don't skip reshape ranges from bitmap state Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 14/21] md/md-llbitmap: clamp state-machine walks to tracked bits Yu Kuai
` (8 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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().
Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
drivers/md/md-llbitmap.c | 172 +++++++++++++++++++++++++++++++++++++++
1 file changed, 172 insertions(+)
diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index bdc6ec8972f0..794ac0eaf437 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -502,6 +502,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,
@@ -934,6 +942,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;
@@ -1839,6 +1874,120 @@ 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;
+
+ if (!new->sectors)
+ return;
+
+ dst_start = div64_u64(new->offset, llbitmap->reshape_chunksize);
+ dst_end = div64_u64(new->end - 1, llbitmap->reshape_chunksize);
+
+ for (dst = dst_start; dst <= dst_end; dst++) {
+ 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);
+ }
+}
+
static void llbitmap_reshape_finish(struct mddev *mddev)
{
struct llbitmap *llbitmap = mddev->bitmap;
@@ -1863,6 +2012,28 @@ 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);
+ llbitmap_reshape_merge(llbitmap, &old, &new);
+}
+
static void llbitmap_write_sb(struct llbitmap *llbitmap)
{
int nr_blocks = DIV_ROUND_UP(BITMAP_DATA_OFFSET, llbitmap->io_size);
@@ -2162,6 +2333,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 [flat|nested] 23+ messages in thread
* [PATCH v3 14/21] md/md-llbitmap: clamp state-machine walks to tracked bits
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (12 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 13/21] md/md-llbitmap: remap checkpointed bits as reshape progresses Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 15/21] md/raid10: reject llbitmap reshape when md chunk shrinks Yu Kuai
` (7 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 794ac0eaf437..bb83d15247d2 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1011,7 +1011,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 [flat|nested] 23+ messages in thread
* [PATCH v3 15/21] md/raid10: reject llbitmap reshape when md chunk shrinks
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (13 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 14/21] md/md-llbitmap: clamp state-machine walks to tracked bits Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 16/21] md/raid10: wire llbitmap reshape lifecycle Yu Kuai
` (6 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 [flat|nested] 23+ messages in thread
* [PATCH v3 16/21] md/raid10: wire llbitmap reshape lifecycle
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (14 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 15/21] md/raid10: reject llbitmap reshape when md chunk shrinks Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 17/21] md/raid10: split reshape bios before bitmap accounting Yu Kuai
` (5 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 [flat|nested] 23+ messages in thread
* [PATCH v3 17/21] md/raid10: split reshape bios before bitmap accounting
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (15 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 16/21] md/raid10: wire llbitmap reshape lifecycle Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 18/21] md/raid5: add exact old and new llbitmap mapping helpers Yu Kuai
` (4 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index e50e4adee389..fd364a2e2fd9 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -1911,6 +1911,12 @@ 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, §ors,
+ &conf->bio_split);
+ if (!bio)
+ return true;
+
if (!__make_request(mddev, bio, sectors))
md_write_end(mddev);
--
2.51.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v3 18/21] md/raid5: add exact old and new llbitmap mapping helpers
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (16 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 17/21] md/raid10: split reshape bios before bitmap accounting Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 19/21] md/raid5: reject llbitmap reshape when md chunk shrinks Yu Kuai
` (3 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 35910bc6683b..8b28afa538e2 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,
@@ -9054,6 +9063,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 = {
@@ -9083,6 +9106,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 =
{
@@ -9113,6 +9139,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 =
@@ -9144,6 +9173,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 [flat|nested] 23+ messages in thread
* [PATCH v3 19/21] md/raid5: reject llbitmap reshape when md chunk shrinks
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (17 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 18/21] md/raid5: add exact old and new llbitmap mapping helpers Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 20/21] md/raid5: wire llbitmap reshape lifecycle Yu Kuai
` (2 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 8b28afa538e2..f0817599dd27 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -8509,6 +8509,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 [flat|nested] 23+ messages in thread
* [PATCH v3 20/21] md/raid5: wire llbitmap reshape lifecycle
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (18 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 19/21] md/raid5: reject llbitmap reshape when md chunk shrinks Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-28 8:49 ` [PATCH v3 21/21] md/raid5: split reshape bios before bitmap accounting Yu Kuai
2026-07-29 15:30 ` [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 yu kuai
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, 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 f0817599dd27..f70b2f611980 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)
@@ -8574,6 +8588,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);
@@ -8658,10 +8678,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);
@@ -8688,8 +8717,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 [flat|nested] 23+ messages in thread
* [PATCH v3 21/21] md/raid5: split reshape bios before bitmap accounting
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (19 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 20/21] md/raid5: wire llbitmap reshape lifecycle Yu Kuai
@ 2026-07-28 8:49 ` Yu Kuai
2026-07-29 15:30 ` [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 yu kuai
21 siblings, 0 replies; 23+ messages in thread
From: Yu Kuai @ 2026-07-28 8:49 UTC (permalink / raw)
To: Song Liu
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, yukuai, linux-raid,
linux-kernel
From: Yu Kuai <yukuai@fygo.io>
Use the shared mddev_bio_split_at_reshape_offset() helper so RAID5
submits only one-side bios to llbitmap during reshape.
Signed-off-by: Yu Kuai <yukuai@fygo.io>
---
drivers/md/raid5.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index f70b2f611980..30e18ce7c0e3 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -6184,6 +6184,14 @@ static bool raid5_make_request(struct mddev *mddev, struct bio * bi)
return true;
}
+ bi = mddev_bio_split_at_reshape_offset(mddev, bi, NULL,
+ &conf->bio_split);
+ if (!bi) {
+ if (rw == WRITE)
+ md_write_end(mddev);
+ return true;
+ }
+
logical_sector = bi->bi_iter.bi_sector & ~((sector_t)RAID5_STRIPE_SECTORS(conf)-1);
bi->bi_next = NULL;
--
2.51.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
` (20 preceding siblings ...)
2026-07-28 8:49 ` [PATCH v3 21/21] md/raid5: split reshape bios before bitmap accounting Yu Kuai
@ 2026-07-29 15:30 ` yu kuai
21 siblings, 0 replies; 23+ messages in thread
From: yu kuai @ 2026-07-29 15:30 UTC (permalink / raw)
To: Song Liu, yu kuai
Cc: Li Nan, Xiao Ni, Mykola Marzhan, Su Yue, linux-raid, linux-kernel
Hi,
在 2026/7/28 16:49, Yu Kuai 写道:
> From: Yu Kuai <yukuai@fygo.io>
>
> Hi,
>
> This series adds llbitmap support for online reshape in RAID10 and RAID5.
>
> llbitmap has a different set of constraints from the existing bitmap code:
> there is one live bitmap instance, each bit state has richer semantics, and
> reshape can change the mapping from logical array ranges to bitmap ranges.
> The series therefore adds exact bitmap range mapping hooks, tracks old and
> new llbitmap geometry during reshape, remaps checkpointed bits as reshape
> progresses, and wires the reshape lifecycle into RAID10 and RAID5.
>
> The main rules are:
>
> 1. split bios at the reshape position before bitmap accounting, so one bio
> is never accounted with mixed old/new geometry;
> 2. do not skip reshape ranges from stale llbitmap state, because reshape
> progress is checkpointed by array metadata;
> 3. remap llbitmap bits when reshape progress is checkpointed;
> 4. reject llbitmap reshape if mddev->chunk_sectors shrinks, because the
> effective data range represented by existing bitmap bits can shrink.
>
> The first patch fixes RAID5 bitmap stripe rounding before the exact mapping
> hooks are added, so the later RAID5 helper split starts from correct
> sector_t division semantics. The rest of the series prepares generic bitmap
> and llbitmap infrastructure, wires RAID10, and then wires RAID5 with exact
> old/new stripe mapping.
There are some real problems reported by sashiko review, will send v4 set soon.
>
> 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.
>
> Yu Kuai (21):
> 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 | 659 ++++++++++++++++++++++++++++++++++++---
> drivers/md/md.c | 64 +++-
> drivers/md/md.h | 8 +
> drivers/md/raid10.c | 49 +++
> drivers/md/raid5.c | 123 ++++++--
> 7 files changed, 842 insertions(+), 78 deletions(-)
>
>
> base-commit: 55b77337bdd088c77461588e5ec094421b89911b
--
Thanks,
Kuai
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-07-29 15:30 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-28 8:49 [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 Yu Kuai
2026-07-28 8:49 ` [PATCH v3 01/21] md/raid5: round bitmap stripes with sector division Yu Kuai
2026-07-28 8:49 ` [PATCH v3 02/21] md: add exact bitmap mapping and reshape hooks Yu Kuai
2026-07-28 8:49 ` [PATCH v3 03/21] md: skip bitmap accounting for empty write ranges Yu Kuai
2026-07-28 8:49 ` [PATCH v3 04/21] md: add helper to split bios at reshape offset Yu Kuai
2026-07-28 8:49 ` [PATCH v3 05/21] md/md-llbitmap: track bitmap sync_size explicitly Yu Kuai
2026-07-28 8:49 ` [PATCH v3 06/21] md/md-llbitmap: allocate page controls independently Yu Kuai
2026-07-28 8:49 ` [PATCH v3 07/21] md/md-llbitmap: grow the page cache in place for reshape Yu Kuai
2026-07-28 8:49 ` [PATCH v3 08/21] md/md-llbitmap: track target reshape geometry fields Yu Kuai
2026-07-28 8:49 ` [PATCH v3 09/21] md/md-llbitmap: finish reshape geometry Yu Kuai
2026-07-28 8:49 ` [PATCH v3 10/21] md/md-llbitmap: refuse reshape while llbitmap still needs sync Yu Kuai
2026-07-28 8:49 ` [PATCH v3 11/21] md/md-llbitmap: add reshape range mapping helpers Yu Kuai
2026-07-28 8:49 ` [PATCH v3 12/21] md/md-llbitmap: don't skip reshape ranges from bitmap state Yu Kuai
2026-07-28 8:49 ` [PATCH v3 13/21] md/md-llbitmap: remap checkpointed bits as reshape progresses Yu Kuai
2026-07-28 8:49 ` [PATCH v3 14/21] md/md-llbitmap: clamp state-machine walks to tracked bits Yu Kuai
2026-07-28 8:49 ` [PATCH v3 15/21] md/raid10: reject llbitmap reshape when md chunk shrinks Yu Kuai
2026-07-28 8:49 ` [PATCH v3 16/21] md/raid10: wire llbitmap reshape lifecycle Yu Kuai
2026-07-28 8:49 ` [PATCH v3 17/21] md/raid10: split reshape bios before bitmap accounting Yu Kuai
2026-07-28 8:49 ` [PATCH v3 18/21] md/raid5: add exact old and new llbitmap mapping helpers Yu Kuai
2026-07-28 8:49 ` [PATCH v3 19/21] md/raid5: reject llbitmap reshape when md chunk shrinks Yu Kuai
2026-07-28 8:49 ` [PATCH v3 20/21] md/raid5: wire llbitmap reshape lifecycle Yu Kuai
2026-07-28 8:49 ` [PATCH v3 21/21] md/raid5: split reshape bios before bitmap accounting Yu Kuai
2026-07-29 15:30 ` [PATCH v3 00/21] md/md-llbitmap: support reshape for RAID10 and RAID5 yu kuai
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®