Linux RAID subsystem development
 help / color / mirror / Atom feed
* Re: [PATCH] md: do overflow check for sb->bblog_shift in super_1_load()
From: yu kuai @ 2026-07-22  2:29 UTC (permalink / raw)
  To: colyli, yukuai; +Cc: linux-raid, linux-block, stable, Ramesh Adhikari
In-Reply-To: <20260720111400.2120834-1-colyli@fygo.io>

在 2026/7/20 19:14, colyli@fygo.io 写道:

> 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: 2699b67223aca ("md: load/store badblock list from v1.x metadata")
> Fixes: 1726c77467833 ("badblocks: improve badblocks_set() for multiple ranges handling")
> Cc:stable@vger.kernel.org
> Cc: Ramesh Adhikari<adhikari.resume@gmail.com>
> Signed-off-by: Coly Li<colyli@fygo.io>
> ---
>   drivers/md/md.c | 7 +++++++
>   1 file changed, 7 insertions(+)
Reviewed-by: Yu Kuai <yukuai@fygo.io>

-- 
Thanks,
Kuai

^ permalink raw reply

* Re: [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped
From: yu kuai @ 2026-07-22  4:00 UTC (permalink / raw)
  To: Yunye Zhao, Song Liu, yu kuai
  Cc: Li Nan, Xiao Ni, Joseph Qi, linux-raid, linux-kernel
In-Reply-To: <20260720062026.19245-1-yunye.zhao@linux.alibaba.com>

Hi,

在 2026/7/20 14:20, Yunye Zhao 写道:
> Hi Kuai,
>
> On 2026/7/17 14:27, Yu Kuai wrote:
>>> md_do_sync()'s main loop advances io_sectors only when I/O is actually
>>> issued (skipped == 0).  When sync_request() keeps returning skipped == 1,
>>> io_sectors never increases, [...]
>> That's not expected, io_sectors should always increase in the skip case.
> Sorry, my description was not accurate. The problem is not that io_sectors
> stays 0. md_do_sync()'s loop exit condition is j == max_sectors, and in
> raid10_sync_request()'s recovery path sectors is always 128, so for a very
> large max_sectors the loop iterates a huge number of times.
>
> raid10_sync_request(), recovery branch:
>
>      max_sync = RESYNC_PAGES << (PAGE_SHIFT-9);      /* 128 */
>      must_sync = md_bitmap_start_sync(mddev, sect, &sync_blocks, true);
>      if (sync_blocks < max_sync)   /* sync_blocks huge, never true */
>          max_sync = sync_blocks;   /* so max_sync stays 128 */
>      ...
>      if (biolist == NULL) {
>          *skipped = 1;
>          return max_sync;          /* 128; the large span is discarded */
>      }
>
> Back in md_do_sync()'s main loop, j then crawls forward 128 sectors per
> call until it reaches max_sectors:
>
>      while (j < max_sectors) {
>          sectors = mddev->pers->sync_request(mddev, j, max_sectors, &skipped);
>          if (!skipped)             /* skipped == 1 -> io_sectors stays 0 */
>              io_sectors += sectors;
>          j += sectors;             /* j += 128 only */
>          if (last_check + window > io_sectors || j == max_sectors)
>              continue;
>      }
>
>  From the vmcore:
>
>      sync_blocks returned = 0x5ED03680 (~1.59e8 sectors), clamped to 128
>      recovery max_sectors = dev_sectors = 2^40
>      iterations = 2^40 / 128 = 2^33 (~8.6e9)

Please update and fix the commit message "io_sectors never increases", this
really is misleading.

>
>> What kernel version you're testing?
> 6.6.102. I also tested mainline and hit the same problem.
>
>> If this is latest kernel, bitmap_start_sync() need to be fixed. It can't
>> return skip while setting skipping sectors to 0.
> bitmap_start_sync() is actually fine -- it returns a large clean span
> (0x5ED03680 above). The recovery path just discards it: max_sync is only
> ever clamped down, so it returns 128 regardless.
>
>> And since this is dead loop, a cond_resched() will not fix anything.
> Agreed -- cond_resched() only stops the watchdog; the thread still spins
> ~8.6e9 no-op iterations and pins a CPU for ~313s.
>
> For v2 I'd fix this at the source: make the recovery path honour the clean
> span reported by the bitmap instead of capping the skip at 128. Does that
> direction look right to you?

This patch is still valuable, even a 5s lockup is not acceptable.

And I think this is not a problem for llbitmap, it skips one bit at a time and
bits are limited to at most 4k*128. Perhaps bitmap can do this as well, it can
definably return much bigger skipping sectors from bitmap_start_sync().

>
> Thanks,
> Yunye

-- 
Thanks,
Kuai

^ permalink raw reply

* [PATCH] md/raid10: honor REQ_NOWAIT in wait_blocked_dev()
From: dayou5941 @ 2026-07-22  7:26 UTC (permalink / raw)
  To: song, yukuai; +Cc: linux-raid, liyouhong

From: liyouhong <liyouhong@kylinos.cn>

raid10_write_request() already respects REQ_NOWAIT for barrier and reshape
waits, but wait_blocked_dev() still always sleeps in
md_wait_for_blocked_rdev() when a member is Blocked / BlockedBadBlocks.
A NOWAIT write can therefore block in the submit path despite the flag.

Mirror raid1's wait_blocked_rdev(): if nowait is set and an rdev is
blocked, return false without waiting. The write path releases the
barrier, frees r10_bio, and completes the bio with wouldblock.

Discard still calls wait_blocked_dev(..., false). REQ_NOWAIT for discard is
already handled at wait_barrier(); honoring it again in wait_blocked_dev()
is a separate, harder-to-hit path (e.g. io_uring discard) and is left for
a follow-up to keep this fix focused on the reproduced write hang.

Fixes: c9aa889b035f ("md: raid10 add nowait support")
Signed-off-by: liyouhong <liyouhong@kylinos.cn>
---
 drivers/md/raid10.c | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 0a3cfdd3f5df..2ccca42e0702 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -1293,7 +1293,8 @@ static void raid10_write_one_disk(struct mddev *mddev, struct r10bio *r10_bio,
 	}
 }
 
