[PATCH 77/79] block: rnull: add `virt_boundary` option

Andreas Hindborg
2026-02-15T23:43:22Z

Add a configfs attribute to configure the virtual memory boundary mask
for the rnull block device. This allows testing how drivers and
filesystems handle devices with specific alignment requirements.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/configfs.rs | 5 +++++
drivers/block/rnull/rnull.rs | 17 ++++++++++++++++-
2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index 9eaace8b4257e..8b9ad8a1a243b 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -123,6 +123,7 @@ fn make_group(
poll_queues: 27,
fua: 28,
max_sectors: 29,
+ virt_boundary: 30,
],
};

@@ -210,6 +211,7 @@ fn make_group(
#[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
init_hctx_inject,
max_sectors: 0,
+ virt_boundary: false,
}),
}),
default_groups,
@@ -293,6 +295,7 @@ struct DeviceConfigInner {
#[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
init_hctx_inject: Arc<FaultConfig>,
max_sectors: u32,
+ virt_boundary: bool,
}

#[vtable]
@@ -351,6 +354,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
#[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
timeout_inject: guard.timeout_inject.clone(),
max_sectors: guard.max_sectors,
+ virt_boundary: guard.virt_boundary,
})?);
guard.powered = true;
} else if guard.powered && !power_op {
@@ -629,3 +633,4 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
}
configfs_simple_bool_field!(DeviceConfig, 28, fua);
configfs_simple_field!(DeviceConfig, 29, max_sectors, u32);
+configfs_simple_bool_field!(DeviceConfig, 30, virt_boundary);
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 495a810f4f4e1..dfdb56bd76f52 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -28,7 +28,10 @@
BadBlocks, //
},
bio::Segment,
- error::{BlkError, BlkResult},
+ error::{
+ BlkError,
+ BlkResult, //
+ },
mq::{
self,
gen_disk::{
@@ -53,6 +56,7 @@
impl_has_hr_timer,
new_mutex,
new_spinlock,
+ page::PAGE_SIZE,
pr_info,
prelude::*,
revocable::Revocable,
@@ -210,6 +214,10 @@
default: 0,
description: "Maximum size of a command (in 512B sectors)",
},
+ virt_boundary: u8 {
+ default: 0,
+ description: "Set alignment requirement for IO buffers to be page size.",
+ },
},
}

@@ -287,6 +295,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
#[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
timeout_inject: Arc::pin_init(FaultConfig::new(c"timeout_inject"), GFP_KERNEL)?,
max_sectors: *module_parameters::max_sectors.value(),
+ virt_boundary: *module_parameters::virt_boundary.value() != 0,
})?;
disks.push(disk, GFP_KERNEL)?;
}
@@ -342,6 +351,7 @@ struct NullBlkOptions<'a> {
#[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
timeout_inject: Arc<FaultConfig>,
max_sectors: u32,
+ virt_boundary: bool,
}

static SHARED_TAG_SET: SetOnce<Arc<TagSet<NullBlkDevice>>> = SetOnce::new();
@@ -414,6 +424,7 @@ fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
#[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
timeout_inject,
max_sectors,
+ virt_boundary,
} = options;

let mut flags = mq::tag_set::Flags::default();
@@ -512,6 +523,10 @@ fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
.forced_unit_access(forced_unit_access && storage.cache_enabled())
.max_sectors(max_sectors);

+ if virt_boundary {
+ builder = builder.virt_boundary_mask(PAGE_SIZE - 1);
+ }
+
#[cfg(CONFIG_BLK_DEV_ZONED)]
{
builder = builder

--
2.51.2