* Re: [PATCH v2 3/3] dm: add documentation for dm-inlinecrypt target
From: Milan Broz @ 2026-04-10 17:07 UTC (permalink / raw)
To: Linlin Zhang, linux-block, ebiggers, mpatocka
Cc: linux-kernel, adrianvovk, dm-devel, quic_mdalam, israelr, hch,
axboe
In-Reply-To: <20260410134031.2880675-4-linlin.zhang@oss.qualcomm.com>
On 4/10/26 3:40 PM, Linlin Zhang wrote:
> This adds the admin-guide documentation for dm-inlinecrypt.
>
> dm-inlinecrypt.rst is the guide to using dm-inlinecrypt.
>
> Signed-off-by: Linlin Zhang <linlin.zhang@oss.qualcomm.com>
> ---
...
> +
> +<cipher>
> + Encryption cipher type.
> +
> + The cipher specifications format is::
> +
> + cipher
> +
> + Examples::
> +
> + aes-xts-plain64
> +
> + The cipher type is correspond one-to-one with encryption modes. For
... with encryption modes supported for inline crypto in block layer?
In your patch only BLK_ENCRYPTION_MODE_AES_256_XTS.
> + instance, the corresponding crypto mode of aes-xts-plain64 is
> + BLK_ENCRYPTION_MODE_AES_256_XTS.
...
> +iv_large_sectors
> + IV generators will use sector number counted in <sector_size> units
> + instead of default 512 bytes sectors.
> +
> + For example, if <sector_size> is 4096 bytes, plain64 IV for the second
> + sector will be 8 (without flag) and 1 if iv_large_sectors is present.
> + The <iv_offset> must be multiple of <sector_size> (in 512 bytes units)
> + if this flag is specified.
Is it true? I see this comment in the code:
/* dm-inlinecrypt doesn't implement iv_large_sectors=false. */
...
> +Example scripts
> +===============
> +LUKS (Linux Unified Key Setup) is now the preferred way to set up disk
> +encryption with dm-inlinecrypt using the 'cryptsetup' utility, see
> +https://gitlab.com/cryptsetup/cryptsetup
Cryptsetup has no support for inlinecrypt and it is question if it should have.
It would require additional options and maybe LUKS2 metadata flag to make it persistent.
How did you test it? Please remove this cryptsetup example.
It can be added later when userspace get this functionality.
...> +
> + #!/bin/sh
> + # Create a inlinecrypt device using cryptsetup and LUKS header with default cipher
> + cryptsetup luksFormat $1
> + cryptsetup luksOpen $1 inlinecrypt1
ditto. This example will use dm-crypt, not dm-inlinecrypt.
Milan
^ permalink raw reply
* [RFC PATCH 2/2] block: rnull: implement dummy timeout callback
From: Wenzhao Liao @ 2026-04-10 16:47 UTC (permalink / raw)
To: Andreas Hindborg, Jens Axboe, Miguel Ojeda, linux-block,
rust-for-linux
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Alice Ryhl, Trevor Gross, Danilo Krummrich, linux-kernel
In-Reply-To: <20260410164704.1986859-1-wenzhaoliao@ruc.edu.cn>
Implement the new Operations::timeout callback for rnull and return
TimeoutReturn::ResetTimer.
Using ResetTimer keeps the behavior close to the existing blk-mq
default timeout handling while proving that the Rust timeout
abstraction wires cleanly into a driver.
Signed-off-by: Wenzhao Liao <wenzhaoliao@ruc.edu.cn>
---
drivers/block/rnull/rnull.rs | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 0ca8715febe8..3833a5bec7a4 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -97,4 +97,8 @@ fn complete(rq: ARef<mq::Request<Self>>) {
// point, and so `end_ok` cannot fail.
.expect("Fatal error - expected to be able to end request");
}
+
+ fn timeout(_rq: &mq::Request<Self>) -> mq::TimeoutReturn {
+ mq::TimeoutReturn::ResetTimer
+ }
}
--
2.34.1
^ permalink raw reply related
* [RFC PATCH 1/2] rust: block: mq: safely abstract the timeout callback
From: Wenzhao Liao @ 2026-04-10 16:47 UTC (permalink / raw)
To: Andreas Hindborg, Jens Axboe, Miguel Ojeda, linux-block,
rust-for-linux
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Alice Ryhl, Trevor Gross, Danilo Krummrich, linux-kernel
In-Reply-To: <20260410164704.1986859-1-wenzhaoliao@ruc.edu.cn>
Add a typed TimeoutReturn enum for blk-mq timeout handlers and
extend the Operations trait with an optional timeout callback.
The new callback borrows Request instead of taking an ARef because
timeout is a synchronous notification from blk-mq and does not
transfer request ownership to the driver.
Wire the callback into OperationsVTable and keep drivers that do not
implement timeout on the existing timeout: None path.
Signed-off-by: Wenzhao Liao <wenzhaoliao@ruc.edu.cn>
---
rust/kernel/block/mq.rs | 2 +-
rust/kernel/block/mq/operations.rs | 40 +++++++++++++++++++++++++++++-
2 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index 1fd0d54dd549..cfcfcd99addc 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -98,6 +98,6 @@
mod request;
mod tag_set;
-pub use operations::Operations;
+pub use operations::{Operations, TimeoutReturn};
pub use request::Request;
pub use tag_set::TagSet;
diff --git a/rust/kernel/block/mq/operations.rs b/rust/kernel/block/mq/operations.rs
index 8ad46129a52c..a8ff5eb8dd31 100644
--- a/rust/kernel/block/mq/operations.rs
+++ b/rust/kernel/block/mq/operations.rs
@@ -16,6 +16,16 @@
type ForeignBorrowed<'a, T> = <T as ForeignOwnable>::Borrowed<'a>;
+/// Return value for blk-mq timeout handlers.
+#[repr(u32)]
+pub enum TimeoutReturn {
+ /// The driver completed the request or will complete it later.
+ Done = bindings::blk_eh_timer_return_BLK_EH_DONE,
+
+ /// Reset the request timer and keep waiting for completion.
+ ResetTimer = bindings::blk_eh_timer_return_BLK_EH_RESET_TIMER,
+}
+
/// Implement this trait to interface blk-mq as block devices.
///
/// To implement a block device driver, implement this trait as described in the
@@ -46,6 +56,11 @@ fn queue_rq(
/// Called by the kernel when the request is completed.
fn complete(rq: ARef<Request<Self>>);
+ /// Called by the kernel when a request times out.
+ fn timeout(_rq: &Request<Self>) -> TimeoutReturn {
+ build_error!(crate::error::VTABLE_DEFAULT_ERROR)
+ }
+
/// Called by the kernel to poll the device for completed requests. Only
/// used for poll queues.
fn poll() -> bool {
@@ -163,6 +178,25 @@ impl<T: Operations> OperationsVTable<T> {
T::complete(aref);
}
+ /// This function is called by the C kernel. A pointer to this function is
+ /// installed in the `blk_mq_ops` vtable for the driver.
+ ///
+ /// # Safety
+ ///
+ /// This function may only be called by blk-mq C infrastructure. `rq` must
+ /// point to a valid request that is still live for the duration of this
+ /// callback.
+ unsafe extern "C" fn timeout_callback(
+ rq: *mut bindings::request,
+ ) -> bindings::blk_eh_timer_return {
+ // SAFETY: `rq` is valid as required by the safety requirements for
+ // this function, and the private data is initialized while the request
+ // is live.
+ let rq = unsafe { &*rq.cast::<Request<T>>() };
+
+ T::timeout(rq) as bindings::blk_eh_timer_return
+ }
+
/// This function is called by the C kernel. A pointer to this function is
/// installed in the `blk_mq_ops` vtable for the driver.
///
@@ -262,7 +296,11 @@ impl<T: Operations> OperationsVTable<T> {
put_budget: None,
set_rq_budget_token: None,
get_rq_budget_token: None,
- timeout: None,
+ timeout: if T::HAS_TIMEOUT {
+ Some(Self::timeout_callback)
+ } else {
+ None
+ },
poll: if T::HAS_POLL {
Some(Self::poll_callback)
} else {
--
2.34.1
^ permalink raw reply related
* [RFC PATCH 0/2] rust: block: add a borrowed blk-mq timeout callback
From: Wenzhao Liao @ 2026-04-10 16:47 UTC (permalink / raw)
To: Andreas Hindborg, Jens Axboe, Miguel Ojeda, linux-block,
rust-for-linux
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Alice Ryhl, Trevor Gross, Danilo Krummrich, linux-kernel
This small RFC series fills a missing blk-mq callback in the Rust block
layer. Today, Rust block drivers cannot participate in request timeout
handling because the Rust blk-mq vtable hardcodes `timeout: None`.
Patch 1 adds a typed `TimeoutReturn` wrapper for
`enum blk_eh_timer_return` and extends `mq::Operations` with an
optional `timeout(&Request<Self>) -> TimeoutReturn` callback.
The key design choice is to model timeout as a shared borrow of
`Request`, not as `ARef<Request<_>>`. In this tree, blk-mq invokes
`timeout(struct request *)` synchronously while the block layer retains
ownership of the request. The timeout path is therefore a notification
over a request whose lifetime is guaranteed for the duration of the
call, not an ownership handoff to the driver.
Representing the callback as `&Request` keeps that contract explicit in
the type system, avoids fabricating a fresh `ARef`, and avoids touching
the request wrapper refcount from the timeout path entirely. It also
prevents the Rust API from implying that drivers may persist or take
ownership of the request from this callback.
For compatibility, the vtable only installs the timeout callback when
`#[vtable]` generates `HAS_TIMEOUT`; drivers that do not implement the
method continue to expose `timeout: None`, preserving existing behavior.
Patch 2 wires the new abstraction into `rnull` as a minimal reference
user. `rnull` implements the callback and returns
`TimeoutReturn::ResetTimer`, which keeps behavior close to the current
default timeout handling while proving that the Rust abstraction wires
cleanly into a real driver.
This tree uses the `timeout(struct request *)` blk-mq ABI, so the RFC is
intentionally scoped to that signature and does not model the newer
`reserved` parameter variant.
Build-tested with:
make -C /home/lwz/rfl-dev/worktrees/rnull-upstream-contribution \
O=/tmp/c2saferust-rnull-upstream-build LLVM=-15 defconfig
make -C /home/lwz/rfl-dev/worktrees/rnull-upstream-contribution \
O=/tmp/c2saferust-rnull-upstream-build LLVM=-15 rustavailable
make -C /home/lwz/rfl-dev/worktrees/rnull-upstream-contribution \
O=/tmp/c2saferust-rnull-upstream-build LLVM=-15 vmlinux
make -C /home/lwz/rfl-dev/worktrees/rnull-upstream-contribution \
O=/tmp/c2saferust-rnull-upstream-build LLVM=-15 \
drivers/block/rnull/rnull_mod.ko
with `CONFIG_RUST=y`, `CONFIG_CONFIGFS_FS=y`, and
`CONFIG_BLK_DEV_RUST_NULL=m` enabled in the out-of-tree build.
Comments are especially welcome on whether `&Request<Self>` is the right
long-term Rust surface for blk-mq timeout handling.
Wenzhao Liao (2):
rust: block: mq: safely abstract the timeout callback
block: rnull: implement dummy timeout callback
drivers/block/rnull/rnull.rs | 4 +++
rust/kernel/block/mq.rs | 2 +-
rust/kernel/block/mq/operations.rs | 40 +++++++++++++++++++++++++++++-
3 files changed, 44 insertions(+), 2 deletions(-)
--
2.34.1
^ permalink raw reply
* Re: Re: [RFC PATCH 0/2] rust block-mq TagSet flags plumbing and rnull blocking wiring
From: Miguel Ojeda @ 2026-04-10 16:35 UTC (permalink / raw)
To: wenzhaoliao
Cc: Andreas Hindborg, axboe, ojeda, linux-block, rust-for-linux,
bjorn3_gh, aliceryhl, lossin, boqun, dakr, gary, sunke, tmgross,
linux-kernel
In-Reply-To: <ANIA6QDpKJDgGvi6bWi53Krz.1.1775836309095.Hmail.2023000929@ruc.edu.cn>
On Fri, Apr 10, 2026 at 5:52 PM wenzhaoliao <wenzhaoliao@ruc.edu.cn> wrote:
>
> On a broader note, Could you recommend any specific missing abstractions—either in the block subsystem, configfs, or elsewhere—that the RFL project currently needs? Alternatively, is there a specific TODO list or tracking repository you would recommend us to look at so we can pick up the right tasks without stepping on anyone's toes?
I would recommend looking at the notes I have at
https://rust-for-linux.com/contributing#finding-tasks-and-issues-to-work-on
and the links there -- I hope that helps a bit! :)
(The kernel doesn't merge abstractions without a user, so what we need
are use cases to justify the abstractions.)
Cheers,
Miguel
^ permalink raw reply
* Re: [PATCH blktests 1/1] common/scsi_debug: use _patient_rmmod() to unload scsi_debug
From: Bart Van Assche @ 2026-04-10 16:34 UTC (permalink / raw)
To: Shin'ichiro Kawasaki, linux-block; +Cc: Yi Zhang
In-Reply-To: <20260410053016.4013504-1-shinichiro.kawasaki@wdc.com>
On 4/9/26 10:30 PM, Shin'ichiro Kawasaki wrote:
> - modprobe -qr scsi_debug >&/dev/null
> + _patient_rmmod scsi_debug >&/dev/null
The output of none of the other _patient_rmmod calls is redirected to
/dev/null. I propose to leave out the output redirection because of
consistency.
Thanks,
Bart.
^ permalink raw reply
* Re:Re: [RFC PATCH 0/2] rust block-mq TagSet flags plumbing and rnull blocking wiring
From: wenzhaoliao @ 2026-04-10 15:51 UTC (permalink / raw)
To: Andreas Hindborg
Cc: axboe, ojeda, linux-block, rust-for-linux, bjorn3_gh, aliceryhl,
lossin, boqun, dakr, gary, sunke, tmgross, linux-kernel
Hi Andreas,
Thanks for pointing me to that patch series! I will definitely take a closer look and help test/review the patches over there to support getting this functionality merged.
Just to make sure we don't duplicate any other ongoing work, does that series (or any of your pending branches) already cover the blk_mq_ops->timeout block device callback and the enum blk_eh_timer_return abstraction? If not, we thought that might be a good next candidate to tackle.
On a broader note, Could you recommend any specific missing abstractions—either in the block subsystem, configfs, or elsewhere—that the RFL project currently needs? Alternatively, is there a specific TODO list or tracking repository you would recommend us to look at so we can pick up the right tasks without stepping on anyone's toes?
Thanks again for your time and guidance!
Best regards,
Wenzhao Liao
^ permalink raw reply
* [PATCH v2 1/2] block: add pgmap check to biovec_phys_mergeable
From: Naman Jain @ 2026-04-10 15:34 UTC (permalink / raw)
To: Jens Axboe
Cc: Christoph Hellwig, Chaitanya Kulkarni, John Hubbard,
Logan Gunthorpe, linux-kernel, linux-block, Saurabh Sengar,
Long Li, Michael Kelley, namjain
In-Reply-To: <20260410153414.4159050-1-namjain@linux.microsoft.com>
biovec_phys_mergeable() is used by the request merge, DMA mapping,
and integrity merge paths to decide if two physically contiguous
bvec segments can be coalesced into one. It currently has no check
for whether the segments belong to different dev_pagemaps.
When zone device memory is registered in multiple chunks, each chunk
gets its own dev_pagemap. A single bio can legitimately contain
bvecs from different pgmaps -- iov_iter_extract_bvecs() breaks at
pgmap boundaries but the outer loop in bio_iov_iter_get_pages()
continues filling the same bio. If such bvecs are physically
contiguous, biovec_phys_mergeable() will coalesce them, making it
impossible to recover the correct pgmap for the merged segment
via page_pgmap().
Add a zone_device_pages_have_same_pgmap() check to prevent merging
bvec segments that span different pgmaps.
Fixes: 49580e690755 ("block: add check when merging zone device pages")
Cc: stable@vger.kernel.org
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Naman Jain <namjain@linux.microsoft.com>
---
block/blk.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/block/blk.h b/block/blk.h
index ec4674cdf2ead..50a41db039133 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -127,6 +127,8 @@ static inline bool biovec_phys_mergeable(struct request_queue *q,
if (addr1 + vec1->bv_len != addr2)
return false;
+ if (!zone_device_pages_have_same_pgmap(vec1->bv_page, vec2->bv_page))
+ return false;
if (xen_domain() && !xen_biovec_phys_mergeable(vec1, vec2->bv_page))
return false;
if ((addr1 | mask) != ((addr2 + vec2->bv_len - 1) | mask))
--
2.43.0
^ permalink raw reply related
* [PATCH v2 2/2] block: relax pgmap check in bio_add_page for compatible zone device pages
From: Naman Jain @ 2026-04-10 15:34 UTC (permalink / raw)
To: Jens Axboe
Cc: Christoph Hellwig, Chaitanya Kulkarni, John Hubbard,
Logan Gunthorpe, linux-kernel, linux-block, Saurabh Sengar,
Long Li, Michael Kelley, namjain
In-Reply-To: <20260410153414.4159050-1-namjain@linux.microsoft.com>
bio_add_page() and bio_integrity_add_page() reject pages from different
dev_pagemaps entirely, returning 0 even when those pages have compatible
DMA mapping requirements. This forces callers to start a new bio when
buffers span pgmap boundaries, even though the pages could safely coexist
as separate bvec entries.
This matters for guests where memory is registered through
devm_memremap_pages() with MEMORY_DEVICE_GENERIC in multiple calls,
creating separate dev_pagemaps for each chunk. When a direct I/O buffer
spans two such chunks, bio_add_page() rejects the second page, forcing an
unnecessary bio split or I/O failure.
Introduce zone_device_pages_compatible() in blk.h to check whether two
pages can coexist in the same bio as separate bvec entries. The block DMA
iterator (blk_dma_map_iter_start) caches the P2PDMA mapping state from the
first segment and applies it to all others, so P2PDMA pages from different
pgmaps must not be mixed, and neither must P2PDMA and non-P2PDMA pages.
All other combinations (MEMORY_DEVICE_GENERIC pages from different pgmaps,
or MEMORY_DEVICE_GENERIC with normal RAM) use the same dma_map_phys path
and are safe.
Replace the blanket zone_device_pages_have_same_pgmap() rejection with
zone_device_pages_compatible(), while keeping
zone_device_pages_have_same_pgmap() as a merge guard.
Pages from different pgmaps can be added as separate bvec entries but
must not be coalesced into the same segment, as that would make
it impossible to recover the correct pgmap via page_pgmap().
Fixes: 49580e690755 ("block: add check when merging zone device pages")
Cc: stable@vger.kernel.org
Signed-off-by: Naman Jain <namjain@linux.microsoft.com>
---
block/bio-integrity.c | 6 +++---
block/bio.c | 6 +++---
block/blk.h | 19 +++++++++++++++++++
3 files changed, 25 insertions(+), 6 deletions(-)
diff --git a/block/bio-integrity.c b/block/bio-integrity.c
index e79eaf0477943..e54c6e06e1cbb 100644
--- a/block/bio-integrity.c
+++ b/block/bio-integrity.c
@@ -231,10 +231,10 @@ int bio_integrity_add_page(struct bio *bio, struct page *page,
if (bip->bip_vcnt > 0) {
struct bio_vec *bv = &bip->bip_vec[bip->bip_vcnt - 1];
- if (!zone_device_pages_have_same_pgmap(bv->bv_page, page))
+ if (!zone_device_pages_compatible(bv->bv_page, page))
return 0;
-
- if (bvec_try_merge_hw_page(q, bv, page, len, offset)) {
+ if (zone_device_pages_have_same_pgmap(bv->bv_page, page) &&
+ bvec_try_merge_hw_page(q, bv, page, len, offset)) {
bip->bip_iter.bi_size += len;
return len;
}
diff --git a/block/bio.c b/block/bio.c
index 641ef0928d735..c52a0bd1e8993 100644
--- a/block/bio.c
+++ b/block/bio.c
@@ -1048,10 +1048,10 @@ int bio_add_page(struct bio *bio, struct page *page,
if (bio->bi_vcnt > 0) {
struct bio_vec *bv = &bio->bi_io_vec[bio->bi_vcnt - 1];
- if (!zone_device_pages_have_same_pgmap(bv->bv_page, page))
+ if (!zone_device_pages_compatible(bv->bv_page, page))
return 0;
-
- if (bvec_try_merge_page(bv, page, len, offset)) {
+ if (zone_device_pages_have_same_pgmap(bv->bv_page, page) &&
+ bvec_try_merge_page(bv, page, len, offset)) {
bio->bi_iter.bi_size += len;
return len;
}
diff --git a/block/blk.h b/block/blk.h
index 50a41db039133..b998a7761faf3 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -136,6 +136,25 @@ static inline bool biovec_phys_mergeable(struct request_queue *q,
return true;
}
+/*
+ * Check if two pages from potentially different zone device pgmaps can
+ * coexist as separate bvec entries in the same bio.
+ *
+ * The block DMA iterator (blk_dma_map_iter_start) caches the P2PDMA mapping
+ * state from the first segment and applies it to all subsequent segments, so
+ * P2PDMA pages from different pgmaps must not be mixed in the same bio.
+ *
+ * Other zone device types (FS_DAX, GENERIC) use the same dma_map_phys() path
+ * as normal RAM. PRIVATE and COHERENT pages never appear in bios.
+ */
+static inline bool zone_device_pages_compatible(const struct page *a,
+ const struct page *b)
+{
+ if (is_pci_p2pdma_page(a) || is_pci_p2pdma_page(b))
+ return zone_device_pages_have_same_pgmap(a, b);
+ return true;
+}
+
static inline bool __bvec_gap_to_prev(const struct queue_limits *lim,
struct bio_vec *bprv, unsigned int offset)
{
--
2.43.0
^ permalink raw reply related
* [PATCH v2 0/2] block: fix pgmap handling for zone device pages in bio merge paths
From: Naman Jain @ 2026-04-10 15:34 UTC (permalink / raw)
To: Jens Axboe
Cc: Christoph Hellwig, Chaitanya Kulkarni, John Hubbard,
Logan Gunthorpe, linux-kernel, linux-block, Saurabh Sengar,
Long Li, Michael Kelley, namjain
When zone device memory is registered in multiple chunks, each chunk
gets its own dev_pagemap. A single bio can contain bvecs from different
pgmaps -- iov_iter_extract_bvecs() breaks at pgmap boundaries but the
outer loop in bio_iov_iter_get_pages() continues filling the same bio.
There are two problems with the current code:
1. biovec_phys_mergeable() has no pgmap check, so the request merge,
DMA mapping, and integrity merge paths can coalesce physically
contiguous bvec segments from different pgmaps. This makes it
impossible to recover the correct pgmap for the merged segment
via page_pgmap().
2. bio_add_page() and bio_integrity_add_page() reject pages from a
different pgmap entirely (returning 0), rather than just skipping
the merge and adding them as new bvec entries. This forces callers
to start a new bio unnecessarily.
Patch 1 fixes the merge-path gap by adding a pgmap check to
biovec_phys_mergeable().
Patch 2 introduces zone_device_pages_compatible() which replaces the
blanket zone_device_pages_have_same_pgmap() rejection in bio_add_page()
and bio_integrity_add_page(). Pages that are safe to coexist as separate
bvec entries (e.g. MEMORY_DEVICE_GENERIC from different pgmaps) are now
accepted, while P2PDMA pages from different pgmaps or mixed P2PDMA and
non-P2PDMA pages are still rejected, since the DMA iterator caches the
P2PDMA mapping state from the first segment.
zone_device_pages_have_same_pgmap() is kept as a merge guard so pages
from different pgmaps are not coalesced into the same bvec segment.
Changes since v1:
https://lore.kernel.org/all/20260401082329.1602328-1-namjain@linux.microsoft.com/
- Reworked patch 2 to introduce zone_device_pages_compatible() which
rejects P2PDMA pages from different pgmaps at the bio-building level,
not just at merge time. The previous version only moved the pgmap check
into the merge conditional without preventing incompatible pages from
being added as separate bvec entries. (Christoph Hellwig)
Naman Jain (2):
block: add pgmap check to biovec_phys_mergeable
block: relax pgmap check in bio_add_page for compatible zone device
pages
block/bio-integrity.c | 6 +++---
block/bio.c | 6 +++---
block/blk.h | 21 +++++++++++++++++++++
3 files changed, 27 insertions(+), 6 deletions(-)
--
2.43.0
^ permalink raw reply
* Re: [PATCH 04/61] ext4: Prefer IS_ERR_OR_NULL over manual NULL check
From: Theodore Ts'o @ 2026-04-10 15:18 UTC (permalink / raw)
To: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
linux-sctp, linux-security-module, linux-sh, linux-sound,
linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
netdev, ntfs3, samba-technical, sched-ext, target-devel,
tipc-discussion, v9fs, Philipp Hahn
Cc: Theodore Ts'o, Andreas Dilger
In-Reply-To: <20260310-b4-is_err_or_null-v1-4-bd63b656022d@avm.de>
On Tue, 10 Mar 2026 12:48:30 +0100, Philipp Hahn wrote:
> Prefer using IS_ERR_OR_NULL() over using IS_ERR() and a manual NULL
> check.
>
> Change generated with coccinelle.
Applied, thanks!
[04/61] ext4: Prefer IS_ERR_OR_NULL over manual NULL check
commit: 1d749e110277ce4103f27bd60d6181e52c0cc1e3
Best regards,
--
Theodore Ts'o <tytso@mit.edu>
^ permalink raw reply
* [PATCH v2 3/3] dm: add documentation for dm-inlinecrypt target
From: Linlin Zhang @ 2026-04-10 13:40 UTC (permalink / raw)
To: linux-block, ebiggers, mpatocka, gmazyland
Cc: linux-kernel, adrianvovk, dm-devel, quic_mdalam, israelr, hch,
axboe
In-Reply-To: <20260410134031.2880675-1-linlin.zhang@oss.qualcomm.com>
This adds the admin-guide documentation for dm-inlinecrypt.
dm-inlinecrypt.rst is the guide to using dm-inlinecrypt.
Signed-off-by: Linlin Zhang <linlin.zhang@oss.qualcomm.com>
---
.../device-mapper/dm-inlinecrypt.rst | 122 ++++++++++++++++++
1 file changed, 122 insertions(+)
create mode 100644 Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst
diff --git a/Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst b/Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst
new file mode 100644
index 000000000000..c302ba73fc38
--- /dev/null
+++ b/Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst
@@ -0,0 +1,122 @@
+========
+dm-inlinecrypt
+========
+
+Device-Mapper's "inlinecrypt" target provides transparent encryption of block devices
+using the inline encryption hardware.
+
+For a more detailed description of inline encryption, see:
+https://docs.kernel.org/block/inline-encryption.html
+
+Parameters::
+
+ <cipher> <key> <iv_offset> <device path> \
+ <offset> [<#opt_params> <opt_params>]
+
+<cipher>
+ Encryption cipher type.
+
+ The cipher specifications format is::
+
+ cipher
+
+ Examples::
+
+ aes-xts-plain64
+
+ The cipher type is correspond one-to-one with encryption modes. For
+ instance, the corresponding crypto mode of aes-xts-plain64 is
+ BLK_ENCRYPTION_MODE_AES_256_XTS.
+
+<key>
+ Key used for encryption. It is encoded either as a hexadecimal number
+ or it can be passed as <key_string> prefixed with single colon
+ character (':') for keys residing in kernel keyring service.
+ You can only use key sizes that are valid for the selected cipher.
+ Note that the size in bytes of a valid key must be in bellow range.
+
+ [BLK_CRYPTO_KEY_TYPE_RAW, BLK_CRYPTO_KEY_TYPE_HW_WRAPPED]
+
+<key_string>
+ The kernel keyring key is identified by string in following format:
+ <key_size>:<key_type>:<key_description>.
+
+<key_size>
+ The encryption key size in bytes. The kernel key payload size must match
+ the value passed in <key_size>.
+
+<key_type>
+ Either 'logon', or 'trusted' kernel key type.
+
+<key_description>
+ The kernel keyring key description inlinecrypt target should look for
+ when loading key of <key_type>.
+
+<iv_offset>
+ The IV offset is a sector count that is added to the sector number
+ before creating the IV.
+
+<device path>
+ This is the device that is going to be used as backend and contains the
+ encrypted data. You can specify it as a path like /dev/xxx or a device
+ number <major>:<minor>.
+
+<offset>
+ Starting sector within the device where the encrypted data begins.
+
+<#opt_params>
+ Number of optional parameters. If there are no optional parameters,
+ the optional parameters section can be skipped or #opt_params can be zero.
+ Otherwise #opt_params is the number of following arguments.
+
+ Example of optional parameters section:
+ allow_discards sector_size:4096 iv_large_sectors
+
+allow_discards
+ Block discard requests (a.k.a. TRIM) are passed through the inlinecrypt
+ device. The default is to ignore discard requests.
+
+ WARNING: Assess the specific security risks carefully before enabling this
+ option. For example, allowing discards on encrypted devices may lead to
+ the leak of information about the ciphertext device (filesystem type,
+ used space etc.) if the discarded blocks can be located easily on the
+ device later.
+
+sector_size:<bytes>
+ Use <bytes> as the encryption unit instead of 512 bytes sectors.
+ This option can be in range 512 - 4096 bytes and must be power of two.
+ Virtual device will announce this size as a minimal IO and logical sector.
+
+iv_large_sectors
+ IV generators will use sector number counted in <sector_size> units
+ instead of default 512 bytes sectors.
+
+ For example, if <sector_size> is 4096 bytes, plain64 IV for the second
+ sector will be 8 (without flag) and 1 if iv_large_sectors is present.
+ The <iv_offset> must be multiple of <sector_size> (in 512 bytes units)
+ if this flag is specified.
+
+Example scripts
+===============
+LUKS (Linux Unified Key Setup) is now the preferred way to set up disk
+encryption with dm-inlinecrypt using the 'cryptsetup' utility, see
+https://gitlab.com/cryptsetup/cryptsetup
+
+::
+
+ #!/bin/sh
+ # Create a inlinecrypt device using dmsetup
+ dmsetup create inlinecrypt1 --table "0 `blockdev --getsz $1` inlinecrypt aes-xts-plain64 babebabebabebabebabebabebabebabebabebabebabebabebabebabebabebabe 0 $1 0"
+
+::
+
+ #!/bin/sh
+ # Create a inlinecrypt device using dmsetup when encryption key is stored in keyring service
+ dmsetup create inlinecrypt2 --table "0 `blockdev --getsz $1` inlinecrypt aes-xts-plain64 :64:logon:fde:dminlinecrypt_test_key 0 $1 0"
+
+::
+
+ #!/bin/sh
+ # Create a inlinecrypt device using cryptsetup and LUKS header with default cipher
+ cryptsetup luksFormat $1
+ cryptsetup luksOpen $1 inlinecrypt1
--
2.34.1
^ permalink raw reply related
* [PATCH v2 2/3] dm-inlinecrypt: add target for inline block device encryption
From: Linlin Zhang @ 2026-04-10 13:40 UTC (permalink / raw)
To: linux-block, ebiggers, mpatocka, gmazyland
Cc: linux-kernel, adrianvovk, dm-devel, quic_mdalam, israelr, hch,
axboe
In-Reply-To: <20260410134031.2880675-1-linlin.zhang@oss.qualcomm.com>
From: Eric Biggers <ebiggers@google.com>
Add a new device-mapper target "dm-inlinecrypt" that is similar to
dm-crypt but uses the blk-crypto API instead of the regular crypto API.
This allows it to take advantage of inline encryption hardware such as
that commonly built into UFS host controllers.
The table syntax matches dm-crypt's, but for now only a stripped-down
set of parameters is supported. For example, for now AES-256-XTS is the
only supported cipher.
dm-inlinecrypt is based on Android's dm-default-key with the
controversial passthrough support removed. Note that due to the removal
of passthrough support, use of dm-inlinecrypt in combination with
fscrypt causes double encryption of file contents (similar to dm-crypt +
fscrypt), with the fscrypt layer not being able to use the inline
encryption hardware. This makes dm-inlinecrypt unusable on systems such
as Android that use fscrypt and where a more optimized approach is
needed. It is however suitable as a replacement for dm-crypt.
dm-inlinecrypt supports both keyring key and hex key, the former avoids
the key to be exposed in dm-table message. Similar to dm-default-key in
Android, it will fallabck to the software block crypto once the inline
crypto hardware cannot support the expected cipher.
Test:
dmsetup create inlinecrypt_logon --table "0 `blockdev --getsz $1` \
inlinecrypt aes-xts-plain64 :64:logon:fde:dminlinecrypt_test_key 0 $1 0"
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: Linlin Zhang <linlin.zhang@oss.qualcomm.com>
---
drivers/md/Kconfig | 10 +
drivers/md/Makefile | 1 +
drivers/md/dm-inlinecrypt.c | 559 ++++++++++++++++++++++++++++++++++++
3 files changed, 570 insertions(+)
create mode 100644 drivers/md/dm-inlinecrypt.c
diff --git a/drivers/md/Kconfig b/drivers/md/Kconfig
index c58a9a8ea54e..aa541cc22ecc 100644
--- a/drivers/md/Kconfig
+++ b/drivers/md/Kconfig
@@ -313,6 +313,16 @@ config DM_CRYPT
If unsure, say N.
+config DM_INLINECRYPT
+ tristate "Inline encryption target support"
+ depends on BLK_DEV_DM
+ depends on BLK_INLINE_ENCRYPTION
+ help
+ This device-mapper target is similar to dm-crypt, but it uses the
+ blk-crypto API instead of the regular crypto API. This allows it to
+ take advantage of inline encryption hardware such as that commonly
+ built into UFS host controllers.
+
config DM_SNAPSHOT
tristate "Snapshot target"
depends on BLK_DEV_DM
diff --git a/drivers/md/Makefile b/drivers/md/Makefile
index c338cc6fbe2e..517d1f7d8288 100644
--- a/drivers/md/Makefile
+++ b/drivers/md/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_DM_UNSTRIPED) += dm-unstripe.o
obj-$(CONFIG_DM_BUFIO) += dm-bufio.o
obj-$(CONFIG_DM_BIO_PRISON) += dm-bio-prison.o
obj-$(CONFIG_DM_CRYPT) += dm-crypt.o
+obj-$(CONFIG_DM_INLINECRYPT) += dm-inlinecrypt.o
obj-$(CONFIG_DM_DELAY) += dm-delay.o
obj-$(CONFIG_DM_DUST) += dm-dust.o
obj-$(CONFIG_DM_FLAKEY) += dm-flakey.o
diff --git a/drivers/md/dm-inlinecrypt.c b/drivers/md/dm-inlinecrypt.c
new file mode 100644
index 000000000000..b6e98fdf8af1
--- /dev/null
+++ b/drivers/md/dm-inlinecrypt.c
@@ -0,0 +1,559 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2024 Google LLC
+ */
+
+#include <linux/blk-crypto.h>
+#include <linux/ctype.h>
+#include <linux/device-mapper.h>
+#include <linux/hex.h>
+#include <linux/module.h>
+#include <keys/user-type.h>
+
+#define DM_MSG_PREFIX "inlinecrypt"
+
+static const struct dm_inlinecrypt_cipher {
+ const char *name;
+ enum blk_crypto_mode_num mode_num;
+} dm_inlinecrypt_ciphers[] = {
+ {
+ .name = "aes-xts-plain64",
+ .mode_num = BLK_ENCRYPTION_MODE_AES_256_XTS,
+ },
+};
+
+/**
+ * struct inlinecrypt_ctx - private data of an inlinecrypt target
+ * @dev: the underlying device
+ * @start: starting sector of the range of @dev which this target actually maps.
+ * For this purpose a "sector" is 512 bytes.
+ * @cipher_string: the name of the encryption algorithm being used
+ * @iv_offset: starting offset for IVs. IVs are generated as if the target were
+ * preceded by @iv_offset 512-byte sectors.
+ * @sector_size: crypto sector size in bytes (usually 4096)
+ * @sector_bits: log2(sector_size)
+ * @key: the encryption key to use
+ * @max_dun: the maximum DUN that may be used (computed from other params)
+ */
+struct inlinecrypt_ctx {
+ struct dm_dev *dev;
+ sector_t start;
+ const char *cipher_string;
+ unsigned int key_size;
+ u64 iv_offset;
+ unsigned int sector_size;
+ unsigned int sector_bits;
+ struct blk_crypto_key key;
+ u64 max_dun;
+};
+
+static const struct dm_inlinecrypt_cipher *
+lookup_cipher(const char *cipher_string)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dm_inlinecrypt_ciphers); i++) {
+ if (strcmp(cipher_string, dm_inlinecrypt_ciphers[i].name) == 0)
+ return &dm_inlinecrypt_ciphers[i];
+ }
+ return NULL;
+}
+
+static void inlinecrypt_dtr(struct dm_target *ti)
+{
+ struct inlinecrypt_ctx *ctx = ti->private;
+
+ if (ctx->dev) {
+ if (ctx->key.size)
+ blk_crypto_evict_key(ctx->dev->bdev, &ctx->key);
+ dm_put_device(ti, ctx->dev);
+ }
+ kfree_sensitive(ctx->cipher_string);
+ kfree_sensitive(ctx);
+}
+
+static bool contains_whitespace(const char *str)
+{
+ while (*str)
+ if (isspace(*str++))
+ return true;
+ return false;
+}
+
+static int set_key_user(struct key *key, char *bin_key,
+ const unsigned int bin_key_size)
+{
+ const struct user_key_payload *ukp;
+
+ ukp = user_key_payload_locked(key);
+ if (!ukp)
+ return -EKEYREVOKED;
+
+ if (bin_key_size != ukp->datalen)
+ return -EINVAL;
+
+ memcpy(bin_key, ukp->data, bin_key_size);
+
+ return 0;
+}
+
+static int inlinecrypt_get_keyring_key(const char *key_string, u8 *bin_key,
+ const unsigned int bin_key_size)
+{
+ char *key_desc;
+ int ret;
+ struct key_type *type;
+ struct key *key;
+ int (*set_key)(struct key *key, char *bin_key,
+ const unsigned int bin_key_size);
+
+ /*
+ * Reject key_string with whitespace. dm core currently lacks code for
+ * proper whitespace escaping in arguments on DM_TABLE_STATUS path.
+ */
+ if (contains_whitespace(key_string)) {
+ DMERR("whitespace chars not allowed in key string");
+ return -EINVAL;
+ }
+
+ /* look for next ':' separating key_type from key_description */
+ key_desc = strchr(key_string, ':');
+ if (!key_desc || key_desc == key_string || !strlen(key_desc + 1))
+ return -EINVAL;
+
+ if (!strncmp(key_string, "logon:", key_desc - key_string + 1)) {
+ type = &key_type_logon;
+ set_key = set_key_user;
+ } else {
+ return -EINVAL;
+ }
+
+ key = request_key(type, key_desc + 1, NULL);
+ if (IS_ERR(key))
+ return PTR_ERR(key);
+
+ down_read(&key->sem);
+
+ ret = set_key(key, (char *)bin_key, bin_key_size);
+ if (ret < 0) {
+ up_read(&key->sem);
+ key_put(key);
+ return ret;
+ }
+
+ up_read(&key->sem);
+ key_put(key);
+
+ return ret;
+}
+
+static int inlinecrypt_get_key(const char *key_string,
+ u8 key[BLK_CRYPTO_MAX_ANY_KEY_SIZE],
+ const unsigned int key_size)
+{
+ int ret = 0;
+
+ /* ':' means the key is in kernel keyring, short-circuit normal key processing */
+ if (key_string[0] == ':') {
+ if (key_size > BLK_CRYPTO_MAX_ANY_KEY_SIZE) {
+ DMERR("Invalid keysize");
+ return -EINVAL;
+ }
+ /* key string should be :<logon|user>:<key_desc> */
+ ret = inlinecrypt_get_keyring_key(key_string + 1, key, key_size);
+ goto out;
+ }
+
+ if (key_size > 2 * BLK_CRYPTO_MAX_ANY_KEY_SIZE
+ || key_size % 2
+ || !key_size) {
+ DMERR("Invalid keysize");
+ return -EINVAL;
+ }
+ if (hex2bin(key, key_string, key_size) != 0)
+ ret = -EINVAL;
+
+out:
+ return ret;
+}
+
+static int get_key_size(char **key_string)
+{
+ char *colon, dummy;
+ int ret;
+
+ if (*key_string[0] != ':')
+ return strlen(*key_string) >> 1;
+
+ /* look for next ':' in key string */
+ colon = strpbrk(*key_string + 1, ":");
+ if (!colon)
+ return -EINVAL;
+
+ if (sscanf(*key_string + 1, "%u%c", &ret, &dummy) != 2 || dummy != ':')
+ return -EINVAL;
+
+ /* remaining key string should be :<logon|user>:<key_desc> */
+ *key_string = colon;
+
+ return ret;
+}
+
+static int inlinecrypt_ctr_optional(struct dm_target *ti,
+ unsigned int argc, char **argv)
+{
+ struct inlinecrypt_ctx *ctx = ti->private;
+ struct dm_arg_set as;
+ static const struct dm_arg _args[] = {
+ {0, 3, "Invalid number of feature args"},
+ };
+ unsigned int opt_params;
+ const char *opt_string;
+ bool iv_large_sectors = false;
+ char dummy;
+ int err;
+
+ as.argc = argc;
+ as.argv = argv;
+
+ err = dm_read_arg_group(_args, &as, &opt_params, &ti->error);
+ if (err)
+ return err;
+
+ while (opt_params--) {
+ opt_string = dm_shift_arg(&as);
+ if (!opt_string) {
+ ti->error = "Not enough feature arguments";
+ return -EINVAL;
+ }
+ if (!strcmp(opt_string, "allow_discards")) {
+ ti->num_discard_bios = 1;
+ } else if (sscanf(opt_string, "sector_size:%u%c",
+ &ctx->sector_size, &dummy) == 1) {
+ if (ctx->sector_size < SECTOR_SIZE ||
+ ctx->sector_size > 4096 ||
+ !is_power_of_2(ctx->sector_size)) {
+ ti->error = "Invalid sector_size";
+ return -EINVAL;
+ }
+ } else if (!strcmp(opt_string, "iv_large_sectors")) {
+ iv_large_sectors = true;
+ } else {
+ ti->error = "Invalid feature arguments";
+ return -EINVAL;
+ }
+ }
+
+ /* dm-inlinecrypt doesn't implement iv_large_sectors=false. */
+ if (ctx->sector_size != SECTOR_SIZE && !iv_large_sectors) {
+ ti->error = "iv_large_sectors must be specified";
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Construct an inlinecrypt mapping:
+ * <cipher> [<key>|:<key_size>:<logon>:<key_description>] <iv_offset> <dev_path> <start>
+ *
+ * This syntax matches dm-crypt's, but the set of supported functionality has
+ * been stripped down.
+ */
+static int inlinecrypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)
+{
+ struct inlinecrypt_ctx *ctx;
+ const struct dm_inlinecrypt_cipher *cipher;
+ u8 raw_key[BLK_CRYPTO_MAX_ANY_KEY_SIZE];
+ unsigned int dun_bytes;
+ unsigned long long tmpll;
+ char dummy;
+ int err;
+
+ if (argc < 5) {
+ ti->error = "Not enough arguments";
+ return -EINVAL;
+ }
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx) {
+ ti->error = "Out of memory";
+ return -ENOMEM;
+ }
+ ti->private = ctx;
+
+ /* <cipher> */
+ ctx->cipher_string = kstrdup(argv[0], GFP_KERNEL);
+ if (!ctx->cipher_string) {
+ ti->error = "Out of memory";
+ err = -ENOMEM;
+ goto bad;
+ }
+ cipher = lookup_cipher(ctx->cipher_string);
+ if (!cipher) {
+ ti->error = "Unsupported cipher";
+ err = -EINVAL;
+ goto bad;
+ }
+
+ /* <key> */
+ ctx->key_size = get_key_size(&argv[1]);
+ if (ctx->key_size < 0) {
+ ti->error = "Cannot parse key size";
+ return -EINVAL;
+ }
+ err = inlinecrypt_get_key(argv[1], raw_key, ctx->key_size);
+ if (err) {
+ ti->error = "Malformed key string";
+ goto bad;
+ }
+
+ /* <iv_offset> */
+ if (sscanf(argv[2], "%llu%c", &ctx->iv_offset, &dummy) != 1) {
+ ti->error = "Invalid iv_offset sector";
+ err = -EINVAL;
+ goto bad;
+ }
+
+ /* <dev_path> */
+ err = dm_get_device(ti, argv[3], dm_table_get_mode(ti->table),
+ &ctx->dev);
+ if (err) {
+ ti->error = "Device lookup failed";
+ goto bad;
+ }
+
+ /* <start> */
+ if (sscanf(argv[4], "%llu%c", &tmpll, &dummy) != 1 ||
+ tmpll != (sector_t)tmpll) {
+ ti->error = "Invalid start sector";
+ err = -EINVAL;
+ goto bad;
+ }
+ ctx->start = tmpll;
+
+ /* optional arguments */
+ ctx->sector_size = SECTOR_SIZE;
+ if (argc > 5) {
+ err = inlinecrypt_ctr_optional(ti, argc - 5, &argv[5]);
+ if (err)
+ goto bad;
+ }
+ ctx->sector_bits = ilog2(ctx->sector_size);
+ if (ti->len & ((ctx->sector_size >> SECTOR_SHIFT) - 1)) {
+ ti->error = "Device size is not a multiple of sector_size";
+ err = -EINVAL;
+ goto bad;
+ }
+
+ ctx->max_dun = (ctx->iv_offset + ti->len - 1) >>
+ (ctx->sector_bits - SECTOR_SHIFT);
+ dun_bytes = DIV_ROUND_UP(fls64(ctx->max_dun), 8);
+
+ err = blk_crypto_init_key(&ctx->key, raw_key, ctx->key_size,
+ BLK_CRYPTO_KEY_TYPE_RAW,
+ cipher->mode_num, dun_bytes,
+ ctx->sector_size);
+ if (err) {
+ ti->error = "Error initializing blk-crypto key";
+ goto bad;
+ }
+
+ err = blk_crypto_start_using_key(ctx->dev->bdev, &ctx->key);
+ if (err) {
+ ti->error = "Error starting to use blk-crypto";
+ goto bad;
+ }
+
+ ti->num_flush_bios = 1;
+
+ err = 0;
+ goto out;
+
+bad:
+ inlinecrypt_dtr(ti);
+out:
+ memzero_explicit(raw_key, sizeof(raw_key));
+ return err;
+}
+
+static int inlinecrypt_map(struct dm_target *ti, struct bio *bio)
+{
+ const struct inlinecrypt_ctx *ctx = ti->private;
+ sector_t sector_in_target;
+ u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE] = {};
+
+ bio_set_dev(bio, ctx->dev->bdev);
+
+ /*
+ * If the bio is a device-level request which doesn't target a specific
+ * sector, there's nothing more to do.
+ */
+ if (bio_sectors(bio) == 0)
+ return DM_MAPIO_REMAPPED;
+
+ /*
+ * The bio should never have an encryption context already, since
+ * dm-inlinecrypt doesn't pass through any inline encryption
+ * capabilities to the layer above it.
+ */
+ if (WARN_ON_ONCE(bio_has_crypt_ctx(bio)))
+ return DM_MAPIO_KILL;
+
+ /* Map the bio's sector to the underlying device. (512-byte sectors) */
+ sector_in_target = dm_target_offset(ti, bio->bi_iter.bi_sector);
+ bio->bi_iter.bi_sector = ctx->start + sector_in_target;
+ /*
+ * If the bio doesn't have any data (e.g. if it's a DISCARD request),
+ * there's nothing more to do.
+ */
+ if (!bio_has_data(bio))
+ return DM_MAPIO_REMAPPED;
+
+ /* Calculate the DUN and enforce data-unit (crypto sector) alignment. */
+ dun[0] = ctx->iv_offset + sector_in_target; /* 512-byte sectors */
+ if (dun[0] & ((ctx->sector_size >> SECTOR_SHIFT) - 1))
+ return DM_MAPIO_KILL;
+ dun[0] >>= ctx->sector_bits - SECTOR_SHIFT; /* crypto sectors */
+
+ /*
+ * This check isn't necessary as we should have calculated max_dun
+ * correctly, but be safe.
+ */
+ if (WARN_ON_ONCE(dun[0] > ctx->max_dun))
+ return DM_MAPIO_KILL;
+
+ bio_crypt_set_ctx(bio, &ctx->key, dun, GFP_NOIO);
+
+ /*
+ * Since we've added an encryption context to the bio and
+ * blk-crypto-fallback may be needed to process it, it's necessary to
+ * use the fallback-aware bio submission code rather than
+ * unconditionally returning DM_MAPIO_REMAPPED.
+ *
+ * To get the correct accounting for a dm target in the case where
+ * __blk_crypto_submit_bio() doesn't take ownership of the bio (returns
+ * true), call __blk_crypto_submit_bio() directly and return
+ * DM_MAPIO_REMAPPED in that case, rather than relying on
+ * blk_crypto_submit_bio() which calls submit_bio() in that case.
+ */
+ if (__blk_crypto_submit_bio(bio))
+ return DM_MAPIO_REMAPPED;
+ return DM_MAPIO_SUBMITTED;
+}
+
+static void inlinecrypt_status(struct dm_target *ti, status_type_t type,
+ unsigned int status_flags, char *result,
+ unsigned int maxlen)
+{
+ const struct inlinecrypt_ctx *ctx = ti->private;
+ unsigned int sz = 0;
+ int num_feature_args = 0;
+
+ switch (type) {
+ case STATUSTYPE_INFO:
+ case STATUSTYPE_IMA:
+ result[0] = '\0';
+ break;
+
+ case STATUSTYPE_TABLE:
+ /*
+ * Warning: like dm-crypt, dm-inlinecrypt includes the key in
+ * the returned table. Userspace is responsible for redacting
+ * the key when needed.
+ */
+ DMEMIT("%s %*phN %llu %s %llu", ctx->cipher_string,
+ ctx->key.size, ctx->key.bytes, ctx->iv_offset,
+ ctx->dev->name, ctx->start);
+ num_feature_args += !!ti->num_discard_bios;
+ if (ctx->sector_size != SECTOR_SIZE)
+ num_feature_args += 2;
+ if (num_feature_args != 0) {
+ DMEMIT(" %d", num_feature_args);
+ if (ti->num_discard_bios)
+ DMEMIT(" allow_discards");
+ if (ctx->sector_size != SECTOR_SIZE) {
+ DMEMIT(" sector_size:%u", ctx->sector_size);
+ DMEMIT(" iv_large_sectors");
+ }
+ }
+ break;
+ }
+}
+
+static int inlinecrypt_prepare_ioctl(struct dm_target *ti,
+ struct block_device **bdev, unsigned int cmd,
+ unsigned long arg, bool *forward)
+{
+ const struct inlinecrypt_ctx *ctx = ti->private;
+ const struct dm_dev *dev = ctx->dev;
+
+ *bdev = dev->bdev;
+
+ /* Only pass ioctls through if the device sizes match exactly. */
+ return ctx->start != 0 || ti->len != bdev_nr_sectors(dev->bdev);
+}
+
+static int inlinecrypt_iterate_devices(struct dm_target *ti,
+ iterate_devices_callout_fn fn,
+ void *data)
+{
+ const struct inlinecrypt_ctx *ctx = ti->private;
+
+ return fn(ti, ctx->dev, ctx->start, ti->len, data);
+}
+
+#ifdef CONFIG_BLK_DEV_ZONED
+static int inlinecrypt_report_zones(struct dm_target *ti,
+ struct dm_report_zones_args *args,
+ unsigned int nr_zones)
+{
+ const struct inlinecrypt_ctx *ctx = ti->private;
+
+ return dm_report_zones(ctx->dev->bdev, ctx->start,
+ ctx->start + dm_target_offset(ti, args->next_sector),
+ args, nr_zones);
+}
+#else
+#define inlinecrypt_report_zones NULL
+#endif
+
+static void inlinecrypt_io_hints(struct dm_target *ti,
+ struct queue_limits *limits)
+{
+ const struct inlinecrypt_ctx *ctx = ti->private;
+ const unsigned int sector_size = ctx->sector_size;
+
+ limits->logical_block_size =
+ max_t(unsigned int, limits->logical_block_size, sector_size);
+ limits->physical_block_size =
+ max_t(unsigned int, limits->physical_block_size, sector_size);
+ limits->io_min = max_t(unsigned int, limits->io_min, sector_size);
+ limits->dma_alignment = limits->logical_block_size - 1;
+}
+
+static struct target_type inlinecrypt_target = {
+ .name = "inlinecrypt",
+ .version = {1, 0, 0},
+ /*
+ * Do not set DM_TARGET_PASSES_CRYPTO, since dm-inlinecrypt consumes the
+ * crypto capability itself.
+ */
+ .features = DM_TARGET_ZONED_HM,
+ .module = THIS_MODULE,
+ .ctr = inlinecrypt_ctr,
+ .dtr = inlinecrypt_dtr,
+ .map = inlinecrypt_map,
+ .status = inlinecrypt_status,
+ .prepare_ioctl = inlinecrypt_prepare_ioctl,
+ .iterate_devices = inlinecrypt_iterate_devices,
+ .report_zones = inlinecrypt_report_zones,
+ .io_hints = inlinecrypt_io_hints,
+};
+
+module_dm(inlinecrypt);
+
+MODULE_AUTHOR("Eric Biggers <ebiggers@google.com>");
+MODULE_AUTHOR("Linlin Zhang <linlin.zhang@oss.qualcomm.com>");
+MODULE_DESCRIPTION(DM_NAME " target for inline encryption");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related
* [PATCH v2 1/3] block: export blk-crypto symbols required by dm-inlinecrypt
From: Linlin Zhang @ 2026-04-10 13:40 UTC (permalink / raw)
To: linux-block, ebiggers, mpatocka, gmazyland
Cc: linux-kernel, adrianvovk, dm-devel, quic_mdalam, israelr, hch,
axboe
In-Reply-To: <20260410134031.2880675-1-linlin.zhang@oss.qualcomm.com>
From: Eric Biggers <ebiggers@google.com>
bio_crypt_set_ctx(), blk_crypto_init_key(), and
blk_crypto_start_using_key() are needed to use inline encryption; see
Documentation/block/inline-encryption.rst. Export them so that
dm-inlinecrypt can use them. The only reason these weren't exported
before was that inline encryption was previously used only by fs/crypto/
which is built-in code.
Signed-off-by: Eric Biggers <ebiggers@google.com>
---
block/blk-crypto.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/block/blk-crypto.c b/block/blk-crypto.c
index 856d3c5b1fa0..40a99a859748 100644
--- a/block/blk-crypto.c
+++ b/block/blk-crypto.c
@@ -116,6 +116,7 @@ void bio_crypt_set_ctx(struct bio *bio, const struct blk_crypto_key *key,
bio->bi_crypt_context = bc;
}
+EXPORT_SYMBOL_GPL(bio_crypt_set_ctx);
void __bio_crypt_free_ctx(struct bio *bio)
{
@@ -349,6 +350,7 @@ int blk_crypto_init_key(struct blk_crypto_key *blk_key,
return 0;
}
+EXPORT_SYMBOL_GPL(blk_crypto_init_key);
bool blk_crypto_config_supported_natively(struct block_device *bdev,
const struct blk_crypto_config *cfg)
@@ -399,6 +401,7 @@ int blk_crypto_start_using_key(struct block_device *bdev,
}
return blk_crypto_fallback_start_using_mode(key->crypto_cfg.crypto_mode);
}
+EXPORT_SYMBOL_GPL(blk_crypto_start_using_key);
/**
* blk_crypto_evict_key() - Evict a blk_crypto_key from a block_device
--
2.34.1
^ permalink raw reply related
* [PATCH v2 0/3] dm-inlinecrypt: add target for inline block device encryption
From: Linlin Zhang @ 2026-04-10 13:40 UTC (permalink / raw)
To: linux-block, ebiggers, mpatocka, gmazyland
Cc: linux-kernel, adrianvovk, dm-devel, quic_mdalam, israelr, hch,
axboe
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=UTF-8, Size: 1582 bytes --]
This patch series is based on Erics work posted at:
https://lore.kernel.org/all/ecbb7ea8-11f6-30c1-ad77-bd984c52ca33@quicinc.com/
Erics patches introduce a new dm target, dm-inlinecrypt, to support inline
block-device encryption. The implementation builds on the work previously done
in Androids dm-default-key, but intentionally drops passthrough support,
as that functionality does not appear likely to be accepted upstream in the
near future. With this limitation, dm-inlinecrypt is positioned as a
practical replacement for dm-crypt, rather than a general passthrough
mechanism.
On top of Erics series, keyring key support is added in dm-inlinecrypt. Thus,
both keyring key and hex key are feasible for dm-inlinecrypt. In addition,
dm-inlinecrypt.rst is added as the user-guide of dm-inlinecrypt.
V1:
https://lore.kernel.org/all/20260304121729.1532469-1-linlin.zhang@oss.qualcomm.com/
Eric Biggers (2):
block: export blk-crypto symbols required by dm-inlinecrypt
dm-inlinecrypt: add target for inline block device encryption
Linlin Zhang (1):
dm: add documentation for dm-inlinecrypt target
.../device-mapper/dm-inlinecrypt.rst | 122 ++++
block/blk-crypto.c | 3 +
drivers/md/Kconfig | 10 +
drivers/md/Makefile | 1 +
drivers/md/dm-inlinecrypt.c | 559 ++++++++++++++++++
5 files changed, 695 insertions(+)
create mode 100644 Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst
create mode 100644 drivers/md/dm-inlinecrypt.c
--
2.34.1
^ permalink raw reply
* Re: [PATCH 4/8] FOLD: block: change the defer in task context interface to be procedural
From: Matthew Wilcox @ 2026-04-10 13:26 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Tal Zussman, Jens Axboe, Christian Brauner, Darrick J. Wong,
Carlos Maiolino, Al Viro, Jan Kara, Dave Chinner, Bart Van Assche,
Gao Xiang, linux-block, linux-kernel, linux-xfs, linux-fsdevel,
linux-mm
In-Reply-To: <20260410061725.GA24667@lst.de>
On Fri, Apr 10, 2026 at 08:17:25AM +0200, Christoph Hellwig wrote:
> On Thu, Apr 09, 2026 at 09:18:50PM +0100, Matthew Wilcox wrote:
> > On Thu, Apr 09, 2026 at 06:02:17PM +0200, Christoph Hellwig wrote:
> > > @@ -1836,9 +1837,7 @@ void bio_endio(struct bio *bio)
> > > }
> > > #endif
> > >
> > > - if (!in_task() && bio_flagged(bio, BIO_COMPLETE_IN_TASK))
> > > - bio_queue_completion(bio);
> > > - else if (bio->bi_end_io)
> > > + if (bio->bi_end_io)
> > > bio->bi_end_io(bio);
> >
> > What I liked about this before is that we had one central place that
> > needed to be changed. This change means that every bi_end_io now needs
> > to check whether the BIO can be completed in its context.
>
> Yes. On the other hand we can actually use it when we don't know if
> we need to offload beforehand, which enabls the two later conversions
> and probably more.
I don't understand why we need to remove _this_ way to defer completions
to take context in order to _add_ the ability to defer completions
inside the bi_end_io handler.
^ permalink raw reply
* Re: [PATCH] ublk: fix tautological comparison warning in ublk_ctrl_reg_buf
From: Jens Axboe @ 2026-04-10 13:04 UTC (permalink / raw)
To: linux-block, Ming Lei; +Cc: Caleb Sander Mateos, kernel test robot
In-Reply-To: <20260410124136.3983429-1-tom.leiming@gmail.com>
On Fri, 10 Apr 2026 20:41:36 +0800, Ming Lei wrote:
> On 32-bit architectures, 'unsigned long size' can never exceed
> UBLK_SHMEM_BUF_SIZE_MAX (1ULL << 32), causing a tautological
> comparison warning. Validate buf_reg.len (__u64) directly before
> using it, and consolidate all input validation into a single check.
>
> Also remove the unnecessary local variables 'addr' and 'size' since
> buf_reg.addr and buf_reg.len can be used directly.
>
> [...]
Applied, thanks!
[1/1] ublk: fix tautological comparison warning in ublk_ctrl_reg_buf
commit: 36446de0c30c62b9d89502fd36c4904996d86ecd
Best regards,
--
Jens Axboe
^ permalink raw reply
* [PATCH] ublk: fix tautological comparison warning in ublk_ctrl_reg_buf
From: Ming Lei @ 2026-04-10 12:41 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Caleb Sander Mateos, Ming Lei, kernel test robot
On 32-bit architectures, 'unsigned long size' can never exceed
UBLK_SHMEM_BUF_SIZE_MAX (1ULL << 32), causing a tautological
comparison warning. Validate buf_reg.len (__u64) directly before
using it, and consolidate all input validation into a single check.
Also remove the unnecessary local variables 'addr' and 'size' since
buf_reg.addr and buf_reg.len can be used directly.
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202604101952.3NOzqnu9-lkp@intel.com/
Fixes: 23b3b6f0b584 ("ublk: widen ublk_shmem_buf_reg.len to __u64 for 4GB buffer support")
Signed-off-by: Ming Lei <tom.leiming@gmail.com>
---
drivers/block/ublk_drv.c | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 247c1ce8ce8a..49fb584e392b 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -5330,7 +5330,7 @@ static int ublk_ctrl_reg_buf(struct ublk_device *ub,
{
void __user *argp = (void __user *)(unsigned long)header->addr;
struct ublk_shmem_buf_reg buf_reg;
- unsigned long addr, size, nr_pages;
+ unsigned long nr_pages;
struct page **pages = NULL;
unsigned int gup_flags;
unsigned int memflags;
@@ -5352,14 +5352,12 @@ static int ublk_ctrl_reg_buf(struct ublk_device *ub,
if (buf_reg.reserved)
return -EINVAL;
- addr = buf_reg.addr;
- size = buf_reg.len;
- nr_pages = size >> PAGE_SHIFT;
-
- if (!size || size > UBLK_SHMEM_BUF_SIZE_MAX ||
- !PAGE_ALIGNED(size) || !PAGE_ALIGNED(addr))
+ if (!buf_reg.len || buf_reg.len > UBLK_SHMEM_BUF_SIZE_MAX ||
+ !PAGE_ALIGNED(buf_reg.len) || !PAGE_ALIGNED(buf_reg.addr))
return -EINVAL;
+ nr_pages = buf_reg.len >> PAGE_SHIFT;
+
/* Pin pages before any locks (may sleep) */
pages = kvmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL);
if (!pages)
@@ -5369,7 +5367,7 @@ static int ublk_ctrl_reg_buf(struct ublk_device *ub,
if (!(buf_reg.flags & UBLK_SHMEM_BUF_READ_ONLY))
gup_flags |= FOLL_WRITE;
- pinned = pin_user_pages_fast(addr, nr_pages, gup_flags, pages);
+ pinned = pin_user_pages_fast(buf_reg.addr, nr_pages, gup_flags, pages);
if (pinned < 0) {
ret = pinned;
goto err_free_pages;
--
2.53.0
^ permalink raw reply related
* Re: [RFC PATCH 0/2] rust block-mq TagSet flags plumbing and rnull blocking wiring
From: Andreas Hindborg @ 2026-04-10 12:39 UTC (permalink / raw)
To: Wenzhao Liao, axboe, ojeda, linux-block, rust-for-linux
Cc: bjorn3_gh, aliceryhl, lossin, boqun, dakr, gary, sunke, tmgross,
linux-kernel
In-Reply-To: <20260410090829.1409430-1-wenzhaoliao@ruc.edu.cn>
Hi Wenzhao,
"Wenzhao Liao" <wenzhaoliao@ruc.edu.cn> writes:
> This RFC series fills a practical gap in the Rust block-mq abstraction by
> exposing blk_mq_tag_set.flags safely, then wires one in-tree consumer
> (`rnull`) via configfs as a reference.
Patches for this functionality is already submitted to list [1]. Please
take a look if those patches solve your requirement. If you require this
functionality in the kernel, please help by reviewing the patches in
that series.
Best regards,
Andreas Hindborg
[1] https://lore.kernel.org/rust-for-linux/20260216-rnull-v6-19-rc5-send-v1-12-de9a7af4b469@kernel.org/
^ permalink raw reply
* Re: [PATCH 07/13] libmultipath: Add delayed removal support
From: John Garry @ 2026-04-10 11:49 UTC (permalink / raw)
To: Nilay Shroff, Hannes Reinecke, hch, kbusch, sagi, axboe,
martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-block, linux-kernel
In-Reply-To: <8502c8d6-2958-4c46-bdba-95b91c2ceb10@linux.ibm.com>
On 10/04/2026 11:51, Nilay Shroff wrote:
>> About the module refcounting, as I mentioned earlier it's hard to test
>> this effectively. We could use lsmod to check refcount on nvme ko
>> during the delayed removal window and ensure that it was incremented.
>> I'm not sure if it is robust and whether the complexity is worth it.
>>
> Regarding module refcnt, I think that's easily available
> if we read /sys/module/<mod-name>/refcnt. We may not
> need to parse lsmod output.
Sure, so we would be testing this behaviour:
# echo 40 >
/sys/class/nvme-subsystem/nvme-subsys1/nvme1n1/delayed_removal_secs
# cat /sys/module/nvme_core/refcnt
3
# nvme disconnect -n nvme-test-target
[ 562.533253] nvme nvme1: Removing ctrl: NQN "nvme-test-target"
[ 562.565462] nvme nvme2: Removing ctrl: NQN "nvme-test-target"
NQN:nvme-test-target disconnected 2 controller(s)
# cat /sys/module/nvme_core/refcnt
4
# sleep 40
# cat /sys/module/nvme_core/refcnt
3
#
File /sys/module/nvme_core/refcnt would not exist for when it's builtin,
but I don't think that blktests even support builtin modules.
Cheers
^ permalink raw reply
* Re: [PATCH 07/13] libmultipath: Add delayed removal support
From: Nilay Shroff @ 2026-04-10 10:51 UTC (permalink / raw)
To: John Garry, Hannes Reinecke, hch, kbusch, sagi, axboe,
martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-block, linux-kernel
In-Reply-To: <8d9c2ee9-0c2b-4c64-badc-b5b0fc1eaf67@oracle.com>
On 4/10/26 3:19 PM, John Garry wrote:
> On 10/04/2026 10:09, Nilay Shroff wrote:
>>>> It seems there may be a race here if we attempt to write to $ns before
>>>> the reconnect has completed in _delayed_nvme_reconnect_ctrl.
>>>>
>>>> If the intention is simply to verify that the controller reconnect occurs
>>>> within the delayed removal window and test pwrite,
>>>
>>> Not exactly. I want to verify that if I write between the disconnect and the reconnect, then we write succeeds.
>>
>> Okay, got it — I think I misunderstood the intention earlier.
>>
>> So the goal here is to verify that if a write is issued during the
>> delayed removal window is in progress (i.e., when there is temporarily
>> no active path), the write should be queued. Once the reconnect succeeds,
>> the queued write should then be unblocked and sent to the target.
>
> Yeah, that's it. Otherwise, the write will be queued but then eventually fail (for no reconnect).
>
>>
>> If this understanding is correct, then this looks like a good test
>> to me.
> thanks
>
> About the module refcounting, as I mentioned earlier it's hard to test this effectively. We could use lsmod to check refcount on nvme ko during the delayed removal window and ensure that it was incremented. I'm not sure if it is robust and whether the complexity is worth it.
>
Regarding module refcnt, I think that's easily available
if we read /sys/module/<mod-name>/refcnt. We may not
need to parse lsmod output.
Thanks,
--Nilay
^ permalink raw reply
* Re: [PATCH 07/13] libmultipath: Add delayed removal support
From: John Garry @ 2026-04-10 9:49 UTC (permalink / raw)
To: Nilay Shroff, Hannes Reinecke, hch, kbusch, sagi, axboe,
martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-block, linux-kernel
In-Reply-To: <a1d72045-7b0e-4354-8365-f21f03765659@linux.ibm.com>
On 10/04/2026 10:09, Nilay Shroff wrote:
>>> It seems there may be a race here if we attempt to write to $ns before
>>> the reconnect has completed in _delayed_nvme_reconnect_ctrl.
>>>
>>> If the intention is simply to verify that the controller reconnect
>>> occurs
>>> within the delayed removal window and test pwrite,
>>
>> Not exactly. I want to verify that if I write between the disconnect
>> and the reconnect, then we write succeeds.
>
> Okay, got it — I think I misunderstood the intention earlier.
>
> So the goal here is to verify that if a write is issued during the
> delayed removal window is in progress (i.e., when there is temporarily
> no active path), the write should be queued. Once the reconnect succeeds,
> the queued write should then be unblocked and sent to the target.
Yeah, that's it. Otherwise, the write will be queued but then eventually
fail (for no reconnect).
>
> If this understanding is correct, then this looks like a good test
> to me.
thanks
About the module refcounting, as I mentioned earlier it's hard to test
this effectively. We could use lsmod to check refcount on nvme ko during
the delayed removal window and ensure that it was incremented. I'm not
sure if it is robust and whether the complexity is worth it.
Cheers,
John
^ permalink raw reply
* Re: [PATCH 07/13] libmultipath: Add delayed removal support
From: Nilay Shroff @ 2026-04-10 9:09 UTC (permalink / raw)
To: John Garry, Hannes Reinecke, hch, kbusch, sagi, axboe,
martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-block, linux-kernel
In-Reply-To: <b77d5eab-d50f-4102-8bfb-f907cf39ca56@oracle.com>
On 4/10/26 2:25 PM, John Garry wrote:
> On 10/04/2026 08:06, Nilay Shroff wrote:
>>> # Part b: Ensure writes work for intermittent disconnect
>>> _nvme_connect_subsys
>>>
>>> nvmedev=$(_find_nvme_dev "${def_subsysnqn}")
>>> ns=$(_find_nvme_ns "${def_subsys_uuid}")
>>> echo 10 > "/sys/block/"$ns"/delayed_removal_secs"
>>> bytes_written=$(run_xfs_io_pwritev2 /dev/"$ns" 4096)
>>> if [ "$bytes_written" != 4096 ]; then
>>> echo "could not write successfully initially"
>>> fi
>>> sleep 1
>>> _nvme_disconnect_ctrl "${nvmedev}"
>>> sleep 1
>>> ns=$(_find_nvme_ns "${def_subsys_uuid}")
>>> if [[ "${ns}" = "" ]]; then
>>> echo "could not find ns after disconnect"
>>> fi
>>> _delayed_nvme_reconnect_ctrl &
>>> sleep 1
>>> bytes_written=$(run_xfs_io_pwritev2 /dev/"$ns" 4096)
>>> if [ "$bytes_written" != 4096 ]; then
>>> echo "could not write successfully with reconnect"
>>> fi
>>
>> It seems there may be a race here if we attempt to write to $ns before
>> the reconnect has completed in _delayed_nvme_reconnect_ctrl.
>>
>> If the intention is simply to verify that the controller reconnect occurs
>> within the delayed removal window and test pwrite,
>
> Not exactly. I want to verify that if I write between the disconnect and the reconnect, then we write succeeds.
Okay, got it — I think I misunderstood the intention earlier.
So the goal here is to verify that if a write is issued during the
delayed removal window is in progress (i.e., when there is temporarily
no active path), the write should be queued. Once the reconnect succeeds,
the queued write should then be unblocked and sent to the target.
If this understanding is correct, then this looks like a good test
to me.
Thanks,
--Nilay
^ permalink raw reply
* [RFC PATCH 1/2] rust: block: mq: safely expose TagSet flags
From: Wenzhao Liao @ 2026-04-10 9:08 UTC (permalink / raw)
To: axboe, a.hindborg, ojeda, linux-block, rust-for-linux
Cc: bjorn3_gh, aliceryhl, lossin, boqun, dakr, gary, sunke, tmgross,
linux-kernel
In-Reply-To: <20260410090829.1409430-1-wenzhaoliao@ruc.edu.cn>
TagSet::new() currently hardcodes blk_mq_tag_set.flags to 0.
That prevents Rust block drivers from declaring blk-mq queue flags.
Introduce TagSetFlags as a typed wrapper for BLK_MQ_F_* bits.
Add TagSet::new_with_flags() so drivers can pass flags explicitly.
Keep TagSet::new() as a compatibility wrapper using empty flags.
Re-export TagSetFlags from kernel::block::mq for driver imports.
Build-tested with LLVM=-15 in an out-of-tree rust-next build.
Validation includes vmlinux and drivers/block/rnull/rnull_mod.ko.
Signed-off-by: Wenzhao Liao <wenzhaoliao@ruc.edu.cn>
---
rust/kernel/block/mq.rs | 2 +-
rust/kernel/block/mq/tag_set.rs | 86 ++++++++++++++++++++++++++++++++-
2 files changed, 86 insertions(+), 2 deletions(-)
diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index 1fd0d54dd549..799afdf36539 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -100,4 +100,4 @@
pub use operations::Operations;
pub use request::Request;
-pub use tag_set::TagSet;
+pub use tag_set::{TagSet, TagSetFlags};
diff --git a/rust/kernel/block/mq/tag_set.rs b/rust/kernel/block/mq/tag_set.rs
index dae9df408a86..72d9bce5b11f 100644
--- a/rust/kernel/block/mq/tag_set.rs
+++ b/rust/kernel/block/mq/tag_set.rs
@@ -16,6 +16,80 @@
use core::{convert::TryInto, marker::PhantomData};
use pin_init::{pin_data, pinned_drop, PinInit};
+/// Flags that control blk-mq tag set behavior.
+///
+/// They can be combined with the operators `|`, `&`, and `!`.
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct TagSetFlags(u32);
+
+impl TagSetFlags {
+ /// Returns an empty instance where no flags are set.
+ pub const fn empty() -> Self {
+ Self(0)
+ }
+
+ /// Register as a blocking blk-mq driver device.
+ pub const BLOCKING: Self = Self::new(bindings::BLK_MQ_F_BLOCKING as u32);
+
+ /// Use an underlying blk-mq device for completing I/O.
+ pub const STACKING: Self = Self::new(bindings::BLK_MQ_F_STACKING as u32);
+
+ /// Share hardware contexts between tags.
+ pub const TAG_HCTX_SHARED: Self = Self::new(bindings::BLK_MQ_F_TAG_HCTX_SHARED as u32);
+
+ /// Allocate tags on a round-robin basis.
+ pub const TAG_RR: Self = Self::new(bindings::BLK_MQ_F_TAG_RR as u32);
+
+ /// Disable the I/O scheduler by default.
+ pub const NO_SCHED_BY_DEFAULT: Self =
+ Self::new(bindings::BLK_MQ_F_NO_SCHED_BY_DEFAULT as u32);
+
+ /// Check whether `flags` is contained in `self`.
+ pub fn contains(self, flags: Self) -> bool {
+ (self & flags) == flags
+ }
+
+ pub(crate) const fn as_raw(self) -> u32 {
+ self.0
+ }
+
+ const fn all_bits() -> u32 {
+ Self::BLOCKING.0
+ | Self::STACKING.0
+ | Self::TAG_HCTX_SHARED.0
+ | Self::TAG_RR.0
+ | Self::NO_SCHED_BY_DEFAULT.0
+ }
+
+ const fn new(value: u32) -> Self {
+ Self(value)
+ }
+}
+
+impl core::ops::BitOr for TagSetFlags {
+ type Output = Self;
+
+ fn bitor(self, rhs: Self) -> Self::Output {
+ Self(self.0 | rhs.0)
+ }
+}
+
+impl core::ops::BitAnd for TagSetFlags {
+ type Output = Self;
+
+ fn bitand(self, rhs: Self) -> Self::Output {
+ Self(self.0 & rhs.0)
+ }
+}
+
+impl core::ops::Not for TagSetFlags {
+ type Output = Self;
+
+ fn not(self) -> Self::Output {
+ Self(!self.0 & Self::all_bits())
+ }
+}
+
/// A wrapper for the C `struct blk_mq_tag_set`.
///
/// `struct blk_mq_tag_set` contains a `struct list_head` and so must be pinned.
@@ -37,6 +111,16 @@ pub fn new(
nr_hw_queues: u32,
num_tags: u32,
num_maps: u32,
+ ) -> impl PinInit<Self, error::Error> {
+ Self::new_with_flags(nr_hw_queues, num_tags, num_maps, TagSetFlags::empty())
+ }
+
+ /// Try to create a new tag set with the given blk-mq flags.
+ pub fn new_with_flags(
+ nr_hw_queues: u32,
+ num_tags: u32,
+ num_maps: u32,
+ flags: TagSetFlags,
) -> impl PinInit<Self, error::Error> {
let tag_set: bindings::blk_mq_tag_set = pin_init::zeroed();
let tag_set: Result<_> = core::mem::size_of::<RequestDataWrapper>()
@@ -49,7 +133,7 @@ pub fn new(
numa_node: bindings::NUMA_NO_NODE,
queue_depth: num_tags,
cmd_size,
- flags: 0,
+ flags: flags.as_raw(),
driver_data: core::ptr::null_mut::<crate::ffi::c_void>(),
nr_maps: num_maps,
..tag_set
--
2.34.1
^ permalink raw reply related
* [RFC PATCH 2/2] block: rnull: support BLK_MQ_F_BLOCKING via configfs
From: Wenzhao Liao @ 2026-04-10 9:08 UTC (permalink / raw)
To: axboe, a.hindborg, ojeda, linux-block, rust-for-linux
Cc: bjorn3_gh, aliceryhl, lossin, boqun, dakr, gary, sunke, tmgross,
linux-kernel
In-Reply-To: <20260410090829.1409430-1-wenzhaoliao@ruc.edu.cn>
Add a new configfs boolean attribute named blocking.
Advertise blocking in the rnull features list.
On power-on, map blocking=1 to TagSetFlags::BLOCKING.
Create the tag set with TagSet::new_with_flags().
Keep default blocking=0 to preserve existing behavior.
Like other parameters, blocking writes return -EBUSY while powered on.
Validated with LLVM=-15 out-of-tree builds and QEMU runtime tests.
Signed-off-by: Wenzhao Liao <wenzhaoliao@ruc.edu.cn>
---
drivers/block/rnull/configfs.rs | 32 +++++++++++++++++++++++++++++++-
drivers/block/rnull/rnull.rs | 11 +++++++++--
2 files changed, 40 insertions(+), 3 deletions(-)
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index 7c2eb5c0b722..a9d46a511340 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -35,7 +35,7 @@ impl AttributeOperations<0> for Config {
fn show(_this: &Config, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
let mut writer = kernel::str::Formatter::new(page);
- writer.write_str("blocksize,size,rotational,irqmode\n")?;
+ writer.write_str("blocksize,size,rotational,irqmode,blocking\n")?;
Ok(writer.bytes_written())
}
}
@@ -58,6 +58,7 @@ fn make_group(
rotational: 2,
size: 3,
irqmode: 4,
+ blocking: 5,
],
};
@@ -73,6 +74,7 @@ fn make_group(
disk: None,
capacity_mib: 4096,
irq_mode: IRQMode::None,
+ blocking: false,
name: name.try_into()?,
}),
}),
@@ -122,6 +124,7 @@ struct DeviceConfigInner {
rotational: bool,
capacity_mib: u64,
irq_mode: IRQMode,
+ blocking: bool,
disk: Option<GenDisk<NullBlkDevice>>,
}
@@ -152,6 +155,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
guard.rotational,
guard.capacity_mib,
guard.irq_mode,
+ guard.blocking,
)?);
guard.powered = true;
} else if guard.powered && !power_op {
@@ -259,3 +263,29 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
Ok(())
}
}
+
+#[vtable]
+impl configfs::AttributeOperations<5> for DeviceConfig {
+ type Data = DeviceConfig;
+
+ fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
+ let mut writer = kernel::str::Formatter::new(page);
+
+ if this.data.lock().blocking {
+ writer.write_str("1\n")?;
+ } else {
+ writer.write_str("0\n")?;
+ }
+
+ Ok(writer.bytes_written())
+ }
+
+ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
+ if this.data.lock().powered {
+ return Err(EBUSY);
+ }
+
+ this.data.lock().blocking = kstrtobool_bytes(page)?;
+ Ok(())
+ }
+}
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 0ca8715febe8..d7ebd504d8df 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -11,7 +11,7 @@
mq::{
self,
gen_disk::{self, GenDisk},
- Operations, TagSet,
+ Operations, TagSet, TagSetFlags,
},
},
prelude::*,
@@ -51,8 +51,15 @@ fn new(
rotational: bool,
capacity_mib: u64,
irq_mode: IRQMode,
+ blocking: bool,
) -> Result<GenDisk<Self>> {
- let tagset = Arc::pin_init(TagSet::new(1, 256, 1), GFP_KERNEL)?;
+ let flags = if blocking {
+ TagSetFlags::BLOCKING
+ } else {
+ TagSetFlags::empty()
+ };
+
+ let tagset = Arc::pin_init(TagSet::new_with_flags(1, 256, 1, flags), GFP_KERNEL)?;
let queue_data = Box::new(QueueData { irq_mode }, GFP_KERNEL)?;
--
2.34.1
^ 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