-static void wait_blocked_dev(struct mddev *mddev, struct r10bio *r10_bio)
+static bool wait_blocked_dev(struct mddev *mddev, struct r10bio *r10_bio,
+			     bool nowait)
 {
 	struct r10conf *conf = mddev->private;
 	struct md_rdev *blocked_rdev;
@@ -1323,6 +1324,8 @@ static void wait_blocked_dev(struct mddev *mddev, struct r10bio *r10_bio)
 				set_bit(BlockedBadBlocks, &rdev->flags);
 
 			if (rdev_blocked(rdev)) {
+				if (nowait)
+					return false;
 				blocked_rdev = rdev;
 				atomic_inc(&rdev->nr_pending);
 				break;
@@ -1331,6 +1334,8 @@ static void wait_blocked_dev(struct mddev *mddev, struct r10bio *r10_bio)
 
 		rrdev = conf->mirrors[i].replacement;
 		if (rrdev && rdev_blocked(rrdev)) {
+			if (nowait)
+				return false;
 			atomic_inc(&rrdev->nr_pending);
 			blocked_rdev = rrdev;
 			break;
@@ -1347,6 +1352,7 @@ static void wait_blocked_dev(struct mddev *mddev, struct r10bio *r10_bio)
 		wait_barrier(conf, false);
 		goto retry_wait;
 	}
+	return true;
 }
 
 static bool raid10_write_request(struct mddev *mddev, struct bio *bio,
@@ -1421,7 +1427,12 @@ static bool raid10_write_request(struct mddev *mddev, struct bio *bio,
 	r10_bio->read_slot = -1; /* make sure repl_bio gets freed */
 	raid10_find_phys(conf, r10_bio);
 
-	wait_blocked_dev(mddev, r10_bio);
+	if (!wait_blocked_dev(mddev, r10_bio, bio->bi_opf & REQ_NOWAIT)) {
+		allow_barrier(conf);
+		free_r10bio(r10_bio);
+		bio_wouldblock_error(bio);
+		return false;
+	}
 
 	max_sectors = r10_bio->sectors;
 
@@ -1748,7 +1759,7 @@ static int raid10_handle_discard(struct mddev *mddev, struct bio *bio)
 	r10_bio->sectors = 0;
 	r10_bio->read_slot = -1;
 	memset(r10_bio->devs, 0, sizeof(r10_bio->devs[0]) * geo->raid_disks);
-	wait_blocked_dev(mddev, r10_bio);
+	wait_blocked_dev(mddev, r10_bio, false);
 
 	/*
 	 * For far layout it needs more than one r10bio to cover all regions.


^ permalink raw reply related

* Re: [PATCH] md/raid10: honor REQ_NOWAIT in wait_blocked_dev()
From: sashiko-bot @ 2026-07-22  7:53 UTC (permalink / raw)
  To: dayou5941; +Cc: linux-raid, yukuai
In-Reply-To: <20260722072645.2714771-1-dayou5941@163.com>

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

Pre-existing issues:
- [Critical] Dropping the barrier in `wait_blocked_dev()` allows concurrent reshape to invalidate the physical mapping, leading to data corruption.
- [High] Pre-existing memory leak of `r10_bio` in `raid10_write_request()` when returning early due to `REQ_NOWAIT`.
- [High] `wait_blocked_dev()` incorrectly checks the first `conf->copies` physical disks for blocked state, rather than the dynamically calculated target disks.
- [High] Allocation of `r10_bio` unconditionally sleeps, violating `REQ_NOWAIT` semantics.
- [High] TOCTOU Use-After-Free of `md_rdev` due to missing `rcu_read_lock()` in `raid10_write_request()` and `wait_blocked_dev()`.
--

--- Patch [1]: [PATCH] md/raid10: honor REQ_NOWAIT in wait_blocked_dev() ---
commit 4ef20896ea5200b8191d2a0afa7c08883eacb0e6
Author: liyouhong <liyouhong@kylinos.cn>

md/raid10: honor REQ_NOWAIT in wait_blocked_dev()

The patch modifies wait_blocked_dev() in the raid10 driver to honor the
REQ_NOWAIT flag. It returns false immediately if a blocked device is
encountered during a nowait write request, instead of sleeping. The write
path then releases the barrier, frees the bio, and returns a wouldblock error.

> diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
> index 0a3cfdd3f5df8..2ccca42e07029 100644
> --- a/drivers/md/raid10.c
> +++ b/drivers/md/raid10.c
> @@ -1293,7 +1293,8 @@ static void raid10_write_one_disk(struct mddev *mddev, struct r10bio *r10_bio,
>  	}
>  }
>  
> -static void wait_blocked_dev(struct mddev *mddev, struct r10bio *r10_bio)
> +static bool wait_blocked_dev(struct mddev *mddev, struct r10bio *r10_bio,
> +			     bool nowait)
>  {

[Severity: High]
This isn't a bug introduced by this patch, but how does wait_blocked_dev()
identify the correct mapped target disks?

The loop examines conf->mirrors[i] instead of using the dynamically
calculated target disk indices:

wait_blocked_dev() {
    ...
    for (i = 0; i < conf->copies; i++) {
        struct md_rdev *rdev, *rrdev;
        rdev = conf->mirrors[i].rdev;
    ...
}

Might this loop hardcode the check for the first conf->copies physical
disks, ignoring the actual target disks stored in r10_bio->devs[i].devnum
by raid10_find_phys()? This could risk bypassing blocked checks for disks
that the IO will actually touch.

>  	struct r10conf *conf = mddev->private;
>  	struct md_rdev *blocked_rdev;
[ ... ]
> @@ -1323,6 +1324,8 @@ static void wait_blocked_dev(struct mddev *mddev, struct r10bio *r10_bio)
>  				set_bit(BlockedBadBlocks, &rdev->flags);
>  
>  			if (rdev_blocked(rdev)) {
> +				if (nowait)
> +					return false;
>  				blocked_rdev = rdev;
>  				atomic_inc(&rdev->nr_pending);

[Severity: High]
This is a pre-existing issue, but is there a use-after-free risk for
md_rdev here?

Because atomic_inc(&rdev->nr_pending) is executed without holding
rcu_read_lock(), could a concurrent raid10_remove_disk() check the
pending count, find it zero, and call md_kick_rdev_from_array(),
freeing the device before the increment occurs?

A similar pattern happens in raid10_write_request() where
atomic_inc(&rdev->nr_pending) is also called without rcu_read_lock()
protection.

>  				break;
>  			}
[ ... ]
> @@ -1347,6 +1352,7 @@ static void wait_blocked_dev(struct mddev *mddev, struct r10bio *r10_bio)
>  		wait_barrier(conf, false);

[Severity: Critical]
This isn't a bug introduced by this patch, but can dropping the barrier
allow a concurrent reshape to invalidate the physical mapping?

When a blocked device is found, the synchronization barrier is dropped:

wait_blocked_dev() {
    ...
    allow_barrier(conf);
    md_wait_for_blocked_rdev(blocked_rdev, mddev);
    wait_barrier(conf, false);
    ...
}

Since raid10_find_phys() was already called before wait_blocked_dev(),
could a waiting reshape thread freeze the array, move data, and advance
reshape_progress while the barrier is dropped? If that happens,
raid10_write_request() might use a stale physical mapping in
r10_bio->devs to submit the IO after the barrier is re-acquired.

>  		goto retry_wait;
>  	}
> +	return true;
>  }
>  
>  static bool raid10_write_request(struct mddev *mddev, struct bio *bio,
> @@ -1421,7 +1427,12 @@ static bool raid10_write_request(struct mddev *mddev, struct bio *bio,
>  	r10_bio->read_slot = -1; /* make sure repl_bio gets freed */
>  	raid10_find_phys(conf, r10_bio);
>  
> -	wait_blocked_dev(mddev, r10_bio);
> +	if (!wait_blocked_dev(mddev, r10_bio, bio->bi_opf & REQ_NOWAIT)) {

[Severity: High]
This is a pre-existing issue, but are we leaking the r10_bio structure
on early returns in raid10_write_request()?

When REQ_NOWAIT is set and the IO overlaps with a clustered resync or
reshape, the function correctly bails out early, for example:

raid10_write_request() {
    ...
    if (bio->bi_opf & REQ_NOWAIT) {
        allow_barrier(conf);
        bio_wouldblock_error(bio);
        return false;
    }
    ...
}

If the function returns false without calling free_r10bio(r10_bio), will
this permanently leak the slab memory allocated for r10_bio?

[Severity: High]
This isn't a bug introduced by this patch, but would the initial allocation
of r10_bio violate REQ_NOWAIT semantics?

Before reaching the newly added nowait logic, the caller (__make_request)
unconditionally allocates r10_bio using GFP_NOIO:

__make_request() {
    ...
    r10_bio = mempool_alloc(&conf->r10bio_pool, GFP_NOIO);
    ...
}

Since GFP_NOIO allows direct reclaim, could this allocation sleep if the
mempool is exhausted, regardless of whether bio->bi_opf & REQ_NOWAIT
is set?

> +		allow_barrier(conf);
> +		free_r10bio(r10_bio);
> +		bio_wouldblock_error(bio);
> +		return false;
> +	}
>  
>  	max_sectors = r10_bio->sectors;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722072645.2714771-1-dayou5941@163.com?part=1

^ permalink raw reply

* Re: [PATCH v2 6/7] md/raid1,raid10: skip futile retries on P2PDMA mapping failures
From: Leon Romanovsky @ 2026-07-22  8:30 UTC (permalink / raw)
  To: Logan Gunthorpe
  Cc: Mykola Marzhan, Jens Axboe, Song Liu, Yu Kuai, Keith Busch,
	Christoph Hellwig, Sagi Grimberg, linux-block, linux-raid,
	linux-nvme, Li Nan, Xiao Ni, Guoqing Jiang, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Bjorn Helgaas,
	Shivaji Kant, Pranjal Shrivastava, Henrique Carvalho,
	linux-kernel, linux-rdma, linux-pci
In-Reply-To: <9f846c85-f427-433a-b87d-8e9e3a4406e7@deltatee.com>

On Tue, Jul 21, 2026 at 05:38:53PM -0600, Logan Gunthorpe wrote:
> 
> 
> On 2026-07-21 11:50, Mykola Marzhan wrote:
> > I have split it in v3 into a raid1 patch and a raid10 patch.
> > I am not sure about separate "introduce the bit" patches.
> > R1BIO_P2PDMA and R10BIO_P2PDMA are entries in the two personalities'
> > with own r*bio state enums.
> > The commit messages are shorter now.
> 
> That helps a bit, thanks.
> 
> 
> > An early return doesn't work here: on a mapping failure the
> > function must still fall through to set R1BIO_WriteError, so the
> > completion path runs the (single, whole-range) retry and records
> > the bad range -- only WantReplacement and the FailFast md_error()
> > are skipped.  v3 folds the two tests into one branch now,
> > which reads better and keeps the fall-through. Please check v3.
> 
> The early return works if you create a helper.
> 
> > I compared the two sides after splitting: the P2PDMA handling is
> > symmetric, and the remaining differences are pre-existing
> > personality differences rather than missed fixes.
> > A shared helper would be heavier than the two open-coded branches,
> > so I kept it per-personality.
> 
> Yeah, I still think it's a bit of a mess so I dug into it a bit more
> myself.
> 
> I realized the real problem in this patch set was reusing
> BLK_STS_TARGET. I think I may have used that a long time ago and those
> paths have since been removed. Using it here doesn't quite make sense
> because we end up with those awkward checks (is P2PDMA enabled *and* the
> error is a BLK_STS_TARGET). I don't think that can ever really be
> correct here seeing something could, in theory return BLK_STS_TARGET for
> another reason even when P2PDMA is enabled. So that should be cleaned up.

I had thoughts about this series too. It seems best to avoid
the P2P path entirely unless all legs support P2P.

Can the md/raid code determine P2P support in advance?

Thanks

^ permalink raw reply

* [PATCH v4 0/9] block,md,nvme: correct handling of unsupported P2PDMA transfers
From: Mykola Marzhan @ 2026-07-22 18:58 UTC (permalink / raw)
  To: Jens Axboe, Song Liu, Yu Kuai, Keith Busch, Christoph Hellwig,
	Sagi Grimberg, linux-block, linux-raid, linux-nvme
  Cc: Li Nan, Xiao Ni, Guoqing Jiang, Leon Romanovsky, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Logan Gunthorpe,
	Bjorn Helgaas, Shivaji Kant, Pranjal Shrivastava,
	Henrique Carvalho, linux-kernel, linux-rdma, linux-pci

md treats an unsupported P2PDMA transfer to a member as success, and
nvme-rdma retries one forever.  Whether peer memory can be DMA-mapped
depends on the PCIe topology between the two devices, and since v6.17
the failure completes as BLK_STS_INVAL: retryable for multipath and
deliberately ignored by md for member failures, so mirrors silently
diverge.

v1-v3 restored BLK_STS_TARGET for these failures and tagged P2PDMA
bios in md.  Logan Gunthorpe proposed a dedicated BLK_STS_P2PDMA
instead, so no consumer needs to know whether an I/O was P2P, and
wrote the core patches; v4 is built on them.  Patches 1, 5, 7 and 8
are his and keep his authorship (7 and 8 with completion-handler
additions).

  1  block: add BLK_STS_P2PDMA (stable, v6.17)
  2  md: keep REQ_NOMERGE on P2PDMA bios
  3  md/raid1: serialize non-write-behind writes on CollisionCheck
     rdevs (pre-existing bug patch 4 would widen; stable)
  4  md/raid1: no write-behind for P2PDMA bios
  5  md/raid1,raid10: factor out raid1_write_error() helper
  6  md/raid1,raid10: keep REQ_NOMERGE on narrow_write_error()
     retry clones
  7  md/raid1,raid10: skip futile retries on P2PDMA mapping
     failures
  8  md/raid1,raid10: set IO_BLOCKED in case of BLK_STS_P2PDMA
  9  nvme-rdma: return BLK_STS_P2PDMA (stable, v7.1; needs patch 1)

Routing: patch 1 block, 2-8 md, 9 nvme; 7-9 use the status patch 1
adds.  Patch 1 alone already fixes the divergence (md stops ignoring
the failure) and the multipath retry storm; the later patches only
refine the fallout, so the series bisects safely.  The md patches
fix a v7.2-rc1 regression (02666132403a) and carry no stable tags.

Tested with the QEMU raid1/raid10 error matrix and a ConnectX-4 Lx.
After patch 1, block-layer error injection accepts status=P2PDMA, so
the md completion paths can be exercised on any member with no P2P
hardware.  The patches were developed with AI assistance (see the
Assisted-by trailers); all code was human-reviewed and tested.

Against v7.2-rc4 (Logan's base).

Changes in v4:
 - new BLK_STS_P2PDMA (Logan) replaces restoring BLK_STS_TARGET; the
   R1BIO_P2PDMA/R10BIO_P2PDMA state bits are gone and the md changes
   shrink to completion-time status tests
 - blk_dma_map_iter_start() gets the explicit
   PCI_P2PDMA_MAP_NOT_SUPPORTED case (Logan's patch; v3 kept
   default: only)
 - write completions record the whole failed range as bad blocks
   instead of narrow_write_error()'s per-block retries; v3's coarse
   retry is gone -- the new status is deterministic, nothing to
   re-attempt
 - read path: bi_status is evaluated before bio_put()

Link: https://lore.kernel.org/linux-raid/20260718162547.448892-1-mykola@meshstor.io/ [v1]
Link: https://lore.kernel.org/linux-raid/20260719105327.864949-1-mykola@meshstor.io/ [v2]
Link: https://lore.kernel.org/linux-raid/20260721174502.111503-1-mykola@meshstor.io/ [v3]
Link: https://lore.kernel.org/linux-raid/9f846c85-f427-433a-b87d-8e9e3a4406e7@deltatee.com/ [Logan's proposal]

Logan Gunthorpe (4):
  block: add BLK_STS_P2PDMA for unsupported peer-to-peer transfers
  md/raid1,raid10: factor out raid1_write_error() helper
  md/raid1,raid10: skip futile retries on P2PDMA mapping failures
  md/raid1,raid10: set IO_BLOCKED in case of BLK_STS_P2PDMA

Mykola Marzhan (5):
  md: ensure REQ_NOMERGE is set on P2PDMA bios
  md/raid1: serialize non-write-behind writes on CollisionCheck rdevs
  md/raid1: don't use write-behind for P2PDMA bios
  md/raid1,raid10: keep REQ_NOMERGE on narrow_write_error() retry clones
  nvme-rdma: return BLK_STS_P2PDMA for unsupported P2P transfers

 block/blk-core.c          |  3 +++
 block/blk-mq-dma.c        |  3 ++-
 drivers/md/md.c           | 10 ++++++--
 drivers/md/md.h           | 15 ++++++++++++
 drivers/md/raid1-10.c     | 16 +++++++++++++
 drivers/md/raid1.c        | 48 +++++++++++++++++++++++----------------
 drivers/md/raid10.c       | 26 ++++++++++++---------
 drivers/nvme/host/rdma.c  | 38 ++++++++++++++++++-------------
 include/linux/blk_types.h | 10 ++++++++
 9 files changed, 120 insertions(+), 49 deletions(-)


base-commit: 1590cf0329716306e948a8fc29f1d3ee87d3989f
-- 
2.52.0


^ permalink raw reply

* [PATCH v4 1/9] block: add BLK_STS_P2PDMA for unsupported peer-to-peer transfers
From: Mykola Marzhan @ 2026-07-22 18:58 UTC (permalink / raw)
  To: Jens Axboe, Song Liu, Yu Kuai, Keith Busch, Christoph Hellwig,
	Sagi Grimberg, linux-block, linux-raid, linux-nvme
  Cc: Li Nan, Xiao Ni, Guoqing Jiang, Leon Romanovsky, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Logan Gunthorpe,
	Bjorn Helgaas, Shivaji Kant, Pranjal Shrivastava,
	Henrique Carvalho, linux-kernel, linux-rdma, linux-pci
In-Reply-To: <20260722185841.449934-1-mykola@meshstor.io>

From: Logan Gunthorpe <logang@deltatee.com>

blk_dma_map_iter_start() reports BLK_STS_INVAL when a P2PDMA transfer
is attempted between two devices whose PCIe topology cannot route it.
blk_path_error() treats INVAL as retryable, so multipath requeues the
I/O forever, and callers cannot tell it apart from an invalid request.
md also ignores it for member failures, so a mirror silently diverges.

Restoring the old BLK_STS_TARGET would stay ambiguous -- nvme maps
NS_NOT_READY, LBA_RANGE and CMD_INTERRUPTED to it too.  Add a dedicated
BLK_STS_P2PDMA, non-retryable in blk_path_error(), and return it from
blk_dma_map_iter_start().

Fixes: 858299dc6160 ("block: add scatterlist-less DMA mapping helpers")
Fixes: 7ce3c1dd78fc ("nvme-pci: convert the data mapping to blk_rq_dma_map")
Cc: stable@vger.kernel.org # v6.17
Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
 block/blk-core.c          |  3 +++
 block/blk-mq-dma.c        |  3 ++-
 include/linux/blk_types.h | 10 ++++++++++
 3 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/block/blk-core.c b/block/blk-core.c
index 365641266c9e..83cb5464265f 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -177,6 +177,9 @@ static const struct {
 
 	/* Command duration limit device-side timeout */
 	ENT(DURATION_LIMIT,	-ETIME,		"duration limit exceeded"),
+
+	/* no PCIe P2PDMA route between initiator and target */
+	ENT(P2PDMA,		-EREMOTEIO,	"peer-to-peer transfer unroutable"),
 	ENT(INVAL,		-EINVAL,	"invalid"),
 
 	/* everything else not covered above: */
diff --git a/block/blk-mq-dma.c b/block/blk-mq-dma.c
index bfdb9ed70741..e88d6b3524e6 100644
--- a/block/blk-mq-dma.c
+++ b/block/blk-mq-dma.c
@@ -189,8 +189,9 @@ static bool blk_dma_map_iter_start(struct request *req, struct device *dma_dev,
 		 */
 	case PCI_P2PDMA_MAP_NONE:
 		break;
+	case PCI_P2PDMA_MAP_NOT_SUPPORTED:
 	default:
-		iter->status = BLK_STS_INVAL;
+		iter->status = BLK_STS_P2PDMA;
 		return false;
 	}
 
diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h
index 8808ee76e73c..c8de7809320a 100644
--- a/include/linux/blk_types.h
+++ b/include/linux/blk_types.h
@@ -165,6 +165,15 @@ typedef u16 blk_short_t;
  */
 #define BLK_STS_DURATION_LIMIT	((__force blk_status_t)17)
 
+/*
+ * BLK_STS_P2PDMA is returned when a peer-to-peer DMA transfer cannot be
+ * mapped (typically because there is no route between the initiator and the
+ * target that the platform's PCIe topology and ACS settings allow). This
+ * is a property of the initiator/target pair, not of the target device's
+ * health. This error should never be retried.
+ */
+#define BLK_STS_P2PDMA	((__force blk_status_t)18)
+
 /*
  * Invalid size or alignment.
  */
@@ -188,6 +197,7 @@ static inline bool blk_path_error(blk_status_t error)
 	case BLK_STS_NOTSUPP:
 	case BLK_STS_NOSPC:
 	case BLK_STS_TARGET:
+	case BLK_STS_P2PDMA:
 	case BLK_STS_RESV_CONFLICT:
 	case BLK_STS_MEDIUM:
 	case BLK_STS_PROTECTION:
-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 2/9] md: ensure REQ_NOMERGE is set on P2PDMA bios
From: Mykola Marzhan @ 2026-07-22 18:58 UTC (permalink / raw)
  To: Jens Axboe, Song Liu, Yu Kuai, Keith Busch, Christoph Hellwig,
	Sagi Grimberg, linux-block, linux-raid, linux-nvme
  Cc: Li Nan, Xiao Ni, Guoqing Jiang, Leon Romanovsky, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Logan Gunthorpe,
	Bjorn Helgaas, Shivaji Kant, Pranjal Shrivastava,
	Henrique Carvalho, linux-kernel, linux-rdma, linux-pci
In-Reply-To: <20260722185841.449934-1-mykola@meshstor.io>

md_submit_bio() strips REQ_NOMERGE from every bio.  For P2PDMA bios
the flag is what keeps requests single-provider (see
__bio_add_page()): merging a P2PDMA bio with one over a different
pgmap, or over host memory, maps the merged request with the wrong
bus address.  Set the flag on P2PDMA bios -- bios built via
bio_iov_bvec_set() arrive without it -- and keep stripping it
otherwise.

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 <mykola@meshstor.io>
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
---
 drivers/md/md.c | 10 ++++++++--
 drivers/md/md.h | 15 +++++++++++++++
 2 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/drivers/md/md.c b/drivers/md/md.c
index d1465bcd86c8..3770bdb4d4b2 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -451,8 +451,14 @@ static void md_submit_bio(struct bio *bio)
 		return;
 	}
 
-	/* bio could be mergeable after passing to underlayer */
-	bio->bi_opf &= ~REQ_NOMERGE;
+	/*
+	 * A bio md split may merge again below md -- except P2PDMA bios,
+	 * which must stay single-provider (see __bio_add_page()).
+	 */
+	if (md_bio_is_p2pdma(bio))
+		bio->bi_opf |= REQ_NOMERGE;
+	else
+		bio->bi_opf &= ~REQ_NOMERGE;
 
 	md_handle_request(mddev, bio);
 }
diff --git a/drivers/md/md.h b/drivers/md/md.h
index d8daf0f75cbb..73df1a6eccd8 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -11,8 +11,10 @@
 #include <linux/blkdev.h>
 #include <linux/backing-dev.h>
 #include <linux/badblocks.h>
+#include <linux/bio.h>
 #include <linux/kobject.h>
 #include <linux/list.h>
+#include <linux/memremap.h>
 #include <linux/mm.h>
 #include <linux/mutex.h>
 #include <linux/timer.h>
@@ -22,6 +24,19 @@
 #include <trace/events/block.h>
 
 #define MaxSector (~(sector_t)0)
+
+/*
+ * P2P and host pages never mix within a bio, so the first bvec is
+ * representative.  Read bi_io_vec directly: bio_first_bvec_all()
+ * WARNs on the split clones md handles, and data-less bios have no
+ * bi_io_vec.  Not valid after the bio's iterator is consumed.
+ */
+static inline bool md_bio_is_p2pdma(struct bio *bio)
+{
+	return bio_has_data(bio) && bio->bi_io_vec &&
+	       is_pci_p2pdma_page(bio->bi_io_vec->bv_page);
+}
+
 /*
  * Number of guaranteed raid bios in case of extreme VM load:
  */
-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 3/9] md/raid1: serialize non-write-behind writes on CollisionCheck rdevs
From: Mykola Marzhan @ 2026-07-22 18:58 UTC (permalink / raw)
  To: Jens Axboe, Song Liu, Yu Kuai, Keith Busch, Christoph Hellwig,
	Sagi Grimberg, linux-block, linux-raid, linux-nvme
  Cc: Li Nan, Xiao Ni, Guoqing Jiang, Leon Romanovsky, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Logan Gunthorpe,
	Bjorn Helgaas, Shivaji Kant, Pranjal Shrivastava,
	Henrique Carvalho, linux-kernel, linux-rdma, linux-pci
In-Reply-To: <20260722185841.449934-1-mykola@meshstor.io>

A write that skips write-behind (full behind queue, a waiting
reader, failed behind-bio allocation) races the in-flight behind
write it overlaps: nothing orders the two on the write-mostly
member, and if the older behind data lands last the member keeps
stale data for sectors already acknowledged as rewritten.

Serialize on CollisionCheck, which marks exactly the rdevs owning a
serial tree: serialize_policy rdevs plus write-mostly members when
write-behind arms serialization -- the case the MD_SERIALIZE_POLICY
test misses.  remove_serial() under the same condition.  This also
avoids the old gate's latent NULL deref for rdevs hot-added under
serialize_policy (they never get a serial tree).

Fixes: d0d2d8ba0494 ("md/raid1: introduce wait_for_serialization")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-fable-5
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
 drivers/md/raid1.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
index afe2ca96ad8c..997f79c05db7 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -564,7 +564,7 @@ static void raid1_end_write_request(struct bio *bio)
 				call_bio_endio(r1_bio);
 			}
 		}
