Since commit 02666132403a ("md: propagate BLK_FEAT_PCI_P2PDMA from member devices to RAID device") raid1 and raid10 arrays accept P2PDMA (peer device memory) bios, and a member that cannot DMA-map the peer's pages fails its leg bio with BLK_STS_TARGET (from blk_dma_map_iter_start()). That mapping failure is a property of the peer-device/member pairing, not of the medium: re-submitting the same peer pages to the same member cannot succeed, and there is nothing on the disk to repair. Routing it through the stock error machinery misfires on every path: - narrow_write_error() re-issues the failed write in badblock-sized chunks, serializing hundreds of guaranteed-to-fail synchronous bios through raid1d/raid10d per failed write (256 for a 1 MiB write on 4K logical blocks, 2048 on 512e). - the write-error handler sets WantReplacement, so a hot spare is pulled in, rebuilt onto, and the healthy member is then evicted by spare_active() -- and if the spare is also unreachable from the peer, the cycle consumes the next spare. - fix_read_error() probes members with kernel pages, starting with the failing member itself. That probe succeeds (the member is healthy for host memory), so the routine rewrites nothing, records nothing, and logs nothing -- but each invocation costs a full freeze_array() quiesce and a tick of the read-error budget. The budget exists to evict members whose medium keeps producing corrected errors; its hourly decay is sized for sporadic medium errors, while mapping failures are deterministic and arrive at I/O rate. On an asymmetric PCIe topology a mixed host/P2P read workload charges 20 errors to the unreachable leg within a second and kicks a perfectly healthy member. - On FailFast members both paths short-circuit into md_error(), so a single unroutable peer-memory I/O evicts a healthy mirror outright. Track P2PDMA masters with a new r1bio/r10bio state bit, set at submission where bio_has_data() is still meaningful; a leg's mapping failure is then "BLK_STS_TARGET and the master carries P2PDMA pages", cheap to test on every completion path. Handle it explicitly: - Writes: probe the whole range with one retry and record one bad range if it also fails. BLK_STS_TARGET is also produced for device conditions that narrow_write_error()'s retry loop recovers from (e.g. NVME_SC_NS_NOT_READY and NVME_SC_CMD_INTERRUPTED via nvme_error_status()), and md cannot tell the two apart from bi_status alone, so it must not skip the retry outright. A single whole-range probe observes the outcome instead of predicting it. WriteErrorSeen is still set (it gates the write-path badblocks consult), but WantReplacement is not: replacing a member cannot fix a topology property, and spending a spare on it destroys redundancy management for real failures. - Reads: mark the leg IO_BLOCKED so read_balance() picks another mirror, and skip the freeze/fix cycle and the budget charge. The same TARGET ambiguity exists here, but a redirected read leaves nothing to fence, and fix_read_error()'s host-page probe fails the same way under e.g. NVME_SC_NS_NOT_READY -- charging the budget would evict a healthy member for a firmware activation window. A genuinely failing member is still evicted via host reads, writes and BLK_STS_MEDIUM errors. If no mirror can serve the read the master bio fails with EIO as before (md completes masters by Uptodate state, as for any failed mirror I/O), now without kicking healthy members on the way. - FailFast: a mapping failure is rejected at map time and never reaches the wire, so it is no evidence of device unreliability; don't let it trigger the FailFast md_error() -- the same reasoning commit f7b24c7b41f2 ("md/raid1,raid10: don't fail devices for invalid IO errors") applied to BLK_STS_INVAL. raid10 replacement legs keep their stock policy: badblocks are never recorded on a replacement, so failing it is the only outcome that cannot leave a silent hole in a rebuilding replacement. This belongs in the same release as the BLK_STS_TARGET restoration: with that fix alone, an unroutable leg costs the retry storms and the healthy-member evictions above. Measured on QEMU q35 rigs (one CMB provider, two NVMe members), 8KiB peer-memory I/O, reads x90 under concurrent host reads: scenario stock error machinery with this patch asym reads 90/90 ok, healthy far 90/90 ok, no eviction, leg kicked after ~20 no budget charge unreach reads all EIO, one healthy all EIO, no members member kicked kicked p2p TARGET 16 chunk retries from 1 whole-range probe, write raid1d, then badblocks same badblocks, master ok A transient TARGET recovers through the single probe with no badblocks recorded, and a mapping failure no longer trips FailFast eviction while a genuine I/O error still does -- both verified on the same rig. Fixes: 02666132403a ("md: propagate BLK_FEAT_PCI_P2PDMA from member devices to RAID device") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Mykola Marzhan --- drivers/md/raid1.c | 56 +++++++++++++++++++++++++++++++----- drivers/md/raid1.h | 2 ++ drivers/md/raid10.c | 69 ++++++++++++++++++++++++++++++++++++++------- drivers/md/raid10.h | 2 ++ 4 files changed, 112 insertions(+), 17 deletions(-) diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index f562b6bd438b..61f635463475 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -483,12 +483,22 @@ static void raid1_end_write_request(struct bio *bio) * 'one mirror IO has finished' event handler: */ if (bio->bi_status && !ignore_error) { + /* + * A P2PDMA mapping failure reflects the peer/member + * pairing, not member health: don't pull in a spare + * or trip FailFast for it. + */ + bool p2pdma_unmappable = bio->bi_status == BLK_STS_TARGET && + test_bit(R1BIO_P2PDMA, &r1_bio->state); + set_bit(WriteErrorSeen, &rdev->flags); - if (!test_and_set_bit(WantReplacement, &rdev->flags)) + if (!p2pdma_unmappable && + !test_and_set_bit(WantReplacement, &rdev->flags)) set_bit(MD_RECOVERY_NEEDED, & conf->mddev->recovery); - if (test_bit(FailFast, &rdev->flags) && + if (!p2pdma_unmappable && + test_bit(FailFast, &rdev->flags) && (bio->bi_opf & MD_FAILFAST) && /* We never try FailFast to WriteMostly devices */ !test_bit(WriteMostly, &rdev->flags)) { @@ -1378,6 +1388,8 @@ static void raid1_read_request(struct mddev *mddev, struct bio *bio, else init_r1bio(r1_bio, mddev, bio); r1_bio->sectors = max_read_sectors; + if (md_bio_is_p2pdma(bio)) + set_bit(R1BIO_P2PDMA, &r1_bio->state); /* * make_request() can abort the operation when read-ahead is being @@ -1557,6 +1569,8 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio, r1_bio = alloc_r1bio(mddev, bio); r1_bio->sectors = max_sectors; + if (md_bio_is_p2pdma(bio)) + set_bit(R1BIO_P2PDMA, &r1_bio->state); /* first select target devices under rcu_lock and * inc refcount on their rdev. Record them by setting @@ -2525,7 +2539,7 @@ static void fix_read_error(struct r1conf *conf, struct r1bio *r1_bio) } } -static void narrow_write_error(struct r1bio *r1_bio, int i) +static void narrow_write_error(struct r1bio *r1_bio, int i, bool coarse) { struct mddev *mddev = r1_bio->mddev; struct r1conf *conf = mddev->private; @@ -2539,6 +2553,11 @@ static void narrow_write_error(struct r1bio *r1_bio, int i) * It is conceivable that the bio doesn't exactly align with * blocks. We must handle this somehow. * + * With 'coarse', retry the whole range as one bio and record + * one bad range if it fails: for P2PDMA mapping failures, + * which fail every block identically, while the single retry + * still lets a cleared transient error recover. + * * We currently own a reference on the rdev. */ @@ -2553,9 +2572,12 @@ static void narrow_write_error(struct r1bio *r1_bio, int i) block_sectors = roundup(1 << rdev->badblocks.shift, lbs); sector = r1_bio->sector; - sectors = ((sector + block_sectors) - & ~(sector_t)(block_sectors - 1)) - - sector; + if (coarse) + sectors = sect_to_write; + else + sectors = ((sector + block_sectors) + & ~(sector_t)(block_sectors - 1)) + - sector; while (sect_to_write) { struct bio *wbio; @@ -2636,8 +2658,18 @@ static void handle_write_finished(struct r1conf *conf, struct r1bio *r1_bio) * narrow down and record precise write * errors. */ + bool coarse = r1_bio->bios[m]->bi_status == + BLK_STS_TARGET && + test_bit(R1BIO_P2PDMA, &r1_bio->state); + fail = true; - narrow_write_error(r1_bio, m); + /* + * A P2PDMA mapping failure fails the whole range + * identically: probe it once (coarse) instead of + * narrowing block by block. A transient TARGET + * recovers via the probe with nothing recorded. + */ + narrow_write_error(r1_bio, m, coarse); rdev_dec_pending(conf->mirrors[m].rdev, conf->mddev); } @@ -2664,6 +2696,9 @@ static void handle_read_error(struct r1conf *conf, struct r1bio *r1_bio) { struct md_rdev *rdev = conf->mirrors[r1_bio->read_disk].rdev; struct bio *bio = r1_bio->bios[r1_bio->read_disk]; + /* evaluate before the bio_put() below */ + bool p2pdma_error = bio->bi_status == BLK_STS_TARGET && + test_bit(R1BIO_P2PDMA, &r1_bio->state); struct mddev *mddev = conf->mddev; sector_t sector; @@ -2683,6 +2718,13 @@ static void handle_read_error(struct r1conf *conf, struct r1bio *r1_bio) */ if (mddev->ro) { r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED; + } else if (p2pdma_error) { + /* + * The peer cannot reach this member; nothing on the + * medium to fix. Skip the read-error budget and + * FailFast, just keep this leg out of the retry. + */ + r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED; } else if (test_bit(FailFast, &rdev->flags)) { md_error(mddev, rdev); } else { diff --git a/drivers/md/raid1.h b/drivers/md/raid1.h index c98d43a7ae99..61b788a99d14 100644 --- a/drivers/md/raid1.h +++ b/drivers/md/raid1.h @@ -184,6 +184,8 @@ enum r1bio_state { R1BIO_MadeGood, R1BIO_WriteError, R1BIO_FailFast, +/* the master bio carries PCI P2PDMA (peer device memory) pages */ + R1BIO_P2PDMA, }; static inline int sector_to_idx(sector_t sector) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index f7ef903a3d4e..144d07b6029c 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -482,13 +482,24 @@ static void raid10_end_write_request(struct bio *bio) */ md_error(rdev->mddev, rdev); else { + /* + * A P2PDMA mapping failure reflects the + * peer/member pairing, not member health: don't + * pull in a spare or trip FailFast for it. + */ + bool p2pdma_unmappable = + bio->bi_status == BLK_STS_TARGET && + test_bit(R10BIO_P2PDMA, &r10_bio->state); + set_bit(WriteErrorSeen, &rdev->flags); - if (!test_and_set_bit(WantReplacement, &rdev->flags)) + if (!p2pdma_unmappable && + !test_and_set_bit(WantReplacement, &rdev->flags)) set_bit(MD_RECOVERY_NEEDED, &rdev->mddev->recovery); dec_rdev = 0; - if (test_bit(FailFast, &rdev->flags) && + if (!p2pdma_unmappable && + test_bit(FailFast, &rdev->flags) && (bio->bi_opf & MD_FAILFAST)) { md_error(rdev->mddev, rdev); } @@ -1170,6 +1181,9 @@ static void raid10_read_request(struct mddev *mddev, struct bio *bio, */ gfp_t gfp = err_path ? (GFP_NOIO | __GFP_HIGH) : GFP_NOIO; + if (md_bio_is_p2pdma(bio)) + set_bit(R10BIO_P2PDMA, &r10_bio->state); + if (slot >= 0 && r10_bio->devs[slot].rdev) { /* * This is an error retry, but we cannot @@ -1357,6 +1371,9 @@ static bool raid10_write_request(struct mddev *mddev, struct bio *bio, sector_t sectors; int max_sectors; + if (md_bio_is_p2pdma(bio)) + set_bit(R10BIO_P2PDMA, &r10_bio->state); + if ((mddev_is_clustered(mddev) && mddev->cluster_ops->area_resyncing(mddev, WRITE, bio->bi_iter.bi_sector, @@ -2786,7 +2803,7 @@ static void fix_read_error(struct r10conf *conf, struct mddev *mddev, struct r10 } } -static void narrow_write_error(struct r10bio *r10_bio, int i) +static void narrow_write_error(struct r10bio *r10_bio, int i, bool coarse) { struct bio *bio = r10_bio->master_bio; struct mddev *mddev = r10_bio->mddev; @@ -2800,6 +2817,11 @@ static void narrow_write_error(struct r10bio *r10_bio, int i) * It is conceivable that the bio doesn't exactly align with * blocks. We must handle this. * + * With 'coarse', retry the whole range as one bio and record + * one bad range if it fails: for P2PDMA mapping failures, + * which fail every block identically, while the single retry + * still lets a cleared transient error recover. + * * We currently own a reference to the rdev. */ @@ -2814,9 +2836,12 @@ static void narrow_write_error(struct r10bio *r10_bio, int i) block_sectors = roundup(1 << rdev->badblocks.shift, lbs); sector = r10_bio->sector; - sectors = ((r10_bio->sector + block_sectors) - & ~(sector_t)(block_sectors - 1)) - - sector; + if (coarse) + sectors = sect_to_write; + else + sectors = ((r10_bio->sector + block_sectors) + & ~(sector_t)(block_sectors - 1)) + - sector; while (sect_to_write) { struct bio *wbio; @@ -2856,6 +2881,7 @@ static void handle_read_error(struct mddev *mddev, struct r10bio *r10_bio) { int slot = r10_bio->read_slot; struct bio *bio; + bool p2pdma_error; struct r10conf *conf = mddev->private; struct md_rdev *rdev = r10_bio->devs[slot].rdev; @@ -2868,17 +2894,28 @@ static void handle_read_error(struct mddev *mddev, struct r10bio *r10_bio) * frozen. */ bio = r10_bio->devs[slot].bio; + /* evaluate before the bio_put() below */ + p2pdma_error = bio->bi_status == BLK_STS_TARGET && + test_bit(R10BIO_P2PDMA, &r10_bio->state); bio_put(bio); r10_bio->devs[slot].bio = NULL; if (mddev->ro) r10_bio->devs[slot].bio = IO_BLOCKED; - else if (!test_bit(FailFast, &rdev->flags)) { + else if (p2pdma_error) { + /* + * The peer cannot reach this member; nothing on the + * medium to fix. Skip the read-error budget and + * FailFast, just keep this leg out of the retry. + */ + r10_bio->devs[slot].bio = IO_BLOCKED; + } else if (test_bit(FailFast, &rdev->flags)) { + md_error(mddev, rdev); + } else { freeze_array(conf, 1); fix_read_error(conf, mddev, r10_bio); unfreeze_array(conf); - } else - md_error(mddev, rdev); + } rdev_dec_pending(rdev, mddev); r10_bio->state = 0; @@ -2947,8 +2984,20 @@ static void handle_write_completed(struct r10conf *conf, struct r10bio *r10_bio) r10_bio->sectors, 0); rdev_dec_pending(rdev, conf->mddev); } else if (bio != NULL && bio->bi_status) { + bool coarse = bio->bi_status == + BLK_STS_TARGET && + test_bit(R10BIO_P2PDMA, + &r10_bio->state); + fail = true; - narrow_write_error(r10_bio, m); + /* + * A P2PDMA mapping failure fails the whole + * range identically: probe it once (coarse) + * instead of narrowing block by block. A + * transient TARGET recovers via the probe + * with nothing recorded. + */ + narrow_write_error(r10_bio, m, coarse); rdev_dec_pending(rdev, conf->mddev); } bio = r10_bio->devs[m].repl_bio; diff --git a/drivers/md/raid10.h b/drivers/md/raid10.h index ec79d87fb92f..a2e1554f77db 100644 --- a/drivers/md/raid10.h +++ b/drivers/md/raid10.h @@ -174,6 +174,8 @@ enum r10bio_state { R10BIO_Previous, /* failfast devices did receive failfast requests. */ R10BIO_FailFast, +/* the master bio carries PCI P2PDMA (peer device memory) pages */ + R10BIO_P2PDMA, R10BIO_Discard, }; #endif -- 2.43.0