From f565925810cb8bc799421485770e15d922ef766a Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Mon, 22 Jun 2026 20:46:49 +0800 Subject: [PATCH 01/53] md/raid5: protect bitmap batch counters aka seq_flush/seq_write consistency kcsan detect race : - raid5d() closes the current bitmap batch by updating conf->seq_flush under conf->device_lock. - __add_stripe_bio() read conf->seq_flush without that lock when assigning sh->bm_seq. so, protect seq_flush/seq_write consistency for multiple CPUs by READ_ONCE()/WRITE_ONCE() under the path without held device_lock. re-explain the stripe batch sequence number update flow: 1. sh->bm_seq declare which batch number the stripe belongs to when perform bitmap-related write. ==> bm_seq = seq_flush+1 2. stripe be handled, * if sh->bm_seq - conf->seq_write > 0, means the batch stripes **newer than** the last written batch, it cannot proceed yet, queued on bitmap_list. * otherwise , has already proceed. 3. raid5d() `++seq_flush` to closes the current batch, means * no more stripes join that old batch * just-closed batch ready to write-out to disk 4. raid5d() calls bitmap hooks unplug() or writeout, then, `++seq_write` to the same as bm_seq. - seq_flush - for producer, to close batches. - seq_write - for consumer, the checkpoint number. the report: ==================================== BUG: KCSAN: data-race in __add_stripe_bio / raid5d write to 0xffff88ba5625d470 of 4 bytes by task 82401 on cpu 0: raid5d+0x1d9/0xba0 [.....] read to 0xffff88ba5625d470 of 4 bytes by task 82421 on cpu 8: __add_stripe_bio+0x332/0x400 raid5_make_request+0x6ac/0x2930 md_handle_request+0x4a2/0xa40 md_submit_bio+0x109/0x1a0 __submit_bio+0x2ec/0x390 [.....] Fixes: 7c13edc87510 ("md: incorporate new plugging into raid5.") v1 -> v2: - remove WRITE_ONCE(conf->seq_write) in held device_lock path. - remove READ_ONCE(conf->seq_flush) in held device_lock path. Signed-off-by: Chen Cheng Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260622124649.1780233-1-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index ffb5fcde54a9..a6c52fb1fe68 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -3553,7 +3553,7 @@ static void __add_stripe_bio(struct stripe_head *sh, struct bio *bi, sh->dev[dd_idx].sector); if (conf->mddev->bitmap && firstwrite && !sh->batch_head) { - sh->bm_seq = conf->seq_flush+1; + sh->bm_seq = READ_ONCE(conf->seq_flush) + 1; set_bit(STRIPE_BIT_DELAY, &sh->state); } } @@ -5799,7 +5799,7 @@ static void make_discard_request(struct mddev *mddev, struct bio *bi) } spin_unlock_irq(&sh->stripe_lock); if (conf->mddev->bitmap) { - sh->bm_seq = conf->seq_flush + 1; + sh->bm_seq = READ_ONCE(conf->seq_flush) + 1; set_bit(STRIPE_BIT_DELAY, &sh->state); } @@ -6849,12 +6849,14 @@ static void raid5d(struct md_thread *thread) if ( !list_empty(&conf->bitmap_list)) { /* Now is a good time to flush some bitmap updates */ - conf->seq_flush++; + int seq = conf->seq_flush + 1; + + WRITE_ONCE(conf->seq_flush, seq); spin_unlock_irq(&conf->device_lock); if (md_bitmap_enabled(mddev, true)) mddev->bitmap_ops->unplug(mddev, true); spin_lock_irq(&conf->device_lock); - conf->seq_write = conf->seq_flush; + conf->seq_write = seq; activate_bit_delay(conf, conf->temp_inactive_list); } raid5_activate_delayed(conf); From 371f7a1b392edc8b7cf449cc7713179b588f2d0e Mon Sep 17 00:00:00 2001 From: Sajal Gupta Date: Mon, 22 Jun 2026 19:36:03 +0530 Subject: [PATCH 02/53] md/raid5-ppl: fix use-after-free in ppl_do_flush() The loop in ppl_do_flush() continues iterating after calling ppl_io_unit_finished(), touching io->pending_flushes and leading to a use-after-free. Add a break statement to stop the loop once io is freed. Fixes: 1532d9e87e8b ("raid5-ppl: PPL support for disks with write-back cache enabled") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/ajJF2wKYWRk4GGCK@stanley.mountain/ Signed-off-by: Sajal Gupta Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260622142146.56637-1-sajal2005gupta@gmail.com Signed-off-by: Yu Kuai --- drivers/md/raid5-ppl.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/raid5-ppl.c b/drivers/md/raid5-ppl.c index 7be1648c4e4f..7f8a9d3fd578 100644 --- a/drivers/md/raid5-ppl.c +++ b/drivers/md/raid5-ppl.c @@ -643,8 +643,10 @@ static void ppl_do_flush(struct ppl_io_unit *io) log->disk_flush_bitmap = 0; for (i = flushed_disks ; i < raid_disks; i++) { - if (atomic_dec_and_test(&io->pending_flushes)) + if (atomic_dec_and_test(&io->pending_flushes)) { ppl_io_unit_finished(io); + break; + } } } From e12e619c2e2d0c3f42b14e9ef1ab778e696ffffd Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Tue, 23 Jun 2026 15:59:40 +0800 Subject: [PATCH 03/53] md/raid1: protect sequential read hints for read balance The patch just suppress KCSAN noise. No functional change. KCSAN reports a race, point to update_read_sectors() update next_seq_sect vs. read next_seq_sect. Protect next_seq_sect and seq_start with READ_ONCE/WRITE_ONCE, otherwise, read balance see stale sequential-read hints. KCSAN report: ============== BUG: KCSAN: data-race in raid1_read_request / raid1_read_request write to 0xffff8e3a2d6736d0 of 8 bytes by task 593784 on cpu 10: raid1_read_request+0xe5a/0x19f0 raid1_make_request+0xdf/0x1990 md_handle_request+0x4a2/0xa40 [...] read to 0xffff8e3a2d6736d0 of 8 bytes by task 593776 on cpu 11: raid1_read_request+0xe3f/0x19f0 raid1_make_request+0xdf/0x1990 md_handle_request+0x4a2/0xa40 [...] value changed: 0x0000000000356368 -> 0x0000000000356370 Signed-off-by: Chen Cheng Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260623075940.2476255-1-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/raid1.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index afe2ca96ad8c..4d7f33a2faaa 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -604,9 +604,9 @@ static void update_read_sectors(struct r1conf *conf, int disk, struct raid1_info *info = &conf->mirrors[disk]; atomic_inc(&info->rdev->nr_pending); - if (info->next_seq_sect != this_sector) - info->seq_start = this_sector; - info->next_seq_sect = this_sector + len; + if (READ_ONCE(info->next_seq_sect) != this_sector) + WRITE_ONCE(info->seq_start, this_sector); + WRITE_ONCE(info->next_seq_sect, this_sector + len); } static int choose_first_rdev(struct r1conf *conf, struct r1bio *r1_bio, @@ -735,8 +735,7 @@ static int choose_slow_rdev(struct r1conf *conf, struct r1bio *r1_bio, static bool is_sequential(struct r1conf *conf, int disk, struct r1bio *r1_bio) { - /* TODO: address issues with this check and concurrency. */ - return conf->mirrors[disk].next_seq_sect == r1_bio->sector || + return READ_ONCE(conf->mirrors[disk].next_seq_sect) == r1_bio->sector || READ_ONCE(conf->mirrors[disk].head_position) == r1_bio->sector; } @@ -747,15 +746,18 @@ static bool is_sequential(struct r1conf *conf, int disk, struct r1bio *r1_bio) static bool should_choose_next(struct r1conf *conf, int disk) { struct raid1_info *mirror = &conf->mirrors[disk]; + sector_t seq_start, next_seq_sect; int opt_iosize; if (!test_bit(Nonrot, &mirror->rdev->flags)) return false; opt_iosize = bdev_io_opt(mirror->rdev->bdev) >> 9; - return opt_iosize > 0 && mirror->seq_start != MaxSector && - mirror->next_seq_sect > opt_iosize && - mirror->next_seq_sect - opt_iosize >= mirror->seq_start; + seq_start = READ_ONCE(mirror->seq_start); + next_seq_sect = READ_ONCE(mirror->next_seq_sect); + return opt_iosize > 0 && seq_start != MaxSector && + next_seq_sect > opt_iosize && + next_seq_sect - opt_iosize >= seq_start; } static bool rdev_readable(struct md_rdev *rdev, struct r1bio *r1_bio) From 6cb6ab75bdf2f49c0adb0fd6971886b082932eaa Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Wed, 24 Jun 2026 10:40:42 +0800 Subject: [PATCH 04/53] md/raid5: fix lockless max_nr_stripes reads max_nr_stripes is updated under cache_size_mutex in the stripe cache grow/shrink paths, while is_inactive_blocked() and raid5_end_read_request() read it without that lock. Use READ_ONCE() for those reads in lockless path to match the WRITE_ONCE() updates and avoid KCSAN data race reports. A similar issue was previously fixed in commit-id: dfd2bf436709b2bccb78c2dda550dde93700efa7. Fixes: 0009fad03337 ("raid5 improve too many read errors msg by adding limits") Fixes: 3514da58be9c ("md/raid5: Make is_inactive_blocked() helper") KCSAN report: ================= BUG: KCSAN: data-race in grow_one_stripe / is_inactive_blocked write (marked) to 0xffff8f01f0b5a268 of 4 bytes by task 12616 on cpu 9: grow_one_stripe+0x2d8/0x320 raid5d+0xb57/0xba0 md_thread+0x15a/0x2d0 [..........] read to 0xffff8f01f0b5a268 of 4 bytes by task 12670 on cpu 11: is_inactive_blocked+0x97/0xc0 raid5_get_active_stripe+0x2fd/0xa70 raid5_make_request+0x4aa/0x2940 [..........] value changed: 0x000003b9 -> 0x000003ba Signed-off-by: Chen Cheng Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260624024042.2561803-1-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index a6c52fb1fe68..992d0b14822e 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -801,7 +801,7 @@ static bool is_inactive_blocked(struct r5conf *conf, int hash) return true; return (atomic_read(&conf->active_stripes) < - (conf->max_nr_stripes * 3 / 4)); + (READ_ONCE(conf->max_nr_stripes) * 3 / 4)); } struct stripe_head *raid5_get_active_stripe(struct r5conf *conf, @@ -2785,6 +2785,7 @@ static void raid5_end_read_request(struct bio * bi) } else { int retry = 0; int set_bad = 0; + int max_nr_stripes = READ_ONCE(conf->max_nr_stripes); clear_bit(R5_UPTODATE, &sh->dev[i].flags); if (!(bi->bi_status == BLK_STS_PROTECTION)) @@ -2810,13 +2811,12 @@ static void raid5_end_read_request(struct bio * bi) mdname(conf->mddev), (unsigned long long)s, rdev->bdev); - } else if (atomic_read(&rdev->read_errors) - > conf->max_nr_stripes) { + } else if (atomic_read(&rdev->read_errors) > max_nr_stripes) { if (!test_bit(Faulty, &rdev->flags)) { pr_warn("md/raid:%s: %d read_errors > %d stripes\n", mdname(conf->mddev), atomic_read(&rdev->read_errors), - conf->max_nr_stripes); + max_nr_stripes); pr_warn("md/raid:%s: Too many read errors, failing device %pg.\n", mdname(conf->mddev), rdev->bdev); } From 788e4139463f74b945600237f7186411015d996e Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Wed, 24 Jun 2026 15:58:24 +0800 Subject: [PATCH 05/53] md/raid5: fix reshape deadlock while failed devices more than max degraded reshape stripe lifetime: - start reshape ==> reshape_request(): * get destination stripe, - if need to copy source data chunks, set STRIPE_EXPANDING; - or, if new regions past the old end of the array, zero-filled, no need source data, set STRIPE_EXPANDING | STRIPE_READY * get source stripe, - set STRIPE_EXPAND_SOURCE - handle expand stripe ==> handle_stripe(): reshape use reconstruct-write to construct stripe, four stages: 1. prepare source data chunks for old geometry stripe - fill source stripe data by read or compute 2. move data from old geometry source stripe to new geometry destination stripe - source stripe clear STRIPE_EXPAND_SOURCE - drain data from source to destination stripe - mark stripe chunk as R5_Expanded|R5_UPTODATE when the drain from source chunk to destination chunk is completed - all stripe chunks drain are completed, then mark STRIPE_EXPAND_READY 3. calculate p/q chunks for destination stripe - if destination stripe doesn't depends on source dstripe, then we can clear STRIPE_EXPANDING 4. write-out to disks and release - set R5_Wantwrite|R5_Locked, writeout to disk - if write-out succeeded, clear STRIPE_EXPAND_READY, and decrement reshape_stripe, call md_done_sync() to report reshape progress. 1. cleanup the following kinds of **destination stripe** when failed device more than max degraded: - new regions past the old end of the array, zero-filled in place, requires no source data. (STRIPE_EXPANDING | STRIPE_EXPAND_READY) - prepare source data chunks already done, and writeout failed (STRIPE_EXPAND_READY) 2. destination stripes that need source data (STRIPE_EXPANDING, no STRIPE_HANDLE) - these kind of stripes sit idle in the stripe cache and are never seen by handle_stripe(). So clean up indirectly when their source stripe (type 3) is processed. 3. source stripes (STRIPE_EXPAND_SOURCE) - hit handle_stripe() after their member disks are marked Faulty. - clear STRIPE_EXPAND_SOURCE, finds and cleanup all dependent destination stripes that were waiting for data. - walks the source's data disks, compute the corresponding destination sector, looks up the destination stripe, and do cleanup(clear flags, dec counters, call md_done_sync()) Reproducer: - Create a 4-disk RAID5 with mdadm on top of 5 disposable test disks wrapped by dm targets. - Add the 5th device as a spare and start a 4 -> 5 reshape. - Wait until /sys/block/mdX/md/sync_action reports "reshape". - Inject failures on two members so reshape exceeds max_degraded. - After a few seconds, write "frozen" to /sys/block/mdX/md/sync_action. Before this fix, the write blocks indefinitely. Read-error variant: - Use dm-dust on /dev/sd[b-f]. - Preload bad blocks on two source members, e.g. dust0 and dust1: dmsetup message dust0 0 addbadblock dmsetup message dust1 0 addbadblock - Start reshape: mdadm -C /dev/mdX -e 1.2 -l 5 -n 4 -c 64 \ --assume-clean /dev/mapper/dust{0..3} mdadm --manage /dev/mdX --add /dev/mapper/dust4 mdadm --grow /dev/mdX -n 5 --backup-file=/tmp/grow.backup & - Once reshape starts, enable the injected read failures: dmsetup message dust0 0 enable dmsetup message dust1 0 enable - Then: echo frozen > /sys/block/mdX/md/sync_action hangs forever before the fix. Write-error variant: - Use dm-flakey on /dev/sd[b-f]. - Start the same 4 -> 5 reshape on flakey0..flakey4. - Once reshape starts, switch two members, e.g. flakey3 and flakey4, to error_writes. - Then: echo frozen > /sys/block/mdX/md/sync_action hangs forever before the fix. md_do_sync() exits its main loop on MD_RECOVERY_INTR but then blocks forever at: wait_event(mddev->recovery_wait, !atomic_read(&mddev->recovery_active)); After the fix recovery_active drains to zero, md_do_sync() prints md/raid:md0: Cannot continue operation (2/5 failed). md: md0: reshape interrupted. Signed-off-by: Chen Cheng Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260624075824.2601110-1-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 992d0b14822e..83f8deefd03b 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -3745,6 +3745,79 @@ handle_failed_sync(struct r5conf *conf, struct stripe_head *sh, md_sync_error(conf->mddev); } +/* + * handle_failed_reshape - handle failed stripes when reshape failed and + * degraded devices >= max_degraded + * + * handle following kinds of stripe: + * 1. cleanup the following kinds of destination stripe: + * - new regions past the old end of the array, zero-filled in place, + * requires no source data. + * (STRIPE_EXPANDING | STRIPE_EXPAND_READY) + * - prepare source data chunks already done, and writeout failed + * (STRIPE_EXPAND_READY) + * 2. dest stripes that need source data (STRIPE_EXPANDING, no STRIPE_HANDLE) + * - these kind of stripes sit idle in the stripe cache and are never seen + * by handle_stripe(). So clean up indirectly when their source stripe + * (type 3) is processed. + * 3. src stripes (STRIPE_EXPAND_SOURCE) + * - hit handle_stripe() after their member disks are marked Faulty. + * - clear STRIPE_EXPAND_SOURCE, finds and cleanup all dependent destination + * stripes that were waiting for data. + * - walks the source's data disks, compute the corresponding destination + * sector, looks up the destination stripe, and do cleanup(clear flags, + * dec counters, call md_done_sync()) + */ +static void handle_failed_reshape(struct r5conf *conf, struct stripe_head *sh, + struct stripe_head_state *s) +{ + int i; + bool was_expanding = test_and_clear_bit(STRIPE_EXPANDING, &sh->state); + bool was_ready = test_and_clear_bit(STRIPE_EXPAND_READY, &sh->state); + + if (was_expanding || was_ready) { + atomic_dec(&conf->reshape_stripes); + wake_up(&conf->wait_for_reshape); + md_done_sync(conf->mddev, RAID5_STRIPE_SECTORS(conf)); + } + + s->expanded = 0; + s->expanding = 0; + + /* release the destination stripes that are waiting to be filled */ + if (test_and_clear_bit(STRIPE_EXPAND_SOURCE, &sh->state)) { + for (i = 0; i < sh->disks; i++) { + int dd_idx; + struct stripe_head *sh2; + sector_t bn, sec; + + if (i == sh->pd_idx) + continue; + if (conf->level == 6 && i == sh->qd_idx) + continue; + + bn = raid5_compute_blocknr(sh, i, 1); + sec = raid5_compute_sector(conf, bn, 0, &dd_idx, NULL); + sh2 = raid5_get_active_stripe(conf, NULL, sec, + R5_GAS_NOBLOCK | + R5_GAS_NOQUIESCE); + if (!sh2) + continue; + + if (test_and_clear_bit(STRIPE_EXPANDING, &sh2->state)) { + atomic_dec(&conf->reshape_stripes); + wake_up(&conf->wait_for_reshape); + md_done_sync(conf->mddev, + RAID5_STRIPE_SECTORS(conf)); + } + + clear_bit(STRIPE_EXPAND_READY, &sh2->state); + + raid5_release_stripe(sh2); + } + } +} + static int want_replace(struct stripe_head *sh, int disk_idx) { struct md_rdev *rdev; @@ -5025,6 +5098,8 @@ static void handle_stripe(struct stripe_head *sh) handle_failed_stripe(conf, sh, &s, disks); if (s.syncing + s.replacing) handle_failed_sync(conf, sh, &s); + if (s.expanding + s.expanded) + handle_failed_reshape(conf, sh, &s); } /* Now we check to see if any write operations have recently From a47431dfb3538a1485f65b68a0605a05307b5b2d Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Sat, 27 Jun 2026 18:25:19 +0800 Subject: [PATCH 06/53] md/raid5: protect lockless recovery_offset accesses during reshape During reshape: - reshape_request() advances rdev->recovery_offset for non-In_sync devices locklessly. - analyse_stripe() reads rdev->recovery_offset locklessly to decide: a. use a replacement device to read ? b. a device can already be treated as in-sync for the current stripe ? one possible scenario is: CPU1 CPU2 reshape_request() -> mddev->curr_resync_completed = sector_nr -> if (!mddev->reshape_backwards) -> rdev->recovery_offset = sector_nr analyse_stripe(sh) -> rdev = conf->disks[i].replacement -> if (rdev->recovery_offset >= sh->sector + stripe_sectors) set_bit(R5_ReadRepl) -> or -> if (sh->sector + stripe_sectors <= rdev->recovery_offset) set_bit(R5_Insync) And it could be: - reading from a replacement before it is recovered far enough; or - treating a not-yet-recovered device as in-sync for the current stripe. Fixes: db0505d32066 ("md: be cautious about using ->curr_resync_completed for ->recovery_offset") The race report: ================================================================== BUG: KCSAN: data-race in ops_run_io / reshape_request write to 0xffff8bdee168b270 of 8 bytes by task 1704 on cpu 10: reshape_request+0x1292/0x17b0 raid5_sync_request+0x815/0xa00 md_do_sync.cold+0xf8d/0x1516 [......] read to 0xffff8bdee168b270 of 8 bytes by task 1696 on cpu 9: ops_run_io+0xc25/0x1960 handle_stripe+0x2273/0x4570 handle_active_stripes.isra.0+0x6e0/0xa50 raid5d+0x7d5/0xb90 [......] value changed: 0x0000000000091a00 -> 0x0000000000091b00 ================================================================== Signed-off-by: Chen Cheng Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260627102519.136940-1-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 50 +++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 83f8deefd03b..4f967574bb1f 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -3824,11 +3824,10 @@ static int want_replace(struct stripe_head *sh, int disk_idx) int rv = 0; rdev = sh->raid_conf->disks[disk_idx].replacement; - if (rdev - && !test_bit(Faulty, &rdev->flags) - && !test_bit(In_sync, &rdev->flags) - && (rdev->recovery_offset <= sh->sector - || rdev->mddev->resync_offset <= sh->sector)) + if (rdev && !test_bit(Faulty, &rdev->flags) && + !test_bit(In_sync, &rdev->flags) && + (READ_ONCE(rdev->recovery_offset) <= sh->sector || + rdev->mddev->resync_offset <= sh->sector)) rv = 1; return rv; } @@ -4745,7 +4744,8 @@ static void analyse_stripe(struct stripe_head *sh, struct stripe_head_state *s) */ rdev = conf->disks[i].replacement; if (rdev && !test_bit(Faulty, &rdev->flags) && - rdev->recovery_offset >= sh->sector + RAID5_STRIPE_SECTORS(conf) && + READ_ONCE(rdev->recovery_offset) >= + sh->sector + RAID5_STRIPE_SECTORS(conf) && !rdev_has_badblock(rdev, sh->sector, RAID5_STRIPE_SECTORS(conf))) set_bit(R5_ReadRepl, &dev->flags); @@ -4787,7 +4787,7 @@ static void analyse_stripe(struct stripe_head *sh, struct stripe_head_state *s) } else if (test_bit(In_sync, &rdev->flags)) set_bit(R5_Insync, &dev->flags); else if (sh->sector + RAID5_STRIPE_SECTORS(conf) <= - rdev->recovery_offset) { + READ_ONCE(rdev->recovery_offset)) { /* * in sync if: * - normal IO, or @@ -5533,13 +5533,13 @@ static int raid5_read_one_chunk(struct mddev *mddev, struct bio *raid_bio) rdev = conf->disks[dd_idx].replacement; if (!rdev || test_bit(Faulty, &rdev->flags) || - rdev->recovery_offset < end_sector) { + READ_ONCE(rdev->recovery_offset) < end_sector) { rdev = conf->disks[dd_idx].rdev; if (!rdev) return 0; if (test_bit(Faulty, &rdev->flags) || !(test_bit(In_sync, &rdev->flags) || - rdev->recovery_offset >= end_sector)) + READ_ONCE(rdev->recovery_offset) >= end_sector)) return 0; } @@ -6502,8 +6502,8 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk if (rdev->raid_disk >= 0 && !test_bit(Journal, &rdev->flags) && !test_bit(In_sync, &rdev->flags) && - rdev->recovery_offset < sector_nr) - rdev->recovery_offset = sector_nr; + READ_ONCE(rdev->recovery_offset) < sector_nr) + WRITE_ONCE(rdev->recovery_offset, sector_nr); conf->reshape_checkpoint = jiffies; set_bit(MD_SB_CHANGE_DEVS, &mddev->sb_flags); @@ -6611,8 +6611,8 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk if (rdev->raid_disk >= 0 && !test_bit(Journal, &rdev->flags) && !test_bit(In_sync, &rdev->flags) && - rdev->recovery_offset < sector_nr) - rdev->recovery_offset = sector_nr; + READ_ONCE(rdev->recovery_offset) < sector_nr) + WRITE_ONCE(rdev->recovery_offset, sector_nr); conf->reshape_checkpoint = jiffies; set_bit(MD_SB_CHANGE_DEVS, &mddev->sb_flags); md_wakeup_thread(mddev->thread); @@ -8133,9 +8133,9 @@ static int raid5_run(struct mddev *mddev) /* Hack because v0.91 doesn't store recovery_offset properly. */ if (mddev->major_version == 0 && mddev->minor_version > 90) - rdev->recovery_offset = reshape_offset; + WRITE_ONCE(rdev->recovery_offset, reshape_offset); - if (rdev->recovery_offset < reshape_offset) { + if (READ_ONCE(rdev->recovery_offset) < reshape_offset) { /* We need to check old and new layout */ if (!only_parity(rdev->raid_disk, conf->algorithm, @@ -8290,10 +8290,10 @@ static int raid5_spare_active(struct mddev *mddev) for (i = 0; i < conf->raid_disks; i++) { rdev = conf->disks[i].rdev; replacement = conf->disks[i].replacement; - if (replacement - && replacement->recovery_offset == MaxSector - && !test_bit(Faulty, &replacement->flags) - && !test_and_set_bit(In_sync, &replacement->flags)) { + if (replacement && + READ_ONCE(replacement->recovery_offset) == MaxSector && + !test_bit(Faulty, &replacement->flags) && + !test_and_set_bit(In_sync, &replacement->flags)) { /* Replacement has just become active. */ if (!rdev || !test_and_clear_bit(In_sync, &rdev->flags)) @@ -8308,10 +8308,10 @@ static int raid5_spare_active(struct mddev *mddev) rdev->sysfs_state); } sysfs_notify_dirent_safe(replacement->sysfs_state); - } else if (rdev - && rdev->recovery_offset == MaxSector - && !test_bit(Faulty, &rdev->flags) - && !test_and_set_bit(In_sync, &rdev->flags)) { + } else if (rdev && + READ_ONCE(rdev->recovery_offset) == MaxSector && + !test_bit(Faulty, &rdev->flags) && + !test_and_set_bit(In_sync, &rdev->flags)) { count++; sysfs_notify_dirent_safe(rdev->sysfs_state); } @@ -8680,7 +8680,7 @@ static int raid5_start_reshape(struct mddev *mddev) >= conf->previous_raid_disks) set_bit(In_sync, &rdev->flags); else - rdev->recovery_offset = 0; + WRITE_ONCE(rdev->recovery_offset, 0); /* Failure here is OK */ sysfs_link_rdev(mddev, rdev); @@ -8732,7 +8732,7 @@ static void end_reshape(struct r5conf *conf) if (rdev->raid_disk >= 0 && !test_bit(Journal, &rdev->flags) && !test_bit(In_sync, &rdev->flags)) - rdev->recovery_offset = MaxSector; + WRITE_ONCE(rdev->recovery_offset, MaxSector); spin_unlock_irq(&conf->device_lock); wake_up(&conf->wait_for_reshape); From 3fe5b7c9fb72ccc29bfd0f955b124892af7e3674 Mon Sep 17 00:00:00 2001 From: Abd-Alrhman Masalkhi Date: Sun, 28 Jun 2026 14:27:37 +0000 Subject: [PATCH 07/53] md: remove REQ_NOWAIT support from raid1/10/456 REQ_NOWAIT support in md personalities that can block internally is fundamentally incomplete. While reads can avoid some blocking paths, write requests can still encounter cases where one mirror succeeds while another returns -EAGAIN. At that point md cannot distinguish queue pressure from a real device failure, so it can neither record a bad block nor safely retry the write without REQ_NOWAIT, leaving mirrors with divergent data. Rather than continue advertising REQ_NOWAIT support for personalities that cannot implement it correctly, remove it from raid1, raid10 and raid456. Keep REQ_NOWAIT for linear and raid0, which only remap bios to their underlying devices; stacked limits will still clear the feature if any component device lacks REQ_NOWAIT support. Fixes: bf2c411bb1cf ("md: raid456 add nowait support") Fixes: c9aa889b035f ("md: raid10 add nowait support") Fixes: 5aa705039c4f ("md: raid1 add nowait support") Fixes: f51d46d0e7cb ("md: add support for REQ_NOWAIT") Suggested-by: Yu Kuai Signed-off-by: Abd-Alrhman Masalkhi Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260628142737.1051059-1-abd.masalkhi@gmail.com Signed-off-by: Yu Kuai --- drivers/md/md-bitmap.c | 9 +--- drivers/md/md-bitmap.h | 2 +- drivers/md/md-linear.c | 1 + drivers/md/md-llbitmap.c | 10 +--- drivers/md/md.c | 6 +-- drivers/md/raid0.c | 1 + drivers/md/raid1-10.c | 8 ++-- drivers/md/raid1.c | 97 +++++++++------------------------------ drivers/md/raid10.c | 98 +++++++++++----------------------------- drivers/md/raid5.c | 13 ------ 10 files changed, 60 insertions(+), 185 deletions(-) diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c index 0f02e2956398..7d778fe1c47c 100644 --- a/drivers/md/md-bitmap.c +++ b/drivers/md/md-bitmap.c @@ -2064,23 +2064,18 @@ static void bitmap_end_behind_write(struct mddev *mddev) bitmap->mddev->bitmap_info.max_write_behind); } -static bool bitmap_wait_behind_writes(struct mddev *mddev, bool nowait) +static void bitmap_wait_behind_writes(struct mddev *mddev) { struct bitmap *bitmap = mddev->bitmap; /* wait for behind writes to complete */ if (bitmap && atomic_read(&bitmap->behind_writes) > 0) { - if (nowait) - return false; - pr_debug("md:%s: behind writes in progress - waiting to stop.\n", mdname(mddev)); /* need to kick something here to make sure I/O goes? */ wait_event(bitmap->behind_wait, atomic_read(&bitmap->behind_writes) == 0); } - - return true; } static void bitmap_destroy(struct mddev *mddev) @@ -2090,7 +2085,7 @@ static void bitmap_destroy(struct mddev *mddev) if (!bitmap) /* there was no bitmap */ return; - bitmap_wait_behind_writes(mddev, false); + bitmap_wait_behind_writes(mddev); if (!test_bit(MD_SERIALIZE_POLICY, &mddev->flags)) mddev_destroy_serial_pool(mddev, NULL); diff --git a/drivers/md/md-bitmap.h b/drivers/md/md-bitmap.h index f46674bdfeb9..214f623c7e79 100644 --- a/drivers/md/md-bitmap.h +++ b/drivers/md/md-bitmap.h @@ -98,7 +98,7 @@ struct bitmap_operations { void (*start_behind_write)(struct mddev *mddev); void (*end_behind_write)(struct mddev *mddev); - bool (*wait_behind_writes)(struct mddev *mddev, bool nowait); + void (*wait_behind_writes)(struct mddev *mddev); md_bitmap_fn *start_write; md_bitmap_fn *end_write; diff --git a/drivers/md/md-linear.c b/drivers/md/md-linear.c index fdff250d0d51..73b367b61b87 100644 --- a/drivers/md/md-linear.c +++ b/drivers/md/md-linear.c @@ -71,6 +71,7 @@ static int linear_set_limits(struct mddev *mddev) int err; md_init_stacking_limits(&lim); + lim.features |= BLK_FEAT_NOWAIT; lim.max_hw_sectors = mddev->chunk_sectors; lim.logical_block_size = mddev->logical_block_size; lim.max_write_zeroes_sectors = mddev->chunk_sectors; diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c index 5a4e2abaa757..2a2b38c663c3 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -1574,19 +1574,13 @@ static void llbitmap_end_behind_write(struct mddev *mddev) wake_up(&llbitmap->behind_wait); } -static bool llbitmap_wait_behind_writes(struct mddev *mddev, bool nowait) +static void llbitmap_wait_behind_writes(struct mddev *mddev) { struct llbitmap *llbitmap = mddev->bitmap; - if (llbitmap && atomic_read(&llbitmap->behind_writes) > 0) { - if (nowait) - return false; - + if (llbitmap && atomic_read(&llbitmap->behind_writes) > 0) wait_event(llbitmap->behind_wait, atomic_read(&llbitmap->behind_writes) == 0); - } - - return true; } static ssize_t bits_show(struct mddev *mddev, char *page) diff --git a/drivers/md/md.c b/drivers/md/md.c index d1465bcd86c8..997c26568b9e 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -6283,7 +6283,7 @@ void md_init_stacking_limits(struct queue_limits *lim) { blk_set_stacking_limits(lim); lim->features = BLK_FEAT_WRITE_CACHE | BLK_FEAT_FUA | - BLK_FEAT_IO_STAT | BLK_FEAT_NOWAIT; + BLK_FEAT_IO_STAT; } EXPORT_SYMBOL_GPL(md_init_stacking_limits); @@ -6631,7 +6631,6 @@ int md_run(struct mddev *mddev) int err; struct md_rdev *rdev; struct md_personality *pers; - bool nowait = true; if (list_empty(&mddev->disks)) /* cannot run an array with no devices.. */ @@ -6702,7 +6701,6 @@ int md_run(struct mddev *mddev) } } sysfs_notify_dirent_safe(rdev->sysfs_state); - nowait = nowait && bdev_nowait(rdev->bdev); } pers = get_pers(mddev->level, mddev->clevel); @@ -7050,7 +7048,7 @@ EXPORT_SYMBOL_GPL(md_stop_writes); static void mddev_detach(struct mddev *mddev) { if (md_bitmap_enabled(mddev, false)) - mddev->bitmap_ops->wait_behind_writes(mddev, false); + mddev->bitmap_ops->wait_behind_writes(mddev); if (mddev->pers && mddev->pers->quiesce && !is_md_suspended(mddev)) { mddev->pers->quiesce(mddev, 1); mddev->pers->quiesce(mddev, 0); diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c index 2c000b3a5f49..35e103f0c2c3 100644 --- a/drivers/md/raid0.c +++ b/drivers/md/raid0.c @@ -385,6 +385,7 @@ static int raid0_set_limits(struct mddev *mddev) int err; md_init_stacking_limits(&lim); + lim.features |= BLK_FEAT_NOWAIT; lim.max_hw_sectors = mddev->chunk_sectors; lim.max_write_zeroes_sectors = mddev->chunk_sectors; lim.max_hw_wzeroes_unmap_sectors = mddev->chunk_sectors; diff --git a/drivers/md/raid1-10.c b/drivers/md/raid1-10.c index 56a56a4da4f8..3b0e230692ba 100644 --- a/drivers/md/raid1-10.c +++ b/drivers/md/raid1-10.c @@ -290,9 +290,8 @@ static inline bool raid1_should_read_first(struct mddev *mddev, } /* - * bio with REQ_RAHEAD or REQ_NOWAIT can fail at anytime, before such IO is - * submitted to the underlying disks, hence don't record badblocks or retry - * in this case. + * bio with REQ_RAHEAD can fail at anytime, before such IO is submitted to the + * underlying disks, hence don't record badblocks or retry in this case. * * BLK_STS_INVAL means the bio was not valid for the underlying device. This * is a user error, not a device failure, so retrying or recording bad blocks @@ -300,6 +299,5 @@ static inline bool raid1_should_read_first(struct mddev *mddev, */ static inline bool raid1_should_handle_error(struct bio *bio) { - return !(bio->bi_opf & (REQ_RAHEAD | REQ_NOWAIT)) && - bio->bi_status != BLK_STS_INVAL; + return !(bio->bi_opf & REQ_RAHEAD) && bio->bi_status != BLK_STS_INVAL; } diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 4d7f33a2faaa..4dfd95f28e7f 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -1053,10 +1053,8 @@ static void lower_barrier(struct r1conf *conf, sector_t sector_nr) wake_up(&conf->wait_barrier); } -static bool _wait_barrier(struct r1conf *conf, int idx, bool nowait) +static void _wait_barrier(struct r1conf *conf, int idx) { - bool ret = true; - /* * We need to increase conf->nr_pending[idx] very early here, * then raise_barrier() can be blocked when it waits for @@ -1087,7 +1085,7 @@ static bool _wait_barrier(struct r1conf *conf, int idx, bool nowait) */ if (!READ_ONCE(conf->array_frozen) && !atomic_read(&conf->barrier[idx])) - return ret; + return; /* * After holding conf->resync_lock, conf->nr_pending[idx] @@ -1106,26 +1104,18 @@ static bool _wait_barrier(struct r1conf *conf, int idx, bool nowait) wake_up_barrier(conf); /* Wait for the barrier in same barrier unit bucket to drop. */ - /* Return false when nowait flag is set */ - if (nowait) { - ret = false; - } else { - wait_event_lock_irq(conf->wait_barrier, - !conf->array_frozen && - !atomic_read(&conf->barrier[idx]), - conf->resync_lock); - atomic_inc(&conf->nr_pending[idx]); - } + wait_event_lock_irq(conf->wait_barrier, !conf->array_frozen && + !atomic_read(&conf->barrier[idx]), + conf->resync_lock); + atomic_inc(&conf->nr_pending[idx]); atomic_dec(&conf->nr_waiting[idx]); spin_unlock_irq(&conf->resync_lock); - return ret; } -static bool wait_read_barrier(struct r1conf *conf, sector_t sector_nr, bool nowait) +static void wait_read_barrier(struct r1conf *conf, sector_t sector_nr) { int idx = sector_to_idx(sector_nr); - bool ret = true; /* * Very similar to _wait_barrier(). The difference is, for read @@ -1137,7 +1127,7 @@ static bool wait_read_barrier(struct r1conf *conf, sector_t sector_nr, bool nowa atomic_inc(&conf->nr_pending[idx]); if (!READ_ONCE(conf->array_frozen)) - return ret; + return; spin_lock_irq(&conf->resync_lock); atomic_inc(&conf->nr_waiting[idx]); @@ -1149,27 +1139,19 @@ static bool wait_read_barrier(struct r1conf *conf, sector_t sector_nr, bool nowa wake_up_barrier(conf); /* Wait for array to be unfrozen */ - /* Return false when nowait flag is set */ - if (nowait) { - /* Return false when nowait flag is set */ - ret = false; - } else { - wait_event_lock_irq(conf->wait_barrier, - !conf->array_frozen, - conf->resync_lock); - atomic_inc(&conf->nr_pending[idx]); - } + wait_event_lock_irq(conf->wait_barrier, !conf->array_frozen, + conf->resync_lock); + atomic_inc(&conf->nr_pending[idx]); atomic_dec(&conf->nr_waiting[idx]); spin_unlock_irq(&conf->resync_lock); - return ret; } -static bool wait_barrier(struct r1conf *conf, sector_t sector_nr, bool nowait) +static void wait_barrier(struct r1conf *conf, sector_t sector_nr) { int idx = sector_to_idx(sector_nr); - return _wait_barrier(conf, idx, nowait); + _wait_barrier(conf, idx); } static void _allow_barrier(struct r1conf *conf, int idx) @@ -1344,7 +1326,6 @@ static void raid1_read_request(struct mddev *mddev, struct bio *bio, int max_sectors; int rdisk; bool r1bio_existed = !!r1_bio; - bool nowait = bio->bi_opf & REQ_NOWAIT; /* * An md cloned bio indicates we are in the error path. @@ -1364,16 +1345,7 @@ static void raid1_read_request(struct mddev *mddev, struct bio *bio, * Still need barrier for READ in case that whole * array is frozen. */ - if (!wait_read_barrier(conf, bio->bi_iter.bi_sector, nowait)) { - bio_wouldblock_error(bio); - - if (r1bio_existed) { - set_bit(R1BIO_Returned, &r1_bio->state); - raid_end_bio_io(r1_bio); - } - - return; - } + wait_read_barrier(conf, bio->bi_iter.bi_sector); if (!r1_bio) r1_bio = alloc_r1bio(mddev, bio); @@ -1408,14 +1380,10 @@ static void raid1_read_request(struct mddev *mddev, struct bio *bio, md_bitmap_enabled(mddev, false)) { /* * Reading from a write-mostly device must take care not to - * over-take any writes that are 'behind' - */ - mddev_add_trace_msg(mddev, "raid1 wait behind writes"); - if (!mddev->bitmap_ops->wait_behind_writes(mddev, nowait)) { - bio_wouldblock_error(bio); - set_bit(R1BIO_Returned, &r1_bio->state); - goto err_handle; - } + * over-take any writes that are 'behind' + */ + mddev_add_trace_msg(mddev, "raid1 wait behind writes"); + mddev->bitmap_ops->wait_behind_writes(mddev); } if (max_sectors < bio_sectors(bio)) { @@ -1437,7 +1405,6 @@ static void raid1_read_request(struct mddev *mddev, struct bio *bio, } read_bio = bio_alloc_clone(mirror->rdev->bdev, bio, gfp, &mddev->bio_set); - read_bio->bi_opf &= ~REQ_NOWAIT; r1_bio->bios[rdisk] = read_bio; read_bio->bi_iter.bi_sector = r1_bio->sector + @@ -1456,7 +1423,7 @@ static void raid1_read_request(struct mddev *mddev, struct bio *bio, raid_end_bio_io(r1_bio); } -static bool wait_blocked_rdev(struct mddev *mddev, struct bio *bio) +static void wait_blocked_rdev(struct mddev *mddev, struct bio *bio) { struct r1conf *conf = mddev->private; int disks = conf->raid_disks * 2; @@ -1476,9 +1443,6 @@ static bool wait_blocked_rdev(struct mddev *mddev, struct bio *bio) set_bit(BlockedBadBlocks, &rdev->flags); if (rdev_blocked(rdev)) { - if (bio->bi_opf & REQ_NOWAIT) - return false; - mddev_add_trace_msg(rdev->mddev, "raid1 wait rdev %d blocked", rdev->raid_disk); atomic_inc(&rdev->nr_pending); @@ -1486,8 +1450,6 @@ static bool wait_blocked_rdev(struct mddev *mddev, struct bio *bio) goto retry; } } - - return true; } static void raid1_start_write_behind(struct mddev *mddev, struct r1bio *r1_bio, @@ -1523,18 +1485,12 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio, unsigned long flags; int first_clone; bool write_behind = false; - bool nowait = bio->bi_opf & REQ_NOWAIT; bool is_discard = op_is_discard(bio->bi_opf); sector_t sector = bio->bi_iter.bi_sector; if (mddev_is_clustered(mddev) && mddev->cluster_ops->area_resyncing(mddev, WRITE, sector, bio_end_sector(bio))) { - - if (nowait) { - bio_wouldblock_error(bio); - return false; - } wait_event_idle(conf->wait_barrier, !mddev->cluster_ops->area_resyncing(mddev, WRITE, sector, @@ -1546,15 +1502,9 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio, * thread has put up a bar for new requests. * Continue immediately if no resync is active currently. */ - if (!wait_barrier(conf, sector, nowait)) { - bio_wouldblock_error(bio); - return false; - } + wait_barrier(conf, sector); - if (!wait_blocked_rdev(mddev, bio)) { - bio_wouldblock_error(bio); - goto err_allow_barrier; - } + wait_blocked_rdev(mddev, bio); r1_bio = alloc_r1bio(mddev, bio); r1_bio->sectors = max_sectors; @@ -1683,7 +1633,6 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio, wait_for_serialization(rdev, r1_bio); } - mbio->bi_opf &= ~REQ_NOWAIT; r1_bio->bios[i] = mbio; mbio->bi_iter.bi_sector = sector + rdev->data_offset; @@ -1722,8 +1671,6 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio, } free_r1bio(r1_bio); - -err_allow_barrier: allow_barrier(conf, sector); return false; @@ -1852,7 +1799,7 @@ static void close_sync(struct r1conf *conf) int idx; for (idx = 0; idx < BARRIER_BUCKETS_NR; idx++) { - _wait_barrier(conf, idx, false); + _wait_barrier(conf, idx); _allow_barrier(conf, idx); } diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 0a3cfdd3f5df..4b702e832f06 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -1002,32 +1002,22 @@ static bool wait_barrier_nolock(struct r10conf *conf) return false; } -static bool wait_barrier(struct r10conf *conf, bool nowait) +static void wait_barrier(struct r10conf *conf) { - bool ret = true; - if (wait_barrier_nolock(conf)) - return true; + return; write_seqlock_irq(&conf->resync_lock); if (conf->barrier) { - /* Return false when nowait flag is set */ - if (nowait) { - ret = false; - } else { - conf->nr_waiting++; - mddev_add_trace_msg(conf->mddev, "raid10 wait barrier"); - wait_event_barrier(conf, stop_waiting_barrier(conf)); - conf->nr_waiting--; - } + conf->nr_waiting++; + mddev_add_trace_msg(conf->mddev, "raid10 wait barrier"); + wait_event_barrier(conf, stop_waiting_barrier(conf)); + conf->nr_waiting--; if (!conf->nr_waiting) wake_up(&conf->wait_barrier); } - /* Only increment nr_pending when we wait */ - if (ret) - atomic_inc(&conf->nr_pending); + atomic_inc(&conf->nr_pending); write_sequnlock_irq(&conf->resync_lock); - return ret; } static void allow_barrier(struct r10conf *conf) @@ -1119,30 +1109,22 @@ static void raid10_unplug(struct blk_plug_cb *cb, bool from_schedule) * currently. * 2. If IO spans the reshape position. Need to wait for reshape to pass. */ -static bool regular_request_wait(struct mddev *mddev, struct r10conf *conf, +static void regular_request_wait(struct mddev *mddev, struct r10conf *conf, struct bio *bio, sector_t sectors) { - /* Bail out if REQ_NOWAIT is set for the bio */ - if (!wait_barrier(conf, bio->bi_opf & REQ_NOWAIT)) { - bio_wouldblock_error(bio); - return false; - } + wait_barrier(conf); + while (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery) && bio->bi_iter.bi_sector < conf->reshape_progress && bio->bi_iter.bi_sector + sectors > conf->reshape_progress) { allow_barrier(conf); - if (bio->bi_opf & REQ_NOWAIT) { - bio_wouldblock_error(bio); - return false; - } mddev_add_trace_msg(conf->mddev, "raid10 wait reshape"); wait_event(conf->wait_barrier, conf->reshape_progress <= bio->bi_iter.bi_sector || conf->reshape_progress >= bio->bi_iter.bi_sector + sectors); - wait_barrier(conf, false); + wait_barrier(conf); } - return true; } static void raid10_read_request(struct mddev *mddev, struct bio *bio, @@ -1191,10 +1173,7 @@ static void raid10_read_request(struct mddev *mddev, struct bio *bio, } } - if (!regular_request_wait(mddev, conf, bio, r10_bio->sectors)) { - free_r10bio(r10_bio); - return; - } + regular_request_wait(mddev, conf, bio, r10_bio->sectors); rdev = read_balance(conf, r10_bio, &max_sectors); if (!rdev) { @@ -1215,7 +1194,7 @@ static void raid10_read_request(struct mddev *mddev, struct bio *bio, allow_barrier(conf); bio = bio_submit_split_bioset(bio, max_sectors, &conf->bio_split); - wait_barrier(conf, false); + wait_barrier(conf); if (!bio) { set_bit(R10BIO_Returned, &r10_bio->state); goto err_handle; @@ -1231,7 +1210,6 @@ static void raid10_read_request(struct mddev *mddev, struct bio *bio, r10_bio->master_bio = bio; } read_bio = bio_alloc_clone(rdev->bdev, bio, gfp, &mddev->bio_set); - read_bio->bi_opf &= ~REQ_NOWAIT; r10_bio->devs[slot].bio = read_bio; r10_bio->devs[slot].rdev = rdev; @@ -1265,7 +1243,6 @@ static void raid10_write_one_disk(struct mddev *mddev, struct r10bio *r10_bio, conf->mirrors[devnum].rdev; mbio = bio_alloc_clone(rdev->bdev, bio, GFP_NOIO, &mddev->bio_set); - mbio->bi_opf &= ~REQ_NOWAIT; if (replacement) r10_bio->devs[n_copy].repl_bio = mbio; else @@ -1344,7 +1321,7 @@ static void wait_blocked_dev(struct mddev *mddev, struct r10bio *r10_bio) "raid10 %s wait rdev %d blocked", __func__, blocked_rdev->raid_disk); md_wait_for_blocked_rdev(blocked_rdev, mddev); - wait_barrier(conf, false); + wait_barrier(conf); goto retry_wait; } } @@ -1361,28 +1338,14 @@ static bool raid10_write_request(struct mddev *mddev, struct bio *bio, mddev->cluster_ops->area_resyncing(mddev, WRITE, bio->bi_iter.bi_sector, bio_end_sector(bio)))) { - DEFINE_WAIT(w); - /* Bail out if REQ_NOWAIT is set for the bio */ - if (bio->bi_opf & REQ_NOWAIT) { - bio_wouldblock_error(bio); - return false; - } - for (;;) { - prepare_to_wait(&conf->wait_barrier, - &w, TASK_IDLE); - if (!mddev->cluster_ops->area_resyncing(mddev, WRITE, - bio->bi_iter.bi_sector, bio_end_sector(bio))) - break; - schedule(); - } - finish_wait(&conf->wait_barrier, &w); + wait_event_idle(conf->wait_barrier, + !mddev->cluster_ops->area_resyncing(mddev, WRITE, + bio->bi_iter.bi_sector, + bio_end_sector(bio))); } sectors = r10_bio->sectors; - if (!regular_request_wait(mddev, conf, bio, sectors)) { - free_r10bio(r10_bio); - return false; - } + regular_request_wait(mddev, conf, bio, sectors); if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery) && (mddev->reshape_backwards @@ -1395,11 +1358,6 @@ static bool raid10_write_request(struct mddev *mddev, struct bio *bio, set_mask_bits(&mddev->sb_flags, 0, BIT(MD_SB_CHANGE_DEVS) | BIT(MD_SB_CHANGE_PENDING)); md_wakeup_thread(mddev->thread); - if (bio->bi_opf & REQ_NOWAIT) { - allow_barrier(conf); - bio_wouldblock_error(bio); - return false; - } mddev_add_trace_msg(conf->mddev, "raid10 wait reshape metadata"); wait_event(mddev->sb_wait, @@ -1494,7 +1452,7 @@ static bool raid10_write_request(struct mddev *mddev, struct bio *bio, allow_barrier(conf); bio = bio_submit_split_bioset(bio, r10_bio->sectors, &conf->bio_split); - wait_barrier(conf, false); + wait_barrier(conf); if (!bio) { set_bit(R10BIO_Returned, &r10_bio->state); goto err_handle; @@ -1637,11 +1595,7 @@ static int raid10_handle_discard(struct mddev *mddev, struct bio *bio) if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery)) return -EAGAIN; - if (!wait_barrier(conf, bio->bi_opf & REQ_NOWAIT)) { - bio_wouldblock_error(bio); - md_write_end(mddev); - return 0; - } + wait_barrier(conf); /* * Check reshape again to avoid reshape happens after checking @@ -1692,7 +1646,7 @@ static int raid10_handle_discard(struct mddev *mddev, struct bio *bio) allow_barrier(conf); /* Resend the fist split part */ submit_bio_noacct(split); - wait_barrier(conf, false); + wait_barrier(conf); } div_u64_rem(bio_end, stripe_size, &remainder); if (remainder) { @@ -1712,7 +1666,7 @@ static int raid10_handle_discard(struct mddev *mddev, struct bio *bio) /* Resend the second split part */ submit_bio_noacct(bio); bio = split; - wait_barrier(conf, false); + wait_barrier(conf); } bio_start = bio->bi_iter.bi_sector; @@ -1870,7 +1824,7 @@ static int raid10_handle_discard(struct mddev *mddev, struct bio *bio) end_disk_offset += geo->stride; atomic_inc(&first_r10bio->remaining); raid_end_discard_bio(r10_bio); - wait_barrier(conf, false); + wait_barrier(conf); goto retry_discard; } @@ -2069,7 +2023,7 @@ static void print_conf(struct r10conf *conf) static void close_sync(struct r10conf *conf) { - wait_barrier(conf, false); + wait_barrier(conf); allow_barrier(conf); mempool_exit(&conf->r10buf_pool); @@ -4702,7 +4656,7 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, if (need_flush || time_after(jiffies, conf->reshape_checkpoint + 10*HZ)) { /* Need to update reshape_position in metadata */ - wait_barrier(conf, false); + wait_barrier(conf); mddev->reshape_position = conf->reshape_progress; if (mddev->reshape_backwards) mddev->curr_resync_completed = raid10_size(mddev, 0, 0) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 4f967574bb1f..552624bbec91 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -5793,10 +5793,6 @@ static void make_discard_request(struct mddev *mddev, struct bio *bi) struct bio *orig_bi = bi; int stripe_sectors; - /* We need to handle this when io_uring supports discard/trim */ - if (WARN_ON_ONCE(bi->bi_opf & REQ_NOWAIT)) - return; - if (mddev->reshape_position != MaxSector) /* Skip discard while reshape is happening */ return; @@ -6266,15 +6262,6 @@ static bool raid5_make_request(struct mddev *mddev, struct bio * bi) pr_debug("raid456: %s, logical %llu to %llu\n", __func__, bi->bi_iter.bi_sector, ctx->last_sector); - /* Bail out if conflicts with reshape and REQ_NOWAIT is set */ - if ((bi->bi_opf & REQ_NOWAIT) && - get_reshape_loc(mddev, conf, logical_sector) == LOC_INSIDE_RESHAPE) { - bio_wouldblock_error(bi); - if (rw == WRITE) - md_write_end(mddev); - mempool_free(ctx, conf->ctx_pool); - return true; - } md_account_bio(mddev, &bi); /* From c7d34d17ea43ebc86b45d439ebb435e11ca44bca Mon Sep 17 00:00:00 2001 From: Abd-Alrhman Masalkhi Date: Wed, 8 Jul 2026 11:20:03 +0000 Subject: [PATCH 08/53] md: recheck spare changes before starting sync remove_spares() and remove_and_add_spares() modify the array's rdev configuration. These operations are only safe after the array has been suspended. md_start_sync() checks whether spare configuration changes are needed before taking reconfig_mutex. However, the rdev state can change before the mutex is acquired, so the initial check can become stale. In that case, md_choose_sync_action() may remove or replace rdevs while normal I/O is still accessing them. The race can occur as follows: raid10d Worker Normal IO ____________ _______________________ ______________________ raid10_write_request() wait_blocked_dev() set Blocked set Faulty Skip Faulty rdev rrdev->nr_pending++ .repl_bio = bio removeable_rdev = false . array not suspended . lock mddev goto err_handle lock mddev (wait) . update sb . clear Blocked . . unlock mddev . lock mddev (acquires) remove_spares() removeable_rdev = true raid10_remove_disk() rdev = replacement replacement = NULL rdev_dec_pending(NULL) unlock mddev (NULL)->nr_pending-- In this case, rdev_dec_pending() is called with a NULL pointer, resulting in a NULL pointer dereference when attempting to decrement nr_pending. Fix this by suspending the array when spare configuration changes are needed, including for non-read-write arrays, and checking again after taking reconfig_mutex. If the array was not already suspended and a change is now needed, release the mutex, suspend the array, and reacquire the mutex before continuing. Fixes: bc08041b32ab ("md: suspend array in md_start_sync() if array need reconfiguration") Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260628142420.1051027-1-abd.masalkhi@gmail.com?part=3 Signed-off-by: Abd-Alrhman Masalkhi Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260708112003.474537-1-abd.masalkhi@gmail.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 997c26568b9e..dc848a24f592 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -10179,13 +10179,25 @@ static void md_start_sync(struct work_struct *ws) * If reshape is still in progress, spares won't be added or removed * from conf until reshape is done. */ - if (mddev->reshape_position == MaxSector && + if ((mddev->reshape_position == MaxSector || !md_is_rdwr(mddev)) && md_spares_need_change(mddev)) { suspend = true; mddev_suspend(mddev, false); } mddev_lock_nointr(mddev); + + /* + * The spare configuration can change before reconfig_mutex is acquired. + * Recheck while holding the lock and suspend if needed. + */ + if (!suspend && (mddev->reshape_position == MaxSector || !md_is_rdwr(mddev)) && + md_spares_need_change(mddev)) { + mddev_unlock(mddev); + mddev_suspend_and_lock_nointr(mddev); + suspend = true; + } + if (!md_is_rdwr(mddev)) { /* * On a read-only array we can: From 86d801e895b853667a886918998e1628fcc3174e Mon Sep 17 00:00:00 2001 From: Abd-Alrhman Masalkhi Date: Fri, 10 Jul 2026 10:15:16 +0000 Subject: [PATCH 09/53] md/raid1: restrict atomic write limits and handle runtime constraints Restrict the RAID1 atomic write limits by setting chunk_sectors to BARRIER_UNIT_SECTOR_SIZE so that atomic writes never straddle a barrier unit. A bio that passes block-layer validation may still become unserviceable within RAID1 due to bad blocks or write-behind constraints. In the former case, complete the bio with EIO. In the latter case, disable write-behind rather than failing the bio with EIO. Fixes: f2a38abf5f1c ("md/raid1: Atomic write support") Fixes: a4c55c902670 ("md/raid1: simplify raid1_write_request() error handling") Reviewed-by: John Garry Signed-off-by: Abd-Alrhman Masalkhi Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260710101521.1714-3-abd.masalkhi@gmail.com Signed-off-by: Yu Kuai --- drivers/md/raid1.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 4dfd95f28e7f..e9baba7b241f 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -1485,6 +1485,7 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio, unsigned long flags; int first_clone; bool write_behind = false; + bool atomic = bio->bi_opf & REQ_ATOMIC; bool is_discard = op_is_discard(bio->bi_opf); sector_t sector = bio->bi_iter.bi_sector; @@ -1531,6 +1532,8 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio, */ if (!is_discard && rdev && test_bit(WriteMostly, &rdev->flags)) write_behind = true; + if (atomic && max_sectors > BIO_MAX_VECS * (PAGE_SIZE >> 9)) + write_behind = false; r1_bio->bios[i] = NULL; if (!rdev || test_bit(Faulty, &rdev->flags)) @@ -1556,19 +1559,6 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio, if (is_bad) { int good_sectors; - /* - * We cannot atomically write this, so just - * error in that case. It could be possible to - * atomically write other mirrors, but the - * complexity of supporting that is not worth - * the benefit. - */ - if (bio->bi_opf & REQ_ATOMIC) { - bio->bi_status = BLK_STS_NOTSUPP; - bio_endio(bio); - goto err_dec_pending; - } - good_sectors = first_bad - sector; if (good_sectors < max_sectors) max_sectors = good_sectors; @@ -1589,6 +1579,11 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio, max_sectors = min_t(int, max_sectors, BIO_MAX_VECS * (PAGE_SIZE >> 9)); if (max_sectors < bio_sectors(bio)) { + if (atomic) { + bio_io_error(bio); + goto err_dec_pending; + } + bio = bio_submit_split_bioset(bio, max_sectors, &conf->bio_split); if (!bio) @@ -3177,6 +3172,7 @@ static int raid1_set_limits(struct mddev *mddev) md_init_stacking_limits(&lim); lim.max_write_zeroes_sectors = 0; lim.max_hw_wzeroes_unmap_sectors = 0; + lim.chunk_sectors = BARRIER_UNIT_SECTOR_SIZE; lim.logical_block_size = mddev->logical_block_size; lim.features |= BLK_FEAT_ATOMIC_WRITES; lim.features |= BLK_FEAT_PCI_P2PDMA; From 3409bf2f9678d769a4c33bd232a3571c51fac481 Mon Sep 17 00:00:00 2001 From: Abd-Alrhman Masalkhi Date: Fri, 10 Jul 2026 10:15:17 +0000 Subject: [PATCH 10/53] md/raid10: consistently fail atomic writes that require splitting RAID10 currently handles one badblock path explicitly by failing atomic writes with EIO. However, another badblock path can also reduce the writable range and force the bio through bio_submit_split_bioset(), which implicitly completes the bio with EINVAL. Fix this by handling atomic writes in the common split check. If RAID10 determines that an atomic write would require splitting, complete the bio with EIO. Fixes: a1d9b4fd42d9 ("md/raid10: Atomic write support") Signed-off-by: Abd-Alrhman Masalkhi Reviewed-by: Yu Kuai Reviewed-by: John Garry Link: https://patch.msgid.link/20260710101521.1714-4-abd.masalkhi@gmail.com Signed-off-by: Yu Kuai --- drivers/md/raid10.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 4b702e832f06..d08a4ad76115 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -1333,6 +1333,7 @@ static bool raid10_write_request(struct mddev *mddev, struct bio *bio, int i, k; sector_t sectors; int max_sectors; + bool atomic = bio->bi_opf & REQ_ATOMIC; if ((mddev_is_clustered(mddev) && mddev->cluster_ops->area_resyncing(mddev, WRITE, @@ -1420,16 +1421,6 @@ static bool raid10_write_request(struct mddev *mddev, struct bio *bio, if (is_bad) { int good_sectors; - /* - * We cannot atomically write this, so just - * error in that case. It could be possible to - * atomically write other mirrors, but the - * complexity of supporting that is not worth - * the benefit. - */ - if (bio->bi_opf & REQ_ATOMIC) - goto err_handle; - good_sectors = first_bad - dev_sector; if (good_sectors < max_sectors) max_sectors = good_sectors; @@ -1449,6 +1440,9 @@ static bool raid10_write_request(struct mddev *mddev, struct bio *bio, r10_bio->sectors = max_sectors; if (r10_bio->sectors < bio_sectors(bio)) { + if (atomic) + goto err_handle; + allow_barrier(conf); bio = bio_submit_split_bioset(bio, r10_bio->sectors, &conf->bio_split); From addb977450a662e1961d272b6ebfcb477d115044 Mon Sep 17 00:00:00 2001 From: Abd-Alrhman Masalkhi Date: Fri, 10 Jul 2026 10:15:18 +0000 Subject: [PATCH 11/53] md/raid10: remove unnecessary barrier around bio_submit_split_bioset() raid10_write_request() drops the barrier before calling bio_submit_split_bioset() and reacquires it afterwards. This is no longer necessary because the split bio cannot re-enter raid10_write_request() while the barrier is held. The allow_barrier()/wait_barrier() pair was introduced by commit e820d55cb99d ("md: fix raid10 hang issue caused by barrier") when submit_flushes() called md_handle_request() directly, allowing re-entry into raid10_write_request(). Since v5.2, submit_flushes() has instead gone through submit_bio(), eliminating that recursion. submit_flushes() was later removed entirely by commit b75197e86e6d ("md: Remove flush handling"). Currently, raid10_write_request() is only entered from the bio submission path, so the split bio submitted by bio_submit_split_bioset() cannot recurse back into wait_barrier(). Remove the redundant allow_barrier()/wait_barrier() pair around bio_submit_split_bioset(). Signed-off-by: Abd-Alrhman Masalkhi Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260710101521.1714-5-abd.masalkhi@gmail.com Signed-off-by: Yu Kuai --- drivers/md/raid10.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index d08a4ad76115..3e45013d9607 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -1443,10 +1443,8 @@ static bool raid10_write_request(struct mddev *mddev, struct bio *bio, if (atomic) goto err_handle; - allow_barrier(conf); bio = bio_submit_split_bioset(bio, r10_bio->sectors, &conf->bio_split); - wait_barrier(conf); if (!bio) { set_bit(R10BIO_Returned, &r10_bio->state); goto err_handle; From 6bc3deb600ee8c234204c6f4ea703ee04a11de2f Mon Sep 17 00:00:00 2001 From: Hiroshi Nishida Date: Fri, 10 Jul 2026 06:23:29 -0700 Subject: [PATCH 12/53] md: widen badblock sectors param from int to sector_t The badblocks core API -- badblocks_set(), badblocks_clear() and badblocks_check() -- and the is_badblock() helper all take the range length as sector_t. The md wrappers rdev_set_badblocks(), rdev_clear_badblocks() and rdev_has_badblock(), however, declared the same length as int, narrowing sector_t to int and back again in the middle of an otherwise 64-bit clean path. Change the sectors parameter to sector_t in these three wrappers so it matches the core API and is_badblock(). No functional change: current callers pass per-I/O or per-resync-chunk lengths well within int range. This just removes a gratuitous truncation point and keeps the type consistent end to end. Signed-off-by: Hiroshi Nishida Link: https://patch.msgid.link/20260710132329.7273-3-nishidafmly@gmail.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 4 ++-- drivers/md/md.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index dc848a24f592..c6b9b4705c94 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -10563,7 +10563,7 @@ EXPORT_SYMBOL(md_finish_reshape); /* Bad block management */ /* Returns true on success, false on failure */ -bool rdev_set_badblocks(struct md_rdev *rdev, sector_t s, int sectors, +bool rdev_set_badblocks(struct md_rdev *rdev, sector_t s, sector_t sectors, int is_new) { struct mddev *mddev = rdev->mddev; @@ -10603,7 +10603,7 @@ bool rdev_set_badblocks(struct md_rdev *rdev, sector_t s, int sectors, } EXPORT_SYMBOL_GPL(rdev_set_badblocks); -void rdev_clear_badblocks(struct md_rdev *rdev, sector_t s, int sectors, +void rdev_clear_badblocks(struct md_rdev *rdev, sector_t s, sector_t sectors, int is_new) { if (is_new) diff --git a/drivers/md/md.h b/drivers/md/md.h index d8daf0f75cbb..1b47af09c4e2 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -311,7 +311,7 @@ static inline int is_badblock(struct md_rdev *rdev, sector_t s, sector_t sectors } static inline int rdev_has_badblock(struct md_rdev *rdev, sector_t s, - int sectors) + sector_t sectors) { sector_t first_bad; sector_t bad_sectors; @@ -319,9 +319,9 @@ static inline int rdev_has_badblock(struct md_rdev *rdev, sector_t s, return is_badblock(rdev, s, sectors, &first_bad, &bad_sectors); } -extern bool rdev_set_badblocks(struct md_rdev *rdev, sector_t s, int sectors, +extern bool rdev_set_badblocks(struct md_rdev *rdev, sector_t s, sector_t sectors, int is_new); -extern void rdev_clear_badblocks(struct md_rdev *rdev, sector_t s, int sectors, +extern void rdev_clear_badblocks(struct md_rdev *rdev, sector_t s, sector_t sectors, int is_new); struct md_cluster_info; struct md_cluster_operations; From 798d79a7e4b04819a8ee575e5e1911215489e2ae Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Sat, 11 Jul 2026 18:03:50 +0800 Subject: [PATCH 13/53] md: suspend array when sync_action=reshape raid10 needs to resize/swap r10bio_pool when reshape changes raid_disks, and, don't let new requests keep allocating r10bio objects from the old pool while that transition is in progress. suspend and lock array before mddev_start_reshape(), and resume it on exit. Other sync_action ops are unchanged. Signed-off-by: Chen Cheng Link: https://patch.msgid.link/20260711100352.425177-2-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index c6b9b4705c94..addf3aeec85f 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -5263,21 +5263,28 @@ action_store(struct mddev *mddev, const char *page, size_t len) if (!mddev->pers || !mddev->pers->sync_request) return -EINVAL; + action = md_sync_action_by_name(page); + if (action == ACTION_RESHAPE) { + ret = mddev_suspend(mddev, true); + if (ret) + return ret; + } retry: if (work_busy(&mddev->sync_work)) flush_work(&mddev->sync_work); ret = mddev_lock(mddev); - if (ret) + if (ret) { + if (action == ACTION_RESHAPE) + mddev_resume(mddev); return ret; + } if (work_busy(&mddev->sync_work)) { mddev_unlock(mddev); goto retry; } - action = md_sync_action_by_name(page); - /* TODO: mdadm rely on "idle" to start sync_thread. */ if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery)) { switch (action) { @@ -5347,6 +5354,8 @@ action_store(struct mddev *mddev, const char *page, size_t len) out: mddev_unlock(mddev); + if (action == ACTION_RESHAPE) + mddev_resume(mddev); return ret; } From 8e9171decb5ed5c3fe0430a559f45ca556e94d26 Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Sat, 11 Jul 2026 18:03:51 +0800 Subject: [PATCH 14/53] md/raid10: resize r10bio_pool for reshape When reshape grows raid_disks, the pool must also switch to new geometry object size , and allocate a new geometry size pool and replace the old. But not for shrinking reshape, because regular I/O can still use the prev geo for sectors that have not crossed reshape_progress yet. Signed-off-by: Chen Cheng Link: https://patch.msgid.link/20260711100352.425177-3-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/raid10.c | 46 ++++++++++++++++++++++++++++++++------------- drivers/md/raid10.h | 2 +- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 3e45013d9607..3f2da07676e7 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -103,13 +103,23 @@ static inline struct r10bio *get_resync_r10bio(struct bio *bio) return get_resync_pages(bio)->raid_bio; } -static void * r10bio_pool_alloc(gfp_t gfp_flags, void *data) +static inline int calc_r10bio_size(unsigned int raid_disks) { - struct r10conf *conf = data; - int size = offsetof(struct r10bio, devs[conf->geo.raid_disks]); + return offsetof(struct r10bio, devs[raid_disks]); +} - /* allocate a r10bio with room for raid_disks entries in the - * bios array */ +static mempool_t *create_r10bio_pool(unsigned int raid_disks) +{ + int size = calc_r10bio_size(raid_disks); + + return mempool_create_kmalloc_pool(NR_RAID_BIOS, size); +} + +static struct r10bio *alloc_r10bio(unsigned int raid_disks, gfp_t gfp_flags) +{ + int size = calc_r10bio_size(raid_disks); + + /* allocate a r10bio sized for current geometry */ return kzalloc(size, gfp_flags); } @@ -137,7 +147,7 @@ static void * r10buf_pool_alloc(gfp_t gfp_flags, void *data) int nalloc, nalloc_rp; struct resync_pages *rps; - r10_bio = r10bio_pool_alloc(gfp_flags, conf); + r10_bio = alloc_r10bio(conf->geo.raid_disks, gfp_flags); if (!r10_bio) return NULL; @@ -277,7 +287,7 @@ static void free_r10bio(struct r10bio *r10_bio) struct r10conf *conf = r10_bio->mddev->private; put_all_bios(conf, r10_bio); - mempool_free(r10_bio, &conf->r10bio_pool); + mempool_free(r10_bio, conf->r10bio_pool); } static void put_buf(struct r10bio *r10_bio) @@ -1492,7 +1502,7 @@ static bool __make_request(struct mddev *mddev, struct bio *bio, int sectors) struct r10conf *conf = mddev->private; struct r10bio *r10_bio; - r10_bio = mempool_alloc(&conf->r10bio_pool, GFP_NOIO); + r10_bio = mempool_alloc(conf->r10bio_pool, GFP_NOIO); r10_bio->master_bio = bio; r10_bio->sectors = sectors; @@ -1688,7 +1698,7 @@ static int raid10_handle_discard(struct mddev *mddev, struct bio *bio) (last_stripe_index << geo->chunk_shift); retry_discard: - r10_bio = mempool_alloc(&conf->r10bio_pool, GFP_NOIO); + r10_bio = mempool_alloc(conf->r10bio_pool, GFP_NOIO); r10_bio->mddev = mddev; r10_bio->state = 0; r10_bio->sectors = 0; @@ -3790,7 +3800,7 @@ static void raid10_free_conf(struct r10conf *conf) if (!conf) return; - mempool_exit(&conf->r10bio_pool); + mempool_destroy(conf->r10bio_pool); kfree(conf->mirrors); kfree(conf->mirrors_old); kfree(conf->mirrors_new); @@ -3837,9 +3847,8 @@ static struct r10conf *setup_conf(struct mddev *mddev) conf->geo = geo; conf->copies = copies; - err = mempool_init(&conf->r10bio_pool, NR_RAID_BIOS, r10bio_pool_alloc, - rbio_pool_free, conf); - if (err) + conf->r10bio_pool = create_r10bio_pool(conf->geo.raid_disks); + if (!conf->r10bio_pool) goto out; err = bioset_init(&conf->bio_split, BIO_POOL_SIZE, 0, 0); @@ -4333,6 +4342,7 @@ static int raid10_start_reshape(struct mddev *mddev) struct md_rdev *rdev; int spares = 0; int ret; + mempool_t *new_pool = NULL; if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery)) return -EBUSY; @@ -4369,6 +4379,11 @@ static int raid10_start_reshape(struct mddev *mddev) return -EINVAL; conf->offset_diff = min_offset_diff; + if (mddev->delta_disks > 0) { + new_pool = create_r10bio_pool(new.raid_disks); + if (!new_pool) + return -ENOMEM; + } spin_lock_irq(&conf->device_lock); if (conf->mirrors_new) { memcpy(conf->mirrors_new, conf->mirrors, @@ -4469,6 +4484,10 @@ static int raid10_start_reshape(struct mddev *mddev) mddev->raid_disks = conf->geo.raid_disks; mddev->reshape_position = conf->reshape_progress; set_bit(MD_SB_CHANGE_DEVS, &mddev->sb_flags); + if (new_pool) { + mempool_destroy(conf->r10bio_pool); + conf->r10bio_pool = new_pool; + } clear_bit(MD_RECOVERY_SYNC, &mddev->recovery); clear_bit(MD_RECOVERY_CHECK, &mddev->recovery); @@ -4491,6 +4510,7 @@ static int raid10_start_reshape(struct mddev *mddev) conf->reshape_safe = MaxSector; mddev->reshape_position = MaxSector; spin_unlock_irq(&conf->device_lock); + mempool_destroy(new_pool); return ret; } diff --git a/drivers/md/raid10.h b/drivers/md/raid10.h index ec79d87fb92f..b711626a5db7 100644 --- a/drivers/md/raid10.h +++ b/drivers/md/raid10.h @@ -87,7 +87,7 @@ struct r10conf { */ wait_queue_head_t wait_barrier; - mempool_t r10bio_pool; + mempool_t *r10bio_pool; mempool_t r10buf_pool; struct page *tmppage; struct bio_set bio_split; From fe8d6b0187469c91d57dbc25ece5b503bfb3bc26 Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Sat, 11 Jul 2026 18:03:52 +0800 Subject: [PATCH 15/53] md/raid10: free r10bio before ending master_bio in raid_end_bio_io() and raid_end_discard_bio() origin flow: bio_endio(master_bio); /* may drop active_io to zero */ allow_barrier(conf); free_r10bio(r10_bio); /* reads conf->geo, returns to pool */ one scenario is: CPU A (softirq, raid_end_bio_io) CPU B (action_store) --> reshape ================================ =============================== bio_endio(master_bio) md_end_clone_io percpu_ref_put -> 0 wait_event wakeup, and, mddev_suspend return raid10_start_reshape: setup_geo(&conf->geo, new) ... mempool_destroy(old_pool) conf->r10bio_pool = new_pool allow_barrier(conf) free_r10bio(r10_bio) put_all_bios: for (i=0; igeo.raid_disks; i++) ==> old obj, new geo, OOB mempool_free(r10_bio, conf->r10bio_pool) ==> old-geometry obj freed into new pool so .. fix by reorder the flow: free_r10bio(r10_bio) bio_endio(master_bio) allow_barrier(conf) raid_end_discard_bio() is exactly the same. Signed-off-by: Chen Cheng Link: https://patch.msgid.link/20260711100352.425177-4-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/raid10.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 3f2da07676e7..ed3c6fbe65f7 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -331,20 +331,24 @@ static void raid_end_bio_io(struct r10bio *r10_bio) { struct bio *bio = r10_bio->master_bio; struct r10conf *conf = r10_bio->mddev->private; + bool returned = true; if (!test_and_set_bit(R10BIO_Returned, &r10_bio->state)) { if (!test_bit(R10BIO_Uptodate, &r10_bio->state)) bio->bi_status = BLK_STS_IOERR; - bio_endio(bio); + returned = false; } + free_r10bio(r10_bio); + + if (!returned) + bio_endio(bio); + /* * Wake up any possible resync thread that waits for the device * to go idle. */ allow_barrier(conf); - - free_r10bio(r10_bio); } /* @@ -1537,9 +1541,11 @@ static void raid_end_discard_bio(struct r10bio *r10bio) free_r10bio(r10bio); r10bio = first_r10bio; } else { + struct bio *master_bio = r10bio->master_bio; + md_write_end(r10bio->mddev); - bio_endio(r10bio->master_bio); free_r10bio(r10bio); + bio_endio(master_bio); break; } } From 85764f475f3b3abd956bf8eaeaa643b367676cf4 Mon Sep 17 00:00:00 2001 From: Genjian Zhang Date: Sun, 12 Jul 2026 00:13:26 +0800 Subject: [PATCH 16/53] md/raid5: complete discard bios while reshape is active make_discard_request() returns without completing the bio when reshape is in progress. Discard callers block in submit_bio_wait() waiting for a completion that never arrives. The caller hangs in uninterruptible sleep, and this does not resolve when reshape finishes. Complete the bio with BLK_STS_AGAIN so userspace can retry after reshape, consistent with the existing policy of not processing discard during reshape. Tested on a loop-backed RAID5 array during mdadm --grow: without this patch, blkdiscard hangs in bio_await() and remains in uninterruptible sleep after md reports "reshape done"; with this patch it returns -EAGAIN instead. Signed-off-by: Genjian Zhang Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260711161326.962336-1-zhanggenjian@126.com Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 552624bbec91..e5348cebf12d 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -5794,8 +5794,7 @@ static void make_discard_request(struct mddev *mddev, struct bio *bi) int stripe_sectors; if (mddev->reshape_position != MaxSector) - /* Skip discard while reshape is happening */ - return; + goto complete_again; if (!raid5_discard_limits(mddev, bi)) return; @@ -5882,6 +5881,11 @@ static void make_discard_request(struct mddev *mddev, struct bio *bi) } bio_endio(bi); + return; + +complete_again: + /* Skip discard while reshape is happening */ + bio_endio_status(bi, BLK_STS_AGAIN); } static bool ahead_of_reshape(struct mddev *mddev, sector_t sector, From 2911cd0a0f4366a7e06832bc5f0a7fdcc138e4dc Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Sat, 18 Jul 2026 11:42:36 +0800 Subject: [PATCH 17/53] md/bitmap: resume array on backlog_store() error path backlog_store() suspends the array before checking whether a write-mostly device exists. If no such device exists, the error path only unlocks reconfig_mutex and leaves the array suspended, blocking subsequent I/O. Use mddev_unlock_and_resume() to release both states. Fixes: 58226942ad3d ("md: use new apis to suspend array before mddev_create/destroy_serial_pool") Signed-off-by: Chen Cheng Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260718034236.4119093-1-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/md-bitmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c index 7d778fe1c47c..9730aab9bcff 100644 --- a/drivers/md/md-bitmap.c +++ b/drivers/md/md-bitmap.c @@ -2857,7 +2857,7 @@ backlog_store(struct mddev *mddev, const char *buf, size_t len) if (!has_write_mostly) { pr_warn_ratelimited("%s: can't set backlog, no write mostly device available\n", mdname(mddev)); - mddev_unlock(mddev); + mddev_unlock_and_resume(mddev); return -EINVAL; } From bace2010dd7ac07bc980575afb135c406730a7fe Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Sat, 18 Jul 2026 16:42:18 +0800 Subject: [PATCH 18/53] md: scope memalloc_noio to allocation critical sections Storing a memalloc_noio_save() token in mddev->noio_flags lets one task save the token and another task restore it. With concurrent suspend sysfs writes, task A can enter PF_MEMALLOC_NOIO, return to userspace still in that scope, and later task B can restore A's saved token. Avoid tying the token lifetime to mddev. Keep mddev_suspend() and mddev_resume() only responsible for array suspension, and enter PF_MEMALLOC_NOIO only in the MD paths that allocate memory after the array has been suspended. Restore the token before resuming the array. A reproducer repeatedly writes suspend_lo and suspend_hi from concurrent workers and checks each worker's /proc/self/stat flags before and after the sysfs write. Link: https://github.com/chencheng-fnnas/reproducer/blob/main/repro-md-noio-token-leak.sh Fixes: 78f57ef9d50a ("md: use memalloc scope APIs in mddev_suspend()/mddev_resume()") Signed-off-by: Chen Cheng Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260718084218.417895-1-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/md-bitmap.c | 3 +++ drivers/md/md.c | 53 ++++++++++++++++++++++++++++-------------- drivers/md/md.h | 1 - drivers/md/raid5.c | 14 +++++++---- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c index 9730aab9bcff..7e4fbca93ccb 100644 --- a/drivers/md/md-bitmap.c +++ b/drivers/md/md-bitmap.c @@ -2624,10 +2624,12 @@ static ssize_t location_store(struct mddev *mddev, const char *buf, size_t len) { int rv; + unsigned int noio_flags; rv = mddev_suspend_and_lock(mddev); if (rv) return rv; + noio_flags = memalloc_noio_save(); if (mddev->pers) { if (mddev->recovery || mddev->sync_thread) { @@ -2714,6 +2716,7 @@ location_store(struct mddev *mddev, const char *buf, size_t len) } rv = 0; out: + memalloc_noio_restore(noio_flags); mddev_unlock_and_resume(mddev); if (rv) return rv; diff --git a/drivers/md/md.c b/drivers/md/md.c index addf3aeec85f..3d6357f8fc04 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -233,23 +233,21 @@ static int rdev_need_serial(struct md_rdev *rdev) void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev) { int ret = 0; + unsigned int noio_flags; if (rdev && !rdev_need_serial(rdev) && !test_bit(CollisionCheck, &rdev->flags)) return; + noio_flags = memalloc_noio_save(); if (!rdev) ret = rdevs_init_serial(mddev); else ret = rdev_init_serial(rdev); if (ret) - return; + goto out; if (mddev->serial_info_pool == NULL) { - /* - * already in memalloc noio context by - * mddev_suspend() - */ mddev->serial_info_pool = mempool_create_kmalloc_pool(NR_SERIAL_INFOS, sizeof(struct serial_info)); @@ -258,6 +256,8 @@ void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev) pr_err("can't alloc memory pool for serialization\n"); } } +out: + memalloc_noio_restore(noio_flags); } /* @@ -516,9 +516,6 @@ int mddev_suspend(struct mddev *mddev, bool interruptible) */ WRITE_ONCE(mddev->suspended, mddev->suspended + 1); - /* restrict memory reclaim I/O during raid array is suspend */ - mddev->noio_flag = memalloc_noio_save(); - mutex_unlock(&mddev->suspend_mutex); return 0; } @@ -535,9 +532,6 @@ static void __mddev_resume(struct mddev *mddev, bool recovery_needed) return; } - /* entred the memalloc scope from mddev_suspend() */ - memalloc_noio_restore(mddev->noio_flag); - percpu_ref_resurrect(&mddev->active_io); wake_up(&mddev->sb_wait); @@ -4047,6 +4041,7 @@ level_store(struct mddev *mddev, const char *buf, size_t len) char clevel[16]; ssize_t rv; size_t slen = len; + unsigned int noio_flags; struct md_personality *pers, *oldpers; long level; void *priv, *oldpriv; @@ -4058,6 +4053,7 @@ level_store(struct mddev *mddev, const char *buf, size_t len) rv = mddev_suspend_and_lock(mddev); if (rv) return rv; + noio_flags = memalloc_noio_save(); if (mddev->pers == NULL) { memcpy(mddev->clevel, buf, slen); @@ -4233,6 +4229,7 @@ level_store(struct mddev *mddev, const char *buf, size_t len) md_new_event(); rv = len; out_unlock: + memalloc_noio_restore(noio_flags); mddev_unlock_and_resume(mddev); return rv; } @@ -4412,6 +4409,7 @@ static ssize_t raid_disks_store(struct mddev *mddev, const char *buf, size_t len) { unsigned int n; + unsigned int noio_flags; int err; err = kstrtouint(buf, 10, &n); @@ -4421,6 +4419,7 @@ raid_disks_store(struct mddev *mddev, const char *buf, size_t len) err = mddev_suspend_and_lock(mddev); if (err) return err; + noio_flags = memalloc_noio_save(); if (mddev->pers) { if (n != mddev->raid_disks) err = update_raid_disks(mddev, n); @@ -4444,6 +4443,7 @@ raid_disks_store(struct mddev *mddev, const char *buf, size_t len) } else mddev->raid_disks = n; out_unlock: + memalloc_noio_restore(noio_flags); mddev_unlock_and_resume(mddev); return err ? err : len; } @@ -4824,6 +4824,7 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len) int minor; dev_t dev; struct md_rdev *rdev; + unsigned int noio_flags; int err; if (!*buf || *e != ':' || !e[1] || e[1] == '\n') @@ -4839,6 +4840,7 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len) err = mddev_suspend_and_lock(mddev); if (err) return err; + noio_flags = memalloc_noio_save(); if (mddev->persistent) { rdev = md_import_device(dev, mddev->major_version, mddev->minor_version); @@ -4857,6 +4859,7 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len) rdev = md_import_device(dev, -1, -1); if (IS_ERR(rdev)) { + memalloc_noio_restore(noio_flags); mddev_unlock_and_resume(mddev); return PTR_ERR(rdev); } @@ -4864,6 +4867,7 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len) out: if (err) export_rdev(rdev); + memalloc_noio_restore(noio_flags); mddev_unlock_and_resume(mddev); if (!err) md_new_event(); @@ -8331,8 +8335,10 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode, unsigned int cmd, unsigned long arg) { int err = 0; + unsigned int noio_flags = 0; void __user *argp = (void __user *)arg; struct mddev *mddev = NULL; + bool suspend; err = md_ioctl_valid(cmd); if (err) @@ -8382,13 +8388,15 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode, if (!md_is_rdwr(mddev)) flush_work(&mddev->sync_work); - err = md_ioctl_need_suspend(cmd) ? mddev_suspend_and_lock(mddev) : - mddev_lock(mddev); + suspend = md_ioctl_need_suspend(cmd); + err = suspend ? mddev_suspend_and_lock(mddev) : mddev_lock(mddev); if (err) { pr_debug("md: ioctl lock interrupted, reason %d, cmd %d\n", err, cmd); goto out; } + if (suspend) + noio_flags = memalloc_noio_save(); if (cmd == SET_ARRAY_INFO) { err = __md_set_array_info(mddev, argp); @@ -8513,8 +8521,12 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode, err != -EINVAL) mddev->hold_active = 0; - md_ioctl_need_suspend(cmd) ? mddev_unlock_and_resume(mddev) : - mddev_unlock(mddev); + if (suspend) { + memalloc_noio_restore(noio_flags); + mddev_unlock_and_resume(mddev); + } else { + mddev_unlock(mddev); + } out: if (cmd == STOP_ARRAY_RO || (err && cmd == STOP_ARRAY)) @@ -10182,6 +10194,7 @@ static void md_start_sync(struct work_struct *ws) struct mddev *mddev = container_of(ws, struct mddev, sync_work); int spares = 0; bool suspend = false; + unsigned int noio_flags = 0; char *name; /* @@ -10192,6 +10205,7 @@ static void md_start_sync(struct work_struct *ws) md_spares_need_change(mddev)) { suspend = true; mddev_suspend(mddev, false); + noio_flags = memalloc_noio_save(); } mddev_lock_nointr(mddev); @@ -10205,6 +10219,7 @@ static void md_start_sync(struct work_struct *ws) mddev_unlock(mddev); mddev_suspend_and_lock_nointr(mddev); suspend = true; + noio_flags = memalloc_noio_save(); } if (!md_is_rdwr(mddev)) { @@ -10250,8 +10265,10 @@ static void md_start_sync(struct work_struct *ws) * https://bugzilla.kernel.org/show_bug.cgi?id=218200 * Therefore, use __mddev_resume(mddev, false). */ - if (suspend) + if (suspend) { + memalloc_noio_restore(noio_flags); __mddev_resume(mddev, false); + } md_wakeup_thread(mddev->sync_thread); sysfs_notify_dirent_safe(mddev->sysfs_action); md_new_event(); @@ -10270,8 +10287,10 @@ static void md_start_sync(struct work_struct *ws) * https://bugzilla.kernel.org/show_bug.cgi?id=218200 * Therefore, use __mddev_resume(mddev, false). */ - if (suspend) + if (suspend) { + memalloc_noio_restore(noio_flags); __mddev_resume(mddev, false); + } wake_up(&resync_wait); if (test_and_clear_bit(MD_RECOVERY_RECOVER, &mddev->recovery) && diff --git a/drivers/md/md.h b/drivers/md/md.h index 1b47af09c4e2..bb2eb5f39914 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -621,7 +621,6 @@ struct mddev { struct md_cluster_info *cluster_info; struct md_cluster_operations *cluster_ops; unsigned int good_device_nr; /* good device num within cluster raid */ - unsigned int noio_flag; /* for memalloc scope API */ /* * Temporarily store rdev that will be finally removed when diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index e5348cebf12d..e2c5a7072aca 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -2471,11 +2471,6 @@ static int scribble_alloc(struct raid5_percpu *percpu, sizeof(unsigned int) * (num + 2); void *scribble; - /* - * If here is in raid array suspend context, it is in memalloc noio - * context as well, there is no potential recursive memory reclaim - * I/Os with the GFP_KERNEL flag. - */ scribble = kvmalloc_array(cnt, obj_size, GFP_KERNEL); if (!scribble) return -ENOMEM; @@ -2490,6 +2485,7 @@ static int scribble_alloc(struct raid5_percpu *percpu, static int resize_chunks(struct r5conf *conf, int new_disks, int new_sectors) { unsigned long cpu; + unsigned int noio_flags; int err = 0; /* Never shrink. */ @@ -2498,6 +2494,7 @@ static int resize_chunks(struct r5conf *conf, int new_disks, int new_sectors) return 0; raid5_quiesce(conf->mddev, true); + noio_flags = memalloc_noio_save(); cpus_read_lock(); for_each_present_cpu(cpu) { @@ -2511,6 +2508,7 @@ static int resize_chunks(struct r5conf *conf, int new_disks, int new_sectors) } cpus_read_unlock(); + memalloc_noio_restore(noio_flags); raid5_quiesce(conf->mddev, false); if (!err) { @@ -7107,6 +7105,7 @@ raid5_store_stripe_size(struct mddev *mddev, const char *page, size_t len) { struct r5conf *conf; unsigned long new; + unsigned int noio_flags = 0; int err; int size; @@ -7147,6 +7146,7 @@ raid5_store_stripe_size(struct mddev *mddev, const char *page, size_t len) goto out_unlock; } + noio_flags = memalloc_noio_save(); mutex_lock(&conf->cache_size_mutex); size = conf->max_nr_stripes; @@ -7163,6 +7163,7 @@ raid5_store_stripe_size(struct mddev *mddev, const char *page, size_t len) mutex_unlock(&conf->cache_size_mutex); out_unlock: + memalloc_noio_restore(noio_flags); mddev_unlock_and_resume(mddev); return err ?: len; } @@ -9043,6 +9044,7 @@ static void *raid6_takeover(struct mddev *mddev) static int raid5_change_consistency_policy(struct mddev *mddev, const char *buf) { struct r5conf *conf; + unsigned int noio_flags; int err; err = mddev_suspend_and_lock(mddev); @@ -9054,6 +9056,7 @@ static int raid5_change_consistency_policy(struct mddev *mddev, const char *buf) return -ENODEV; } + noio_flags = memalloc_noio_save(); if (strncmp(buf, "ppl", 3) == 0) { /* ppl only works with RAID 5 */ if (!raid5_has_ppl(conf) && conf->level == 5) { @@ -9093,6 +9096,7 @@ static int raid5_change_consistency_policy(struct mddev *mddev, const char *buf) if (!err) md_update_sb(mddev, 1); + memalloc_noio_restore(noio_flags); mddev_unlock_and_resume(mddev); return err; From 35d522bd32462afcf1981dab6da8a9256c26c1e0 Mon Sep 17 00:00:00 2001 From: Coly Li Date: Mon, 20 Jul 2026 19:14:00 +0800 Subject: [PATCH 19/53] md: do overflow check for sb->bblog_shift in super_1_load() In super_1_load(), sb->bblog_shift is an __u8 type value loaded from on- disk superblock. It is used for badblocks API badblocks_set() by the following sequence, 1930 rdev->badblocks.shift = sb->bblog_shift; 1931 for (i = 0 ; i < (sectors << (9-3)) ; i++, bbp++) { 1932 u64 bb = le64_to_cpu(*bbp); 1933 int count = bb & (0x3ff); 1934 u64 sector = bb >> 10; 1935 sector <<= sb->bblog_shift; 1936 count <<= sb->bblog_shift; 1937 if (bb + 1 == 0) 1938 break; 1939 if (!badblocks_set(&rdev->badblocks, sector, count, 1)) 1940 return -EINVAL; 1941 } bb->bblog_shit is in range of 0-255, variable sector is 64bit width, for an invalid bb->bblog_shit, it is possible to make sector be overflowed by the following calculation, 1935 sector <<= sb->bblog_shift; Then in turn when call badblocks_set() at line 1939 with the invalid rdev->badblocks.shift set at line 1930, may result an overflow inside _badblocks_clear() in block/badblocks.c. Although there are many places to call badblocks APIs, the non-zero shift value is only used in super_1_load(), other places always use 0 as the shift value. Therefore it is unnecessary to do a general shift value overflow check inside badblock API, and just check here as the caller. This may avoid unnecessary check, make the badblocks API code more simple and elegant. Fixes: 2699b67223ac ("md: load/store badblock list from v1.x metadata") Fixes: 1726c7746783 ("badblocks: improve badblocks_set() for multiple ranges handling") Cc: stable@vger.kernel.org Cc: Ramesh Adhikari Signed-off-by: Coly Li Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260720111400.2120834-1-colyli@fygo.io Signed-off-by: Yu Kuai --- drivers/md/md.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/md/md.c b/drivers/md/md.c index 3d6357f8fc04..f280664a8b32 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -1914,6 +1914,13 @@ static int super_1_load(struct md_rdev *rdev, struct md_rdev *refdev, int minor_ rdev->bb_page, REQ_OP_READ, true)) return -EIO; bbp = (__le64 *)page_address(rdev->bb_page); + + /* check for badblocks api. */ + if (sb->bblog_shift >= BITS_PER_TYPE(sector_t)) { + pr_err("md: %pg: bogus bblog_shift %u for badblocks.\n", + rdev->bdev, sb->bblog_shift); + return -EINVAL; + } rdev->badblocks.shift = sb->bblog_shift; for (i = 0 ; i < (sectors << (9-3)) ; i++, bbp++) { u64 bb = le64_to_cpu(*bbp); From 140234b2380ffb8ffb0cfc46fee0e822f43adef7 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 23 Jul 2026 13:27:41 +0200 Subject: [PATCH 20/53] md/raid1: create serial pool adding rdev to array with serialize_policy=1 The following bug has been observed with kernel 7.1.3 after adding a new rdev to an existing RAID1 array with serialize_policy enabled: Oops: 0002 [#1] CPU: 0 UID: 0 PID: 19639 Comm: ext4lazyinit Not tainted 7.1.3-1-default RIP: _raw_spin_lock_irqsave+0x27/0x50 CR2: 0000000000004960 Call Trace: wait_for_serialization+0xb9/0x260 [raid1] raid1_make_request+0x762/0xaff [raid1] md_handle_request+0x1c9/0x2e0 [md_mod] The raid1.c code calls wait_for_serialization() if the MD_SERIALIZE_POLICY is set, and wait_for_serialization assumes that rdev->serial is initialized. Normally this will be the case for arrays that have the serialize_policy sysfs attribute set to 1. But when a new rdev is added to an existing array in bind_rdev_to_array(), the condition at mddev_create_serial_pool() causes creation of rdev->serial to be skipped. Fix it. Fixes: 69b00b5bb235 ("md: introduce a new struct for IO serialization") Signed-off-by: Martin Wilck Reviewed-by: Mykola Marzhan Link: https://patch.msgid.link/20260723112741.1206836-1-mwilck@suse.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index f280664a8b32..51b620edbef7 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -235,7 +235,8 @@ void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev) int ret = 0; unsigned int noio_flags; - if (rdev && !rdev_need_serial(rdev) && + if (!test_bit(MD_SERIALIZE_POLICY, &mddev->flags) && + rdev && !rdev_need_serial(rdev) && !test_bit(CollisionCheck, &rdev->flags)) return; From 2f6b2073ea631cb60d5176941b9815192b22a2b8 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:10 +0800 Subject: [PATCH 21/53] md/md-llbitmap: clear flush state after daemon flush llbitmap_flush() sets LLPageFlush on each bitmap page before it queues the daemon worker. The flag tells md_llbitmap_daemon_fn() to ignore the normal barrier_idle expiry check and clean the page immediately. The daemon only tested LLPageFlush. Once a page had been flushed explicitly, the flag stayed set, so later dirty bits on that page also bypassed barrier_idle and were cleaned the next time the daemon ran. That can make a new write look clean much earlier than the configured idle window. Consume LLPageFlush in md_llbitmap_daemon_fn() with test_and_clear_bit() and use the returned value for the current expiry check. The explicit flush still forces the current daemon pass, while later writes on the same page wait for barrier_idle again. This can be reproduced through normal sysfs operations: 1. Create a small RAID1 with --bitmap=lockless and --assume-clean. 2. Set llbitmap/daemon_sleep=1 and llbitmap/barrier_idle=10. 3. Toggle md/array_state from active to readonly and back to active to call llbitmap_flush() without destroying the in-memory bitmap. 4. Write one sector and read llbitmap/bits immediately, after 2 seconds, and after 12 seconds. On the bad kernel the dirty bit is already clean after 2 seconds. With this change it remains dirty until the barrier_idle window expires. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-2-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md-llbitmap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c index 2a2b38c663c3..71e9a21b98b2 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -1066,14 +1066,14 @@ static void md_llbitmap_daemon_fn(struct work_struct *work) for (idx = 0; idx < llbitmap->nr_pages; idx++) { struct llbitmap_page_ctl *pctl = llbitmap->pctl[idx]; + bool flush = test_and_clear_bit(LLPageFlush, &pctl->flags); if (idx > 0) { start = end + 1; end = min(end + PAGE_SIZE, llbitmap->chunks - 1); } - if (!test_bit(LLPageFlush, &pctl->flags) && - time_before(jiffies, pctl->expire)) { + if (!flush && time_before(jiffies, pctl->expire)) { restart = true; continue; } From 4b6cdc56c8412dc59904de2f915c72552b00bf6f Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:11 +0800 Subject: [PATCH 22/53] md/md-llbitmap: use GFP_NOIO for cache allocations llbitmap allocates its in-memory page cache and page-control structures from paths that can already be holding MD reconfiguration or bitmap state locks. For example, component_size_store() takes mddev_lock(), update_size() calls the personality resize method, and llbitmap_resize() can grow the page cache through llbitmap_prepare_resize(). Using GFP_KERNEL in those paths allows direct reclaim to enter filesystem or block I/O while MD resize state is locked. That can recurse back into the same array and wait on state that cannot make progress until the resize path finishes. Use GFP_NOIO for the llbitmap object, cached bitmap pages, page controls, page-control arrays, and percpu_ref initialization. Leave the explicit metadata zeroout path unchanged because it is intentional bitmap I/O rather than reclaim-driven allocation. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-3-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md-llbitmap.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c index 71e9a21b98b2..3cd8373bc9b2 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -521,7 +521,7 @@ static struct page *llbitmap_read_page(struct llbitmap *llbitmap, int idx) if (page) return page; - page = alloc_page(GFP_KERNEL | __GFP_ZERO); + page = alloc_page(GFP_NOIO | __GFP_ZERO); if (!page) return ERR_PTR(-ENOMEM); @@ -616,12 +616,12 @@ static int llbitmap_cache_pages(struct llbitmap *llbitmap) int i; llbitmap->pctl = kmalloc_array(nr_pages, sizeof(void *), - GFP_KERNEL | __GFP_ZERO); + GFP_NOIO | __GFP_ZERO); if (!llbitmap->pctl) return -ENOMEM; size = round_up(size, cache_line_size()); - pctl = kmalloc_array(nr_pages, size, GFP_KERNEL | __GFP_ZERO); + pctl = kmalloc_array(nr_pages, size, GFP_NOIO | __GFP_ZERO); if (!pctl) { kfree(llbitmap->pctl); return -ENOMEM; @@ -640,7 +640,7 @@ static int llbitmap_cache_pages(struct llbitmap *llbitmap) } if (percpu_ref_init(&pctl->active, active_release, - PERCPU_REF_ALLOW_REINIT, GFP_KERNEL)) { + PERCPU_REF_ALLOW_REINIT, GFP_NOIO)) { __free_page(page); llbitmap_free_pages(llbitmap); return -ENOMEM; @@ -1110,7 +1110,7 @@ static int llbitmap_create(struct mddev *mddev) if (ret) return ret; - llbitmap = kzalloc_obj(*llbitmap); + llbitmap = kzalloc_obj(*llbitmap, GFP_NOIO); if (!llbitmap) return -ENOMEM; From dbd21b489ac1afda73de401a446ecad2b53f413c Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:12 +0800 Subject: [PATCH 23/53] md/md-llbitmap: only end fully synced chunks llbitmap_cond_end_sync() is called with the sync thread's current sector. That value is an exclusive progress boundary: sectors below it have completed, but the llbitmap chunk containing it can still be in progress. The old code converted that sector directly to the last bit passed to BitmapActionEndsync. If resync had only advanced part-way into a large llbitmap chunk, the in-progress chunk was marked synced and flushed before the rest of the chunk was repaired. A later bitmap-assisted RAID1 resync could then skip the remainder of that chunk and leave stale mirror data behind. This can be reproduced without editing bitmap metadata by creating a large RAID1 with a lockless bitmap so llbitmap naturally selects a 524288-sector chunk (with the default 128 KiB bitmap area, an array just over 16 TiB is enough), making one mirror stale through the normal degraded write/re-add path, and throttling resync so the daemon checkpoint runs while resync is still inside the first chunk. On the bad kernel, bit 0 is ended early and a stale sector later in the same chunk is skipped. With this fix, bit 0 remains Syncing until resync reaches the next chunk boundary. Round the exclusive progress sector down to the nearest llbitmap chunk boundary and end only chunks strictly below that boundary. Also honor the force argument so callers that need an immediate checkpoint are not suppressed by daemon_sleep. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-4-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md-llbitmap.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c index 3cd8373bc9b2..948bf64c5ad2 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -1450,22 +1450,27 @@ static void llbitmap_cond_end_sync(struct mddev *mddev, sector_t sector, bool force) { struct llbitmap *llbitmap = mddev->bitmap; + sector_t complete; if (sector == 0) { llbitmap->last_end_sync = jiffies; return; } - if (time_before(jiffies, llbitmap->last_end_sync + - HZ * mddev->bitmap_info.daemon_sleep)) + if (!force && time_before(jiffies, llbitmap->last_end_sync + + HZ * mddev->bitmap_info.daemon_sleep)) return; wait_event(mddev->recovery_wait, !atomic_read(&mddev->recovery_active)); mddev->curr_resync_completed = sector; set_bit(MD_SB_CHANGE_CLEAN, &mddev->sb_flags); - llbitmap_state_machine(llbitmap, 0, sector >> llbitmap->chunkshift, - BitmapActionEndsync); + + complete = round_down(sector, llbitmap->chunksize); + if (complete) + llbitmap_state_machine(llbitmap, 0, + (complete >> llbitmap->chunkshift) - 1, + BitmapActionEndsync); __llbitmap_flush(mddev); llbitmap->last_end_sync = jiffies; From a41bb2ee1aca486853e84920565e50c15f86fa5d Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:13 +0800 Subject: [PATCH 24/53] md/raid5: reject zero-sector reshape chunks Sashiko reported that RAID5 can accept a reshape chunk size that becomes zero sectors. chunk_size_store() stores the sysfs byte value as n >> 9, so writing a value below 512 bytes sets mddev->new_chunk_sectors to zero. RAID5 then accepted that pending reshape geometry and raid5_start_reshape() installed it into conf->chunk_sectors, letting reshape code divide by zero. Reject zero-sector chunks both in check_reshape(), where normal sysfs requests are validated, and in raid5_start_reshape(), so assembly/resume paths also cannot install zero chunk geometry. Test script: in QEMU, create a plain three-disk RAID5 array with 64K chunks, write/read back a small pattern, write 1 to /sys/block/md0/md/chunk_size, add a fourth disk, and run mdadm --grow --raid-devices=4 --backup-file=... . The script scans dmesg for divide error/Oops/KASAN signatures. Bad kernel, eb29914412c3: echo 1 > /sys/block/md0/md/chunk_size mdadm --grow /dev/md0 --raid-devices=4 --backup-file=/root/md0-grow.bak Oops: divide error: 0000 [#1] SMP KASAN NOPTI RIP: raid5_get_active_stripe+0x863/0xc10 Call Trace: raid5_sync_request md_do_sync md_thread Kernel panic - not syncing: Fatal exception Fixed kernel: echo 1 > /sys/block/md0/md/chunk_size bash: echo: write error: Invalid argument chunk_write_rc=1 grow_rc=skipped RESULT: REJECTED_ZERO_CHUNK_NO_OOPS Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-5-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index e2c5a7072aca..d128d238e1da 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -8548,6 +8548,8 @@ static int check_reshape(struct mddev *mddev) return 0; /* nothing to do */ if (has_failed(conf)) return -EINVAL; + if (!mddev->new_chunk_sectors) + return -EINVAL; if (mddev->delta_disks < 0 && mddev->reshape_position == MaxSector) { /* We might be able to shrink, but the devices must * be made bigger first. @@ -8591,6 +8593,9 @@ static int raid5_start_reshape(struct mddev *mddev) if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery)) return -EBUSY; + if (!mddev->new_chunk_sectors) + return -EINVAL; + if (!check_stripe_cache(mddev)) return -ENOSPC; From 17ea021ae74987d6064c8195c4922fa025753892 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:14 +0800 Subject: [PATCH 25/53] md/raid5: round bitmap stripes with sector division 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 Link: https://lore.kernel.org/all/20260726185916.2223460-1-mykola@meshstor.io/ Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-6-yukuai@kernel.org Signed-off-by: Yu Kuai --- 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 d128d238e1da..2cc2546a29ae 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -6029,8 +6029,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); @@ -6048,8 +6051,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); From 2a79365b2278f16e163e4024086105693b421601 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:15 +0800 Subject: [PATCH 26/53] md: wait for behind writes before destroying bitmap __md_stop() destroyed the bitmap before calling mddev_detach(). That made mddev_detach() skip bitmap_ops->wait_behind_writes(), because the bitmap was already disconnected from mddev. This was still safe for the legacy bitmap because bitmap_destroy() waits for behind writes itself. llbitmap keeps that wait in its ->wait_behind_writes() operation instead, while ->destroy() tears down the llbitmap storage. With the old ordering, RAID1 behind-write completions could still run after llbitmap storage had been freed. Call mddev_detach() before md_bitmap_destroy() so the common detach path can wait for behind writes while the bitmap is still alive. Only destroy the bitmap after those users are gone. Fixes: 5ab829f1971d ("md/md-llbitmap: introduce new lockless bitmap") Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-7-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 51b620edbef7..b61040315aef 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -7085,8 +7085,8 @@ static void __md_stop(struct mddev *mddev) { struct md_personality *pers = mddev->pers; - md_bitmap_destroy(mddev); mddev_detach(mddev); + md_bitmap_destroy(mddev); spin_lock(&mddev->lock); mddev->pers = NULL; spin_unlock(&mddev->lock); From 45102fc8330525d35675b1c193242bba101df5ee Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:16 +0800 Subject: [PATCH 27/53] md: avoid stale clone I/O accounting timestamps md_clone_bio() always allocates the clone from mddev->io_clone_set, even when queue I/O stats are disabled. In that case it does not call bio_start_io_acct(), but it also left md_io_clone->start_time untouched. The clone private data comes from a mempool and can contain data from a previous user. md_end_clone_io() checks start_time to decide whether it needs to call bio_end_io_acct(), so a stale non-zero value can make the completion path end accounting that was never started for this bio. Set start_time to 0 in the no-stats branch. This keeps the end path tied to whether bio_start_io_acct() actually ran. Fixes: c687297b8845 ("md: also clone new io if io accounting is disabled") Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-8-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/md/md.c b/drivers/md/md.c index b61040315aef..58fb5453a819 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -9448,6 +9448,8 @@ static void md_clone_bio(struct mddev *mddev, struct bio **bio) md_io_clone->mddev = mddev; if (blk_queue_io_stat(bdev->bd_disk->queue)) md_io_clone->start_time = bio_start_io_acct(*bio); + else + md_io_clone->start_time = 0; if (bio_data_dir(*bio) == WRITE && md_bitmap_enabled(mddev, false)) { md_io_clone->offset = (*bio)->bi_iter.bi_sector; From 2116c2f0a0e547615886900e2ed8c529c016499b Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:17 +0800 Subject: [PATCH 28/53] md/md-llbitmap: prevent create failure bitmap UAF llbitmap_create() publishes mddev->bitmap before reading the bitmap superblock. This is needed because llbitmap_read_sb() can initialize a new bitmap and flush it through helpers that use mddev->bitmap. If llbitmap_read_sb() fails, the old cleanup dropped bitmap_info.mutex and freed llbitmap before clearing mddev->bitmap. Readers such as /proc/mdstat rely on bitmap_info.mutex to keep the bitmap pointer stable while collecting bitmap stats, so they could observe the stale pointer after the failed create path released the mutex. Clear mddev->bitmap while still holding bitmap_info.mutex, then free the failed llbitmap after dropping the mutex. This makes mutex-protected readers see either a live bitmap or no bitmap. Fixes: 5ab829f1971d ("md/md-llbitmap: introduce new lockless bitmap") Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-9-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md-llbitmap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c index 948bf64c5ad2..af80a630bd21 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -1126,10 +1126,11 @@ static int llbitmap_create(struct mddev *mddev) mutex_lock(&mddev->bitmap_info.mutex); mddev->bitmap = llbitmap; ret = llbitmap_read_sb(llbitmap); + if (ret) + mddev->bitmap = NULL; mutex_unlock(&mddev->bitmap_info.mutex); if (ret) { kfree(llbitmap); - mddev->bitmap = NULL; } return ret; From 5553d64e01d9a995be6c3de38501c6dd4ceede3b Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:18 +0800 Subject: [PATCH 29/53] md/md-llbitmap: stop daemon timer rearm on destroy llbitmap_destroy() deletes pending_timer before flushing md_llbitmap_io_wq. However, daemon_work can still be queued or running after the timer has been deleted, and the daemon path can arm pending_timer again when it finds dirty chunks that are not ready to flush yet. If that happens during teardown, pending_timer can remain armed after llbitmap is freed and later dereference freed memory. Add a BITMAP_SHUTDOWN bit to llbitmap->flags, set it before deleting the timer, and make the timer and daemon paths stop queueing or rearming work once teardown starts. Cancel daemon_work before flushing the shared workqueue so no already queued daemon instance can race with the free. Use timer_shutdown_sync() so a daemon instance that passed the shutdown check before teardown cannot rearm the timer afterward. BITMAP_SHUTDOWN is a runtime-only state. Mask it out when reading and updating the llbitmap superblock so the shutdown state is never loaded from disk or persisted to disk. Fixes: 5ab829f1971d ("md/md-llbitmap: introduce new lockless bitmap") Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-10-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md-bitmap.h | 1 + drivers/md/md-llbitmap.c | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/md/md-bitmap.h b/drivers/md/md-bitmap.h index 214f623c7e79..890276d9c66e 100644 --- a/drivers/md/md-bitmap.h +++ b/drivers/md/md-bitmap.h @@ -29,6 +29,7 @@ enum bitmap_state { BITMAP_FIRST_USE = 3, /* llbitmap is just created */ BITMAP_CLEAN = 4, /* llbitmap is created with assume_clean */ BITMAP_DAEMON_BUSY = 5, /* llbitmap daemon is not finished after daemon_sleep */ + BITMAP_SHUTDOWN = 6, /* llbitmap is being destroyed */ BITMAP_HOSTENDIAN =15, }; diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c index af80a630bd21..f5efecdb2cc4 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -789,6 +789,7 @@ static enum llbitmap_state llbitmap_state_machine(struct llbitmap *llbitmap, if (state == BitNeedSync || state == BitNeedSyncUnwritten) need_resync = !mddev->degraded; else if (state == BitDirty && + !test_bit(BITMAP_SHUTDOWN, &llbitmap->flags) && !timer_pending(&llbitmap->pending_timer)) mod_timer(&llbitmap->pending_timer, jiffies + mddev->bitmap_info.daemon_sleep * HZ); @@ -981,7 +982,7 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap) else mddev->bitmap_info.space = mddev->bitmap_info.default_space; } - llbitmap->flags = le32_to_cpu(sb->state); + llbitmap->flags = le32_to_cpu(sb->state) & ~BIT(BITMAP_SHUTDOWN); if (test_and_clear_bit(BITMAP_FIRST_USE, &llbitmap->flags)) { ret = llbitmap_init(llbitmap); goto out_put_page; @@ -1037,6 +1038,9 @@ static void llbitmap_pending_timer_fn(struct timer_list *pending_timer) struct llbitmap *llbitmap = container_of(pending_timer, struct llbitmap, pending_timer); + if (test_bit(BITMAP_SHUTDOWN, &llbitmap->flags)) + return; + if (work_busy(&llbitmap->daemon_work)) { pr_warn("md/llbitmap: %s daemon_work not finished in %lu seconds\n", mdname(llbitmap->mddev), @@ -1057,6 +1061,9 @@ static void md_llbitmap_daemon_fn(struct work_struct *work) bool restart; int idx; + if (test_bit(BITMAP_SHUTDOWN, &llbitmap->flags)) + return; + if (llbitmap->mddev->degraded) return; retry: @@ -1096,7 +1103,7 @@ static void md_llbitmap_daemon_fn(struct work_struct *work) goto retry; /* If some page is dirty but not expired, setup timer again */ - if (restart) + if (restart && !test_bit(BITMAP_SHUTDOWN, &llbitmap->flags)) mod_timer(&llbitmap->pending_timer, jiffies + llbitmap->mddev->bitmap_info.daemon_sleep * HZ); } @@ -1179,7 +1186,9 @@ static void llbitmap_destroy(struct mddev *mddev) mutex_lock(&mddev->bitmap_info.mutex); - timer_delete_sync(&llbitmap->pending_timer); + set_bit(BITMAP_SHUTDOWN, &llbitmap->flags); + timer_shutdown_sync(&llbitmap->pending_timer); + cancel_work_sync(&llbitmap->daemon_work); flush_workqueue(md_llbitmap_io_wq); flush_workqueue(md_llbitmap_unplug_wq); @@ -1523,7 +1532,7 @@ static void llbitmap_update_sb(void *data) sb = kmap_local_page(sb_page); sb->events = cpu_to_le64(mddev->events); - sb->state = cpu_to_le32(llbitmap->flags); + sb->state = cpu_to_le32(llbitmap->flags & ~BIT(BITMAP_SHUTDOWN)); sb->chunksize = cpu_to_le32(llbitmap->chunksize); sb->sync_size = cpu_to_le64(mddev->resync_max_sectors); sb->events_cleared = cpu_to_le64(llbitmap->events_cleared); From 87c10252e3d6c28769d93bea9e6a9b592e5809d1 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:19 +0800 Subject: [PATCH 30/53] md: skip bitmap accounting for empty write ranges 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. Personality bitmap mapping 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. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-11-yukuai@kernel.org Signed-off-by: Yu Kuai --- 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 58fb5453a819..f88952371b9b 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -9399,6 +9399,8 @@ static void md_bitmap_start(struct mddev *mddev, mddev->pers->bitmap_sector(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); } @@ -9419,7 +9421,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) @@ -9446,12 +9449,14 @@ 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); else md_io_clone->start_time = 0; - 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)); From ffd3e73d6e0c88f1cc7f5137978427a404fa5026 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:20 +0800 Subject: [PATCH 31/53] md: add helper to split bios at reshape offset Add mddev_bio_split_at_reshape_offset() so personalities can share reshape-offset bio splitting instead of open-coding the same boundary handling in multiple places. The helper first applies the optional max_sectors limit. If reshape is running and the bio crosses reshape_position, it further limits the front bio to the current reshape boundary so callers can account and submit one side of the reshape at a time. Snapshot reshape_position with READ_ONCE(). RAID5 and RAID10 update this field as reshape progresses, while the I/O path only needs one consistent decision point for the current bio. Using an explicit single load avoids a plain lockless access and prevents the compiler from refetching a different boundary while deciding whether and where to split. When a split is needed, bio_submit_split_bioset() submits the remainder and returns the front bio. Callers must therefore continue processing the returned bio, not the original pointer. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-12-yukuai@kernel.org Signed-off-by: Yu Kuai --- 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 f88952371b9b..f0eecdfff1cc 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -9388,6 +9388,45 @@ void md_submit_discard_bio(struct mddev *mddev, struct md_rdev *rdev, } EXPORT_SYMBOL_GPL(md_submit_discard_bio); +struct bio *mddev_bio_split_at_reshape_offset(struct mddev *mddev, + struct bio *bio, + unsigned int *max_sectors, + struct bio_set *bs) +{ + sector_t boundary; + sector_t start; + sector_t end; + unsigned int split_sectors; + + split_sectors = bio_sectors(bio); + if (max_sectors && *max_sectors && *max_sectors < split_sectors) + split_sectors = *max_sectors; + + if (!test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery)) + goto split; + + boundary = READ_ONCE(mddev->reshape_position); + start = bio->bi_iter.bi_sector; + end = bio_end_sector(bio); + if (start >= boundary || end <= boundary) + goto split; + + if (boundary - start < split_sectors) + split_sectors = boundary - start; + +split: + if (max_sectors) + *max_sectors = split_sectors; + if (split_sectors < bio_sectors(bio)) { + bio = bio_submit_split_bioset(bio, split_sectors, bs); + if (bio) + bio->bi_opf |= REQ_NOMERGE; + } + + return bio; +} +EXPORT_SYMBOL_GPL(mddev_bio_split_at_reshape_offset); + static void md_bitmap_start(struct mddev *mddev, struct md_io_clone *md_io_clone) { diff --git a/drivers/md/md.h b/drivers/md/md.h index bb2eb5f39914..8146a6f50a7d 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -920,6 +920,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); From 1ee6fef6e0dbff21fecfc1db05c79ede464e9500 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:21 +0800 Subject: [PATCH 32/53] md: add exact bitmap mapping and reshape hooks 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. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-13-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md-bitmap.c | 8 ++++++++ drivers/md/md-bitmap.h | 8 ++++++++ drivers/md/md-llbitmap.c | 8 ++++++++ drivers/md/md.c | 11 ++++++++--- drivers/md/md.h | 4 ++++ 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c index 7e4fbca93ccb..b7f0d4acce04 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) { @@ -3081,6 +3088,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 890276d9c66e..6478cf9d8816 100644 --- a/drivers/md/md-bitmap.h +++ b/drivers/md/md-bitmap.h @@ -94,6 +94,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 f5efecdb2cc4..1c361f5a97f4 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -1198,6 +1198,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) { @@ -1789,6 +1796,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 f0eecdfff1cc..538ba7bab060 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -9427,6 +9427,12 @@ 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) +{ + mddev->bitmap_ops->prepare_range(mddev, offset, sectors); +} + static void md_bitmap_start(struct mddev *mddev, struct md_io_clone *md_io_clone) { @@ -9434,9 +9440,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); if (!md_io_clone->sectors) return; diff --git a/drivers/md/md.h b/drivers/md/md.h index 8146a6f50a7d..b6d2e8929a0f 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -797,6 +797,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 { From 35320d21e8b90198e94b95a17813415265d5742f Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:22 +0800 Subject: [PATCH 33/53] md/md-llbitmap: track bitmap sync_size explicitly Track llbitmap's own sync_size instead of always using mddev->resync_max_sectors directly. This is the minimal bookkeeping needed before llbitmap can track old and new reshape geometry independently. Reviewed-by: Su Yue Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-14-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md-llbitmap.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c index 1c361f5a97f4..dfb1aff9f485 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 @@ -919,6 +921,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); @@ -939,6 +942,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; @@ -988,6 +992,14 @@ 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; + if (sync_size > mddev->resync_max_sectors) { + pr_err("md/llbitmap: %s: sync_size %llu exceeds array sync size %llu", + mdname(mddev), sync_size, mddev->resync_max_sectors); + goto out_put_page; + } chunksize = le32_to_cpu(sb->chunksize); if (!is_power_of_2(chunksize)) { pr_err("md/llbitmap: %s: chunksize not a power of 2", @@ -1023,8 +1035,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: @@ -1161,6 +1174,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; } @@ -1541,7 +1555,7 @@ static void llbitmap_update_sb(void *data) sb->events = cpu_to_le64(mddev->events); sb->state = cpu_to_le32(llbitmap->flags & ~BIT(BITMAP_SHUTDOWN)); 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); @@ -1559,6 +1573,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); From e9f0d66b5754888866b10fa0ff6c8642bb850028 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:23 +0800 Subject: [PATCH 34/53] md/md-llbitmap: allocate page controls independently 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 Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-15-yukuai@kernel.org Signed-off-by: Yu Kuai --- 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 dfb1aff9f485..5fec1db53436 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -512,13 +512,19 @@ static void llbitmap_write(struct llbitmap *llbitmap, enum llbitmap_state state, llbitmap_set_page_dirty(llbitmap, idx, bit, false); } +static unsigned int llbitmap_used_pages(struct llbitmap *llbitmap, + unsigned long chunks) +{ + return DIV_ROUND_UP(chunks + BITMAP_DATA_OFFSET, PAGE_SIZE); +} + static struct page *llbitmap_read_page(struct llbitmap *llbitmap, int idx) { struct mddev *mddev = llbitmap->mddev; struct page *page = NULL; struct md_rdev *rdev; - if (llbitmap->pctl && llbitmap->pctl[idx]) + if (llbitmap->pctl && idx < llbitmap->nr_pages && llbitmap->pctl[idx]) page = llbitmap->pctl[idx]->page; if (page) return page; @@ -526,6 +532,8 @@ static struct page *llbitmap_read_page(struct llbitmap *llbitmap, int idx) page = alloc_page(GFP_NOIO | __GFP_ZERO); if (!page) return ERR_PTR(-ENOMEM); + if (idx >= llbitmap_used_pages(llbitmap, llbitmap->chunks)) + return page; rdev_for_each(rdev, mddev) { sector_t sector; @@ -596,61 +604,78 @@ static void llbitmap_free_pages(struct llbitmap *llbitmap) for (i = 0; i < llbitmap->nr_pages; i++) { struct llbitmap_page_ctl *pctl = llbitmap->pctl[i]; - if (!pctl || !pctl->page) - break; - - __free_page(pctl->page); + if (!pctl) + continue; + if (pctl->page) + __free_page(pctl->page); percpu_ref_exit(&pctl->active); + kfree(pctl); } - kfree(llbitmap->pctl[0]); kfree(llbitmap->pctl); llbitmap->pctl = NULL; } -static int llbitmap_cache_pages(struct llbitmap *llbitmap) +static struct llbitmap_page_ctl * +llbitmap_alloc_page_ctl(struct llbitmap *llbitmap, int idx) { struct llbitmap_page_ctl *pctl; - unsigned int nr_pages = DIV_ROUND_UP(llbitmap->chunks + - BITMAP_DATA_OFFSET, PAGE_SIZE); + struct page *page; unsigned int size = struct_size(pctl, dirty, BITS_TO_LONGS( llbitmap->blocks_per_page)); + + size = round_up(size, cache_line_size()); + pctl = kzalloc(size, GFP_NOIO); + if (!pctl) + return ERR_PTR(-ENOMEM); + + page = llbitmap_read_page(llbitmap, idx); + + if (IS_ERR(page)) { + kfree(pctl); + return ERR_CAST(page); + } + + if (percpu_ref_init(&pctl->active, active_release, + PERCPU_REF_ALLOW_REINIT, GFP_NOIO)) { + __free_page(page); + kfree(pctl); + return ERR_PTR(-ENOMEM); + } + + pctl->page = page; + pctl->state = page_address(page); + init_waitqueue_head(&pctl->wait); + return pctl; +} + +static unsigned int llbitmap_reserved_pages(struct llbitmap *llbitmap) +{ + return DIV_ROUND_UP(llbitmap->mddev->bitmap_info.space << SECTOR_SHIFT, + PAGE_SIZE); +} + +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 = kmalloc_array(nr_pages, sizeof(void *), - GFP_NOIO | __GFP_ZERO); + llbitmap->pctl = kcalloc(nr_pages, sizeof(*llbitmap->pctl), GFP_NOIO); if (!llbitmap->pctl) return -ENOMEM; - size = round_up(size, cache_line_size()); - pctl = kmalloc_array(nr_pages, size, GFP_NOIO | __GFP_ZERO); - if (!pctl) { - kfree(llbitmap->pctl); - return -ENOMEM; - } - llbitmap->nr_pages = nr_pages; - for (i = 0; i < nr_pages; i++, pctl = (void *)pctl + size) { - struct page *page = llbitmap_read_page(llbitmap, i); + 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] = pctl; - - if (IS_ERR(page)) { + llbitmap->pctl[i] = NULL; llbitmap_free_pages(llbitmap); - return PTR_ERR(page); + return ret; } - - if (percpu_ref_init(&pctl->active, active_release, - PERCPU_REF_ALLOW_REINIT, GFP_NOIO)) { - __free_page(page); - llbitmap_free_pages(llbitmap); - return -ENOMEM; - } - - pctl->page = page; - pctl->state = page_address(page); - init_waitqueue_head(&pctl->wait); } return 0; @@ -924,7 +949,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; @@ -1038,7 +1063,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); From 1cbc6ea5fa6527853803345cb6099917b77b0c61 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:24 +0800 Subject: [PATCH 35/53] md/md-llbitmap: grow the page cache in place for reshape Use the page-control helpers to grow llbitmap's cached pages in place for resize and later reshape preparation, instead of rebuilding the whole cache. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-16-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md-llbitmap.c | 143 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 132 insertions(+), 11 deletions(-) diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c index 5fec1db53436..c34accb9233a 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_NOIO); + 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; @@ -1032,10 +1115,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; } @@ -1184,24 +1267,62 @@ static int llbitmap_create(struct mddev *mddev) static int llbitmap_resize(struct mddev *mddev, sector_t blocks, int chunksize) { struct llbitmap *llbitmap = mddev->bitmap; + sector_t old_blocks = llbitmap->sync_size; + unsigned long old_chunks = llbitmap->chunks; unsigned long chunks; + unsigned long cache_chunks; + int ret = 0; + unsigned long bitmap_chunksize; + bool reshape; + bool quiesced = false; if (chunksize == 0) chunksize = llbitmap->chunksize; - /* If there is enough space, leave the chunksize unchanged. */ - chunks = DIV_ROUND_UP_SECTOR_T(blocks, chunksize); - while (chunks > mddev->bitmap_info.space << SECTOR_SHIFT) { - chunksize = chunksize << 1; - chunks = DIV_ROUND_UP_SECTOR_T(blocks, chunksize); + bitmap_chunksize = chunksize; + llbitmap_calculate_chunks(mddev, blocks, &bitmap_chunksize, &chunks); + + reshape = mddev->delta_disks || mddev->new_level != mddev->level || + mddev->new_layout != mddev->layout || + mddev->new_chunk_sectors != mddev->chunk_sectors; + if (!reshape && bitmap_chunksize != llbitmap->chunksize) + return -EOPNOTSUPP; + if (blocks == old_blocks && chunks == llbitmap->chunks) + return 0; + + if (mddev->pers->quiesce) { + mddev->pers->quiesce(mddev, 1); + quiesced = true; } - llbitmap->chunkshift = ffz(~chunksize); - llbitmap->chunksize = chunksize; - llbitmap->chunks = chunks; - llbitmap->sync_size = blocks; + mutex_lock(&mddev->bitmap_info.mutex); + cache_chunks = reshape ? max(old_chunks, chunks) : chunks; + ret = llbitmap_prepare_resize(llbitmap, old_chunks, chunks, cache_chunks); + if (ret) + goto out; + if (reshape) { + llbitmap->chunks = max(old_chunks, chunks); + } else { + if (blocks < old_blocks && chunks < old_chunks) + llbitmap_mark_range(llbitmap, chunks, old_chunks - 1, + BitUnwritten); + mddev->bitmap_info.chunksize = bitmap_chunksize; + llbitmap->chunks = chunks; + llbitmap->sync_size = blocks; + llbitmap_update_sb(llbitmap); + } + __llbitmap_flush(mddev); + mutex_unlock(&mddev->bitmap_info.mutex); + if (quiesced) + mddev->pers->quiesce(mddev, 0); return 0; + +out: + mutex_unlock(&mddev->bitmap_info.mutex); + if (quiesced) + mddev->pers->quiesce(mddev, 0); + return ret; } static int llbitmap_load(struct mddev *mddev) From 3a92c67aef5aeb0ccf46cf291be87dac0485fd5f Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:25 +0800 Subject: [PATCH 36/53] md/md-llbitmap: track target reshape geometry fields Track llbitmap bookkeeping for the target reshape geometry while keeping a single live bitmap instance. Add the reshape geometry fields, refresh helper, and update the load and resize paths to keep the target geometry in sync. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-17-yukuai@kernel.org Signed-off-by: Yu Kuai --- 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 c34accb9233a..421a9a4ebbae 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; @@ -1030,6 +1066,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); @@ -1146,6 +1183,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: @@ -1302,6 +1340,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) @@ -1310,6 +1351,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); From b094fa9d322302afe65ef67cdd89fada6578320b Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:26 +0800 Subject: [PATCH 37/53] md/md-llbitmap: finish reshape geometry Commit the staged llbitmap geometry when reshape finishes. When assembling a stopped reshape, md_run() creates the bitmap before publishing mddev->pers. 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. Link: https://lore.kernel.org/all/20260726185916.2223460-1-mykola@meshstor.io/ Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-18-yukuai@kernel.org Signed-off-by: Yu Kuai --- 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 421a9a4ebbae..4e7070c14840 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -1371,11 +1371,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; } @@ -1709,6 +1718,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); @@ -2000,6 +2033,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, From 807757d4c3d0e93e88ddad48f34f7075118917cf Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:27 +0800 Subject: [PATCH 38/53] md/md-llbitmap: refuse reshape while llbitmap still needs sync Reject reshape when llbitmap still contains NeedSync or Syncing bits. This keeps reshape from starting until the current llbitmap state has been reconciled. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-19-yukuai@kernel.org Signed-off-by: Yu Kuai --- 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 4e7070c14840..698162517ae9 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -1718,6 +1718,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; @@ -2034,6 +2057,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, From 629c1659e90cdeb1b00c04c9e06028854d22cf2c Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:28 +0800 Subject: [PATCH 39/53] md/md-llbitmap: add reshape range mapping helpers 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. Link: https://lore.kernel.org/all/20260726185916.2223460-1-mykola@meshstor.io/ Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-20-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md-bitmap.c | 2 +- drivers/md/md-bitmap.h | 3 +- drivers/md/md-llbitmap.c | 139 +++++++++++++++++++++++++++++++++++---- drivers/md/md.c | 11 ++-- 4 files changed, 135 insertions(+), 20 deletions(-) diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c index b7f0d4acce04..b8325cb09a37 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 6478cf9d8816..b69c78174f02 100644 --- a/drivers/md/md-bitmap.h +++ b/drivers/md/md-bitmap.h @@ -97,7 +97,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 698162517ae9..1283ad737692 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -433,22 +434,28 @@ static void llbitmap_calculate_chunks(struct mddev *mddev, sector_t blocks, } } -static bool llbitmap_reshaping(struct llbitmap *llbitmap) -{ - return llbitmap->mddev->reshape_position != MaxSector; -} - static sector_t llbitmap_personality_sync_size(struct llbitmap *llbitmap, bool previous) { struct mddev *mddev = llbitmap->mddev; - if (!llbitmap_reshaping(llbitmap) || !mddev->private || !mddev->pers || + if (READ_ONCE(mddev->reshape_position) == MaxSector || + !mddev->private || !mddev->pers || !mddev->pers->bitmap_sync_size) return llbitmap->sync_size; return mddev->pers->bitmap_sync_size(mddev, previous); } +static sector_t llbitmap_logical_size(struct llbitmap *llbitmap, bool previous) +{ + struct mddev *mddev = llbitmap->mddev; + + if (!mddev->private || !mddev->pers || + !mddev->pers->bitmap_array_sectors) + return llbitmap_personality_sync_size(llbitmap, previous); + return mddev->pers->bitmap_array_sectors(mddev, previous); +} + static void llbitmap_refresh_reshape(struct llbitmap *llbitmap) { unsigned long old_chunks = DIV_ROUND_UP_SECTOR_T(llbitmap->sync_size, @@ -466,6 +473,80 @@ static void llbitmap_refresh_reshape(struct llbitmap *llbitmap) llbitmap->chunks = max(old_chunks, llbitmap->reshape_chunks); } +static void llbitmap_map_layout(struct llbitmap *llbitmap, sector_t *offset, + unsigned long *sectors, bool previous) +{ + sector_t limit = llbitmap_logical_size(llbitmap, previous); + sector_t start = *offset; + sector_t end = start + *sectors; + + if (start >= limit) { + *sectors = 0; + return; + } + if (end > limit) + end = limit; + + *offset = start; + *sectors = end - start; + if (!*sectors) + return; + + if (llbitmap->mddev->pers->bitmap_sector_map) + llbitmap->mddev->pers->bitmap_sector_map(llbitmap->mddev, offset, + sectors, previous); + else if (!previous && llbitmap->mddev->pers->bitmap_sector) + llbitmap->mddev->pers->bitmap_sector(llbitmap->mddev, offset, + sectors); +} + +static void llbitmap_encode_range(struct llbitmap *llbitmap, sector_t *offset, + unsigned long *sectors, bool previous) +{ + unsigned long chunksize = previous ? llbitmap->chunksize : + llbitmap->reshape_chunksize; + u64 start; + u64 end; + + if (!*sectors) { + *offset = 0; + return; + } + + start = div64_u64(*offset, chunksize); + end = div64_u64(*offset + *sectors - 1, chunksize); + *offset = (sector_t)start << llbitmap->chunkshift; + *sectors = (end - start + 1) << llbitmap->chunkshift; +} + +static void llbitmap_encode_discard_range(struct llbitmap *llbitmap, + sector_t *offset, + unsigned long *sectors, + bool previous) +{ + unsigned long chunksize = previous ? llbitmap->chunksize : + llbitmap->reshape_chunksize; + sector_t end = *offset + *sectors; + u64 start; + u64 last; + + if (!*sectors) { + *offset = 0; + return; + } + + start = DIV_ROUND_UP_SECTOR_T(*offset, chunksize); + last = div64_u64(end, chunksize); + if (start >= last) { + *offset = 0; + *sectors = 0; + return; + } + + *offset = (sector_t)start << llbitmap->chunkshift; + *sectors = (last - start) << llbitmap->chunkshift; +} + static enum llbitmap_state llbitmap_read(struct llbitmap *llbitmap, loff_t pos) { unsigned int idx; @@ -1409,11 +1490,35 @@ 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) +static bool llbitmap_map_previous(struct llbitmap *llbitmap, sector_t offset, + unsigned long sectors) { - if (mddev->pers->bitmap_sector) - mddev->pers->bitmap_sector(mddev, offset, sectors); + struct mddev *mddev = llbitmap->mddev; + sector_t boundary = READ_ONCE(mddev->reshape_position); + + if (boundary == MaxSector) + return false; + + WARN_ON_ONCE(sectors && offset < boundary && offset + sectors > boundary); + + return mddev->reshape_backwards ? offset < boundary : offset >= boundary; +} + +static void llbitmap_prepare_range(struct mddev *mddev, sector_t *offset, + unsigned long *sectors, bool discard) +{ + 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, @@ -1582,7 +1687,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; } @@ -1592,7 +1701,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) @@ -1637,6 +1750,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 538ba7bab060..e88381beb209 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -9428,21 +9428,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); From 44aa6154e17e8831a1168e600deaff7dcb052a18 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:29 +0800 Subject: [PATCH 40/53] md/md-llbitmap: don't skip reshape ranges from bitmap state Reshape progress is tracked by array metadata rather than llbitmap. Do not let llbitmap skip_sync_blocks() suppress reshape ranges based on stale bitmap state before the corresponding checkpoint is persisted. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-21-yukuai@kernel.org Signed-off-by: Yu Kuai --- 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 1283ad737692..a609e8d44901 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -1707,6 +1707,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; From 9b2d455305f1f9f89ca41865fe625e31353e0bc8 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:30 +0800 Subject: [PATCH 41/53] md/md-llbitmap: remap checkpointed bits as reshape progresses Merge checkpointed old llbitmap state forward as reshape_position advances and record the checkpoint remap through reshape_mark(). Normal write accounting can run while the reshape thread checkpoints a new reshape position. llbitmap_reshape_mark() reads old state bytes, merges them into destination bits, and writes the result back. If llbitmap_start_write() or llbitmap_start_discard() updates the same state bytes at the same time, the two read/modify/write paths can overwrite each other and lose the state from one side. Serialize only this state-byte race with a rwlock. Normal I/O takes the read side around llbitmap_state_machine(), after page active references are raised, so concurrent normal I/O updates still run in parallel. Reshape checkpointing takes the write side only while merging the checkpointed range, avoiding page suspension and avoiding a sleeping mutex in the I/O accounting path. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-22-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/md-llbitmap.c | 204 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c index a609e8d44901..845b8cd10f83 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -302,6 +302,11 @@ struct llbitmap { /* fires on first BitDirty state */ struct timer_list pending_timer; struct work_struct daemon_work; + /* + * Serialize reshape checkpoint remapping against normal I/O bitmap + * updates without blocking concurrent I/O updates on each other. + */ + rwlock_t reshape_lock; unsigned long flags; __u64 events_cleared; @@ -498,6 +503,14 @@ static void llbitmap_map_layout(struct llbitmap *llbitmap, sector_t *offset, else if (!previous && llbitmap->mddev->pers->bitmap_sector) llbitmap->mddev->pers->bitmap_sector(llbitmap->mddev, offset, sectors); + + limit = llbitmap_personality_sync_size(llbitmap, previous); + start = *offset; + end = start + *sectors; + if (start >= limit) + *sectors = 0; + else if (end > limit) + *sectors = limit - start; } static void llbitmap_encode_range(struct llbitmap *llbitmap, sector_t *offset, @@ -930,6 +943,33 @@ static int llbitmap_prepare_resize(struct llbitmap *llbitmap, return 0; } +static enum llbitmap_state +llbitmap_rmerge_state(struct llbitmap *llbitmap, + enum llbitmap_state dst, + enum llbitmap_state src) +{ + bool level_456 = raid_is_456(llbitmap->mddev); + + if (dst == BitNeedSync || dst == BitSyncing || + src == BitNeedSync || src == BitSyncing) + return BitNeedSync; + + if (dst == BitDirty || src == BitDirty) + return BitDirty; + + /* + * Reshape generates valid target parity/data for both already-written + * and not-yet-written regions in the checkpointed range, so a mix of + * clean and unwritten still results in a clean destination bit. + */ + if (level_456 && ((dst == BitClean && src == BitUnwritten) || + (src == BitClean && dst == BitUnwritten))) + return BitClean; + if (dst == BitClean || src == BitClean) + return BitClean; + return BitUnwritten; +} + static void llbitmap_init_state(struct llbitmap *llbitmap) { struct mddev *mddev = llbitmap->mddev; @@ -1306,6 +1346,7 @@ static void md_llbitmap_daemon_fn(struct work_struct *work) if (llbitmap->mddev->degraded) return; + retry: start = 0; end = min(llbitmap->chunks, PAGE_SIZE - BITMAP_DATA_OFFSET) - 1; @@ -1367,6 +1408,7 @@ static int llbitmap_create(struct mddev *mddev) timer_setup(&llbitmap->pending_timer, llbitmap_pending_timer_fn, 0); INIT_WORK(&llbitmap->daemon_work, md_llbitmap_daemon_fn); + rwlock_init(&llbitmap->reshape_lock); atomic_set(&llbitmap->behind_writes, 0); init_waitqueue_head(&llbitmap->behind_wait); @@ -1535,7 +1577,9 @@ static void llbitmap_start_write(struct mddev *mddev, sector_t offset, page_start++; } + read_lock(&llbitmap->reshape_lock); llbitmap_state_machine(llbitmap, start, end, BitmapActionStartwrite); + read_unlock(&llbitmap->reshape_lock); } static void llbitmap_end_write(struct mddev *mddev, sector_t offset, @@ -1567,7 +1611,9 @@ static void llbitmap_start_discard(struct mddev *mddev, sector_t offset, page_start++; } + read_lock(&llbitmap->reshape_lock); llbitmap_state_machine(llbitmap, start, end, BitmapActionDiscard); + read_unlock(&llbitmap->reshape_lock); } static void llbitmap_end_discard(struct mddev *mddev, sector_t offset, @@ -1864,6 +1910,136 @@ static int llbitmap_reshape_can_start(struct mddev *mddev) return ret; } +struct llbitmap_reshape_range { + sector_t offset; + unsigned long sectors; + sector_t start; + sector_t end; +}; + +static enum llbitmap_state +llbitmap_reshape_init_dst(struct llbitmap *llbitmap, unsigned long dst, + const struct llbitmap_reshape_range *new) +{ + u64 bit_start = (u64)dst * llbitmap->reshape_chunksize; + u64 bit_end = bit_start + llbitmap->reshape_chunksize; + + if (!llbitmap->mddev->reshape_backwards) + return bit_start < new->offset ? llbitmap_read(llbitmap, dst) : + BitUnwritten; + return bit_end > new->end ? llbitmap_read(llbitmap, dst) : BitUnwritten; +} + +static void llbitmap_reshape_dst_range(struct llbitmap *llbitmap, + unsigned long dst, + const struct llbitmap_reshape_range *new, + struct llbitmap_reshape_range *dst_range) +{ + sector_t dst_bit_start = (sector_t)dst * llbitmap->reshape_chunksize; + + dst_range->start = max(dst_bit_start, new->offset); + dst_range->end = min(dst_bit_start + llbitmap->reshape_chunksize, + new->end); + dst_range->offset = dst_range->start; + dst_range->sectors = dst_range->end - dst_range->start; +} + +static void llbitmap_reshape_map_range(struct llbitmap *llbitmap, + sector_t lo, sector_t hi, + bool previous, + struct llbitmap_reshape_range *range) +{ + range->offset = lo; + range->sectors = hi - lo; + llbitmap_map_layout(llbitmap, &range->offset, &range->sectors, previous); + range->start = range->offset; + range->end = range->offset + range->sectors; +} + +static bool llbitmap_reshape_src_range(const struct llbitmap_reshape_range *old, + const struct llbitmap_reshape_range *new, + const struct llbitmap_reshape_range *dst, + struct llbitmap_reshape_range *src) +{ + if (!old->sectors) + return false; + + src->start = old->offset + + mul_u64_u64_div_u64(dst->start - new->offset, + old->sectors, new->sectors); + src->end = old->offset + + mul_u64_u64_div_u64_roundup(dst->end - new->offset, + old->sectors, new->sectors); + if (src->end > old->end) + src->end = old->end; + src->offset = src->start; + src->sectors = src->end - src->start; + + return src->sectors; +} + +static enum llbitmap_state llbitmap_rmerge_src(struct llbitmap *llbitmap, + enum llbitmap_state state, + const struct llbitmap_reshape_range *src) +{ + unsigned long bit = div64_u64(src->start, llbitmap->chunksize); + unsigned long end = div64_u64(src->end - 1, llbitmap->chunksize); + + while (bit <= end) { + enum llbitmap_state src_state = llbitmap_read(llbitmap, bit); + + state = llbitmap_rmerge_state(llbitmap, state, src_state); + bit++; + } + + return state; +} + +static void llbitmap_reshape_merge(struct llbitmap *llbitmap, + const struct llbitmap_reshape_range *old, + const struct llbitmap_reshape_range *new) +{ + unsigned long dst_start; + unsigned long dst_end; + unsigned long dst; + bool backwards = false; + + if (!new->sectors) + return; + + dst_start = div64_u64(new->offset, llbitmap->reshape_chunksize); + dst_end = div64_u64(new->end - 1, llbitmap->reshape_chunksize); + if (old->sectors) { + unsigned long src_start = div64_u64(old->offset, + llbitmap->chunksize); + unsigned long src_end = div64_u64(old->end - 1, + llbitmap->chunksize); + + backwards = src_start < dst_start && src_end >= dst_start; + } + + dst = backwards ? dst_end : dst_start; + while (true) { + struct llbitmap_reshape_range dst_range; + struct llbitmap_reshape_range src; + enum llbitmap_state state; + + llbitmap_reshape_dst_range(llbitmap, dst, new, &dst_range); + state = llbitmap_reshape_init_dst(llbitmap, dst, new); + if (llbitmap_reshape_src_range(old, new, &dst_range, &src)) + state = llbitmap_rmerge_src(llbitmap, state, &src); + else + state = llbitmap_rmerge_state(llbitmap, state, BitUnwritten); + llbitmap_write(llbitmap, state, dst); + if (dst == (backwards ? dst_start : dst_end)) + break; + if (backwards) + dst--; + else + dst++; + } +} + static void llbitmap_reshape_finish(struct mddev *mddev) { struct llbitmap *llbitmap = mddev->bitmap; @@ -1888,6 +2064,33 @@ static void llbitmap_reshape_finish(struct mddev *mddev) mddev->pers->quiesce(mddev, 0); } +static void llbitmap_reshape_mark(struct mddev *mddev, sector_t old_pos, + sector_t new_pos) +{ + struct llbitmap *llbitmap = mddev->bitmap; + sector_t lo; + sector_t hi; + struct llbitmap_reshape_range old; + struct llbitmap_reshape_range new; + + if (!llbitmap || old_pos == new_pos) + return; + + lo = min(old_pos, new_pos); + hi = max(old_pos, new_pos); + if (!hi) + return; + + llbitmap_reshape_map_range(llbitmap, lo, hi, true, &old); + llbitmap_reshape_map_range(llbitmap, lo, hi, false, &new); + if (!new.sectors) + return; + + write_lock(&llbitmap->reshape_lock); + llbitmap_reshape_merge(llbitmap, &old, &new); + write_unlock(&llbitmap->reshape_lock); +} + static void llbitmap_write_sb(struct llbitmap *llbitmap) { int nr_blocks = DIV_ROUND_UP(BITMAP_DATA_OFFSET, llbitmap->io_size); @@ -2181,6 +2384,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, From 3fa5499f32f16133e32c9fb696bc6c0a07483396 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:31 +0800 Subject: [PATCH 42/53] md/md-llbitmap: clamp state-machine walks to tracked bits 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. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-23-yukuai@kernel.org Signed-off-by: Yu Kuai --- 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 845b8cd10f83..e1a783ee2032 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -1012,7 +1012,10 @@ static enum llbitmap_state llbitmap_state_machine(struct llbitmap *llbitmap, llbitmap_init_state(llbitmap); return BitNone; } - + if (start >= llbitmap->chunks) + return BitNone; + if (end >= llbitmap->chunks) + end = llbitmap->chunks - 1; while (start <= end) { enum llbitmap_state c = llbitmap_read(llbitmap, start); From ecb66a97af620135f207cddabeb7f589510640fc Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:32 +0800 Subject: [PATCH 43/53] md/raid10: reject llbitmap reshape when md chunk shrinks 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. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-24-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/raid10.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index ed3c6fbe65f7..1c3393467667 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -4245,6 +4245,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 */ From b109d437dbc6741fbd6bc108626756c6e7f7ec20 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:33 +0800 Subject: [PATCH 44/53] md/raid10: wire llbitmap reshape lifecycle 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. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-25-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/raid10.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 1c3393467667..bac9edd28c97 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -4356,6 +4356,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; @@ -4679,6 +4685,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); + 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) @@ -4877,9 +4890,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); @@ -5011,10 +5034,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; @@ -5041,6 +5069,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 = { @@ -5067,6 +5104,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) From aa648f26a985d08f816740bb964a743406a5f40b Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:34 +0800 Subject: [PATCH 45/53] md/raid10: split reshape bios before bitmap accounting Use the shared mddev_bio_split_at_reshape_offset() helper so RAID10 submits only one-side bios to llbitmap during reshape. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-26-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/raid10.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index bac9edd28c97..562a325a7195 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -1848,6 +1848,7 @@ static bool raid10_make_request(struct mddev *mddev, struct bio *bio) { struct r10conf *conf = mddev->private; sector_t chunk_mask = (conf->geo.chunk_mask & conf->prev.chunk_mask); + const int rw = bio_data_dir(bio); int chunk_sects = chunk_mask + 1; int sectors = bio_sectors(bio); @@ -1873,6 +1874,15 @@ static bool raid10_make_request(struct mddev *mddev, struct bio *bio) sectors = chunk_sects - (bio->bi_iter.bi_sector & (chunk_sects - 1)); + + bio = mddev_bio_split_at_reshape_offset(mddev, bio, §ors, + &conf->bio_split); + if (!bio) { + if (rw == WRITE) + md_write_end(mddev); + return true; + } + if (!__make_request(mddev, bio, sectors)) md_write_end(mddev); From 9f59258d300494be3055f2e47f26b21f5c8ad710 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:35 +0800 Subject: [PATCH 46/53] md/raid5: add exact old and new llbitmap mapping helpers Teach RAID5 to export exact old and new llbitmap mappings and the corresponding sync and array sizes for reshape-aware bitmap users. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-27-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 81 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 2cc2546a29ae..88bf5a9ce573 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -6015,6 +6015,34 @@ static enum reshape_loc get_reshape_loc(struct mddev *mddev, return LOC_BEHIND_RESHAPE; } +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; + int sectors_per_chunk; + int dd_idx; + + 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, 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) { @@ -6022,21 +6050,11 @@ static void raid5_bitmap_sector(struct mddev *mddev, sector_t *offset, sector_t start = *offset; sector_t end = start + *sectors; sector_t prev_start = start; - sector_t prev_end = end; - int sectors_per_chunk; + unsigned long prev_sectors = end - start; enum reshape_loc loc; - int dd_idx; - 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); + raid5_bitmap_sector_map(mddev, &start, sectors, false); + end = start + *sectors; /* * For LOC_INSIDE_RESHAPE, this IO will wait for reshape to make @@ -6045,19 +6063,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 @@ -6065,7 +6074,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, @@ -9131,6 +9140,21 @@ 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, mddev->dev_sectors, + conf->previous_raid_disks); + return raid5_size(mddev, mddev->dev_sectors, conf->raid_disks); +} + static struct md_personality raid6_personality = { .head = { @@ -9160,6 +9184,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 = { @@ -9190,6 +9217,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 = @@ -9221,6 +9251,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) From 05a1b89689dfdc567a840dd06ba1327652cc9f58 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:36 +0800 Subject: [PATCH 47/53] md/raid5: reject llbitmap reshape when md chunk shrinks 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. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-28-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 88bf5a9ce573..67d56c92c8a4 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -8580,6 +8580,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, From 816b25aca5b3664637c2b976d0e2a25b71dd88a9 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:37 +0800 Subject: [PATCH 48/53] md/raid5: wire llbitmap reshape lifecycle Prepare llbitmap before RAID5 reshape starts, checkpoint the bitmap before advancing reshape_position, and finish the llbitmap geometry update when reshape completes. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-29-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 67d56c92c8a4..5176de5b5956 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -6497,6 +6497,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) @@ -6606,6 +6613,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) @@ -8648,6 +8662,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); @@ -8732,10 +8752,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); @@ -8762,8 +8791,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; From 661102bb87e43b7e476f3a2ff34e916b800b5627 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 3 Aug 2026 03:50:38 +0800 Subject: [PATCH 49/53] md/raid5: split reshape bios before bitmap accounting RAID5 maps array sectors through different geometries before and after the reshape position. During llbitmap reshape, md core cannot account one bio against both geometries as a single bitmap range, because the old and new bitmap mappings can cover different chunks. Split bios that cross reshape_position before md_account_bio(), so the bitmap only sees ranges that belong to one side of the reshape boundary. mddev_bio_split_at_reshape_offset() uses bio_submit_split_bioset(), which submits the remainder immediately and returns the front split bio. If that front bio later has to wait for reshape, md_handle_request() must not retry the original bio pointer, because after the split that pointer is the already-submitted remainder. Track whether the split happened, clear the temporary BLK_STS_RESOURCE status after the internal clone completion, and resubmit the front bio directly after the reshape wait. Keep the old return-false retry path for unsplit bios, where md_handle_request() still owns the same bio. Tested-by: Mykola Marzhan Link: https://patch.msgid.link/20260802195038.164272-30-yukuai@kernel.org Signed-off-by: Yu Kuai --- drivers/md/raid5.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 5176de5b5956..b91545ce090d 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -6221,9 +6221,11 @@ static bool raid5_make_request(struct mddev *mddev, struct bio * bi) struct r5conf *conf = mddev->private; const int rw = bio_data_dir(bi); struct stripe_request_ctx *ctx; + struct bio *front_bio; sector_t logical_sector; enum stripe_result res; int s, stripe_cnt; + bool split = false; bool on_wq; if (unlikely(bi->bi_opf & REQ_PREFLUSH)) { @@ -6257,6 +6259,18 @@ static bool raid5_make_request(struct mddev *mddev, struct bio * bi) return true; } + front_bio = bi; + bi = mddev_bio_split_at_reshape_offset(mddev, bi, NULL, + &conf->bio_split); + if (!bi) { + if (rw == WRITE) + md_write_end(mddev); + return true; + } + if (bi != front_bio) + split = true; + front_bio = bi; + logical_sector = bi->bi_iter.bi_sector & ~((sector_t)RAID5_STRIPE_SECTORS(conf)-1); bi->bi_next = NULL; @@ -6348,6 +6362,11 @@ static bool raid5_make_request(struct mddev *mddev, struct bio * bi) bio_endio(bi); wait_for_completion(&done); + front_bio->bi_status = BLK_STS_OK; + if (split) { + submit_bio_noacct(front_bio); + return true; + } return false; } From 47f1441b281decde6954a2fa82b4131637d685ac Mon Sep 17 00:00:00 2001 From: Yunye Zhao Date: Thu, 23 Jul 2026 21:55:33 +0800 Subject: [PATCH 50/53] md/raid10: fix still_degraded being inverted in raid10_sync_request() Commit fe6a19d40ceb ("md/md-bitmap: merge md_bitmap_start_sync() into bitmap_operations") converted still_degraded from int to bool, but inverted the assignment in the loop that checks whether the array will still be degraded after the current device is recovered: "still_degraded = 1" became "still_degraded = false". As a result, recovering a device while another mirror is still missing calls md_bitmap_start_sync() with degraded == false, which clears bitmap bits that the still-missing device needs. When that device is re-added, its bitmap-based recovery finds the bits already cleared and skips every region written while the array was degraded, so it is marked In_sync while holding stale data: silent corruption. Reproducer (raid10 near=2, 4 disks, internal bitmap): - fail and remove one disk of each mirror pair - write to the degraded array - re-add both disks and let recovery finish - "check" reports mismatch_cnt=262272 after 256 MiB of degraded writes and file contents differ; the second disk's "recovery" completes in milliseconds because everything is skipped The same conversion in raid1 got it right (still_degraded = true). Restore the correct value. Fixes: fe6a19d40ceb ("md/md-bitmap: merge md_bitmap_start_sync() into bitmap_operations") Cc: stable@vger.kernel.org Signed-off-by: Yunye Zhao Reviewed-by: Mykola Marzhan Reviewed-by: Paul Menzel Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260723135535.101995-2-yunye.zhao@linux.alibaba.com Signed-off-by: Yu Kuai --- drivers/md/raid10.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 562a325a7195..1093c798d9dd 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -3337,7 +3337,7 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr, struct md_rdev *rdev = conf->mirrors[j].rdev; if (rdev == NULL || test_bit(Faulty, &rdev->flags)) { - still_degraded = false; + still_degraded = true; break; } } From 162eb5ba791f6260fdaabae75fa2df20134f67d8 Mon Sep 17 00:00:00 2001 From: Yunye Zhao Date: Thu, 23 Jul 2026 21:55:34 +0800 Subject: [PATCH 51/53] md: add cond_resched() to md_do_sync()'s skip path When sync_request() reports a skipped region (*skipped == 1), md_do_sync()'s main loop advances the cursor and takes an early continue: j += sectors; ... if (last_check + window > io_sectors || j == max_sectors) continue; If the personality returns a small span per call (raid10 recovery returns only 128 sectors), syncing a large, mostly clean array iterates this branch an enormous number of times without ever yielding the CPU. On a non-preemptive kernel the resync thread then trips the soft-lockup watchdog: watchdog: BUG: soft lockup - CPU#149 stuck for 313s! [mdX_resync] md_bitmap_start_sync+0x6f/0xe0 raid10_sync_request+0x2c9/0x1530 [raid10] md_do_sync+0x810/0x1030 md_thread+0xa7/0x150 Add a cond_resched(). This does not reduce the wasted iterations; the excessive iteration count is a raid10 problem addressed separately. Signed-off-by: Yunye Zhao Link: https://patch.msgid.link/20260723135535.101995-3-yunye.zhao@linux.alibaba.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index e88381beb209..42fca0cec8e4 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -9958,8 +9958,10 @@ void md_do_sync(struct md_thread *thread) */ md_new_event(); - if (last_check + window > io_sectors || j == max_sectors) + if (last_check + window > io_sectors || j == max_sectors) { + cond_resched(); continue; + } last_check = io_sectors; repeat: From efdffeb6d4915219a130607f5ae29613d2c5545a Mon Sep 17 00:00:00 2001 From: Wale Zhang Date: Fri, 31 Jul 2026 03:47:29 -0400 Subject: [PATCH 52/53] md: skip discard on unsupported member devices blk_stack_limits() uses min_not_zero() when stacking discard limits. Thus an array containing devices with different discard capabilities can expose discard support as long as at least one member has a non-zero discard limit. raid0 and raid10 use md_submit_discard_bio() to submit a discard bio to each member covered by the request. The helper currently also submits bios to members whose max_discard_sectors is zero. The block layer completes these bios with BLK_STS_NOTSUPP, and bio chaining propagates that status to the original discard request. Discard is optional, so skip members which do not support it. Members that do support discard continue to receive their portion of the request. Signed-off-by: Wale Zhang Link: https://patch.msgid.link/20260731074729.1885314-1-wale.zhang.ftd@gmail.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/md/md.c b/drivers/md/md.c index 42fca0cec8e4..680b34a63cb3 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -9377,6 +9377,10 @@ void md_submit_discard_bio(struct mddev *mddev, struct md_rdev *rdev, { struct bio *discard_bio = NULL; + /* Discard is optional, so silently skip members that do not support it. */ + if (unlikely(!bdev_max_discard_sectors(rdev->bdev))) + return; + __blkdev_issue_discard(rdev->bdev, start, size, GFP_NOIO, &discard_bio); if (!discard_bio) return; From dc386aa0ac0a3ec06c9a3ea9b064b073fb72a916 Mon Sep 17 00:00:00 2001 From: Bruce Johnston Date: Mon, 3 Aug 2026 14:02:39 -0400 Subject: [PATCH 53/53] md/raid1: don't set array_frozen in raid1_takeover() raid1_takeover() sets conf->array_frozen = 1 on the newly-allocated r1conf and nothing ever clears it, so every I/O to the array stalls permanently once _wait_barrier() sees it stuck at 1. This used to be harmless: level_store() called mddev_resume() right after pers->run(), which called raid1_quiesce(mddev, 0) and cleared array_frozen back to 0 regardless of what raid1_takeover() set. Commit b39f35ebe86d ("md: don't quiesce in mddev_suspend()") removed that quiesce(mddev, 0) call, so the pre-set now sticks. setup_conf() already zero-initializes the new r1conf via kzalloc, so just don't set array_frozen here. Same class of bug as commit 892da88d1cd9 ("md/raid10: fix a 'conf->barrier' leakage in raid10_takeover()"), also triggered by b39f35ebe86d. Fixes: b39f35ebe86d ("md: don't quiesce in mddev_suspend()") Link: https://issues.redhat.com/browse/RHEL-191802 Signed-off-by: Bruce Johnston Link: https://patch.msgid.link/20260803180240.1177104-1-bjohnsto@redhat.com Signed-off-by: Yu Kuai --- drivers/md/raid1.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index e9baba7b241f..f0646fb24371 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -3430,8 +3430,6 @@ static void *raid1_takeover(struct mddev *mddev) mddev->new_chunk_sectors = 0; conf = setup_conf(mddev); if (!IS_ERR(conf)) { - /* Array must appear to be quiesced */ - conf->array_frozen = 1; mddev_clear_unsupported_flags(mddev, UNSUPPORTED_MDDEV_FLAGS); }