-	} else if (test_bit(MD_SERIALIZE_POLICY, &rdev->mddev->flags))
+	} else if (test_bit(CollisionCheck, &rdev->flags))
 		remove_serial(rdev, lo, hi);
 	if (r1_bio->bios[mirror] == NULL)
 		rdev_dec_pending(rdev, conf->mddev);
@@ -1677,7 +1677,11 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio,
 			mbio = bio_alloc_clone(rdev->bdev, bio, GFP_NOIO,
 					       &mddev->bio_set);
 
-			if (test_bit(MD_SERIALIZE_POLICY, &mddev->flags))
+			/*
+			 * CollisionCheck marks every rdev with a serial
+			 * tree; order against in-flight write-behind I/O.
+			 */
+			if (test_bit(CollisionCheck, &rdev->flags))
 				wait_for_serialization(rdev, r1_bio);
 		}
 
-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 4/9] md/raid1: don't use write-behind for P2PDMA bios
From: Mykola Marzhan @ 2026-07-22 18:58 UTC (permalink / raw)
  To: Jens Axboe, Song Liu, Yu Kuai, Keith Busch, Christoph Hellwig,
	Sagi Grimberg, linux-block, linux-raid, linux-nvme
  Cc: Li Nan, Xiao Ni, Guoqing Jiang, Leon Romanovsky, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Logan Gunthorpe,
	Bjorn Helgaas, Shivaji Kant, Pranjal Shrivastava,
	Henrique Carvalho, linux-kernel, linux-rdma, linux-pci
