* [PATCH v2 1/7] blk-mq-dma: restore BLK_STS_TARGET for unsupported P2P transfers
From: Mykola Marzhan @ 2026-07-19 10:53 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: <20260719105327.864949-1-mykola@meshstor.io>
Commit 91fb2b6052f7 ("nvme-pci: convert to using dma_map_sgtable()")
deliberately mapped unsupported P2PDMA transfers to BLK_STS_TARGET,
matching dma_map_sgtable()'s -EREMOTEIO: "When this happens, return
BLK_STS_TARGET so the request isn't retried." The conversion to
blk_rq_dma_map silently changed the status to BLK_STS_INVAL, which
regresses two consumers:
- md/raid1 and raid10 ignore BLK_STS_INVAL leg failures
(commit f7b24c7b41f2 ("md/raid1,raid10: don't fail devices for
invalid IO errors"), where it means a request-shaped error that
fails identically on every member). Since commit 02666132403a
("md: propagate BLK_FEAT_PCI_P2PDMA from member devices to RAID device")
P2PDMA bios reach md arrays: a peer-memory write to a member the
peer cannot reach is counted as written, the master bio reports
success, mirrors silently diverge, and on a topology where no
member is reachable the write reports success with zero copies on
stable storage.
- the failure's classification flips: for direct NVMe consumers
(nvme advertises BLK_FEAT_PCI_P2PDMA) the mapping failure has
surfaced as EINVAL instead of the documented -EREMOTEIO since
v6.17, and blk_path_error(BLK_STS_INVAL) is true, so a stacking
consumer would treat it as a retryable path error (dm-mpath is
the only in-tree blk_path_error() caller; P2P bios cannot
currently reach it, but the classification is wrong on its face).
Restore BLK_STS_TARGET. md then routes an unreachable leg through
its per-device error handling (badblocks, mirror
retry for reads); a later patch in this series teaches raid1/raid10
to handle mapping failures without the retry storms that machinery
was built around.
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
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
block/blk-mq-dma.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/block/blk-mq-dma.c b/block/blk-mq-dma.c
index bfdb9ed70741..2eed06bfe791 100644
--- a/block/blk-mq-dma.c
+++ b/block/blk-mq-dma.c
@@ -190,7 +190,15 @@ static bool blk_dma_map_iter_start(struct request *req, struct device *dma_dev,
case PCI_P2PDMA_MAP_NONE:
break;
default:
- iter->status = BLK_STS_INVAL;
+ /*
+ * P2P transfers that the mapping layer cannot support
+ * report BLK_STS_TARGET, matching dma_map_sgtable()'s
+ * -EREMOTEIO and the pre-blk_rq_dma_map nvme behavior:
+ * the failure is a property of this device pairing, so
+ * it must not be retried on another path (blk_path_error)
+ * nor be mistaken for an invalid request.
+ */
+ iter->status = BLK_STS_TARGET;
return false;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v2 0/7] block,md,nvme: correct handling of unsupported P2PDMA transfers
From: Mykola Marzhan @ 2026-07-19 10:53 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
Driving peer-to-peer I/O (NVMe CMB source) through md arrays and
nvme-rdma legs on asymmetric PCIe topologies -- where the peer device
reaches some members/paths but not others -- turns up two failures on
v7.2-rc, both rooted in how an unsupported-P2PDMA mapping failure is
reported. On md: silent data loss -- an unreachable-leg P2PDMA write
counts as written, mirrors silently diverge, and where no member is
reachable the write reports success with zero copies on stable
storage; reads picked to an unreachable leg fail with EIO and are
never retried on the mirror that holds the data. On nvme-rdma: the
transport swallows the DMA layer's mapping errno into a path error,
which under the default multipath configuration livelocks the I/O (a
stacked md mirror hangs) and with nvme_core.multipath=N surfaces as
retryable BLK_STS_TRANSPORT.
The root cause is a block-layer status-code regression, not an md or
nvme bug. Commit 91fb2b6052f7 ("nvme-pci: convert to using
dma_map_sgtable()") deliberately mapped unsupported P2PDMA transfers
to BLK_STS_TARGET ("... return BLK_STS_TARGET so the request isn't
retried"). Commit 858299dc6160 ("block: add scatterlist-less DMA
mapping helpers") silently changed that to BLK_STS_INVAL, and since
commit 7ce3c1dd78fc ("nvme-pci: convert the data mapping to
blk_rq_dma_map") in v6.17 the failure surfaces to direct NVMe
consumers as EINVAL instead of the documented -EREMOTEIO, with
blk_path_error() now classifying it as retryable. md/raid1,raid10
ignore BLK_STS_INVAL leg failures per commit f7b24c7b41f2
("md/raid1,raid10: don't fail devices for invalid IO errors"), where
it means a request-shaped error that fails identically on every
member -- hence the silent divergence. The md leg of the regression
became reachable when md started advertising BLK_FEAT_PCI_P2PDMA in
v7.2-rc1 (commit 02666132403a ("md: propagate BLK_FEAT_PCI_P2PDMA
from member devices to RAID device")).
Patch 1 restores BLK_STS_TARGET at the source. md then handles an
unreachable leg like any other per-device error: badblocks on the
affected member, the master bio succeeds while an In_sync leg holds
the data, and reads are redirected to the other mirror.
Patches 2, 4 and 5 fix independent md-side P2PDMA bugs of the same
vintage: md_submit_bio() strips REQ_NOMERGE (the only request-level
protection against merging P2PDMA segments across pgmaps); raid1
write-behind CPU-copies device BAR memory via bio_copy_data(); and
narrow_write_error()'s retry clones reset bi_opf, dropping the
REQ_NOMERGE protection exactly on the error-retry path. Patch 3 is
a standalone raid1 fix for a pre-existing ordering hole the
write-behind bypass in patch 4 would otherwise widen: writes that
skip write-behind (queue saturation, a waiting reader, allocation
failure, discards -- and now P2PDMA bios) never serialized against
in-flight write-behind I/O to overlapping sectors.
Patch 6 stops the restored device-error machinery from misfiring
where its medium-error assumptions don't hold. A mapping failure is a
property of the peer/member pairing: retrying the same peer pages
against the same member cannot succeed, and there is nothing on the
medium to repair. Without it, narrow_write_error() serializes hundreds
of guaranteed-to-fail chunk retries per failed write; on reads each
unreachable-leg pick costs a full freeze_array() quiesce plus a tick
of the read-error budget -- measured below, a mixed host/P2P read
workload on an asymmetric topology kicks the perfectly healthy far
leg after ~20 failed picks, in under a second; and on FailFast
members a single unroutable I/O evicts a healthy mirror outright.
Because BLK_STS_TARGET is also produced for transient device
conditions md cannot distinguish from bi_status alone, writes probe
the whole range once instead of predicting futility: a real mapping
failure is recorded in one call, a cleared transient recovers with
nothing recorded. Patches 1 and 6 belong in the same release: with
patch 1 alone, restoring the error surfaces the retry storms and
read-path evictions above on 7.2's newly reachable md path.
Patch 7 closes the same hole in the rdma transport. nvme-rdma maps
the data scatterlist with ib_dma_map_sg(), which returns 0 on a
peer-unreachable failure, discarding the -EREMOTEIO that
dma_map_sgtable() documents for exactly this case; the driver reports
it as a path error. Because the multipath head node advertises
BLK_FEAT_PCI_P2PDMA (commit fb0eeeed91f3 ("nvme-multipath: enable PCI
P2PDMA for multipath devices"), v7.2-rc1) and the mapping failure is
deterministic, nvme_failover_req() requeues the bios with a fresh
retry budget every cycle and the I/O never completes -- a hot requeue
livelock that hangs a stacked md mirror instead of failing over; with
nvme_core.multipath=N it burns nvme_max_retries requeues and
completes as retryable BLK_STS_TRANSPORT. Patch 7 maps with
ib_dma_map_sgtable_attrs() to preserve the errno and returns
BLK_STS_TARGET, matching nvme-pci so patch 6's handling covers rdma
legs too, and starts the request only after the map succeeds so
nvme_mpath_start_request() accounting cannot leak on the direct
blk-mq completion. nvme-rdma stays on the scatterlist DMA API: its
fast-reg MR path (ib_map_mr_sg()) consumes scatterlists, so a
blk_rq_dma_map conversion is separate modernization, out of scope for
a fix.
Behavior on v7.2-rc2, QEMU rig (one CMB provider, two NVMe members,
q35 PCIe topologies), 90 8KiB peer-memory reads under concurrent
host reads for the read rows:
topology op v7.2-rc2 patched (md set)
symmetric write+read ok ok
asymmetric write "ok", silent diverge ok + badblocks on
far leg (1 probe)
asymmetric read 41/90 EIO, no mirror 90/90 ok, no
under load retry, no eviction eviction
unreachable write "ok", NO data copied EIO + badblocks
unreachable read EIO EIO, no members
kicked
Also verified on the same rig: a transient injected TARGET recovers
through the single probe with nothing recorded; P2P MEDIUM keeps the
chunked path; host-page TARGET/INVAL handling is unchanged; FailFast
survives mapping failures but still evicts on genuine errors;
write-mostly legs are written directly (no write-behind CPU copy); a
degraded array whose only leg is unreachable gets plain EIO -- no
budget charge, no eviction; an exhausted badblocks table fails the
member as described below. The write/read/injection rows repeat
identically on raid10.
Validation of patch 7: the mechanism -- ib_dma_map_sg() returning 0
where ib_dma_map_sgtable_attrs() preserves -EREMOTEIO -- was
confirmed on a real mlx5 HCA with a map-only probe. The
start-after-map reorder ran over an rxe nvmet-rdma loopback under fio
with crc32c verification; multipath inflight and nr_active accounting
drain to zero, matching the pre-reorder kernel. rxe cannot produce
-EREMOTEIO, so the failure paths were driven by injecting that exact
mechanism at the map call site: unpatched, one 4KiB write livelocked
(132 failover requeues in 8s, a stacked raid1 hung; multipath=N:
retryable BLK_STS_TRANSPORT); patched, it failed immediately as
BLK_STS_TARGET with zero requeues, and the stacked raid1 badblocked
the leg without evicting it.
Known trade-offs of routing through the stock md write machinery:
an unroutable P2P write records badblocks and sets WriteErrorSeen
(which gates the write-path badblocks consult), so later writes
overlapping those ranges skip the member -- host writes do not clear
the entries and repair skips known-bad ranges -- leaving them
single-copy until a re-add rebuild (host memory, so it succeeds)
rewrites and clears them. WantReplacement is deliberately not set
for mapping failures: replacing a member cannot fix a topology
property, and would burn a spare per unreachable member. A member
whose badblocks table is exhausted or disabled is failed on the
first unroutable P2P write, as with any write error md cannot
record. FailFast members are exempted from md_error() for
mapping failures only (nothing reached the wire, so the error says
nothing about device health); genuine I/O errors keep their
immediate-eviction semantics. raid10 additionally fails a
replacement device outright on a P2P write mapping failure (its
stock replacement-write policy), and an IO_BLOCKED slot excludes
that slot's replacement from the read retry -- both pre-existing
raid10 behaviors with a new trigger. On reads, read_balance() keeps
no memory of unreachability, so each far-leg pick still costs one
failed submission before the redirect.
Routing: patch 1 is block-tree material and fixes a v6.17-rc1 status
regression (hence its Cc: stable); patches 2-6 are md (patch 3 fixes
a pre-existing raid1 ordering bug and carries its own Cc: stable;
the rest address exposure that only became reachable with v7.2-rc1);
patch 7 is nvme-rdma and also carries Cc: stable -- the transport
misreport predates the head-node change (Fixes: 23528aa3320a, v7.1)
and is reachable on v7.1 with multipath=N. Patch 7 sources the errno
from dma_map_sgtable() in rdma.c and does not depend on patch 1,
which fixes the separate blk_rq_dma_map path. Patches 1 (block) and
7 (nvme) are independently applicable bug fixes; a maintainer can
take them even if the md discussion runs longer -- if the md side
slips a release, patch 1 turns the silent divergence into loud
per-leg errors, which we consider the right interim behavior.
Single-cycle routing through the relevant trees with acks works for
us: patch 1 block, 2-6 md, 7 nvme.
Per Documentation/process/coding-assistants.rst: the patches were
developed with AI assistance (see the Assisted-by trailers); all code
was human-reviewed and tested as described above.
Series is against v7.2-rc2.
Changes in v2 (after Sashiko AI review of v1 and further internal
review):
- new patch 3: writes that bypass write-behind (including discards)
never serialized against in-flight write-behind I/O to overlapping
sectors; gate the non-behind path on CollisionCheck so host
fallback writes, discards and P2PDMA bios (patch 4) all order
correctly. Verified with an overlap scenario that hangs if the
completion-side removal is missing.
- patch 6: track P2PDMA masters with an r1bio/r10bio state bit set
at submission instead of sniffing bvec pages at completion time;
stop setting WantReplacement for mapping failures (a spare cannot
fix a topology property; verified that a genuine write error
still sets it); document why raid10 replacement legs keep their
stock fail-on-error policy and why the read path charges no
read-error budget.
- patch 7: also convert the metadata scatterlist to
ib_dma_map_sgtable_attrs() -- bio_integrity_map_user() accepts
P2P pages when the queue advertises P2PDMA, so integrity buffers
are not always host memory; state the asymmetric-multipath
failover trade-off explicitly.
- patch 1: version-annotate the stable Cc.
- patches 2, 5: unchanged.
Link: https://lore.kernel.org/linux-raid/20260718162547.448892-1-mykola@meshstor.io/
Mykola Marzhan (7):
blk-mq-dma: restore BLK_STS_TARGET for unsupported P2P transfers
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
md/raid1,raid10: skip futile retries on P2PDMA mapping failures
nvme-rdma: return BLK_STS_TARGET for unsupported P2P transfers
block/blk-mq-dma.c | 10 +++++-
drivers/md/md.c | 14 ++++++--
drivers/md/md.h | 18 ++++++++++
drivers/md/raid1.c | 78 ++++++++++++++++++++++++++++++++++------
drivers/md/raid1.h | 2 ++
drivers/md/raid10.c | 72 +++++++++++++++++++++++++++++++------
drivers/md/raid10.h | 2 ++
drivers/nvme/host/rdma.c | 42 +++++++++++++---------
8 files changed, 198 insertions(+), 40 deletions(-)
base-commit: 8cdeaa50eae8dad34885515f62559ee83e7e8dda
--
2.43.0
^ permalink raw reply
* Re: [PATCH] md: fix soft lockup during resync when sync is repeatedly skipped
From: yu kuai @ 2026-07-19 10:45 UTC (permalink / raw)
To: Yunye Zhao, Song Liu, Yu Kuai
Cc: Li Nan, Xiao Ni, Joseph Qi, linux-raid, linux-kernel
In-Reply-To: <20260717062743.128189-1-yunye.zhao@linux.alibaba.com>
Hi,
在 2026/7/17 14:27, Yunye Zhao 写道:
> 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, the "last_check + window > io_sectors" test
> stays true, and every iteration takes the continue branch:
That's not expected, io_sectors should always increase in the skip case.
>
> sectors = mddev->pers->sync_request(mddev, j, max_sectors, &skipped);
> ...
> if (!skipped)
> io_sectors += sectors;
> j += sectors;
> ...
> if (last_check + window > io_sectors || j == max_sectors)
> continue;
>
> During recovery or resync of a large array with a sparse bitmap, many
> regions that need no syncing are skipped:
>
> raid10_sync_request()
> md_bitmap_start_sync() -> must_sync = false (no bitmap page)
> /* every mirror skipped */
> biolist == NULL -> *skipped = 1; return max_sync;
>
> j then traverses the whole skipped range while io_sectors stays
> unchanged. On a non-preemptive kernel the resync thread (mdX_resync)
> hogs the CPU for a long time and eventually triggers a soft lockup:
>
> 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
What kernel version you're testing? If this is latest kernel, bitmap_start_sync()
need to be fixed. It can't return skip while setting skipping sectors to 0. And
since this is dead loop, a cond_resched() will not fix anything.
>
> Add cond_resched() to this continue path.
>
> Signed-off-by: Yunye Zhao <yunye.zhao@linux.alibaba.com>
> ---
> drivers/md/md.c | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/md/md.c b/drivers/md/md.c
> index d1465bcd86c8..e7411b033490 100644
> --- a/drivers/md/md.c
> +++ b/drivers/md/md.c
> @@ -9881,9 +9881,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:
> if (time_after_eq(jiffies, mark[last_mark] + SYNC_MARK_STEP )) {
--
Thanks,
Kuai
^ permalink raw reply
* Re: [PATCH] md/bitmap: resume array on backlog_store() error path
From: yu kuai @ 2026-07-19 10:32 UTC (permalink / raw)
To: Chen Cheng, linux-raid, yukuai; +Cc: chencheng, linux-kernel
In-Reply-To: <20260718034236.4119093-1-chencheng@fnnas.com>
在 2026/7/18 11:42, Chen Cheng 写道:
> From: Chen Cheng<chencheng@fnnas.com>
>
> 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<chencheng@fnnas.com>
> ---
> drivers/md/md-bitmap.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
Reviewed-by: Yu Kuai <yukuai@fygo.io>
--
Thanks,
Kuai
^ permalink raw reply
* Re: [PATCH] md: handle serial pool allocation failures
From: yu kuai @ 2026-07-19 10:24 UTC (permalink / raw)
To: Chen Cheng, linux-raid, yukuai; +Cc: chencheng, linux-kernel
In-Reply-To: <20260718105551.608500-1-chencheng@fnnas.com>
Hi,
在 2026/7/18 18:55, Chen Cheng 写道:
> From: Chen Cheng <chencheng@fnnas.com>
>
> sashiko-bot report a issue:
> mddev_create_serial_pool() silently ignored allocation failures, allowing
> callers to continue with incomplete serialization state.
>
> validation:
>
> fail_page_alloc with a stacktrace filter for rdev_init_serial() to force
> the serial allocation and its kvmalloc fallback to fail.
>
> A/B result:
> - Without this patch, serialize_policy accepts the write despite the
> injected allocation failure, leaving serialization state
> incomplete.
> - With this patch, the write returns -ENOMEM and serialize_policy remains
> unchanged.
I don't see what's the problem here. If mddev_create_serial_pool() failed, then write behind
will be disabled.
>
> Fixes: 3938f5fb82ae ("md: add serialize_policy sysfs node for raid1")
> Link: https://github.com/chencheng-fnnas/reproducer/blob/main/test-serial-pool-oom.sh
>
> sashiko-bot report:
> =====================
> [Severity: Critical]
> This is a pre-existing issue, but further down in backlog_store(), is it
> possible for mddev_create_serial_pool() to fail silently?
>
> drivers/md/md-bitmap.c:backlog_store() {
> ...
> } else if (backlog && !mddev->serial_info_pool) {
> /* serial_info_pool is needed since backlog is not zero */
> rdev_for_each(rdev, mddev)
> mddev_create_serial_pool(mddev, rdev);
> }
> ...
> }
>
> Since mddev_create_serial_pool() returns void, it hides memory allocation
> failures. If it fails for a disk in this loop, that device is left without
> initialization. Does this silently bypass write-behind serialization for
> that disk, leading to overlapping writes and silent data corruption?
>
> Similarly, if MD_SERIALIZE_POLICY is active, check_and_add_serial() in
> drivers/md/raid1.c will unconditionally dereference rdev->serial:
>
> int idx = sector_to_idx(r1_bio->sector);
> struct serial_in_rdev *serial = &rdev->serial[idx];
> struct serial_info *head_si;
>
> spin_lock_irqsave(&serial->serial_lock, flags);
>
> Can this cause a NULL pointer dereference for devices that failed
> initialization?
There is a per rdev flag CollisionCheck for protection, I don't think there will
be NULL pointer dereference.
>
> Signed-off-by: Chen Cheng <chencheng@fnnas.com>
> ---
> drivers/md/md-bitmap.c | 20 +++++++++++++++-----
> drivers/md/md.c | 33 ++++++++++++++++++++++-----------
> drivers/md/md.h | 2 +-
> drivers/md/raid1.c | 9 +++++++--
> 4 files changed, 45 insertions(+), 19 deletions(-)
>
> diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c
> index 6d495cdf3fb2..e0510e3cae3e 100644
> --- a/drivers/md/md-bitmap.c
> +++ b/drivers/md/md-bitmap.c
> @@ -2211,12 +2211,15 @@ static int bitmap_load(struct mddev *mddev)
> struct md_rdev *rdev;
>
> if (!bitmap)
> goto out;
>
> - rdev_for_each(rdev, mddev)
> - mddev_create_serial_pool(mddev, rdev);
> + rdev_for_each(rdev, mddev) {
> + err = mddev_create_serial_pool(mddev, rdev);
> + if (err)
> + goto out;
> + }
>
> if (mddev_is_clustered(mddev))
> mddev->cluster_ops->load_bitmaps(mddev, mddev->bitmap_info.nodes);
>
> /* Clear out old bitmap info first: Either there is none, or we
> @@ -2868,18 +2871,25 @@ backlog_store(struct mddev *mddev, const char *buf, size_t len)
> /* serial_info_pool is not needed if backlog is zero */
> if (!test_bit(MD_SERIALIZE_POLICY, &mddev->flags))
> mddev_destroy_serial_pool(mddev, NULL);
> } else if (backlog && !mddev->serial_info_pool) {
> /* serial_info_pool is needed since backlog is not zero */
> - rdev_for_each(rdev, mddev)
> - mddev_create_serial_pool(mddev, rdev);
> + rdev_for_each(rdev, mddev) {
> + rv = mddev_create_serial_pool(mddev, rdev);
> + if (rv) {
> + mddev->bitmap_info.max_write_behind = old_mwb;
> + mddev_destroy_serial_pool(mddev, NULL);
> + goto out;
> + }
> + }
> }
> if (old_mwb != backlog)
> bitmap_update_sb(mddev->bitmap);
>
> +out:
> mddev_unlock_and_resume(mddev);
> - return len;
> + return rv ?: len;
> }
>
> static struct md_sysfs_entry bitmap_backlog =
> __ATTR(backlog, S_IRUGO|S_IWUSR, backlog_show, backlog_store);
>
> diff --git a/drivers/md/md.c b/drivers/md/md.c
> index 25e06f088dc1..048ffd28869b 100644
> --- a/drivers/md/md.c
> +++ b/drivers/md/md.c
> @@ -228,18 +228,19 @@ static int rdev_need_serial(struct md_rdev *rdev)
> /*
> * Init resource for rdev(s), then create serial_info_pool if:
> * 1. rdev is the first device which return true from rdev_enable_serial.
> * 2. rdev is NULL, means we want to enable serialization for all rdevs.
> */
> -void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev)
> +int 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;
> + !test_bit(CollisionCheck, &rdev->flags) &&
> + !test_bit(MD_SERIALIZE_POLICY, &mddev->flags))
> + return 0;
>
> noio_flags = memalloc_noio_save();
> if (!rdev)
> ret = rdevs_init_serial(mddev);
> else
> @@ -248,18 +249,22 @@ void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev)
> goto out;
>
> if (mddev->serial_info_pool == NULL) {
> mddev->serial_info_pool =
> mempool_create_kmalloc_pool(NR_SERIAL_INFOS,
> - sizeof(struct serial_info));
> + sizeof(struct serial_info));
> if (!mddev->serial_info_pool) {
> + if (rdev)
> + rdev_uninit_serial(rdev);
> rdevs_uninit_serial(mddev);
> pr_err("can't alloc memory pool for serialization\n");
> + ret = -ENOMEM;
> }
> }
> out:
> memalloc_noio_restore(noio_flags);
> + return ret;
> }
>
> /*
> * Free resource from rdev(s), and destroy serial_info_pool under conditions:
> * 1. rdev is the last device flaged with CollisionCheck.
> @@ -2605,12 +2610,15 @@ static int bind_rdev_to_array(struct md_rdev *rdev, struct mddev *mddev)
> strreplace(b, '/', '!');
>
> rdev->mddev = mddev;
> pr_debug("md: bind<%s>\n", b);
>
> - if (mddev->raid_disks)
> - mddev_create_serial_pool(mddev, rdev);
> + if (mddev->raid_disks) {
> + err = mddev_create_serial_pool(mddev, rdev);
> + if (err)
> + return err;
> + }
>
> if ((err = kobject_add(&rdev->kobj, &mddev->kobj, "dev-%s", b)))
> goto fail;
>
> /* failure here is OK */
> @@ -3118,13 +3126,15 @@ state_store(struct md_rdev *rdev, const char *buf, size_t len)
> md_new_event();
> }
> }
> } else if (cmd_match(buf, "writemostly")) {
> set_bit(WriteMostly, &rdev->flags);
> - mddev_create_serial_pool(rdev->mddev, rdev);
> - need_update_sb = true;
> - err = 0;
> + err = mddev_create_serial_pool(rdev->mddev, rdev);
> + if (err)
> + clear_bit(WriteMostly, &rdev->flags);
> + else
> + need_update_sb = true;
> } else if (cmd_match(buf, "-writemostly")) {
> mddev_destroy_serial_pool(rdev->mddev, rdev);
> clear_bit(WriteMostly, &rdev->flags);
> need_update_sb = true;
> err = 0;
> @@ -5943,12 +5953,13 @@ serialize_policy_store(struct mddev *mddev, const char *buf, size_t len)
> err = -EINVAL;
> goto unlock;
> }
>
> if (value) {
> - mddev_create_serial_pool(mddev, NULL);
> - set_bit(MD_SERIALIZE_POLICY, &mddev->flags);
> + err = mddev_create_serial_pool(mddev, NULL);
> + if (!err)
> + set_bit(MD_SERIALIZE_POLICY, &mddev->flags);
> } else {
> mddev_destroy_serial_pool(mddev, NULL);
> clear_bit(MD_SERIALIZE_POLICY, &mddev->flags);
> }
> unlock:
> diff --git a/drivers/md/md.h b/drivers/md/md.h
> index 76488cd9e81e..e6ea0cc3669a 100644
> --- a/drivers/md/md.h
> +++ b/drivers/md/md.h
> @@ -959,11 +959,11 @@ extern void mddev_resume(struct mddev *mddev);
> extern void md_idle_sync_thread(struct mddev *mddev);
> extern void md_frozen_sync_thread(struct mddev *mddev);
> extern void md_unfrozen_sync_thread(struct mddev *mddev);
>
> extern void md_update_sb(struct mddev *mddev, int force);
> -extern void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev);
> +extern int mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev);
> extern void mddev_destroy_serial_pool(struct mddev *mddev,
> struct md_rdev *rdev);
> struct md_rdev *md_find_rdev_nr_rcu(struct mddev *mddev, int nr);
> struct md_rdev *md_find_rdev_rcu(struct mddev *mddev, dev_t dev);
>
> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
> index 5b9368bd9e70..ef3812806f30 100644
> --- a/drivers/md/raid1.c
> +++ b/drivers/md/raid1.c
> @@ -90,11 +90,11 @@ static int check_and_add_serial(struct md_rdev *rdev, struct r1bio *r1_bio,
> static void wait_for_serialization(struct md_rdev *rdev, struct r1bio *r1_bio)
> {
> struct mddev *mddev = rdev->mddev;
> struct serial_info *si;
>
> - if (WARN_ON(!mddev->serial_info_pool))
> + if (WARN_ON(!mddev->serial_info_pool || !rdev->serial))
> return;
> si = mempool_alloc(mddev->serial_info_pool, GFP_NOIO);
> INIT_LIST_HEAD(&si->waiters);
> INIT_LIST_HEAD(&si->list_node);
> init_completion(&si->ready);
> @@ -109,11 +109,16 @@ static void remove_serial(struct md_rdev *rdev, sector_t lo, sector_t hi)
> struct serial_info *si, *iter_si;
> unsigned long flags;
> int found = 0;
> struct mddev *mddev = rdev->mddev;
> int idx = sector_to_idx(lo);
> - struct serial_in_rdev *serial = &rdev->serial[idx];
> + struct serial_in_rdev *serial;
> +
> + if (WARN_ON(!mddev->serial_info_pool || !rdev->serial))
> + return;
> +
> + serial = &rdev->serial[idx];
>
> spin_lock_irqsave(&serial->serial_lock, flags);
> for (si = raid1_rb_iter_first(&serial->serial_rb, lo, hi);
> si; si = raid1_rb_iter_next(si, lo, hi)) {
> if (si->start == lo && si->last == hi) {
--
Thanks,
Kuai
^ permalink raw reply
* Re: [PATCH v2] md: scope memalloc_noio to allocation critical sections
From: yu kuai @ 2026-07-19 10:03 UTC (permalink / raw)
To: Chen Cheng, linux-raid, yukuai; +Cc: chencheng, linux-kernel
In-Reply-To: <20260718084218.417895-1-chencheng@fnnas.com>
在 2026/7/18 16:42, Chen Cheng 写道:
> From: Chen Cheng<chencheng@fnnas.com>
>
> Save token as mddev-scoped in mddev->noio_flags cause PF_MEMALLOC_NOIO
> leak into task A, while task B restores a token that it never saved.
>
> scenario:
>
> task A mddev task B
> ====== ======= ============
> write suspend_lo
> mddev_suspend()
> suspended == 0
> drain active_io
> suspended = 1
> A: noio_A = memalloc_noio_save()
> A returns with PF_MEMALLOC_NOIO set
>
> write suspend_hi
> mddev_suspend()
> suspended == 1
> suspended = 2
> B returns
> (no save)
>
> mddev_resume()
> suspended = 1
> not last resume
> A returns
> A still has PF_MEMALLOC_NOIO <-- leaked
>
> mddev_resume()
> suspended = 0
> memalloc_noio_restore(noio_A)
> (restores A's token in B)
>
> Fixed by:
> call flags = memalloc_noio_save() directly in mdraid, where:
> 1) after array suspend succeed;
> 2) before allocating memory
>
> Validation:
> repeatedly updates the array's suspend_lo and suspend_hi sysfs from many
> concurrent userspace workers. That makes multiple tasks to call
> mddev_suspend()/mddev_resume() concurrently.
>
> Each worker:
> - reads its initial /proc/self/stat flags and verifies that PF_MEMALLOC_NOIO is not already
> set
> - writes 0 to either suspend_lo or suspend_hi
> - immediately reads its own task flags again
> - reports success if flags & PF_MEMALLOC_NOIO is true after the write returns
>
> 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<chencheng@fnnas.com>
> ---
>
> rfc -> v1:
> - dm-raid side just bypass because suspend and resume not in the
> same task context..
>
> v1 -> v2:
> - just call flags = memalloc_noio_save() directly in mdraid
> (suggest by Yu Kuai)
> ---
> drivers/md/md-bitmap.c | 3 +++
> drivers/md/md.c | 52 ++++++++++++++++++++++++++++--------------
> drivers/md/md.h | 1 -
> drivers/md/raid5.c | 14 ++++++++----
> 4 files changed, 47 insertions(+), 23 deletions(-)
Reviewed-by: Yu Kuai <yukuai@fygo.io>
--
Thanks,
Kuai
^ permalink raw reply
* Re: [PATCHv2] md: clear MD_CLOSING when array_state_store() bails on interrupted lock
From: yu kuai @ 2026-07-19 6:29 UTC (permalink / raw)
To: Jack Wang, Song Liu, Yu Kuai
Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel, Jack Wang
In-Reply-To: <20260717051233.383373-1-jinpu.wang@ionos.com>
Hi,
在 2026/7/17 13:12, Jack Wang 写道:
> From: Jack Wang <jinpu.wang@cloud.ionos.com>
>
> Writing "clear", "readonly", "inactive" or "read_auto" to array_state
> calls mddev_set_closing_and_sync_blockdev(), which sets MD_CLOSING so the
> array cannot be reopened while it is being torn down. The flag is only
> cleared again at the tail of array_state_store() (for readonly, read_auto,
> inactive, or the failed-clear case).
>
> Between setting the flag and reaching that tail there is an early return:
>
> err = mddev_lock(mddev);
> if (err)
> return err;
>
> mddev_lock() is mutex_lock_interruptible() on reconfig_mutex. If the
> writing task is signalled while blocked there - easy to hit when the mutex
> is held for a long time by a running resync/recovery or reshape - it
> returns -EINTR and array_state_store() returns with MD_CLOSING still set.
> do_md_stop()/md_set_readonly() never ran, so the array keeps working
> internally, but every subsequent md_open() now returns -ENODEV for all
> callers: the device still shows up in /proc/mdstat and sysfs is fully
> populated, yet it cannot be opened and cannot be recovered without a
> reboot (the remaining clear paths either need the device open or re-enter
> test_and_set_bit(MD_CLOSING) and bail with -EBUSY before the clear).
>
> Route the interrupted-lock exit through a common label so the flag is
> released. Guard the clear with a set_closing flag that records whether
> this write actually set MD_CLOSING: when mddev->pers is already NULL (for
> example a concurrent STOP_ARRAY is mid-teardown) this write skips the
> mddev_set_closing_and_sync_blockdev() call above, so it must not clear a
> MD_CLOSING that the other thread owns - doing so could let the array be
> reopened while it is being destroyed. This also tightens the pre-existing
> tail clear, which had the same unconditional behaviour.
>
> mddev_unlock() is correctly skipped on the goto path since the lock was
> never acquired.
>
> Fixes: 99b902ac1725 ("md: sync blockdev before stopping raid or setting readonly")
> Assisted-by: Claude:claude-opus-4-8 [Claude Code]
> Signed-off-by: Jack Wang <jinpu.wang@cloud.ionos.com>
> ---
> drivers/md/md.c | 17 ++++++++++++++---
> 1 file changed, 14 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/md/md.c b/drivers/md/md.c
> index d1465bcd86c8..7b7a4d925907 100644
> --- a/drivers/md/md.c
> +++ b/drivers/md/md.c
> @@ -4649,6 +4649,7 @@ array_state_store(struct mddev *mddev, const char *buf, size_t len)
> {
> int err = 0;
> enum array_state st = match_word(buf, array_states);
> + bool set_closing = false;
>
> /* No lock dependent actions */
> switch (st) {
> @@ -4668,6 +4669,7 @@ array_state_store(struct mddev *mddev, const char *buf, size_t len)
> err = mddev_set_closing_and_sync_blockdev(mddev, 0);
> if (err)
> return err;
> + set_closing = true;
> break;
> default:
> break;
> @@ -4696,7 +4698,7 @@ array_state_store(struct mddev *mddev, const char *buf, size_t len)
> }
> err = mddev_lock(mddev);
> if (err)
> - return err;
> + goto out_clear_closing;
Can you just clear MD_CLOSING here and still return err directly? I think this is much
simpler.
>
> switch (st) {
> case inactive:
> @@ -4769,8 +4771,17 @@ array_state_store(struct mddev *mddev, const char *buf, size_t len)
> }
> mddev_unlock(mddev);
>
> - if (st == readonly || st == read_auto || st == inactive ||
> - (err && st == clear))
> +out_clear_closing:
> + /*
> + * Only clear MD_CLOSING if this write actually set it. Otherwise a
> + * concurrent teardown (e.g. STOP_ARRAY) may own the flag - this write
> + * would have skipped setting it above when mddev->pers was already
> + * NULL - and clearing it here would let the array be reopened while it
> + * is being destroyed.
I don't think the comment is correct. Only one caller can set MD_CLOSING, concurrent
teardown can't own the flag since this context already own the flag.
> + */
> + if (set_closing &&
> + (st == readonly || st == read_auto || st == inactive ||
> + (err && st == clear)))
> clear_bit(MD_CLOSING, &mddev->flags);
>
> return err ?: len;
--
Thanks,
Kuai
^ permalink raw reply
* Re: [PATCH 6/6] nvme-rdma: return BLK_STS_TARGET for unsupported P2P transfers
From: sashiko-bot @ 2026-07-18 17:12 UTC (permalink / raw)
To: Mykola Marzhan; +Cc: yukuai, linux-pci, linux-raid
In-Reply-To: <20260718162547.448892-7-mykola@meshstor.io>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Data corruption and PI validation failure due to modifying integrity metadata after DMA mapping.
Pre-existing issues:
- [High] Use-After-Free of request and PDU on the -EIO error path.
--
--- Patch [6]: [PATCH 6/6] nvme-rdma: return BLK_STS_TARGET for unsupported P2P transfers ---
commit efc6b55af9178f85e847e999c50cc6efb530a8b9
Author: Mykola Marzhan <mykola@meshstor.io>
nvme-rdma: return BLK_STS_TARGET for unsupported P2P transfers
This patch addresses an issue where mapping failures for peer memory are
misreported as path errors instead of target errors. It changes DMA
mapping to use ib_dma_map_sgtable_attrs() to preserve error codes and maps
-EREMOTEIO to BLK_STS_TARGET. It also reorders nvme_start_request() to
execute after the mapping succeeds.
Cc: stable@vger.kernel.org # v7.1
> diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c
> index 6909e35427942..b8642cd2fb793 100644
> --- a/drivers/nvme/host/rdma.c
> +++ b/drivers/nvme/host/rdma.c
[ ... ]
> @@ -2039,11 +2038,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);
> +
[Severity: High]
Does moving nvme_start_request() after nvme_rdma_map_data() cause Protection
Information (PI) validation failures?
nvme_start_request() calls blk_mq_start_request(), which eventually calls
blk_integrity_prepare() to insert reference tags into the metadata payload.
Since nvme_rdma_map_data() has already mapped the metadata for DMA
(potentially using SWIOTLB or syncing caches), wouldn't the device receive
stale metadata without the newly generated tags?
> sqe->cqe.done = nvme_rdma_send_done;
>
> ib_dma_sync_single_for_device(dev, sqe->dma,
[ ... ]
> @@ -2063,6 +2064,13 @@ 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;
> + /*
> + * The DMA layer refused to map peer memory to this device: a
> + * property of the pairing, not a path failure. Match nvme-pci
> + * and do not retry (see blk_path_error()).
> + */
> + else if (err == -EREMOTEIO)
> + ret = BLK_STS_TARGET;
> else
> ret = BLK_STS_IOERR;
> nvme_cleanup_cmd(rq);
[Severity: High]
This isn't a bug introduced by this patch, but does the -EIO error path
trigger a use-after-free of rq and req?
If nvme_rdma_map_data() fails with -EIO, we call nvme_host_path_error(), which
synchronously completes and frees rq via blk_mq_end_request().
The code then falls through to nvme_cleanup_cmd(rq) and ib_dma_unmap_single(),
which access the freed rq and its PDU (req).
If the request was immediately re-allocated for another command, could this
read the wrong DMA address and unmap an active DMA mapping?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718162547.448892-1-mykola@meshstor.io?part=6
^ permalink raw reply
* Re: [PATCH 5/6] md/raid1,raid10: skip futile retries on P2PDMA mapping failures
From: sashiko-bot @ 2026-07-18 17:07 UTC (permalink / raw)
To: Mykola Marzhan; +Cc: yukuai, linux-raid, linux-pci
In-Reply-To: <20260718162547.448892-6-mykola@meshstor.io>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] `md_bio_p2pdma_mapping_error()` accesses `bio->bi_io_vec->bv_page` without a valid operation type guard, leading to a wild pointer dereference on data-less bios.
--
--- Patch [5]: [PATCH 5/6] md/raid1,raid10: skip futile retries on P2PDMA mapping failures ---
commit 56153d6522aaf7c00c17b02d6bbccf2b3fb20978
Author: Mykola Marzhan <mykola@meshstor.io>
md/raid1,raid10: skip futile retries on P2PDMA mapping failures
This patch modifies raid1 and raid10 to explicitly handle P2PDMA mapping
failures (BLK_STS_TARGET). It avoids excessive per-block retries and
prevents healthy members from being evicted when peer memory cannot be
DMA-mapped.
> diff --git a/drivers/md/md.h b/drivers/md/md.h
> --- a/drivers/md/md.h
> +++ b/drivers/md/md.h
> @@ -40,6 +40,19 @@ static inline bool md_bio_is_p2pdma(struct bio *bio)
> is_pci_p2pdma_page(bio->bi_io_vec->bv_page);
> }
>
> +/*
> + * True when a leg bio failed because its P2PDMA pages cannot be DMA-mapped
> + * to this member (BLK_STS_TARGET from blk_dma_map_iter_start()). Usable at
> + * completion time, unlike md_bio_is_p2pdma(): leg bios are clones sharing
> + * the master bio's bvec table, which outlives the leg's end_io, but their
> + * iterator is consumed by then, so bio_has_data() cannot gate the access.
> + */
> +static inline bool md_bio_p2pdma_mapping_error(struct bio *bio)
> +{
> + return bio->bi_status == BLK_STS_TARGET && bio->bi_io_vec &&
> + is_pci_p2pdma_page(bio->bi_io_vec->bv_page);
> +}
[Severity: High]
Can this result in a wild pointer dereference for data-less bios?
Data-less bios (like REQ_OP_FLUSH) can legally be allocated with nr_vecs > 0,
such as when they are reused via bio_reset(). For these bios, bio->bi_io_vec
might be non-NULL while bv_page remains uninitialized.
If such a bio fails with BLK_STS_TARGET (for example, from an NVMe drive
returning NVME_SC_NS_NOT_READY), is it safe to access bv_page here and pass
it to is_pci_p2pdma_page() without a bio_has_data() guard?
[ ... ]
> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
> --- a/drivers/md/raid1.c
> +++ b/drivers/md/raid1.c
> @@ -2629,7 +2640,20 @@ static void handle_write_finished(struct r1conf *conf, struct r1bio *r1_bio)
> * errors.
> */
> fail = true;
> - narrow_write_error(r1_bio, m);
> + if (md_bio_p2pdma_mapping_error(r1_bio->bios[m]))
[Severity: High]
Will this error path unconditionally pass failed cloned bios, including
data-less ones like REQ_OP_FLUSH, to the mapping check above?
> + /*
> + * A P2PDMA mapping failure fails the whole
> + * range identically, so narrowing block by
[ ... ]
> diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
> --- a/drivers/md/raid10.c
> +++ b/drivers/md/raid10.c
> @@ -2948,7 +2971,23 @@ 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);
> + if (md_bio_p2pdma_mapping_error(bio))
[Severity: High]
Similarly, does this RAID10 error path pass failed bios to the mapping
check without verifying if the operation actually carries data?
> + /*
> + * A P2PDMA mapping failure fails
> + * the whole range identically, so
> + * narrowing block by block cannot
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718162547.448892-1-mykola@meshstor.io?part=5
^ permalink raw reply
* Re: [PATCH 3/6] md/raid1: don't use write-behind for P2PDMA bios
From: sashiko-bot @ 2026-07-18 16:58 UTC (permalink / raw)
To: Mykola Marzhan; +Cc: linux-pci, yukuai, linux-raid
In-Reply-To: <20260718162547.448892-4-mykola@meshstor.io>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] P2PDMA bios bypass write-behind but fail to serialize against outstanding write-behind I/O, leading to silent data corruption on write-mostly devices.
--
--- Patch [3]: [PATCH 3/6] md/raid1: don't use write-behind for P2PDMA bios ---
commit bbb98e00f3bb1bd5712cfce20170706425ddb589
Author: Mykola Marzhan <mykola@meshstor.io>
md/raid1: don't use write-behind for P2PDMA bios
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.
> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
> index afe2ca96ad8c2..57f64e8901024 100644
> --- a/drivers/md/raid1.c
> +++ b/drivers/md/raid1.c
> @@ -1575,9 +1575,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 && rdev && test_bit(WriteMostly, &rdev->flags) &&
> + !md_bio_is_p2pdma(bio))
> write_behind = true;
>
[Severity: Critical]
This is a pre-existing issue, but by excluding P2PDMA bios from
write-behind, does this expose them to silent data corruption due to
missing serialization checks?
When a normal write to a sector uses write-behind, it can be acknowledged
to the caller once fast devices complete, leaving the write-behind bio
in-flight to the write-mostly device.
If a subsequent P2PDMA write is issued to the same sector, this patch
explicitly disables write-behind for it. Because of this,
r1_bio->behind_master_bio remains NULL, and execution falls into the else
branch further down in raid1_write_request():
drivers/md/raid1.c:raid1_write_request() {
...
if (r1_bio->behind_master_bio) {
mbio = bio_alloc_clone(rdev->bdev,
r1_bio->behind_master_bio,
GFP_NOIO, &mddev->bio_set);
if (test_bit(CollisionCheck, &rdev->flags))
wait_for_serialization(rdev, r1_bio);
...
} else {
mbio = bio_alloc_clone(rdev->bdev, bio, GFP_NOIO,
&mddev->bio_set);
if (test_bit(MD_SERIALIZE_POLICY, &mddev->flags))
wait_for_serialization(rdev, r1_bio);
}
...
}
Because the else branch skips the CollisionCheck test,
wait_for_serialization() is bypassed (unless MD_SERIALIZE_POLICY is globally
set). Can the block layer then reorder these concurrent requests, overwriting
the newer P2PDMA data with the older in-flight write-behind data?
> r1_bio->bios[i] = NULL;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718162547.448892-1-mykola@meshstor.io?part=3
^ permalink raw reply
* Re: [PATCH 4/6] md/raid1,raid10: keep REQ_NOMERGE on narrow_write_error() retry clones
From: sashiko-bot @ 2026-07-18 16:58 UTC (permalink / raw)
To: Mykola Marzhan; +Cc: yukuai, linux-pci, linux-raid
In-Reply-To: <20260718162547.448892-5-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() silently drops REQ_FUA, REQ_PREFLUSH, and REQ_SYNC flags during block-by-block write retries by overwriting the bio's op flags with a bare REQ_OP_WRITE.
--
--- Patch [4]: [PATCH 4/6] md/raid1,raid10: keep REQ_NOMERGE on narrow_write_error() retry clones ---
commit 2b72bc4b60daf0429fd1c6c62c181f2b0048e81d
Author: Mykola Marzhan <mykola@meshstor.io>
md/raid1,raid10: keep REQ_NOMERGE on narrow_write_error() retry clones
This commit ensures that REQ_NOMERGE is retained on P2PDMA retry bios in
narrow_write_error(). It addresses an issue where resetting bi_opf to a
bare REQ_OP_WRITE dropped this protection, potentially allowing unsafe
merging.
> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
> index 57f64e8901024..a300323211911 100644
> --- a/drivers/md/raid1.c
> +++ b/drivers/md/raid1.c
[ ... ]
> @@ -2565,6 +2565,9 @@ static void narrow_write_error(struct r1bio *r1_bio, int i)
> }
>
> wbio->bi_opf = REQ_OP_WRITE;
[Severity: High]
This isn't a bug introduced by this patch, but does this overwrite of
bi_opf with a bare REQ_OP_WRITE drop important operational flags?
If the original write contains data integrity flags like REQ_FUA, REQ_PREFLUSH,
or REQ_SYNC, losing these flags during a retry means the retried write might
only be placed in the disk's volatile cache.
Since bio_alloc_clone() already copies bi_opf from the master bio, overwriting
it here wipes out those flags, which could risk silent data corruption on power
loss if a transient failure triggers this retry path in narrow_write_error().
> + /* 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 0a3cfdd3f5df8..f7ef903a3d4ec 100644
> --- a/drivers/md/raid10.c
> +++ b/drivers/md/raid10.c
[ ... ]
> @@ -2831,6 +2831,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;
[Severity: High]
This isn't a bug introduced by this patch, but similarly here in raid10
narrow_write_error(), does assigning a bare REQ_OP_WRITE silently drop flags
like REQ_FUA and REQ_PREFLUSH?
If an original write has these flags set, the retry might succeed without
them, leading the array to consider the disk perfectly synced while the data
only resides in the disk's volatile cache.
> + /* 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)) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718162547.448892-1-mykola@meshstor.io?part=4
^ permalink raw reply
* Re: [PATCH 1/6] blk-mq-dma: restore BLK_STS_TARGET for unsupported P2P transfers
From: sashiko-bot @ 2026-07-18 16:53 UTC (permalink / raw)
To: Mykola Marzhan; +Cc: linux-pci, yukuai, linux-raid
In-Reply-To: <20260718162547.448892-2-mykola@meshstor.io>
> Commit 91fb2b6052f7 ("nvme-pci: convert to using dma_map_sgtable()")
> deliberately mapped unsupported P2PDMA transfers to BLK_STS_TARGET,
> matching dma_map_sgtable()'s -EREMOTEIO: "When this happens, return
> BLK_STS_TARGET so the request isn't retried." The conversion to
> blk_rq_dma_map silently changed the status to BLK_STS_INVAL, which
> regresses two consumers:
>
> - md/raid1 and raid10 ignore BLK_STS_INVAL leg failures
> (commit f7b24c7b41f2 ("md/raid1,raid10: don't fail devices for
> invalid IO errors"), where it means a request-shaped error that
> fails identically on every member). Since commit 02666132403a
> ("md: propagate BLK_FEAT_PCI_P2PDMA from member devices to RAID device")
> P2PDMA bios reach md arrays: a peer-memory write to a member the
> peer cannot reach is counted as written, the master bio reports
> success, mirrors silently diverge, and on a topology where no
> [ ... ]
> 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
> 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/20260718162547.448892-1-mykola@meshstor.io?part=1
^ permalink raw reply
* Re: [PATCH 2/6] md: ensure REQ_NOMERGE is set on P2PDMA bios
From: sashiko-bot @ 2026-07-18 16:50 UTC (permalink / raw)
To: Mykola Marzhan; +Cc: linux-pci, yukuai, linux-raid
In-Reply-To: <20260718162547.448892-3-mykola@meshstor.io>
> md_submit_bio() unconditionally strips REQ_NOMERGE before passing the
> bio to the personality, an optimization from commit 9c573de3283a ("MD:
> make bio mergeable"): a bio that md has split may become mergeable
> again below md.
>
> For PCI P2PDMA bios the flag is load-bearing, not a hint. The block
> layer sets REQ_NOMERGE on P2PDMA bios (__bio_add_page(), and the
> extraction path of bio_iov_iter_get_pages()) because the DMA mapping
> type of a request is resolved once, from its first segment
> (blk_dma_map_iter_start()), and request-level merging is prevented
> only by REQ_NOMERGE. Stripping it allows the member queue to merge a
> P2PDMA bio with a bio carrying pages of a different pgmap, or host
> memory, mapping the merged segments with the wrong bus address:
> silent data corruption on the member.
>
> Set the flag for P2PDMA bios instead of merely preserving it. No
> in-tree path currently submits P2PDMA pages through the bvec-iter
> path (bio_iov_bvec_set()), which skips the flagging -- but nothing
> structural prevents one, so setting rather than preserving hardens
> md against that gap at the cost of one branch. Everything else keeps
> the original optimization of clearing the flag. This covers every
> personality that advertises BLK_FEAT_PCI_P2PDMA (raid0, raid1,
> raid10), which is why the fix lives in the shared md_submit_bio()
> path.
>
> 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/20260718162547.448892-1-mykola@meshstor.io?part=2
^ permalink raw reply
* [PATCH 6/6] nvme-rdma: return BLK_STS_TARGET for unsupported P2P transfers
From: Mykola Marzhan @ 2026-07-18 16:25 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, 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: <20260718162547.448892-1-mykola@meshstor.io>
Since commit 23528aa3320a ("nvme: enable PCI P2PDMA support for RDMA
transport") nvme-rdma accepts P2PDMA bios, but a mapping failure for
peer memory the HCA cannot reach is misreported as a path error:
ib_dma_map_sg() returns 0, discarding the -EREMOTEIO that
dma_map_sgtable() documents for exactly this case, and the driver
converts it to -EIO -> nvme_host_path_error().
Under the default multipath configuration (the multipath head node
advertises BLK_FEAT_PCI_P2PDMA since commit fb0eeeed91f3
("nvme-multipath: enable PCI P2PDMA for multipath devices")) the
path-error status makes nvme_failover_req() requeue the bios with a
fresh retry budget each cycle, and since the mapping failure is a
deterministic property of the peer/device pairing the I/O simply
never completes: a hot requeue livelock, and a stacked md mirror
hangs instead of failing over to its other leg. With
nvme_core.multipath=N the request burns nvme_max_retries requeues --
nothing ever reaching the wire -- and completes as
BLK_STS_TRANSPORT, which blk_path_error() classifies as retryable
and md/raid1,raid10 treat as a genuine device error: retry storms on
writes, read-error-budget eviction of a healthy member on reads (see
the preceding md patches, whose mapping-failure handling keys on
BLK_STS_TARGET).
Map the data scatterlist with ib_dma_map_sgtable_attrs() so the DMA
layer's error code is preserved, and translate -EREMOTEIO to
BLK_STS_TARGET: the classification nvme-pci established in commit
91fb2b6052f7 ("nvme-pci: convert to using dma_map_sgtable()") and
the preceding blk-mq-dma patch restores (the blk_rq_dma_map
conversion had changed it to BLK_STS_INVAL). The failure is a
property of the peer/device pairing, so it must not be retried on
this or another path. Start the request only after mapping
succeeds, as nvme-pci does: a mapping failure now errors out of
queue_rq on a not-yet-started request, so nvme_mpath_start_request()
accounting is never taken and cannot leak on the direct blk-mq
completion (this also closes the same latent leak for the existing
BLK_STS_IOERR returns). A mapping -ENOMEM now takes the existing
BLK_STS_RESOURCE branch instead of masquerading as a path error, and
a DMA-layer -EINVAL completes as BLK_STS_IOERR instead of a path
error; generic -EIO keeps today's host-path-error behavior, and
virt-DMA devices are unaffected. The metadata scatterlist keeps
ib_dma_map_sg(): integrity buffers are host memory and cannot
produce -EREMOTEIO. Ratelimit the map-failure message -- with an md
mirror steering peer-memory I/O around an unreachable leg it fires
per redirected I/O, not per rare event.
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 | 26 +++++++++++++++++---------
1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c
index 6909e3542794..b8642cd2fb79 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,12 @@ 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.sgl = req->data_sgl.sg_table.sgl;
+ sgt.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 =
@@ -2026,8 +2027,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 +2038,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 +2064,13 @@ 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;
+ /*
+ * The DMA layer refused to map peer memory to this device: a
+ * property of the pairing, not a path failure. Match nvme-pci
+ * and do not retry (see blk_path_error()).
+ */
+ else if (err == -EREMOTEIO)
+ ret = BLK_STS_TARGET;
else
ret = BLK_STS_IOERR;
nvme_cleanup_cmd(rq);
--
2.43.0
^ permalink raw reply related
* [PATCH 5/6] md/raid1,raid10: skip futile retries on P2PDMA mapping failures
From: Mykola Marzhan @ 2026-07-18 16:25 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, 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: <20260718162547.448892-1-mykola@meshstor.io>
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 all three paths:
- 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).
- 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 (peer reaches one member but
not the other) a mixed host/P2P read workload charges 20 errors to
the unreachable leg within a second and kicks a perfectly healthy
member -- destroying redundancy for all I/O because some I/O
cannot route, while every P2P read is meanwhile served correctly
by the reachable mirror.
- On FailFast members both paths short-circuit into md_error(), so a
single unroutable peer-memory I/O evicts a healthy mirror outright.
Handle the mapping failure -- BLK_STS_TARGET on a bio carrying PCI
P2PDMA pages -- 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 retry observes the outcome instead of predicting it: a
mapping failure fails deterministically and is recorded in one
call, collapsing the chunked storm to a single bio, while a
transient error that has since cleared succeeds and records
nothing, exactly as the chunked loop behaves today.
- Reads: mark the leg IO_BLOCKED so read_balance() picks another
mirror, and skip the freeze/fix cycle and the budget charge. If
no mirror can serve the read the master bio fails with EIO as
before, now without kicking healthy members on the way. The P2P
page check is what keeps this narrow: a member that keeps failing
with a device-produced BLK_STS_TARGET (e.g. stuck not-ready) must
still be evictable, so the status alone cannot gate the skip.
- 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.
The first bvec is representative of the whole bio: the block layer
never mixes P2P pages from different pgmaps with each other or with
host pages in one bio (zone_device_pages_compatible()), and reads
bi_io_vec[0] the same way in bio_iov_iter_get_pages(). md's P2PDMA
support is otherwise declared at array setup time through queue
limits, but whether one leg's completion was a mapping refusal is a
property of that bio alone, so the completion path has to look at
the bio. A distinct block status naming the mapping failure exactly
would remove the remaining ambiguity against device-produced
BLK_STS_TARGET and is left as a follow-up.
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 before (patches 1-4) after (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 <mykola@meshstor.io>
---
drivers/md/md.h | 13 +++++++++++
drivers/md/raid1.c | 46 +++++++++++++++++++++++++++++++-----
drivers/md/raid10.c | 57 ++++++++++++++++++++++++++++++++++++++-------
3 files changed, 101 insertions(+), 15 deletions(-)
diff --git a/drivers/md/md.h b/drivers/md/md.h
index 140e2b3670d8..b1abfc89cfc0 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -40,6 +40,19 @@ static inline bool md_bio_is_p2pdma(struct bio *bio)
is_pci_p2pdma_page(bio->bi_io_vec->bv_page);
}
+/*
+ * True when a leg bio failed because its P2PDMA pages cannot be DMA-mapped
+ * to this member (BLK_STS_TARGET from blk_dma_map_iter_start()). Usable at
+ * completion time, unlike md_bio_is_p2pdma(): leg bios are clones sharing
+ * the master bio's bvec table, which outlives the leg's end_io, but their
+ * iterator is consumed by then, so bio_has_data() cannot gate the access.
+ */
+static inline bool md_bio_p2pdma_mapping_error(struct bio *bio)
+{
+ return bio->bi_status == BLK_STS_TARGET && 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:
*/
diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
index a30032321191..241c2994b6a3 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -491,7 +491,9 @@ static void raid1_end_write_request(struct bio *bio)
if (test_bit(FailFast, &rdev->flags) &&
(bio->bi_opf & MD_FAILFAST) &&
/* We never try FailFast to WriteMostly devices */
- !test_bit(WriteMostly, &rdev->flags)) {
+ !test_bit(WriteMostly, &rdev->flags) &&
+ /* A mapping failure says nothing about device health */
+ !md_bio_p2pdma_mapping_error(bio)) {
md_error(r1_bio->mddev, rdev);
}
@@ -2517,7 +2519,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;
@@ -2531,6 +2533,12 @@ 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: used when per-block narrowing
+ * cannot find a good block (P2PDMA mapping failures fail the
+ * whole range identically), while a single retry still tells
+ * a since-cleared transient error apart.
+ *
* We currently own a reference on the rdev.
*/
@@ -2545,9 +2553,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;
@@ -2629,7 +2640,20 @@ static void handle_write_finished(struct r1conf *conf, struct r1bio *r1_bio)
* errors.
*/
fail = true;
- narrow_write_error(r1_bio, m);
+ if (md_bio_p2pdma_mapping_error(r1_bio->bios[m]))
+ /*
+ * A P2PDMA mapping failure fails the whole
+ * range identically, so narrowing block by
+ * block cannot find a good block -- but a
+ * transient device error also surfaces as
+ * BLK_STS_TARGET, so don't assume. Retry
+ * the range once: if it fails, record it in
+ * one go; if it succeeds, there was nothing
+ * wrong with the medium.
+ */
+ narrow_write_error(r1_bio, m, true);
+ else
+ narrow_write_error(r1_bio, m, false);
rdev_dec_pending(conf->mirrors[m].rdev,
conf->mddev);
}
@@ -2656,6 +2680,7 @@ 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];
+ bool p2pdma_error = md_bio_p2pdma_mapping_error(bio);
struct mddev *mddev = conf->mddev;
sector_t sector;
@@ -2675,6 +2700,15 @@ 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 pages cannot be DMA-mapped to this member;
+ * there is nothing to fix on the medium and the member
+ * is healthy for host I/O: don't charge the read-error
+ * budget or fail a FailFast member, 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/raid10.c b/drivers/md/raid10.c
index f7ef903a3d4e..b24429a19254 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -489,7 +489,9 @@ static void raid10_end_write_request(struct bio *bio)
dec_rdev = 0;
if (test_bit(FailFast, &rdev->flags) &&
- (bio->bi_opf & MD_FAILFAST)) {
+ (bio->bi_opf & MD_FAILFAST) &&
+ /* A mapping failure says nothing about device health */
+ !md_bio_p2pdma_mapping_error(bio)) {
md_error(rdev->mddev, rdev);
}
@@ -2786,7 +2788,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 +2802,12 @@ 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: used when per-block narrowing
+ * cannot find a good block (P2PDMA mapping failures fail the
+ * whole range identically), while a single retry still tells
+ * a since-cleared transient error apart.
+ *
* We currently own a reference to the rdev.
*/
@@ -2814,9 +2822,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 +2867,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 +2880,28 @@ static void handle_read_error(struct mddev *mddev, struct r10bio *r10_bio)
* frozen.
*/
bio = r10_bio->devs[slot].bio;
+ p2pdma_error = md_bio_p2pdma_mapping_error(bio);
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 pages cannot be DMA-mapped to this member;
+ * there is nothing to fix on the medium and the member
+ * is healthy for host I/O: don't charge the read-error
+ * budget or fail a FailFast member, 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;
@@ -2948,7 +2971,23 @@ 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);
+ if (md_bio_p2pdma_mapping_error(bio))
+ /*
+ * A P2PDMA mapping failure fails
+ * the whole range identically, so
+ * narrowing block by block cannot
+ * find a good block -- but a
+ * transient device error also
+ * surfaces as BLK_STS_TARGET, so
+ * don't assume. Retry the range
+ * once: if it fails, record it in
+ * one go; if it succeeds, there
+ * was nothing wrong with the
+ * medium.
+ */
+ narrow_write_error(r10_bio, m, true);
+ else
+ narrow_write_error(r10_bio, m, false);
rdev_dec_pending(rdev, conf->mddev);
}
bio = r10_bio->devs[m].repl_bio;
--
2.43.0
^ permalink raw reply related
* [PATCH 4/6] md/raid1,raid10: keep REQ_NOMERGE on narrow_write_error() retry clones
From: Mykola Marzhan @ 2026-07-18 16:25 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, 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: <20260718162547.448892-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>
---
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 57f64e890102..a30032321191 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -2565,6 +2565,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 0a3cfdd3f5df..f7ef903a3d4e 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -2831,6 +2831,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.43.0
^ permalink raw reply related
* [PATCH 3/6] md/raid1: don't use write-behind for P2PDMA bios
From: Mykola Marzhan @ 2026-07-18 16:25 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, 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: <20260718162547.448892-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.
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>
---
drivers/md/raid1.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
index afe2ca96ad8c..57f64e890102 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -1575,9 +1575,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 && rdev && test_bit(WriteMostly, &rdev->flags) &&
+ !md_bio_is_p2pdma(bio))
write_behind = true;
r1_bio->bios[i] = NULL;
--
2.43.0
^ permalink raw reply related
* [PATCH 2/6] md: ensure REQ_NOMERGE is set on P2PDMA bios
From: Mykola Marzhan @ 2026-07-18 16:25 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, 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: <20260718162547.448892-1-mykola@meshstor.io>
md_submit_bio() unconditionally strips REQ_NOMERGE before passing the
bio to the personality, an optimization from commit 9c573de3283a ("MD:
make bio mergeable"): a bio that md has split may become mergeable
again below md.
For PCI P2PDMA bios the flag is load-bearing, not a hint. The block
layer sets REQ_NOMERGE on P2PDMA bios (__bio_add_page(), and the
extraction path of bio_iov_iter_get_pages()) because the DMA mapping
type of a request is resolved once, from its first segment
(blk_dma_map_iter_start()), and request-level merging is prevented
only by REQ_NOMERGE. Stripping it allows the member queue to merge a
P2PDMA bio with a bio carrying pages of a different pgmap, or host
memory, mapping the merged segments with the wrong bus address:
silent data corruption on the member.
Set the flag for P2PDMA bios instead of merely preserving it. No
in-tree path currently submits P2PDMA pages through the bvec-iter
path (bio_iov_bvec_set()), which skips the flagging -- but nothing
structural prevents one, so setting rather than preserving hardens
md against that gap at the cost of one branch. Everything else keeps
the original optimization of clearing the flag. This covers every
personality that advertises BLK_FEAT_PCI_P2PDMA (raid0, raid1,
raid10), which is why the fix lives in the shared md_submit_bio()
path.
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>
---
drivers/md/md.c | 14 ++++++++++++--
drivers/md/md.h | 18 ++++++++++++++++++
2 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/drivers/md/md.c b/drivers/md/md.c
index d1465bcd86c8..3ae4fd4ef381 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -451,8 +451,18 @@ 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 could be mergeable again below md, but for P2PDMA
+ * bios REQ_NOMERGE is load-bearing: the DMA mapping type of a
+ * request is resolved once, from its first segment, so requests
+ * must stay single-provider (see __bio_add_page()). Set the flag
+ * rather than merely preserve it -- bios built through the
+ * bvec-iter path arrive without it.
+ */
+ 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..140e2b3670d8 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,22 @@
#include <trace/events/block.h>
#define MaxSector (~(sector_t)0)
+
+/*
+ * Check if the bio carries PCI P2PDMA (peer device memory) pages. Read
+ * bi_io_vec directly rather than using bio_first_bvec_all(), which WARNs
+ * on cloned bios: md routinely handles split clones, which have
+ * bi_vcnt == 0 but a valid bi_io_vec shared with the parent. P2PDMA and
+ * host pages must not be mixed within one bio, so the first bvec is
+ * representative. Only valid before the bio's iterator is consumed:
+ * bio_has_data() is false at completion time.
+ */
+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.43.0
^ permalink raw reply related
* [PATCH 1/6] blk-mq-dma: restore BLK_STS_TARGET for unsupported P2P transfers
From: Mykola Marzhan @ 2026-07-18 16:25 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, 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: <20260718162547.448892-1-mykola@meshstor.io>
Commit 91fb2b6052f7 ("nvme-pci: convert to using dma_map_sgtable()")
deliberately mapped unsupported P2PDMA transfers to BLK_STS_TARGET,
matching dma_map_sgtable()'s -EREMOTEIO: "When this happens, return
BLK_STS_TARGET so the request isn't retried." The conversion to
blk_rq_dma_map silently changed the status to BLK_STS_INVAL, which
regresses two consumers:
- md/raid1 and raid10 ignore BLK_STS_INVAL leg failures
(commit f7b24c7b41f2 ("md/raid1,raid10: don't fail devices for
invalid IO errors"), where it means a request-shaped error that
fails identically on every member). Since commit 02666132403a
("md: propagate BLK_FEAT_PCI_P2PDMA from member devices to RAID device")
P2PDMA bios reach md arrays: a peer-memory write to a member the
peer cannot reach is counted as written, the master bio reports
success, mirrors silently diverge, and on a topology where no
member is reachable the write reports success with zero copies on
stable storage.
- the failure's classification flips: for direct NVMe consumers
(nvme advertises BLK_FEAT_PCI_P2PDMA) the mapping failure has
surfaced as EINVAL instead of the documented -EREMOTEIO since
v6.17, and blk_path_error(BLK_STS_INVAL) is true, so a stacking
consumer would treat it as a retryable path error (dm-mpath is
the only in-tree blk_path_error() caller; P2P bios cannot
currently reach it, but the classification is wrong on its face).
Restore BLK_STS_TARGET. md then routes an unreachable leg through
its per-device error handling (badblocks, WantReplacement, mirror
retry for reads); a follow-up patch teaches raid1/raid10 to handle
mapping failures without the retry storms that machinery was built
around.
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
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
block/blk-mq-dma.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/block/blk-mq-dma.c b/block/blk-mq-dma.c
index bfdb9ed70741..2eed06bfe791 100644
--- a/block/blk-mq-dma.c
+++ b/block/blk-mq-dma.c
@@ -190,7 +190,15 @@ static bool blk_dma_map_iter_start(struct request *req, struct device *dma_dev,
case PCI_P2PDMA_MAP_NONE:
break;
default:
- iter->status = BLK_STS_INVAL;
+ /*
+ * P2P transfers that the mapping layer cannot support
+ * report BLK_STS_TARGET, matching dma_map_sgtable()'s
+ * -EREMOTEIO and the pre-blk_rq_dma_map nvme behavior:
+ * the failure is a property of this device pairing, so
+ * it must not be retried on another path (blk_path_error)
+ * nor be mistaken for an invalid request.
+ */
+ iter->status = BLK_STS_TARGET;
return false;
}
--
2.43.0
^ permalink raw reply related
* [PATCH 0/6] block,md,nvme: correct handling of unsupported P2PDMA transfers
From: Mykola Marzhan @ 2026-07-18 16:25 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, 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
Driving peer-to-peer I/O (NVMe CMB source) through md arrays and
nvme-rdma legs on asymmetric PCIe topologies -- where the peer device
reaches some members/paths but not others -- turns up two failures on
v7.2-rc, both rooted in how an unsupported-P2PDMA mapping failure is
reported. On md: silent data loss -- an unreachable-leg P2PDMA write
counts as written, mirrors silently diverge, and where no member is
reachable the write reports success with zero copies on stable
storage; reads picked to an unreachable leg fail with EIO and are
never retried on the mirror that holds the data. On nvme-rdma: the
transport swallows the DMA layer's mapping errno into a path error,
which under the default multipath configuration livelocks the I/O (a
stacked md mirror hangs) and with nvme_core.multipath=N surfaces as
retryable BLK_STS_TRANSPORT.
The root cause is a block-layer status-code regression, not an md or
nvme bug. Commit 91fb2b6052f7 ("nvme-pci: convert to using
dma_map_sgtable()") deliberately mapped unsupported P2PDMA transfers
to BLK_STS_TARGET ("... return BLK_STS_TARGET so the request isn't
retried"). Commit 858299dc6160 ("block: add scatterlist-less DMA
mapping helpers") silently changed that to BLK_STS_INVAL, and since
commit 7ce3c1dd78fc ("nvme-pci: convert the data mapping to
blk_rq_dma_map") in v6.17 the failure surfaces to direct NVMe
consumers as EINVAL instead of the documented -EREMOTEIO, with
blk_path_error() now classifying it as retryable. md/raid1,raid10
ignore BLK_STS_INVAL leg failures per commit f7b24c7b41f2
("md/raid1,raid10: don't fail devices for invalid IO errors"), where
it means a request-shaped error that fails identically on every
member -- hence the silent divergence. The md leg of the regression
became reachable when md started advertising BLK_FEAT_PCI_P2PDMA in
v7.2-rc1 (commit 02666132403a ("md: propagate BLK_FEAT_PCI_P2PDMA
from member devices to RAID device")).
Patch 1 restores BLK_STS_TARGET at the source. md then handles an
unreachable leg like any other per-device error: badblocks on the
affected member, the master bio succeeds while an In_sync leg holds
the data, and reads are redirected to the other mirror.
Patches 2-4 fix independent md-side P2PDMA bugs of the same vintage:
md_submit_bio() strips REQ_NOMERGE (the only request-level protection
against merging P2PDMA segments across pgmaps); raid1 write-behind
CPU-copies device BAR memory via bio_copy_data(); and
narrow_write_error()'s retry clones reset bi_opf, dropping the
REQ_NOMERGE protection exactly on the error-retry path.
Patch 5 stops the restored device-error machinery from misfiring
where its medium-error assumptions don't hold. A mapping failure is a
property of the peer/member pairing: retrying the same peer pages
against the same member cannot succeed, and there is nothing on the
medium to repair. Without it, narrow_write_error() serializes dozens
of guaranteed-to-fail chunk retries per failed write; on reads each
unreachable-leg pick costs a full freeze_array() quiesce plus a tick
of the read-error budget -- measured below, a mixed host/P2P read
workload on an asymmetric topology kicks the perfectly healthy far
leg after ~20 failed picks, in under a second; and on FailFast
members a single unroutable I/O evicts a healthy mirror outright.
Because BLK_STS_TARGET is also produced for transient device
conditions md cannot distinguish from bi_status alone, writes probe
the whole range once instead of predicting futility: a real mapping
failure is recorded in one call, a cleared transient recovers with
nothing recorded. Patches 1 and 5 belong in the same release: with
patch 1 alone, restoring the error surfaces the retry storms and
read-path evictions above on 7.2's newly reachable md path.
Patch 6 closes the same hole in the rdma transport. nvme-rdma maps
the data scatterlist with ib_dma_map_sg(), which returns 0 on a
peer-unreachable failure, discarding the -EREMOTEIO that
dma_map_sgtable() documents for exactly this case; the driver reports
it as a path error. Because the multipath head node advertises
BLK_FEAT_PCI_P2PDMA (commit fb0eeeed91f3 ("nvme-multipath: enable PCI
P2PDMA for multipath devices"), v7.2-rc1) and the mapping failure is
deterministic, nvme_failover_req() requeues the bios with a fresh
retry budget every cycle and the I/O never completes -- a hot requeue
livelock that hangs a stacked md mirror instead of failing over; with
nvme_core.multipath=N it burns nvme_max_retries requeues and
completes as retryable BLK_STS_TRANSPORT. Patch 6 maps with
ib_dma_map_sgtable_attrs() to preserve the errno and returns
BLK_STS_TARGET, matching nvme-pci so patch 5's handling covers rdma
legs too, and starts the request only after the map succeeds so
nvme_mpath_start_request() accounting cannot leak on the direct
blk-mq completion. nvme-rdma stays on the scatterlist DMA API: its
fast-reg MR path (ib_map_mr_sg()) consumes scatterlists, so a
blk_rq_dma_map conversion is separate modernization, out of scope for
a fix.
Behavior on v7.2-rc2, QEMU rig (one CMB provider, two NVMe members,
q35 PCIe topologies), 90 8KiB peer-memory reads under concurrent
host reads for the read rows:
topology op v7.2-rc2 patched (1-5)
symmetric write+read ok ok
asymmetric write "ok", silent diverge ok + badblocks on
far leg (1 probe)
asymmetric read 41/90 EIO, no mirror 90/90 ok, no
under load retry, no eviction eviction
unreachable write "ok", NO data copied EIO + badblocks
unreachable read EIO EIO, no members
kicked
Also verified on the same rig: a transient injected TARGET recovers
through the single probe with nothing recorded; P2P MEDIUM keeps the
chunked path; host-page TARGET/INVAL handling is unchanged; FailFast
survives mapping failures but still evicts on genuine errors;
write-mostly legs are written directly (no write-behind CPU copy); a
degraded array whose only leg is unreachable gets plain EIO -- no
budget charge, no eviction; an exhausted badblocks table fails the
member as described below. The write/read/injection rows repeat
identically on raid10.
Validation of patch 6: the mechanism -- ib_dma_map_sg() returning 0
where ib_dma_map_sgtable_attrs() preserves -EREMOTEIO -- was
confirmed on a real mlx5 HCA with a map-only probe. The
start-after-map reorder ran over an rxe nvmet-rdma loopback under fio
with crc32c verification; multipath inflight and nr_active accounting
drain to zero, matching the pre-reorder kernel. rxe cannot produce
-EREMOTEIO, so the failure paths were driven by injecting that exact
mechanism at the map call site: unpatched, one 4KiB write livelocked
(132 failover requeues in 8s, a stacked raid1 hung; multipath=N:
retryable BLK_STS_TRANSPORT); patched, it failed immediately as
BLK_STS_TARGET with zero requeues, and the stacked raid1 badblocked
the leg without evicting it.
Known trade-offs of routing through the stock md write machinery:
an unroutable P2P write records badblocks and sets WriteErrorSeen
and WantReplacement, so later writes overlapping those ranges skip
the member -- host writes do not clear the entries and repair skips
known-bad ranges -- leaving them single-copy until re-add or
replacement recovery (host memory, so it succeeds) rewrites and
clears them; a member whose badblocks table is exhausted or disabled
is failed on the first unroutable P2P write, as with any write error
md cannot record. FailFast members are exempted from md_error() for
mapping failures only (nothing reached the wire, so the error says
nothing about device health); genuine I/O errors keep their
immediate-eviction semantics. raid10 additionally fails a
replacement device outright on a P2P write mapping failure (its
stock replacement-write policy), and an IO_BLOCKED slot excludes
that slot's replacement from the read retry -- both pre-existing
raid10 behaviors with a new trigger. On reads, read_balance() keeps
no memory of unreachability, so each far-leg pick still costs one
failed submission before the redirect; a sticky per-rdev
reachability hint would be md-next follow-up material, as would a
distinct block status naming the mapping failure exactly.
Routing: patch 1 is block-tree material and fixes a v6.17-rc1 status
regression (hence its Cc: stable); patches 2-5 are md and address
exposure that only became reachable with v7.2-rc1; patch 6 is
nvme-rdma and also carries Cc: stable -- the transport misreport
predates the head-node change (Fixes: 23528aa3320a, v7.1) and is
reachable on v7.1 with multipath=N. Patch 6 sources the errno from
dma_map_sgtable() in rdma.c and does not depend on patch 1, which
fixes the separate blk_rq_dma_map path. Patches 1 (block) and 6 (nvme) are
independently applicable bug fixes: each fixes a user-visible defect
on its own and neither depends on the md patches, while 2-5 are
md-side hardening built on patch 1 -- a maintainer can take 1 and 6
even if the md discussion runs longer. Single-cycle routing through
the relevant trees with acks works for us: patch 1 block, 2-5 md,
6 nvme.
Per Documentation/process/coding-assistants.rst: the patches were
developed with AI assistance (see the Assisted-by trailers); all code
was human-reviewed and tested as described above.
Series is against v7.2-rc2.
Mykola Marzhan (6):
blk-mq-dma: restore BLK_STS_TARGET for unsupported P2P transfers
md: ensure REQ_NOMERGE is set on P2PDMA bios
md/raid1: don't use write-behind for P2PDMA bios
md/raid1,raid10: keep REQ_NOMERGE on narrow_write_error() retry clones
md/raid1,raid10: skip futile retries on P2PDMA mapping failures
nvme-rdma: return BLK_STS_TARGET for unsupported P2P transfers
block/blk-mq-dma.c | 10 ++++++-
drivers/md/md.c | 14 ++++++++--
drivers/md/md.h | 31 +++++++++++++++++++++
drivers/md/raid1.c | 56 +++++++++++++++++++++++++++++++------
drivers/md/raid10.c | 60 ++++++++++++++++++++++++++++++++++------
drivers/nvme/host/rdma.c | 26 +++++++++++------
6 files changed, 168 insertions(+), 29 deletions(-)
base-commit: 8cdeaa50eae8dad34885515f62559ee83e7e8dda
--
2.43.0
^ permalink raw reply
* Re: [PATCH v7 3/3] md/raid10: free r10bio before ending master_bio in raid_end_bio_io() and raid_end_discard_bio()
From: Chen Cheng @ 2026-07-18 12:30 UTC (permalink / raw)
To: yukuai, linux-raid; +Cc: linux-kernel
In-Reply-To: <2a399970-710a-49fb-ab72-e1076b05cfbc@fygo.io>
在 2026/7/12 01:43, yu kuai 写道:
>> - if (!test_and_set_bit(R10BIO_Returned, &r10_bio->state)) {
>> - if (!test_bit(R10BIO_Uptodate, &r10_bio->state))
>> - bio->bi_status = BLK_STS_IOERR;
>> + returned = test_and_set_bit(R10BIO_Returned, &state);
>> + if (!returned && !test_bit(R10BIO_Uptodate, &state))
>> + bio->bi_status = BLK_STS_IOERR;
>> +
>> + free_r10bio(r10_bio);
>> +
>> + if (!returned)
>> bio_endio(bio);
>> - }
> Apparently this change is not enough, the R10BIO_Returned case still return
> the master_bio before freeing r10_bio.
>
hi Kuai,
thanks for review.
After read the relate part, two scenarios goto raid_end_bio_io:
a. bio_submit_split_bioset() failed cause early return, and
b. normal raid-10 completion -> finially call raid_end_bio_io()
for (a), protected by md_handle_request()'s active_io ref life-scope.
so, even close master io earily , active_io still alive.
for (b), after fix, free r10bio first. then, close master bio, which
md_end_clone_io() put active_io refcnt.
both is ok , I think.
thanks.
cheng
> I think you should get an active_io reference while allocating r10_bio to
> guarantee that no r10_bio is active while array is suspended.
>
>>
>> /*
>> * Wake up any possible resync thread that waits for the device
>> * to go idle.
>> */
>> allow_barrier(conf);
>> -
>> - free_r10bio(r10_bio);
>> }
>>
>> /*
>> * Update disk head position estimator based on IRQ completion info.
>> */
>> @@ -1579,13 +1582,15 @@ static void raid_end_discard_bio(struct r10bio *r10bio)
>> if (!test_bit(R10BIO_Discard, &r10bio->state)) {
>> first_r10bio = (struct r10bio *)r10bio->master_bio;
^ permalink raw reply
* Re: [PATCH] md: use READ_ONCE() for lockless reads of sb_flags
From: Chen Cheng @ 2026-07-18 11:02 UTC (permalink / raw)
To: yukuai, linux-raid; +Cc: linux-kernel
In-Reply-To: <654cb68a-32e1-4fa7-a735-5a6f833db39e@fygo.io>
在 2026/7/12 02:16, yu kuai 写道:
> Hi,
>
> 在 2026/7/11 10:51, Chen Cheng 写道:
>> From: Chen Cheng <chencheng@fnnas.com>
>>
>> sb_flags is checked without a lock in md, raid1, raid5, and raid10.
>> KCSAN reports these reads as data races.
>>
>> The write side uses atomic bit ops.
>> The read side still has plain loads in a few places.
>>
>> Use READ_ONCE() for the lockless reads of sb_flags.
>>
>> KCSAN reports #1:
>> ======================================
>>
>> BUG: KCSAN: data-race in md_check_recovery / md_write_start
>>
>> write (marked) to 0xffff8e39f897f030 of 8 bytes by task 248146 on cpu 8:
>> md_write_start+0x5dd/0x910
>> raid10_make_request+0x9b/0x1080
>> md_handle_request+0x4a2/0xa40
>> [........]
>>
>> read to 0xffff8e39f897f030 of 8 bytes by task 250445 on cpu 11:
>> md_check_recovery+0x574/0x900
>> raid10d+0xb7/0x2950
>> [........]
>>
>> KCSAN reports #2:
>> ======================================
>> BUG: KCSAN: data-race in md_check_recovery / md_write_start
>>
>> write (marked) to 0xffff8e39e953f030 of 8 bytes by task 540091 on cpu 11:
>> md_write_start+0x5dd/0x910
>> raid1_make_request+0x141/0x1990
>> [........]
>>
>> read to 0xffff8e39e953f030 of 8 bytes by task 580822 on cpu 0:
>> md_check_recovery+0x574/0x900
>> raid1d+0xcc/0x3840
>> [........]
>>
>> value changed: 0x0000000000000002 -> 0x0000000000000006
>>
>> KCSAN reports #3:
>> ======================================
>> BUG: KCSAN: data-race in md_check_recovery / md_do_sync.cold
>>
>> write (marked) to 0xffff8e39e9404030 of 8 bytes by task 492473 on cpu 6:
>> md_do_sync.cold+0x3f6/0x1686
>> [........]
>>
>> read to 0xffff8e39e9404030 of 8 bytes by task 492402 on cpu 3:
>> md_check_recovery+0x16d/0x900
>> raid1d+0xcc/0x3840
>> [........]
>>
>> value changed: 0x0000000000000000 -> 0x0000000000000002
>>
>> KCSAN reports #4:
>> ======================================
>> BUG: KCSAN: data-race in md_do_sync.cold / raid5d
>>
>> write (marked) to 0xffff8e39c35cb030 of 8 bytes by task 192196 on cpu 10:
>> md_do_sync.cold+0x3f6/0x1686
>> md_thread+0x15a/0x2d0
>> [........]
>>
>> read to 0xffff8e39c35cb030 of 8 bytes by task 190759 on cpu 5:
>> raid5d+0x7f9/0xba0
>> md_thread+0x15a/0x2d0
>> [........]
>>
>> value changed: 0x0000000000000000 -> 0x0000000000000002
>>
>> Fixes: 2953079c692d ("md: separate flags for superblock changes")
>>
>> Signed-off-by: Chen Cheng <chencheng@fnnas.com>
>> ---
>> v1 -> v2
>> - pass checkpatch.pl check.
>> ---
>> drivers/md/md.c | 8 ++++----
>> drivers/md/raid1.c | 2 +-
>> drivers/md/raid10.c | 4 ++--
>> drivers/md/raid5.c | 7 ++++---
>> 4 files changed, 11 insertions(+), 10 deletions(-)
>>
>> diff --git a/drivers/md/md.c b/drivers/md/md.c
>> index 096bb64e87bd..e72bf2010287 100644
>> --- a/drivers/md/md.c
>> +++ b/drivers/md/md.c
>> @@ -6830,11 +6830,11 @@ int md_run(struct mddev *mddev)
>> * via sysfs - until a lack of spares is confirmed.
>> */
>> set_bit(MD_RECOVERY_RECOVER, &mddev->recovery);
>> set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
>>
>> - if (mddev->sb_flags)
>> + if (READ_ONCE(mddev->sb_flags))
>> md_update_sb(mddev, 0);
>>
>> if (IS_ENABLED(CONFIG_MD_BITMAP) && !mddev->bitmap_info.file &&
>> !mddev->bitmap_info.offset)
>> md_bitmap_set_none(mddev);
>> @@ -7024,11 +7024,11 @@ static void __md_stop_writes(struct mddev *mddev)
>> mddev->bitmap_ops->flush(mddev);
>> }
>>
>> if (md_is_rdwr(mddev) &&
>> ((!mddev->in_sync && !mddev_is_clustered(mddev)) ||
>> - mddev->sb_flags)) {
>> + READ_ONCE(mddev->sb_flags))) {
>> /* mark array as shutdown cleanly */
>> if (!mddev_is_clustered(mddev))
>> mddev->in_sync = 1;
>> md_update_sb(mddev, 1);
>> }
>> @@ -10294,11 +10294,11 @@ static bool md_should_do_recovery(struct mddev *mddev)
>> /*
>> * MD_SB_CHANGE_PENDING indicates that the array is switching from clean to
>> * active, and no action is needed for now.
>> * All other MD_SB_* flags require to update the superblock.
>> */
>> - if (mddev->sb_flags & ~ (1<<MD_SB_CHANGE_PENDING))
>> + if (READ_ONCE(mddev->sb_flags) & ~(1<<MD_SB_CHANGE_PENDING))
>> return true;
>
> So this should be a v3, and the above line still not fixed. Please check checkpatch
> warnings.
hi.
I run checkpatch.pl to double-check again .. 0 err 0 warn report .
thanks
cheng.
>
>>
>> /*
>> * If the array is not using external metadata and there has been no data
>> * written for some time, then the array's status needs to be set to
>> @@ -10423,11 +10423,11 @@ void md_check_recovery(struct mddev *mddev)
>> spin_lock(&mddev->lock);
>> set_in_sync(mddev);
>> spin_unlock(&mddev->lock);
>> }
>>
>> - if (mddev->sb_flags)
>> + if (READ_ONCE(mddev->sb_flags))
>> md_update_sb(mddev, 0);
>>
>> /*
>> * Never start a new sync thread if MD_RECOVERY_RUNNING is
>> * still set.
>> diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
>> index 29b58583e381..bd6808656edb 100644
>> --- a/drivers/md/raid1.c
>> +++ b/drivers/md/raid1.c
>> @@ -2738,11 +2738,11 @@ static void raid1d(struct md_thread *thread)
>> handle_read_error(conf, r1_bio);
>> else
>> WARN_ON_ONCE(1);
>>
>> cond_resched();
>> - if (mddev->sb_flags & ~(1<<MD_SB_CHANGE_PENDING))
>> + if (READ_ONCE(mddev->sb_flags) & ~(1 << MD_SB_CHANGE_PENDING))
>> md_check_recovery(mddev);
>> }
>> blk_finish_plug(&plug);
>> }
>>
>> diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
>> index adaf9e432e25..3ffa5a19964d 100644
>> --- a/drivers/md/raid10.c
>> +++ b/drivers/md/raid10.c
>> @@ -3030,11 +3030,11 @@ static void raid10d(struct md_thread *thread)
>> handle_read_error(mddev, r10_bio);
>> else
>> WARN_ON_ONCE(1);
>>
>> cond_resched();
>> - if (mddev->sb_flags & ~(1<<MD_SB_CHANGE_PENDING))
>> + if (READ_ONCE(mddev->sb_flags) & ~(1 << MD_SB_CHANGE_PENDING))
>> md_check_recovery(mddev);
>> }
>> blk_finish_plug(&plug);
>> }
>>
>> @@ -4698,11 +4698,11 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr,
>> else
>> mddev->curr_resync_completed = conf->reshape_progress;
>> conf->reshape_checkpoint = jiffies;
>> set_bit(MD_SB_CHANGE_DEVS, &mddev->sb_flags);
>> md_wakeup_thread(mddev->thread);
>> - wait_event(mddev->sb_wait, mddev->sb_flags == 0 ||
>> + wait_event(mddev->sb_wait, READ_ONCE(mddev->sb_flags) == 0 ||
>> test_bit(MD_RECOVERY_INTR, &mddev->recovery));
>> if (test_bit(MD_RECOVERY_INTR, &mddev->recovery)) {
>> allow_barrier(conf);
>> return sectors_done;
>> }
>> diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
>> index ded6a69f7795..cb58b4353995 100644
>> --- a/drivers/md/raid5.c
>> +++ b/drivers/md/raid5.c
>> @@ -1219,11 +1219,11 @@ static void ops_run_io(struct stripe_head *sh, struct stripe_head_state *s)
>> break;
>>
>> if (bad < 0) {
>> set_bit(BlockedBadBlocks, &rdev->flags);
>> if (!conf->mddev->external &&
>> - conf->mddev->sb_flags) {
>> + READ_ONCE(conf->mddev->sb_flags)) {
>> /* It is very unlikely, but we might
>> * still need to write out the
>> * bad block log - better give it
>> * a chance*/
>> md_check_recovery(conf->mddev);
>> @@ -6469,11 +6469,11 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk
>> rdev->recovery_offset = sector_nr;
>>
>> conf->reshape_checkpoint = jiffies;
>> set_bit(MD_SB_CHANGE_DEVS, &mddev->sb_flags);
>> md_wakeup_thread(mddev->thread);
>> - wait_event(mddev->sb_wait, mddev->sb_flags == 0 ||
>> + wait_event(mddev->sb_wait, READ_ONCE(mddev->sb_flags) == 0 ||
>> test_bit(MD_RECOVERY_INTR, &mddev->recovery));
>> if (test_bit(MD_RECOVERY_INTR, &mddev->recovery))
>> return 0;
>> spin_lock_irq(&conf->device_lock);
>> conf->reshape_safe = mddev->reshape_position;
>> @@ -6913,11 +6913,12 @@ static void raid5d(struct md_thread *thread)
>> conf->temp_inactive_list);
>> if (!batch_size && !released)
>> break;
>> handled += batch_size;
>>
>> - if (mddev->sb_flags & ~(1 << MD_SB_CHANGE_PENDING)) {
>> + if (READ_ONCE(mddev->sb_flags) &
>> + ~(1 << MD_SB_CHANGE_PENDING)) {
>> spin_unlock_irq(&conf->device_lock);
>> md_check_recovery(mddev);
>> spin_lock_irq(&conf->device_lock);
>> }
>> }
>
^ permalink raw reply
* [PATCH] md: handle serial pool allocation failures
From: Chen Cheng @ 2026-07-18 10:55 UTC (permalink / raw)
To: linux-raid, yukuai; +Cc: chencheng, chencheng, linux-kernel
From: Chen Cheng <chencheng@fnnas.com>
sashiko-bot report a issue:
mddev_create_serial_pool() silently ignored allocation failures, allowing
callers to continue with incomplete serialization state.
validation:
fail_page_alloc with a stacktrace filter for rdev_init_serial() to force
the serial allocation and its kvmalloc fallback to fail.
A/B result:
- Without this patch, serialize_policy accepts the write despite the
injected allocation failure, leaving serialization state
incomplete.
- With this patch, the write returns -ENOMEM and serialize_policy remains
unchanged.
Fixes: 3938f5fb82ae ("md: add serialize_policy sysfs node for raid1")
Link: https://github.com/chencheng-fnnas/reproducer/blob/main/test-serial-pool-oom.sh
sashiko-bot report:
=====================
[Severity: Critical]
This is a pre-existing issue, but further down in backlog_store(), is it
possible for mddev_create_serial_pool() to fail silently?
drivers/md/md-bitmap.c:backlog_store() {
...
} else if (backlog && !mddev->serial_info_pool) {
/* serial_info_pool is needed since backlog is not zero */
rdev_for_each(rdev, mddev)
mddev_create_serial_pool(mddev, rdev);
}
...
}
Since mddev_create_serial_pool() returns void, it hides memory allocation
failures. If it fails for a disk in this loop, that device is left without
initialization. Does this silently bypass write-behind serialization for
that disk, leading to overlapping writes and silent data corruption?
Similarly, if MD_SERIALIZE_POLICY is active, check_and_add_serial() in
drivers/md/raid1.c will unconditionally dereference rdev->serial:
int idx = sector_to_idx(r1_bio->sector);
struct serial_in_rdev *serial = &rdev->serial[idx];
struct serial_info *head_si;
spin_lock_irqsave(&serial->serial_lock, flags);
Can this cause a NULL pointer dereference for devices that failed
initialization?
Signed-off-by: Chen Cheng <chencheng@fnnas.com>
---
drivers/md/md-bitmap.c | 20 +++++++++++++++-----
drivers/md/md.c | 33 ++++++++++++++++++++++-----------
drivers/md/md.h | 2 +-
drivers/md/raid1.c | 9 +++++++--
4 files changed, 45 insertions(+), 19 deletions(-)
diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c
index 6d495cdf3fb2..e0510e3cae3e 100644
--- a/drivers/md/md-bitmap.c
+++ b/drivers/md/md-bitmap.c
@@ -2211,12 +2211,15 @@ static int bitmap_load(struct mddev *mddev)
struct md_rdev *rdev;
if (!bitmap)
goto out;
- rdev_for_each(rdev, mddev)
- mddev_create_serial_pool(mddev, rdev);
+ rdev_for_each(rdev, mddev) {
+ err = mddev_create_serial_pool(mddev, rdev);
+ if (err)
+ goto out;
+ }
if (mddev_is_clustered(mddev))
mddev->cluster_ops->load_bitmaps(mddev, mddev->bitmap_info.nodes);
/* Clear out old bitmap info first: Either there is none, or we
@@ -2868,18 +2871,25 @@ backlog_store(struct mddev *mddev, const char *buf, size_t len)
/* serial_info_pool is not needed if backlog is zero */
if (!test_bit(MD_SERIALIZE_POLICY, &mddev->flags))
mddev_destroy_serial_pool(mddev, NULL);
} else if (backlog && !mddev->serial_info_pool) {
/* serial_info_pool is needed since backlog is not zero */
- rdev_for_each(rdev, mddev)
- mddev_create_serial_pool(mddev, rdev);
+ rdev_for_each(rdev, mddev) {
+ rv = mddev_create_serial_pool(mddev, rdev);
+ if (rv) {
+ mddev->bitmap_info.max_write_behind = old_mwb;
+ mddev_destroy_serial_pool(mddev, NULL);
+ goto out;
+ }
+ }
}
if (old_mwb != backlog)
bitmap_update_sb(mddev->bitmap);
+out:
mddev_unlock_and_resume(mddev);
- return len;
+ return rv ?: len;
}
static struct md_sysfs_entry bitmap_backlog =
__ATTR(backlog, S_IRUGO|S_IWUSR, backlog_show, backlog_store);
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 25e06f088dc1..048ffd28869b 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -228,18 +228,19 @@ static int rdev_need_serial(struct md_rdev *rdev)
/*
* Init resource for rdev(s), then create serial_info_pool if:
* 1. rdev is the first device which return true from rdev_enable_serial.
* 2. rdev is NULL, means we want to enable serialization for all rdevs.
*/
-void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev)
+int 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;
+ !test_bit(CollisionCheck, &rdev->flags) &&
+ !test_bit(MD_SERIALIZE_POLICY, &mddev->flags))
+ return 0;
noio_flags = memalloc_noio_save();
if (!rdev)
ret = rdevs_init_serial(mddev);
else
@@ -248,18 +249,22 @@ void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev)
goto out;
if (mddev->serial_info_pool == NULL) {
mddev->serial_info_pool =
mempool_create_kmalloc_pool(NR_SERIAL_INFOS,
- sizeof(struct serial_info));
+ sizeof(struct serial_info));
if (!mddev->serial_info_pool) {
+ if (rdev)
+ rdev_uninit_serial(rdev);
rdevs_uninit_serial(mddev);
pr_err("can't alloc memory pool for serialization\n");
+ ret = -ENOMEM;
}
}
out:
memalloc_noio_restore(noio_flags);
+ return ret;
}
/*
* Free resource from rdev(s), and destroy serial_info_pool under conditions:
* 1. rdev is the last device flaged with CollisionCheck.
@@ -2605,12 +2610,15 @@ static int bind_rdev_to_array(struct md_rdev *rdev, struct mddev *mddev)
strreplace(b, '/', '!');
rdev->mddev = mddev;
pr_debug("md: bind<%s>\n", b);
- if (mddev->raid_disks)
- mddev_create_serial_pool(mddev, rdev);
+ if (mddev->raid_disks) {
+ err = mddev_create_serial_pool(mddev, rdev);
+ if (err)
+ return err;
+ }
if ((err = kobject_add(&rdev->kobj, &mddev->kobj, "dev-%s", b)))
goto fail;
/* failure here is OK */
@@ -3118,13 +3126,15 @@ state_store(struct md_rdev *rdev, const char *buf, size_t len)
md_new_event();
}
}
} else if (cmd_match(buf, "writemostly")) {
set_bit(WriteMostly, &rdev->flags);
- mddev_create_serial_pool(rdev->mddev, rdev);
- need_update_sb = true;
- err = 0;
+ err = mddev_create_serial_pool(rdev->mddev, rdev);
+ if (err)
+ clear_bit(WriteMostly, &rdev->flags);
+ else
+ need_update_sb = true;
} else if (cmd_match(buf, "-writemostly")) {
mddev_destroy_serial_pool(rdev->mddev, rdev);
clear_bit(WriteMostly, &rdev->flags);
need_update_sb = true;
err = 0;
@@ -5943,12 +5953,13 @@ serialize_policy_store(struct mddev *mddev, const char *buf, size_t len)
err = -EINVAL;
goto unlock;
}
if (value) {
- mddev_create_serial_pool(mddev, NULL);
- set_bit(MD_SERIALIZE_POLICY, &mddev->flags);
+ err = mddev_create_serial_pool(mddev, NULL);
+ if (!err)
+ set_bit(MD_SERIALIZE_POLICY, &mddev->flags);
} else {
mddev_destroy_serial_pool(mddev, NULL);
clear_bit(MD_SERIALIZE_POLICY, &mddev->flags);
}
unlock:
diff --git a/drivers/md/md.h b/drivers/md/md.h
index 76488cd9e81e..e6ea0cc3669a 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -959,11 +959,11 @@ extern void mddev_resume(struct mddev *mddev);
extern void md_idle_sync_thread(struct mddev *mddev);
extern void md_frozen_sync_thread(struct mddev *mddev);
extern void md_unfrozen_sync_thread(struct mddev *mddev);
extern void md_update_sb(struct mddev *mddev, int force);
-extern void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev);
+extern int mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev);
extern void mddev_destroy_serial_pool(struct mddev *mddev,
struct md_rdev *rdev);
struct md_rdev *md_find_rdev_nr_rcu(struct mddev *mddev, int nr);
struct md_rdev *md_find_rdev_rcu(struct mddev *mddev, dev_t dev);
diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
index 5b9368bd9e70..ef3812806f30 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -90,11 +90,11 @@ static int check_and_add_serial(struct md_rdev *rdev, struct r1bio *r1_bio,
static void wait_for_serialization(struct md_rdev *rdev, struct r1bio *r1_bio)
{
struct mddev *mddev = rdev->mddev;
struct serial_info *si;
- if (WARN_ON(!mddev->serial_info_pool))
+ if (WARN_ON(!mddev->serial_info_pool || !rdev->serial))
return;
si = mempool_alloc(mddev->serial_info_pool, GFP_NOIO);
INIT_LIST_HEAD(&si->waiters);
INIT_LIST_HEAD(&si->list_node);
init_completion(&si->ready);
@@ -109,11 +109,16 @@ static void remove_serial(struct md_rdev *rdev, sector_t lo, sector_t hi)
struct serial_info *si, *iter_si;
unsigned long flags;
int found = 0;
struct mddev *mddev = rdev->mddev;
int idx = sector_to_idx(lo);
- struct serial_in_rdev *serial = &rdev->serial[idx];
+ struct serial_in_rdev *serial;
+
+ if (WARN_ON(!mddev->serial_info_pool || !rdev->serial))
+ return;
+
+ serial = &rdev->serial[idx];
spin_lock_irqsave(&serial->serial_lock, flags);
for (si = raid1_rb_iter_first(&serial->serial_rb, lo, hi);
si; si = raid1_rb_iter_next(si, lo, hi)) {
if (si->start == lo && si->last == hi) {
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v2] md: scope memalloc_noio to allocation critical sections
From: sashiko-bot @ 2026-07-18 8:56 UTC (permalink / raw)
To: Chen Cheng; +Cc: linux-raid, yukuai
In-Reply-To: <20260718084218.417895-1-chencheng@fnnas.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] In `backlog_store()`, an error path unlocks the mddev but fails to resume it, leaving the array permanently suspended.
--
--- Patch [1]: [PATCH v2] md: scope memalloc_noio to allocation critical sections ---
commit 6df6cebcdd6ace34da5732a820e0ad95df14d0fc
Author: Chen Cheng <chencheng@fnnas.com>
md: scope memalloc_noio to allocation critical sections
This patch fixes a token leak issue where PF_MEMALLOC_NOIO could leak across
tasks when saving the token in mddev_suspend() and restoring it in
mddev_resume(). It refactors the code to call memalloc_noio_save() directly
before memory allocation in mdraid operations after the array has been
successfully suspended.
Link: https://github.com/chencheng-fnnas/reproducer/blob/main/repro-md-noio-token-leak.sh
> diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c
> index 0f02e2956398d..f005bc9494ecf 100644
> --- a/drivers/md/md-bitmap.c
> +++ b/drivers/md/md-bitmap.c
> @@ -2629,10 +2629,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();
[ ... ]
While reviewing this file, I noticed a pre-existing issue in backlog_store():
drivers/md/md-bitmap.c:backlog_store() {
...
rv = mddev_suspend_and_lock(mddev);
if (rv)
return rv;
/*
* Without write mostly device, it doesn't make sense to set
* backlog for max_write_behind.
*/
rdev_for_each(rdev, mddev) {
if (test_bit(WriteMostly, &rdev->flags)) {
has_write_mostly = true;
break;
}
}
if (!has_write_mostly) {
pr_warn_ratelimited("%s: can't set backlog, no write mostly device available\n",
mdname(mddev));
mddev_unlock(mddev);
return -EINVAL;
}
...
}
[Severity: High]
This is a pre-existing issue, but the error path for !has_write_mostly in
backlog_store() calls mddev_unlock(mddev) instead of
mddev_unlock_and_resume(mddev).
If a privileged user writes to the bitmap/backlog sysfs file when there are no
write-mostly devices in the array, mddev_suspend_and_lock(mddev) is called, but
the array is never resumed on this error path.
Does this permanently leave mddev->suspended elevated, blocking normal I/O
processing and any future suspend/resume operations on the array?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718084218.417895-1-chencheng@fnnas.com?part=1
^ permalink raw reply
* [PATCH v2] md: scope memalloc_noio to allocation critical sections
From: Chen Cheng @ 2026-07-18 8:42 UTC (permalink / raw)
To: linux-raid, yukuai; +Cc: chencheng, chencheng, linux-kernel
From: Chen Cheng <chencheng@fnnas.com>
Save token as mddev-scoped in mddev->noio_flags cause PF_MEMALLOC_NOIO
leak into task A, while task B restores a token that it never saved.
scenario:
task A mddev task B
====== ======= ============
write suspend_lo
mddev_suspend()
suspended == 0
drain active_io
suspended = 1
A: noio_A = memalloc_noio_save()
A returns with PF_MEMALLOC_NOIO set
write suspend_hi
mddev_suspend()
suspended == 1
suspended = 2
B returns
(no save)
mddev_resume()
suspended = 1
not last resume
A returns
A still has PF_MEMALLOC_NOIO <-- leaked
mddev_resume()
suspended = 0
memalloc_noio_restore(noio_A)
(restores A's token in B)
Fixed by:
call flags = memalloc_noio_save() directly in mdraid, where:
1) after array suspend succeed;
2) before allocating memory
Validation:
repeatedly updates the array's suspend_lo and suspend_hi sysfs from many
concurrent userspace workers. That makes multiple tasks to call
mddev_suspend()/mddev_resume() concurrently.
Each worker:
- reads its initial /proc/self/stat flags and verifies that PF_MEMALLOC_NOIO is not already
set
- writes 0 to either suspend_lo or suspend_hi
- immediately reads its own task flags again
- reports success if flags & PF_MEMALLOC_NOIO is true after the write returns
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 <chencheng@fnnas.com>
---
rfc -> v1:
- dm-raid side just bypass because suspend and resume not in the
same task context..
v1 -> v2:
- just call flags = memalloc_noio_save() directly in mdraid
(suggest by Yu Kuai)
---
drivers/md/md-bitmap.c | 3 +++
drivers/md/md.c | 52 ++++++++++++++++++++++++++++--------------
drivers/md/md.h | 1 -
drivers/md/raid5.c | 14 ++++++++----
4 files changed, 47 insertions(+), 23 deletions(-)
diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c
index 3e40cb45d473..6d495cdf3fb2 100644
--- a/drivers/md/md-bitmap.c
+++ b/drivers/md/md-bitmap.c
@@ -2621,14 +2621,16 @@ location_show(struct mddev *mddev, char *page)
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) {
rv = -EBUSY;
goto out;
@@ -2711,10 +2713,11 @@ location_store(struct mddev *mddev, const char *buf, size_t len)
set_bit(MD_SB_CHANGE_DEVS, &mddev->sb_flags);
md_wakeup_thread(mddev->thread);
}
rv = 0;
out:
+ memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
if (rv)
return rv;
return len;
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 096bb64e87bd..25e06f088dc1 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -231,35 +231,35 @@ static int rdev_need_serial(struct md_rdev *rdev)
* 2. rdev is NULL, means we want to enable serialization for all rdevs.
*/
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));
if (!mddev->serial_info_pool) {
rdevs_uninit_serial(mddev);
pr_err("can't alloc memory pool for serialization\n");
}
}
+out:
+ memalloc_noio_restore(noio_flags);
}
/*
* Free resource from rdev(s), and destroy serial_info_pool under conditions:
* 1. rdev is the last device flaged with CollisionCheck.
@@ -514,13 +514,10 @@ int mddev_suspend(struct mddev *mddev, bool interruptible)
* allow new reshape to start while waiting for io to be done to
* prevent deadlock.
*/
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;
}
EXPORT_SYMBOL_GPL(mddev_suspend);
@@ -533,13 +530,10 @@ static void __mddev_resume(struct mddev *mddev, bool recovery_needed)
if (mddev->suspended) {
mutex_unlock(&mddev->suspend_mutex);
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);
if (recovery_needed)
set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
@@ -4045,10 +4039,11 @@ static ssize_t
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;
struct md_rdev *rdev;
@@ -4056,10 +4051,11 @@ level_store(struct mddev *mddev, const char *buf, size_t len)
return -EINVAL;
rv = mddev_suspend_and_lock(mddev);
if (rv)
return rv;
+ noio_flags = memalloc_noio_save();
if (mddev->pers == NULL) {
memcpy(mddev->clevel, buf, slen);
if (mddev->clevel[slen-1] == '\n')
slen--;
@@ -4231,10 +4227,11 @@ level_store(struct mddev *mddev, const char *buf, size_t len)
md_update_sb(mddev, 1);
sysfs_notify_dirent_safe(mddev->sysfs_level);
md_new_event();
rv = len;
out_unlock:
+ memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
return rv;
}
static struct md_sysfs_entry md_level =
@@ -4410,19 +4407,21 @@ static int update_raid_disks(struct mddev *mddev, int raid_disks);
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);
if (err < 0)
return err;
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);
} else if (mddev->reshape_position != MaxSector) {
struct md_rdev *rdev;
@@ -4442,10 +4441,11 @@ raid_disks_store(struct mddev *mddev, const char *buf, size_t len)
mddev->raid_disks = n;
mddev->reshape_backwards = (mddev->delta_disks < 0);
} else
mddev->raid_disks = n;
out_unlock:
+ memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
return err ? err : len;
}
static struct md_sysfs_entry md_raid_disks =
__ATTR(raid_disks, S_IRUGO|S_IWUSR, raid_disks_show, raid_disks_store);
@@ -4822,10 +4822,11 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len)
char *e;
int major = simple_strtoul(buf, &e, 10);
int minor;
dev_t dev;
struct md_rdev *rdev;
+ unsigned int noio_flags;
int err;
if (!*buf || *e != ':' || !e[1] || e[1] == '\n')
return -EINVAL;
minor = simple_strtoul(e+1, &e, 10);
@@ -4837,10 +4838,11 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len)
return -EOVERFLOW;
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);
if (!IS_ERR(rdev) && !list_empty(&mddev->disks)) {
struct md_rdev *rdev0
@@ -4855,17 +4857,19 @@ new_dev_store(struct mddev *mddev, const char *buf, size_t len)
rdev = md_import_device(dev, -2, -1);
else
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);
}
err = bind_rdev_to_array(rdev, mddev);
out:
if (err)
export_rdev(rdev);
+ memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
if (!err)
md_new_event();
return err ? err : len;
}
@@ -8322,12 +8326,14 @@ static int __md_set_array_info(struct mddev *mddev, void __user *argp)
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)
return err;
@@ -8373,17 +8379,19 @@ 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);
goto unlock;
}
@@ -8504,12 +8512,16 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
unlock:
if (mddev->hold_active == UNTIL_IOCTL &&
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))
clear_bit(MD_CLOSING, &mddev->flags);
return err;
@@ -10173,20 +10185,22 @@ static bool md_choose_sync_action(struct mddev *mddev, int *spares)
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;
/*
* If reshape is still in progress, spares won't be added or removed
* from conf until reshape is done.
*/
if (mddev->reshape_position == MaxSector &&
md_spares_need_change(mddev)) {
suspend = true;
mddev_suspend(mddev, false);
+ noio_flags = memalloc_noio_save();
}
mddev_lock_nointr(mddev);
if (!md_is_rdwr(mddev)) {
/*
@@ -10229,12 +10243,14 @@ static void md_start_sync(struct work_struct *ws)
* md_start_sync was triggered by MD_RECOVERY_NEEDED, so we should
* not set it again. Otherwise, we may cause issue like this one:
* 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();
return;
@@ -10249,12 +10265,14 @@ static void md_start_sync(struct work_struct *ws)
* md_start_sync was triggered by MD_RECOVERY_NEEDED, so we should
* not set it again. Otherwise, we may cause issue like this one:
* 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) &&
mddev->sysfs_action)
sysfs_notify_dirent_safe(mddev->sysfs_action);
diff --git a/drivers/md/md.h b/drivers/md/md.h
index d8daf0f75cbb..76488cd9e81e 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -619,11 +619,10 @@ struct mddev {
mempool_t *serial_info_pool;
void (*sync_super)(struct mddev *mddev, struct md_rdev *rdev);
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
* reconfig_mutex is unlocked, protected by reconfig_mutex.
*/
diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 65ae7d8930fc..863dcb396d8b 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -2454,15 +2454,10 @@ static int scribble_alloc(struct raid5_percpu *percpu,
sizeof(struct page *) * (num + 2) +
sizeof(addr_conv_t) * (num + 2) +
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;
kvfree(percpu->scribble);
@@ -2473,18 +2468,20 @@ 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. */
if (conf->scribble_disks >= new_disks &&
conf->scribble_sectors >= new_sectors)
return 0;
raid5_quiesce(conf->mddev, true);
+ noio_flags = memalloc_noio_save();
cpus_read_lock();
for_each_present_cpu(cpu) {
struct raid5_percpu *percpu;
@@ -2494,10 +2491,11 @@ static int resize_chunks(struct r5conf *conf, int new_disks, int new_sectors)
if (err)
break;
}
cpus_read_unlock();
+ memalloc_noio_restore(noio_flags);
raid5_quiesce(conf->mddev, false);
if (!err) {
conf->scribble_disks = new_disks;
conf->scribble_sectors = new_sectors;
@@ -6993,10 +6991,11 @@ raid5_show_stripe_size(struct mddev *mddev, char *page)
static ssize_t
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;
if (len >= PAGE_SIZE)
return -EINVAL;
@@ -7033,10 +7032,11 @@ raid5_store_stripe_size(struct mddev *mddev, const char *page, size_t len)
mddev->reshape_position != MaxSector || mddev->sysfs_active) {
err = -EBUSY;
goto out_unlock;
}
+ noio_flags = memalloc_noio_save();
mutex_lock(&conf->cache_size_mutex);
size = conf->max_nr_stripes;
shrink_stripes(conf);
@@ -7049,10 +7049,11 @@ raid5_store_stripe_size(struct mddev *mddev, const char *page, size_t len)
err = -ENOMEM;
}
mutex_unlock(&conf->cache_size_mutex);
out_unlock:
+ memalloc_noio_restore(noio_flags);
mddev_unlock_and_resume(mddev);
return err ?: len;
}
static struct md_sysfs_entry
@@ -8940,10 +8941,11 @@ 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);
if (err)
return err;
@@ -8951,10 +8953,11 @@ static int raid5_change_consistency_policy(struct mddev *mddev, const char *buf)
if (!conf) {
mddev_unlock_and_resume(mddev);
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) {
err = log_init(conf, NULL, true);
if (!err) {
@@ -8990,10 +8993,11 @@ 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;
}
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox