[PATCH 08/79] block: rnull: add timer completion mode

Andreas Hindborg
2026-02-15T23:46:00Z

Add a timer completion mode to `rnull`. This will complete requests after a
specified time has elapsed. To use this mode of operation, set `irqmode` to
`2` and write a timeout in nanoseconds to `completion_nsec`.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/configfs.rs | 13 +++++++--
drivers/block/rnull/rnull.rs | 63 ++++++++++++++++++++++++++++++++++++++---
2 files changed, 70 insertions(+), 6 deletions(-)

diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index eafa71dfc40d3..7952f41f42bfd 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -26,7 +26,8 @@
kstrtobool_bytes,
CString, //
},
- sync::Mutex, //
+ sync::Mutex,
+ time, //
};
use macros::{
configfs_simple_bool_field,
@@ -58,7 +59,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,completion_nsec\n")?;
Ok(writer.bytes_written())
}
}
@@ -81,6 +82,7 @@ fn make_group(
rotational: 2,
size: 3,
irqmode: 4,
+ completion_nsec: 5,
],
};

@@ -96,6 +98,7 @@ fn make_group(
disk: None,
capacity_mib: 4096,
irq_mode: IRQMode::None,
+ completion_time: time::Delta::ZERO,
name: name.try_into()?,
}),
}),
@@ -108,6 +111,7 @@ fn make_group(
pub(crate) enum IRQMode {
None,
Soft,
+ Timer,
}

impl TryFrom<u8> for IRQMode {
@@ -117,6 +121,7 @@ fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(Self::None),
1 => Ok(Self::Soft),
+ 2 => Ok(Self::Timer),
_ => Err(EINVAL),
}
}
@@ -127,6 +132,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => f.write_str("0")?,
Self::Soft => f.write_str("1")?,
+ Self::Timer => f.write_str("2")?,
}
Ok(())
}
@@ -146,6 +152,7 @@ struct DeviceConfigInner {
rotational: bool,
capacity_mib: u64,
irq_mode: IRQMode,
+ completion_time: time::Delta,
disk: Option<GenDisk<NullBlkDevice>>,
}

@@ -176,6 +183,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
guard.rotational,
guard.capacity_mib,
guard.irq_mode,
+ guard.completion_time,
)?);
guard.powered = true;
} else if guard.powered && !power_op {
@@ -191,6 +199,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
configfs_simple_bool_field!(DeviceConfig, 2, rotational);
configfs_simple_field!(DeviceConfig, 3, capacity_mib, u64);
configfs_simple_field!(DeviceConfig, 4, irq_mode, IRQMode);
+configfs_simple_field!(DeviceConfig, 5, completion_time, i64, into time::Delta::from_nanos);

impl core::str::FromStr for IRQMode {
type Err = Error;
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 065639fc4f941..55e56c39f1c2c 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -28,6 +28,15 @@
Arc,
Mutex, //
},
+ time::{
+ hrtimer::{
+ HrTimerCallback,
+ HrTimerCallbackContext,
+ HrTimerPointer,
+ HrTimerRestart, //
+ },
+ Delta,
+ },
types::{
OwnableRefCounted,
Owned, //
@@ -61,7 +70,11 @@
},
irqmode: u8 {
default: 0,
- description: "IRQ completion handler. 0-none, 1-softirq",
+ description: "IRQ completion handler. 0-none, 1-softirq, 2-timer",
+ },
+ completion_nsec: u64 {
+ default: 10_000,
+ description: "Time in ns to complete a request in hardware. Default: 10,000ns",
},
},
}
@@ -81,6 +94,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
let mut disks = KVec::new();

let defer_init = move || -> Result<_, Error> {
+ let completion_time: i64 = (*module_parameters::completion_nsec.value()).try_into()?;
for i in 0..(*module_parameters::nr_devices.value()) {
let name = CString::try_from_fmt(fmt!("rnullb{}", i))?;

@@ -90,6 +104,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
*module_parameters::rotational.value() != 0,
*module_parameters::gb.value() * 1024,
(*module_parameters::irqmode.value()).try_into()?,
+ Delta::from_nanos(completion_time),
)?;
disks.push(disk, GFP_KERNEL)?;
}
@@ -113,10 +128,17 @@ fn new(
rotational: bool,
capacity_mib: u64,
irq_mode: IRQMode,
+ completion_time: Delta,
) -> Result<GenDisk<Self>> {
let tagset = Arc::pin_init(TagSet::new(1, 256, 1), GFP_KERNEL)?;

- let queue_data = Box::new(QueueData { irq_mode }, GFP_KERNEL)?;
+ let queue_data = Box::new(
+ QueueData {
+ irq_mode,
+ completion_time,
+ },
+ GFP_KERNEL,
+ )?;

gen_disk::GenDiskBuilder::new()
.capacity_sectors(capacity_mib << (20 - block::SECTOR_SHIFT))
@@ -129,15 +151,43 @@ fn new(

struct QueueData {
irq_mode: IRQMode,
+ completion_time: Delta,
+}
+
+#[pin_data]
+struct Pdu {
+ #[pin]
+ timer: kernel::time::hrtimer::HrTimer<Self>,
+}
+
+impl HrTimerCallback for Pdu {
+ type Pointer<'a> = ARef<mq::Request<NullBlkDevice>>;
+
+ fn run(this: Self::Pointer<'_>, _context: HrTimerCallbackContext<'_, Self>) -> HrTimerRestart {
+ OwnableRefCounted::try_from_shared(this)
+ .map_err(|_e| kernel::error::code::EIO)
+ .expect("Failed to complete request")
+ .end_ok();
+ HrTimerRestart::NoRestart
+ }
+}
+
+kernel::impl_has_hr_timer! {
+ impl HasHrTimer<Self> for Pdu {
+ mode: kernel::time::hrtimer::RelativeMode<kernel::time::Monotonic>,
+ field: self.timer,
+ }
}

#[vtable]
impl Operations for NullBlkDevice {
type QueueData = KBox<QueueData>;
- type RequestData = ();
+ type RequestData = Pdu;

fn new_request_data() -> impl PinInit<Self::RequestData> {
- pin_init::zeroed::<Self::RequestData>()
+ pin_init!(Pdu {
+ timer <- kernel::time::hrtimer::HrTimer::new(),
+ })
}

#[inline(always)]
@@ -145,6 +195,11 @@ fn queue_rq(queue_data: &QueueData, rq: Owned<mq::Request<Self>>, _is_last: bool
match queue_data.irq_mode {
IRQMode::None => rq.end_ok(),
IRQMode::Soft => mq::Request::complete(rq.into()),
+ IRQMode::Timer => {
+ OwnableRefCounted::into_shared(rq)
+ .start(queue_data.completion_time)
+ .dismiss();
+ }
}
Ok(())
}

--
2.51.2