In-Reply-To: <20260722185841.449934-1-mykola@meshstor.io>

alloc_behind_master_bio() copies the bio's data with bio_copy_data(),
a CPU copy. P2PDMA pages are peer device (BAR) memory; generic code
must not assume CPU load/store access to them is safe or fast on
every architecture, and bouncing peer memory through the CPU defeats
the point of a peer-to-peer transfer.

Skip write-behind for P2PDMA bios: they are written directly to all
members, including write-mostly ones.  Ordering against write-behind
I/O in flight to overlapping sectors is preserved: the non-behind
clone path serializes on CollisionCheck rdevs (see the preceding
fix), which covers these bios like any other write that bypasses
write-behind.

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 <mykola@meshstor.io>
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
---
 drivers/md/raid1.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
index 997f79c05db7..7d769b6460ca 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -1523,6 +1523,7 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio,
 	bool write_behind = false;
 	bool nowait = bio->bi_opf & REQ_NOWAIT;
 	bool is_discard = op_is_discard(bio->bi_opf);
+	bool is_p2pdma = md_bio_is_p2pdma(bio);
 	sector_t sector = bio->bi_iter.bi_sector;
 
 	if (mddev_is_clustered(mddev) &&
@@ -1575,9 +1576,12 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio,
 		/*
 		 * The write-behind io is only attempted on drives marked as
 		 * write-mostly, which means we could allocate write behind
-		 * bio later.
+		 * bio later. P2PDMA bios are excluded: write-behind copies
+		 * the data with bio_copy_data(), a CPU copy that cannot be
+		 * assumed safe or fast on P2PDMA (device BAR) pages.
 		 */
-		if (!is_discard && rdev && test_bit(WriteMostly, &rdev->flags))
+		if (!is_discard && !is_p2pdma && rdev &&
+		    test_bit(WriteMostly, &rdev->flags))
 			write_behind = true;
 
 		r1_bio->bios[i] = NULL;
-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 5/9] md/raid1,raid10: factor out raid1_write_error() helper
From: Mykola Marzhan @ 2026-07-22 18:58 UTC (permalink / raw)
  To: Jens Axboe, Song Liu, Yu Kuai, Keith Busch, Christoph Hellwig,
	Sagi Grimberg, linux-block, linux-raid, linux-nvme
  Cc: Li Nan, Xiao Ni, Guoqing Jiang, Leon Romanovsky, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Logan Gunthorpe,
	Bjorn Helgaas, Shivaji Kant, Pranjal Shrivastava,
	Henrique Carvalho, linux-kernel, linux-rdma, linux-pci
In-Reply-To: <20260722185841.449934-1-mykola@meshstor.io>

From: Logan Gunthorpe <logang@deltatee.com>

Factor out a common raid1_write_error() helper from raid1.c and
raid10.c.

This will help reduce code duplication in subsequent patches.

Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
 drivers/md/raid1-10.c | 13 +++++++++++++
 drivers/md/raid1.c    | 13 ++-----------
 drivers/md/raid10.c   | 10 +---------
 3 files changed, 16 insertions(+), 20 deletions(-)

diff --git a/drivers/md/raid1-10.c b/drivers/md/raid1-10.c
index 56a56a4da4f8..cee21452e066 100644
--- a/drivers/md/raid1-10.c
+++ b/drivers/md/raid1-10.c
@@ -303,3 +303,16 @@ static inline bool raid1_should_handle_error(struct bio *bio)
 	return !(bio->bi_opf & (REQ_RAHEAD | REQ_NOWAIT)) &&
 		bio->bi_status != BLK_STS_INVAL;
 }
+
+static inline void raid1_write_error(struct mddev *mddev, struct md_rdev *rdev,
+				     struct bio *bio, bool allow_failfast)
+{
+	set_bit(WriteErrorSeen, &rdev->flags);
+
+	if (!test_and_set_bit(WantReplacement, &rdev->flags))
+		set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
+
+	if (allow_failfast && test_bit(FailFast, &rdev->flags) &&
+	    (bio->bi_opf & MD_FAILFAST))
+		md_error(mddev, rdev);
+}
diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
index 7d769b6460ca..f07edeac6e5a 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -483,17 +483,8 @@ static void raid1_end_write_request(struct bio *bio)
 	 * 'one mirror IO has finished' event handler:
 	 */
 	if (bio->bi_status && !ignore_error) {
-		set_bit(WriteErrorSeen,	&rdev->flags);
-		if (!test_and_set_bit(WantReplacement, &rdev->flags))
-			set_bit(MD_RECOVERY_NEEDED, &
-				conf->mddev->recovery);
-
-		if (test_bit(FailFast, &rdev->flags) &&
-		    (bio->bi_opf & MD_FAILFAST) &&
-		    /* We never try FailFast to WriteMostly devices */
-		    !test_bit(WriteMostly, &rdev->flags)) {
-			md_error(r1_bio->mddev, rdev);
-		}
+		raid1_write_error(conf->mddev, rdev, bio,
+				  !test_bit(WriteMostly, &rdev->flags));
 
 		/*
 		 * When the device is faulty, it is not necessary to
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 0a3cfdd3f5df..5b63175fb382 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -482,16 +482,8 @@ static void raid10_end_write_request(struct bio *bio)
 			 */
 			md_error(rdev->mddev, rdev);
 		else {
-			set_bit(WriteErrorSeen,	&rdev->flags);
-			if (!test_and_set_bit(WantReplacement, &rdev->flags))
-				set_bit(MD_RECOVERY_NEEDED,
-					&rdev->mddev->recovery);
-
 			dec_rdev = 0;
-			if (test_bit(FailFast, &rdev->flags) &&
-			    (bio->bi_opf & MD_FAILFAST)) {
-				md_error(rdev->mddev, rdev);
-			}
+			raid1_write_error(rdev->mddev, rdev, bio, true);
 
 			/*
 			 * When the device is faulty, it is not necessary to
-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 6/9] md/raid1,raid10: keep REQ_NOMERGE on narrow_write_error() retry clones
From: Mykola Marzhan @ 2026-07-22 18:58 UTC (permalink / raw)
  To: Jens Axboe, Song Liu, Yu Kuai, Keith Busch, Christoph Hellwig,
	Sagi Grimberg, linux-block, linux-raid, linux-nvme
  Cc: Li Nan, Xiao Ni, Guoqing Jiang, Leon Romanovsky, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Logan Gunthorpe,
	Bjorn Helgaas, Shivaji Kant, Pranjal Shrivastava,
	Henrique Carvalho, linux-kernel, linux-rdma, linux-pci
In-Reply-To: <20260722185841.449934-1-mykola@meshstor.io>

narrow_write_error() re-issues a failed write in badblock-granularity
chunks, cloning from the master bio and resetting bi_opf to a bare
REQ_OP_WRITE. For a P2PDMA bio that reset drops REQ_NOMERGE, which is
the only request-level protection against the member queue merging
P2PDMA segments across pgmaps or with host memory (see the preceding
md_submit_bio() fix): the retry path would quietly reopen the hole
the submission path closes. Restore the flag on P2PDMA retry clones.

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 <mykola@meshstor.io>
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
---
 drivers/md/raid1.c  | 3 +++
 drivers/md/raid10.c | 3 +++
 2 files changed, 6 insertions(+)

diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
index f07edeac6e5a..19c4dec450de 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -2561,6 +2561,9 @@ static void narrow_write_error(struct r1bio *r1_bio, int i)
 		}
 
 		wbio->bi_opf = REQ_OP_WRITE;
+		/* Keep P2PDMA retry bios unmergeable, like the original */
+		if (md_bio_is_p2pdma(wbio))
+			wbio->bi_opf |= REQ_NOMERGE;
 		wbio->bi_iter.bi_sector = r1_bio->sector;
 		wbio->bi_iter.bi_size = r1_bio->sectors << 9;
 
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 5b63175fb382..428bddddfe71 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -2823,6 +2823,9 @@ static void narrow_write_error(struct r10bio *r10_bio, int i)
 		wbio->bi_iter.bi_sector = wsector +
 				   choose_data_offset(r10_bio, rdev);
 		wbio->bi_opf = REQ_OP_WRITE;
+		/* Keep P2PDMA retry bios unmergeable, like the original */
+		if (md_bio_is_p2pdma(wbio))
+			wbio->bi_opf |= REQ_NOMERGE;
 
 		if (submit_bio_wait(wbio) &&
 		    !rdev_set_badblocks(rdev, wsector, sectors, 0)) {
-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 7/9] md/raid1,raid10: skip futile retries on P2PDMA mapping failures
From: Mykola Marzhan @ 2026-07-22 18:58 UTC (permalink / raw)
  To: Jens Axboe, Song Liu, Yu Kuai, Keith Busch, Christoph Hellwig,
	Sagi Grimberg, linux-block, linux-raid, linux-nvme
  Cc: Li Nan, Xiao Ni, Guoqing Jiang, Leon Romanovsky, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Logan Gunthorpe,
	Bjorn Helgaas, Shivaji Kant, Pranjal Shrivastava,
	Henrique Carvalho, linux-kernel, linux-rdma, linux-pci
In-Reply-To: <20260722185841.449934-1-mykola@meshstor.io>

From: Logan Gunthorpe <logang@deltatee.com>

BLK_STS_P2PDMA reflects the peer/member pairing, not member health:
retrying the same pages on the same member cannot succeed.  Keep
WriteErrorSeen so the range stays fenced, but skip WantReplacement and
FailFast eviction, as commit f7b24c7b41f2 ("md/raid1,raid10: don't fail
devices for invalid IO errors") did for BLK_STS_INVAL.

The failure is whole-range and deterministic, so narrow_write_error()'s
block-by-block submit_bio_wait() retries are pointless.  Record the
whole range as a bad block in the completion handler instead.

Fixes: 02666132403a ("md: propagate BLK_FEAT_PCI_P2PDMA from member devices to RAID device")
Assisted-by: Claude:claude-fable-5
Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
Co-developed-by: Mykola Marzhan <mykola@meshstor.io>
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
 drivers/md/raid1-10.c |  3 +++
 drivers/md/raid1.c    | 12 +++++++++---
 drivers/md/raid10.c   |  8 +++++++-
 3 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/drivers/md/raid1-10.c b/drivers/md/raid1-10.c
index cee21452e066..89daf8558251 100644
--- a/drivers/md/raid1-10.c
+++ b/drivers/md/raid1-10.c
@@ -309,6 +309,9 @@ static inline void raid1_write_error(struct mddev *mddev, struct md_rdev *rdev,
 {
 	set_bit(WriteErrorSeen, &rdev->flags);
 
+	if (bio->bi_status == BLK_STS_P2PDMA)
+		return;
+
 	if (!test_and_set_bit(WantReplacement, &rdev->flags))
 		set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
 
diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
index 19c4dec450de..917d694ef401 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -2624,10 +2624,16 @@ static void handle_write_finished(struct r1conf *conf, struct r1bio *r1_bio)
 			 * narrow down and record precise write
 			 * errors.
 			 */
+			struct md_rdev *rdev = conf->mirrors[m].rdev;
+
 			fail = true;
-			narrow_write_error(r1_bio, m);
-			rdev_dec_pending(conf->mirrors[m].rdev,
-					 conf->mddev);
+			/* Mapping failures are whole-range and deterministic */
+			if (r1_bio->bios[m]->bi_status == BLK_STS_P2PDMA)
+				rdev_set_badblocks(rdev, r1_bio->sector,
+						   r1_bio->sectors, 0);
+			else
+				narrow_write_error(r1_bio, m);
+			rdev_dec_pending(rdev, conf->mddev);
 		}
 	if (fail) {
 		spin_lock_irq(&conf->device_lock);
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 428bddddfe71..9045a3f02dae 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -2940,7 +2940,13 @@ static void handle_write_completed(struct r10conf *conf, struct r10bio *r10_bio)
 				rdev_dec_pending(rdev, conf->mddev);
 			} else if (bio != NULL && bio->bi_status) {
 				fail = true;
-				narrow_write_error(r10_bio, m);
+				/* Mapping failures are whole-range and deterministic */
+				if (bio->bi_status == BLK_STS_P2PDMA)
+					rdev_set_badblocks(rdev,
+							   r10_bio->devs[m].addr,
+							   r10_bio->sectors, 0);
+				else
+					narrow_write_error(r10_bio, m);
 				rdev_dec_pending(rdev, conf->mddev);
 			}
 			bio = r10_bio->devs[m].repl_bio;
-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 8/9] md/raid1,raid10: set IO_BLOCKED in case of BLK_STS_P2PDMA
From: Mykola Marzhan @ 2026-07-22 18:58 UTC (permalink / raw)
  To: Jens Axboe, Song Liu, Yu Kuai, Keith Busch, Christoph Hellwig,
	Sagi Grimberg, linux-block, linux-raid, linux-nvme
  Cc: Li Nan, Xiao Ni, Guoqing Jiang, Leon Romanovsky, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Logan Gunthorpe,
	Bjorn Helgaas, Shivaji Kant, Pranjal Shrivastava,
	Henrique Carvalho, linux-kernel, linux-rdma, linux-pci
In-Reply-To: <20260722185841.449934-1-mykola@meshstor.io>

From: Logan Gunthorpe <logang@deltatee.com>

A read that fails with BLK_STS_P2PDMA cannot succeed against that
member, so mark the leg IO_BLOCKED and let the retry redirect elsewhere.
Skip the read-error machinery: there is nothing on the medium to fix,
fix_read_error()'s probe reads into host pages and would "succeed", and
charging the read-error budget would evict a healthy member under a P2P
read workload.  FailFast eviction is skipped for the same reason -- the
request never reached the wire.

Fixes: 02666132403a ("md: propagate BLK_FEAT_PCI_P2PDMA from member devices to RAID device")
Assisted-by: Claude:claude-fable-5
Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
Co-developed-by: Mykola Marzhan <mykola@meshstor.io>
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
 drivers/md/raid1.c  | 4 +++-
 drivers/md/raid10.c | 5 ++++-
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
index 917d694ef401..76a1426e64f0 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -2658,6 +2658,8 @@ 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_P2PDMA;
 	struct mddev *mddev = conf->mddev;
 	sector_t sector;
 
@@ -2675,7 +2677,7 @@ static void handle_read_error(struct r1conf *conf, struct r1bio *r1_bio)
 	 * read error.  This is all done synchronously while the array is
 	 * frozen.
 	 */
-	if (mddev->ro) {
+	if (mddev->ro || p2pdma_error) {
 		r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED;
 	} else if (test_bit(FailFast, &rdev->flags)) {
 		md_error(mddev, rdev);
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 9045a3f02dae..e35aeac29f06 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -2848,6 +2848,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;
 
@@ -2860,10 +2861,12 @@ 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_P2PDMA;
 	bio_put(bio);
 	r10_bio->devs[slot].bio = NULL;
 
-	if (mddev->ro)
+	if (mddev->ro || p2pdma_error)
 		r10_bio->devs[slot].bio = IO_BLOCKED;
 	else if (!test_bit(FailFast, &rdev->flags)) {
 		freeze_array(conf, 1);
-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 9/9] nvme-rdma: return BLK_STS_P2PDMA for unsupported P2P transfers
From: Mykola Marzhan @ 2026-07-22 18:58 UTC (permalink / raw)
  To: Jens Axboe, Song Liu, Yu Kuai, Keith Busch, Christoph Hellwig,
	Sagi Grimberg, linux-block, linux-raid, linux-nvme
  Cc: Li Nan, Xiao Ni, Guoqing Jiang, Leon Romanovsky, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Logan Gunthorpe,
	Bjorn Helgaas, Shivaji Kant, Pranjal Shrivastava,
	Henrique Carvalho, linux-kernel, linux-rdma, linux-pci
In-Reply-To: <20260722185841.449934-1-mykola@meshstor.io>

When the PCIe topology cannot route a P2P transfer between two devices,
the DMA layer fails the mapping with -EREMOTEIO, but ib_dma_map_sg()
returns 0 and the error is lost. nvme-rdma then fails the I/O with a
retryable status, so multipath requeues it forever and a single path
wastes its whole retry budget on an I/O that can never succeed.

Map the data and metadata scatterlists with ib_dma_map_sgtable_attrs(),
which preserves the error, and translate -EREMOTEIO to the new
BLK_STS_P2PDMA status so the request fails fast instead of being
retried. While at it, call nvme_start_request() only after mapping
succeeds and ratelimit the map-failure message.

Fixes: 23528aa3320a ("nvme: enable PCI P2PDMA support for RDMA transport")
Cc: stable@vger.kernel.org # v7.1
Assisted-by: Claude:claude-fable-5
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
 drivers/nvme/host/rdma.c | 38 ++++++++++++++++++++++----------------
 1 file changed, 22 insertions(+), 16 deletions(-)

diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c
index 6909e3542794..2f50509a7a61 100644
--- a/drivers/nvme/host/rdma.c
+++ b/drivers/nvme/host/rdma.c
@@ -1469,6 +1469,7 @@ static int nvme_rdma_dma_map_req(struct ib_device *ibdev, struct request *rq,
 		int *count, int *pi_count)
 {
 	struct nvme_rdma_request *req = blk_mq_rq_to_pdu(rq);
+	struct sg_table sgt;
 	int ret;
 
 	req->data_sgl.sg_table.sgl = (struct scatterlist *)(req + 1);
@@ -1480,12 +1481,14 @@ static int nvme_rdma_dma_map_req(struct ib_device *ibdev, struct request *rq,
 
 	req->data_sgl.nents = blk_rq_map_sg(rq, req->data_sgl.sg_table.sgl);
 
-	*count = ib_dma_map_sg(ibdev, req->data_sgl.sg_table.sgl,
-			       req->data_sgl.nents, rq_dma_dir(rq));
-	if (unlikely(*count <= 0)) {
-		ret = -EIO;
+	sgt = (struct sg_table) {
+		.sgl		= req->data_sgl.sg_table.sgl,
+		.orig_nents	= req->data_sgl.nents,
+	};
+	ret = ib_dma_map_sgtable_attrs(ibdev, &sgt, rq_dma_dir(rq), 0);
+	if (unlikely(ret))
 		goto out_free_table;
-	}
+	*count = sgt.nents;
 
 	if (blk_integrity_rq(rq)) {
 		req->metadata_sgl->sg_table.sgl =
@@ -1501,14 +1504,14 @@ static int nvme_rdma_dma_map_req(struct ib_device *ibdev, struct request *rq,
 
 		req->metadata_sgl->nents = blk_rq_map_integrity_sg(rq,
 				req->metadata_sgl->sg_table.sgl);
-		*pi_count = ib_dma_map_sg(ibdev,
-					  req->metadata_sgl->sg_table.sgl,
-					  req->metadata_sgl->nents,
-					  rq_dma_dir(rq));
-		if (unlikely(*pi_count <= 0)) {
-			ret = -EIO;
+		sgt = (struct sg_table) {
+			.sgl		= req->metadata_sgl->sg_table.sgl,
+			.orig_nents	= req->metadata_sgl->nents,
+		};
+		ret = ib_dma_map_sgtable_attrs(ibdev, &sgt, rq_dma_dir(rq), 0);
+		if (unlikely(ret))
 			goto out_free_pi_table;
-		}
+		*pi_count = sgt.nents;
 	}
 
 	return 0;
@@ -2026,8 +2029,6 @@ static blk_status_t nvme_rdma_queue_rq(struct blk_mq_hw_ctx *hctx,
 	if (ret)
 		goto unmap_qe;
 
-	nvme_start_request(rq);
-
 	if (IS_ENABLED(CONFIG_BLK_DEV_INTEGRITY) &&
 	    queue->pi_support &&
 	    (c->common.opcode == nvme_cmd_write ||
@@ -2039,11 +2040,13 @@ static blk_status_t nvme_rdma_queue_rq(struct blk_mq_hw_ctx *hctx,
 
 	err = nvme_rdma_map_data(queue, rq, c);
 	if (unlikely(err < 0)) {
-		dev_err(queue->ctrl->ctrl.device,
-			     "Failed to map data (%d)\n", err);
+		dev_err_ratelimited(queue->ctrl->ctrl.device,
+				    "Failed to map data (%d)\n", err);
 		goto err;
 	}
 
+	nvme_start_request(rq);
+
 	sqe->cqe.done = nvme_rdma_send_done;
 
 	ib_dma_sync_single_for_device(dev, sqe->dma,
@@ -2063,6 +2066,9 @@ static blk_status_t nvme_rdma_queue_rq(struct blk_mq_hw_ctx *hctx,
 		ret = nvme_host_path_error(rq);
 	else if (err == -ENOMEM || err == -EAGAIN)
 		ret = BLK_STS_RESOURCE;
+	/* Peer memory unreachable from this device: don't retry. */
+	else if (err == -EREMOTEIO)
+		ret = BLK_STS_P2PDMA;
 	else
 		ret = BLK_STS_IOERR;
 	nvme_cleanup_cmd(rq);
-- 
2.52.0


^ permalink raw reply related

* Re: [PATCH v2 6/7] md/raid1,raid10: skip futile retries on P2PDMA mapping failures
From: Mykola Marzhan @ 2026-07-22 18:59 UTC (permalink / raw)
  To: Logan Gunthorpe
  Cc: Jens Axboe, Song Liu, Yu Kuai, Keith Busch, Christoph Hellwig,
	Sagi Grimberg, linux-block, linux-raid, linux-nvme, Li Nan,
	Xiao Ni, Guoqing Jiang, Leon Romanovsky, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Bjorn Helgaas,
	Shivaji Kant, Pranjal Shrivastava, Henrique Carvalho,
	linux-kernel, linux-rdma, linux-pci
In-Reply-To: <9f846c85-f427-433a-b87d-8e9e3a4406e7@deltatee.com>

> Instead, I think it makes more sense to introduce a new BLK_STS_P2PDMA
> error and use that. ... simplifies patch 6 and 7 significantly.
> https://github.com/lsgunth/linux blk-sts-p2p-err
> Feel free to run with that.

Thank you, it is much cleaner now! v4 is built on your branch,
your patches kept under your authorship.

Two things I changed in them:
1) read patch reads bio->bi_status after bio_put(); v4 moves it
   above the put.
2) for the narrow_write_error(): a P2PDMA failure is whole-range
   and deterministic, so v4 records the whole range as a bad block
   in the completion handler instead of retrying block by block.

Mykola

^ permalink raw reply

* Re: [PATCH v2 6/7] md/raid1,raid10: skip futile retries on P2PDMA mapping failures
From: Mykola Marzhan @ 2026-07-22 19:00 UTC (permalink / raw)
  To: Leon Romanovsky
  Cc: Logan Gunthorpe, Jens Axboe, Song Liu, Yu Kuai, Keith Busch,
	Christoph Hellwig, Sagi Grimberg, linux-block, linux-raid,
	linux-nvme, Li Nan, Xiao Ni, Guoqing Jiang, Jason Gunthorpe,
	Kiran Kumar Modukuri, Chaitanya Kulkarni, Bjorn Helgaas,
	Shivaji Kant, Pranjal Shrivastava, Henrique Carvalho,
	linux-kernel, linux-rdma, linux-pci
In-Reply-To: <20260722083015.GU110966@unreal>

> I had thoughts about this series too. It seems best to avoid
> the P2P path entirely unless all legs support P2P.
>
> Can the md/raid code determine P2P support in advance?

Yes, and it already does: the block layer ANDs the P2PDMA queue
feature across all legs, so an array with a non-P2P member
(e.g. an nvme-tcp leg) never advertises P2P at all.

But that flag can only say "this driver can do P2P in general".
md cannot see which HCA device does the DMA for a fabrics leg —
and with multipath failover it can change at any time.

So the up-front gate covers what is knowable up front.
This series is about the remainder.
In mixed case, one local P2P leg plus a remote replica, with it:
1) a failed read just redirects to a capable mirror;
2) a failed write records bad blocks instead of retrying forever.

Mykola

^ permalink raw reply

* Re: [PATCH v4 5/9] md/raid1,raid10: factor out raid1_write_error() helper
From: sashiko-bot @ 2026-07-22 19:05 UTC (permalink / raw)
  To: Mykola Marzhan; +Cc: yukuai, linux-pci, linux-raid
In-Reply-To: <20260722185841.449934-6-mykola@meshstor.io>

> From: Logan Gunthorpe <logang@deltatee.com>
> 
> Factor out a common raid1_write_error() helper from raid1.c and
> raid10.c.
> 
> This will help reduce code duplication in subsequent patches.
> 
> Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
> Signed-off-by: Mykola Marzhan <mykola@meshstor.io>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722185841.449934-1-mykola@meshstor.io?part=5


^ permalink raw reply

* Re: [PATCH v4 1/9] block: add BLK_STS_P2PDMA for unsupported peer-to-peer transfers
From: sashiko-bot @ 2026-07-22 19:06 UTC (permalink / raw)
  To: Mykola Marzhan; +Cc: linux-raid, yukuai, linux-pci
In-Reply-To: <20260722185841.449934-2-mykola@meshstor.io>

> From: Logan Gunthorpe <logang@deltatee.com>
> 
> blk_dma_map_iter_start() reports BLK_STS_INVAL when a P2PDMA transfer
> is attempted between two devices whose PCIe topology cannot route it.
> blk_path_error() treats INVAL as retryable, so multipath requeues the
> I/O forever, and callers cannot tell it apart from an invalid request.
> md also ignores it for member failures, so a mirror silently diverges.
> 
> Restoring the old BLK_STS_TARGET would stay ambiguous -- nvme maps
> NS_NOT_READY, LBA_RANGE and CMD_INTERRUPTED to it too.  Add a dedicated
> BLK_STS_P2PDMA, non-retryable in blk_path_error(), and return it from
> blk_dma_map_iter_start().
> 
> Fixes: 858299dc6160 ("block: add scatterlist-less DMA mapping helpers")
> Fixes: 7ce3c1dd78fc ("nvme-pci: convert the data mapping to blk_rq_dma_map")
> Cc: stable@vger.kernel.org # v6.17
> Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
> Signed-off-by: Mykola Marzhan <mykola@meshstor.io>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722185841.449934-1-mykola@meshstor.io?part=1


^ permalink raw reply

* Re: [PATCH v4 2/9] md: ensure REQ_NOMERGE is set on P2PDMA bios
From: sashiko-bot @ 2026-07-22 19:07 UTC (permalink / raw)
  To: Mykola Marzhan; +Cc: yukuai, linux-pci, linux-raid
In-Reply-To: <20260722185841.449934-3-mykola@meshstor.io>

> md_submit_bio() strips REQ_NOMERGE from every bio.  For P2PDMA bios
> the flag is what keeps requests single-provider (see
> __bio_add_page()): merging a P2PDMA bio with one over a different
> pgmap, or over host memory, maps the merged request with the wrong
> bus address.  Set the flag on P2PDMA bios -- bios built via
> bio_iov_bvec_set() arrive without it -- and keep stripping it
> otherwise.
> 
> 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 <mykola@meshstor.io>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722185841.449934-1-mykola@meshstor.io?part=2


^ permalink raw reply

* Re: [PATCH v4 7/9] md/raid1,raid10: skip futile retries on P2PDMA mapping failures
From: sashiko-bot @ 2026-07-22 19:10 UTC (permalink / raw)
  To: Mykola Marzhan; +Cc: yukuai, linux-raid, linux-pci
In-Reply-To: <20260722185841.449934-8-mykola@meshstor.io>

> From: Logan Gunthorpe <logang@deltatee.com>
> 
> BLK_STS_P2PDMA reflects the peer/member pairing, not member health:
> retrying the same pages on the same member cannot succeed.  Keep
> WriteErrorSeen so the range stays fenced, but skip WantReplacement and
> FailFast eviction, as commit f7b24c7b41f2 ("md/raid1,raid10: don't fail
> devices for invalid IO errors") did for BLK_STS_INVAL.
> 
> The failure is whole-range and deterministic, so narrow_write_error()'s
> block-by-block submit_bio_wait() retries are pointless.  Record the
> whole range as a bad block in the completion handler instead.
> 
> Fixes: 02666132403a ("md: propagate BLK_FEAT_PCI_P2PDMA from member devices to RAID device")
> Assisted-by: Claude:claude-fable-5
> Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
> Co-developed-by: Mykola Marzhan <mykola@meshstor.io>
> Signed-off-by: Mykola Marzhan <mykola@meshstor.io>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722185841.449934-1-mykola@meshstor.io?part=7


^ permalink raw reply

* Re: [PATCH v4 4/9] md/raid1: don't use write-behind for P2PDMA bios
From: sashiko-bot @ 2026-07-22 19:11 UTC (permalink / raw)
  To: Mykola Marzhan; +Cc: linux-raid, yukuai, linux-pci
In-Reply-To: <20260722185841.449934-5-mykola@meshstor.io>

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

Pre-existing issues:
- [High] raid1_write_request() can block on resource allocations and serialization when processing a REQ_NOWAIT bio, violating non-blocking guarantees.
--

--- Patch [4]: [PATCH v4 4/9] md/raid1: don't use write-behind for P2PDMA bios ---
commit 1a1224f9f3e6ee3f02e62a2f1a4ff02688f39f0d
Author: Mykola Marzhan <mykola@meshstor.io>

md/raid1: don't use write-behind for P2PDMA bios

This commit skips write-behind for P2PDMA bios as they are written directly
to all members, including write-mostly ones. A CPU copy via bio_copy_data()
defeats the point of a peer-to-peer transfer.

> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
> index 997f79c05db7b..7d769b6460ca7 100644
> --- a/drivers/md/raid1.c
> +++ b/drivers/md/raid1.c
> @@ -1523,6 +1523,7 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio,
>  	bool write_behind = false;
>  	bool nowait = bio->bi_opf & REQ_NOWAIT;
>  	bool is_discard = op_is_discard(bio->bi_opf);
> +	bool is_p2pdma = md_bio_is_p2pdma(bio);
>  	sector_t sector = bio->bi_iter.bi_sector;
>  
>  	if (mddev_is_clustered(mddev) &&
[ ... ]
> @@ -1575,9 +1576,12 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio,
>  		/*
>  		 * The write-behind io is only attempted on drives marked as
>  		 * write-mostly, which means we could allocate write behind
> -		 * bio later.
> +		 * bio later. P2PDMA bios are excluded: write-behind copies
> +		 * the data with bio_copy_data(), a CPU copy that cannot be
> +		 * assumed safe or fast on P2PDMA (device BAR) pages.
>  		 */
> -		if (!is_discard && rdev && test_bit(WriteMostly, &rdev->flags))
> +		if (!is_discard && !is_p2pdma && rdev &&
> +		    test_bit(WriteMostly, &rdev->flags))
>  			write_behind = true;
>  
>  		r1_bio->bios[i] = NULL;

[Severity: High]
This is a pre-existing issue, but does raid1_write_request() properly support
REQ_NOWAIT semantics without blocking?

Earlier in raid1_write_request(), we allocate r1_bio unconditionally using
GFP_NOIO, which can sleep under memory pressure:

    r1_bio = alloc_r1bio(mddev, bio);

alloc_r1bio() {
    ...
    r1_bio = mempool_alloc(conf->r1bio_pool, GFP_NOIO);
    ...
}

Later down in raid1_write_request(), if CollisionCheck is set, we can
unconditionally put the thread to sleep, ignoring the REQ_NOWAIT context:

    if (test_bit(CollisionCheck, &rdev->flags))
        wait_for_serialization(rdev, r1_bio);

wait_for_serialization() {
    ...
    si = mempool_alloc(mddev->serial_info_pool, GFP_NOIO);
    ...
    wait_for_completion(&si->ready);
}

Could a REQ_NOWAIT write bio submitted to an md raid1 array block under
memory pressure or in-flight I/O overlapping, causing thread starvation in
io_uring workers?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722185841.449934-1-mykola@meshstor.io?part=4

^ permalink raw reply

* Re: [PATCH v4 3/9] md/raid1: serialize non-write-behind writes on CollisionCheck rdevs
From: sashiko-bot @ 2026-07-22 19:14 UTC (permalink / raw)
  To: Mykola Marzhan; +Cc: linux-pci, yukuai, linux-raid
In-Reply-To: <20260722185841.449934-4-mykola@meshstor.io>

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

New issues:
- [Medium] Hot-added devices silently bypass the MD_SERIALIZE_POLICY serialization.

Pre-existing issues:
- [Critical] Concurrent overlapping I/Os cause RB-tree double-insertion and corruption in `wait_for_serialization` and `remove_serial`.
- [High] Toggling `-writemostly` or `serialize_policy` via sysfs races with in-flight I/Os, causing a Use-After-Free of `rdev->serial` and leaking `serial_info`.
--

--- Patch [3]: [PATCH v4 3/9] md/raid1: serialize non-write-behind writes on CollisionCheck rdevs ---
commit cb65d8bde9fd6c1299852f1cafb33bef4da080a2
Author: Mykola Marzhan <mykola@meshstor.io>

md/raid1: serialize non-write-behind writes on CollisionCheck rdevs

This commit changes the write serialization gating from the global
MD_SERIALIZE_POLICY flag to the per-device CollisionCheck flag. It fixes
a race with write-behind I/O on write-mostly members and avoids a latent
NULL dereference for hot-added devices.

Fixes: d0d2d8ba0494 ("md/raid1: introduce wait_for_serialization")
Cc: stable@vger.kernel.org

> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
> --- a/drivers/md/raid1.c
> +++ b/drivers/md/raid1.c
> @@ -564,7 +564,7 @@ static void raid1_end_write_request(struct bio *bio)
>  				call_bio_endio(r1_bio);
>  			}
>  		}
> -	} else if (test_bit(MD_SERIALIZE_POLICY, &rdev->mddev->flags))
> +	} else if (test_bit(CollisionCheck, &rdev->flags))
>  		remove_serial(rdev, lo, hi);

[Severity: High]
This is a pre-existing issue, but does toggling -writemostly or
serialize_policy via sysfs race with in-flight I/Os here?

When mddev_suspend() is called during reconfiguration, it waits for new I/O
submissions to finish, but it does not wait for in-flight cloned bios on
the underlying disks to complete.

If mddev_destroy_serial_pool() clears CollisionCheck and frees rdev->serial
concurrently, and an in-flight bio completes, raid1_end_write_request()
might read CollisionCheck as true and dereference the freed rdev->serial
inside remove_serial().

If it reads the flag as false, it skips remove_serial(), leaking the
serial_info mempool element.

>  	if (r1_bio->bios[mirror] == NULL)
>  		rdev_dec_pending(rdev, conf->mddev);

[ ... ]

> @@ -1677,7 +1677,11 @@ static bool raid1_write_request(struct mddev *mddev, struct bio *bio,
>  			mbio = bio_alloc_clone(rdev->bdev, bio, GFP_NOIO,
>  					       &mddev->bio_set);
>  
> -			if (test_bit(MD_SERIALIZE_POLICY, &mddev->flags))
> +			/*
> +			 * CollisionCheck marks every rdev with a serial
> +			 * tree; order against in-flight write-behind I/O.
> +			 */
> +			if (test_bit(CollisionCheck, &rdev->flags))
>  				wait_for_serialization(rdev, r1_bio);

[Severity: Medium]
Does this change allow hot-added devices to completely bypass serialization
when the global serialize policy is active?

If an administrator sets serialize_policy=1 and a new device is hot-added,
mddev_create_serial_pool() skips initializing the serial tree for the new
device because it checks rdev_need_serial() and CollisionCheck (which is
not set yet).

Since this gate in raid1_write_request() now only checks CollisionCheck
instead of MD_SERIALIZE_POLICY, the hot-added device skips serialization
entirely.

[Severity: Critical]
This isn't a bug introduced by this patch, but can concurrent overlapping
I/Os cause an RB-tree double-insertion during wait_for_serialization()?

If three overlapping I/Os (A, B, C) arrive in sequence (e.g. A=[25-35],
B=[20-30], C=[10-20]):

I/O A is inserted. I/O B overlaps A, and gets added to A's wait list.
I/O C overlaps B but not A, so C is inserted.

When A completes, it removes itself, inserts B into the tree, and wakes B.
B wakes up and calls check_and_add_serial(), which finds C. B is added to
C's wait list and sleeps again, but remains linked in the RB-tree.

When C completes, it removes itself, splices B's waiters, and calls
raid1_rb_insert() on B again, corrupting the tree structure.

>  		}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722185841.449934-1-mykola@meshstor.io?part=3

^ permalink raw reply

* Re: [PATCH v4 8/9] md/raid1,raid10: set IO_BLOCKED in case of BLK_STS_P2PDMA
From: sashiko-bot @ 2026-07-22 19:16 UTC (permalink / raw)
  To: Mykola Marzhan; +Cc: linux-pci, yukuai, linux-raid
In-Reply-To: <20260722185841.449934-9-mykola@meshstor.io>

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

Pre-existing issues:
- [Critical] NULL pointer dereference when a read error occurs and the failing disk is concurrently removed.
- [High] Barrier count underflow leading to array deadlock for REQ_NOWAIT retries.
--

--- Patch [8]: [PATCH v4 8/9] md/raid1,raid10: set IO_BLOCKED in case of BLK_STS_P2PDMA ---
commit 64027366aa2bf452a9087c9fd113c84027594e8b
Author: Logan Gunthorpe <logang@deltatee.com>

md/raid1,raid10: set IO_BLOCKED in case of BLK_STS_P2PDMA
    
A read that fails with BLK_STS_P2PDMA cannot succeed against that member, so
mark the leg IO_BLOCKED and let the retry redirect elsewhere. This skips the
read-error machinery, avoiding the eviction of healthy members under a P2P
read workload.

> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
> index 917d694ef401a..76a1426e64f02 100644
> --- a/drivers/md/raid1.c
> +++ b/drivers/md/raid1.c
[ ... ]
> @@ -2675,7 +2677,7 @@ static void handle_read_error(struct r1conf *conf, struct r1bio *r1_bio)
>  	 * read error.  This is all done synchronously while the array is
>  	 * frozen.
>  	 */
> -	if (mddev->ro) {
> +	if (mddev->ro || p2pdma_error) {
>  		r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED;
>  	} else if (test_bit(FailFast, &rdev->flags)) {
>  		md_error(mddev, rdev);

[Severity: Critical]
This isn't a bug introduced by this patch, but can we hit a NULL pointer
dereference if the failing disk is concurrently removed?

When a read error occurs, the bio is routed here to handle_read_error().
This function drops the pending count via rdev_dec_pending(rdev), which
could allow a concurrent mdadm --remove to succeed and set
conf->mirrors[r1_bio->read_disk].rdev to NULL.

The retry is then submitted via raid1_read_request(). If all healthy disks
are blocked or unavailable, read_balance() can return -1, triggering this
logging path in raid1_read_request():

raid1_read_request() {
    ...
    if (rdisk < 0) {
        ...
        pr_crit_ratelimited("md/raid1:%s: %pg: unrecoverable I/O read error for block %llu\n",
                            mdname(mddev),
                            conf->mirrors[r1_bio->read_disk].rdev->bdev,
                            ...);
    ...
}

Since rdev is now NULL, will this dereference rdev->bdev and panic the
kernel? For comparison, raid10_read_request() appears to correctly check
if rdev is NULL before logging in this scenario.

[Severity: High]
This is a pre-existing issue, but could this error path lead to a barrier
count underflow and array deadlock when retrying REQ_NOWAIT requests?

If a REQ_NOWAIT read fails and reaches handle_read_error(), it retries by
calling raid1_read_request() with the existing r1_bio.

Inside raid1_read_request(), wait_read_barrier() can return false due to
the nowait flag and the array being concurrently frozen, which leaves
nr_pending unchanged. However, raid1_read_request() then calls
raid_end_bio_io():

raid1_read_request() {
    ...
    if (r1bio_existed) {
        set_bit(R1BIO_Returned, &r1_bio->state);
        raid_end_bio_io(r1_bio);
    }
    ...
}

This calls allow_barrier(), decrementing the barrier count. Control then
returns to handle_read_error(), which ends with:

handle_read_error() {
    ...
    raid1_read_request(mddev, bio, r1_bio->sectors, r1_bio);
    allow_barrier(conf, sector);
}

Does this second allow_barrier() decrement the count again for a single I/O,
causing a net barrier underflow that blocks future raise_barrier() calls
and deadlocks the array?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722185841.449934-1-mykola@meshstor.io?part=8

^ permalink raw reply

* Re: [PATCH v4 6/9] md/raid1,raid10: keep REQ_NOMERGE on narrow_write_error() retry clones
From: sashiko-bot @ 2026-07-22 19:16 UTC (permalink / raw)
  To: Mykola Marzhan; +Cc: linux-raid, linux-pci, yukuai
In-Reply-To: <20260722185841.449934-7-mykola@meshstor.io>

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

Pre-existing issues:
- [High] narrow_write_error() incorrectly coerces dataless operations into data-bearing REQ_OP_WRITE states, causing block layer crashes.
--

--- Patch [6]: [PATCH v4 6/9] md/raid1,raid10: keep REQ_NOMERGE on narrow_write_error() retry clones ---
commit 4017c1a8ef243dcaefd606ee7c22696e4aaccaa1
Author: Mykola Marzhan <mykola@meshstor.io>

md/raid1,raid10: keep REQ_NOMERGE on narrow_write_error() retry clones

This commit fixes a missing REQ_NOMERGE flag on P2PDMA retry clones in
narrow_write_error(). It ensures that member queues do not merge P2PDMA
segments incorrectly when a bio is retried after a write error.

> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
> --- a/drivers/md/raid1.c
> +++ b/drivers/md/raid1.c
> @@ -2561,6 +2561,9 @@ static void narrow_write_error(struct r1bio *r1_bio, int i)
>  		}
>  
>  		wbio->bi_opf = REQ_OP_WRITE;
> +		/* Keep P2PDMA retry bios unmergeable, like the original */
> +		if (md_bio_is_p2pdma(wbio))
> +			wbio->bi_opf |= REQ_NOMERGE;

[Severity: High]
This is a pre-existing issue, but does setting wbio->bi_opf unconditionally to
REQ_OP_WRITE here cause a NULL pointer dereference for dataless requests?

If the array is processing a REQ_OP_SECURE_ERASE request (which has bi_size > 0
but bi_io_vec is NULL) and hits a write error, narrow_write_error() will force
the operation to REQ_OP_WRITE. Because REQ_OP_WRITE expects a data vector, the
subsequent call to bio_trim() in this loop will attempt to advance a NULL
bi_io_vec during chunk iteration, resulting in a crash.

>  		wbio->bi_iter.bi_sector = r1_bio->sector;
>  		wbio->bi_iter.bi_size = r1_bio->sectors << 9;

[ ... ]

> diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
> --- a/drivers/md/raid10.c
> +++ b/drivers/md/raid10.c
> @@ -2823,6 +2823,9 @@ static void narrow_write_error(struct r10bio *r10_bio, int i)
>  		wbio->bi_iter.bi_sector = wsector +
>  				   choose_data_offset(r10_bio, rdev);
>  		wbio->bi_opf = REQ_OP_WRITE;
> +		/* Keep P2PDMA retry bios unmergeable, like the original */
> +		if (md_bio_is_p2pdma(wbio))
> +			wbio->bi_opf |= REQ_NOMERGE;

[Severity: High]
This is a pre-existing issue, but similar to the raid1.c case above, could
this coercion to REQ_OP_WRITE crash lower-level drivers?

If a REQ_OP_SECURE_ERASE request hits a write error and is retried here,
wbio->bi_opf is overridden to REQ_OP_WRITE before calling submit_bio_wait().
When this malformed write bio with a NULL bi_io_vec is sent to lower layers
(like NVMe or SCSI), it can cause a panic during DMA mapping since those
drivers expect a valid bvec for REQ_OP_WRITE.

>  
>  		if (submit_bio_wait(wbio) &&
>  		    !rdev_set_badblocks(rdev, wsector, sectors, 0)) {

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722185841.449934-1-mykola@meshstor.io?part=6

^ permalink raw reply


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