[PATCH v2 11/83] block: rnull: add timer completion mode

From: Andreas Hindborg

Date: Tue Jun 09 2026 - 15:25:06 EST


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@xxxxxxxxxx>
---
drivers/block/rnull/configfs.rs | 34 ++++++++++++++++++++--
drivers/block/rnull/rnull.rs | 63 ++++++++++++++++++++++++++++++++++++++---
2 files changed, 90 insertions(+), 7 deletions(-)

diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index fd309fc17e66..83b474f6da60 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -25,11 +25,15 @@
kstrtobool_bytes,
CString, //
},
- sync::Mutex, //
+ sync::Mutex,
+ time, //
};
use macros::{
+ configfs_attribute,
configfs_simple_bool_field,
- configfs_simple_field, //
+ configfs_simple_field,
+ show_field,
+ store_number_with_power_check, //
};

mod macros;
@@ -56,7 +60,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())
}
}
@@ -79,6 +83,7 @@ fn make_group(
rotational: 2,
size: 3,
irqmode: 4,
+ completion_nsec: 5,
],
};

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

impl TryFrom<u8> for IRQMode {
@@ -115,6 +122,7 @@ fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(Self::None),
1 => Ok(Self::Soft),
+ 2 => Ok(Self::Timer),
_ => Err(EINVAL),
}
}
@@ -125,11 +133,22 @@ 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(())
}
}

+/// Wraps [`time::Delta`] to render the value as a bare nanosecond count for
+/// configfs attributes that historically used this format.
+struct DeltaDisplay(time::Delta);
+
+impl kernel::fmt::Display for DeltaDisplay {
+ fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
+ f.write_fmt(kernel::prelude::fmt!("{}", self.0.as_nanos()))
+ }
+}
+
#[pin_data]
pub(crate) struct DeviceConfig {
#[pin]
@@ -144,6 +163,7 @@ struct DeviceConfigInner {
rotational: bool,
capacity_mib: u64,
irq_mode: IRQMode,
+ completion_time: time::Delta,
disk: Option<GenDisk<NullBlkDevice>>,
}

@@ -174,6 +194,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 {
@@ -189,6 +210,13 @@ 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_attribute!(DeviceConfig, 5,
+ show: |this, page| show_field(DeltaDisplay(this.data.lock().completion_time), page),
+ store: |this, page| store_number_with_power_check(this, page, |data, value: i64| {
+ data.completion_time = time::Delta::from_nanos(value);
+ Ok(())
+ })
+);

impl core::str::FromStr for IRQMode {
type Err = Error;
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index dd7a30519870..3e7a47e6d0e5 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, //
@@ -59,7 +68,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",
},
},
}
@@ -79,6 +92,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))?;

@@ -88,6 +102,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
module_parameters::rotational.value(),
module_parameters::gb.value() * 1024,
module_parameters::irqmode.value().try_into()?,
+ Delta::from_nanos(completion_time),
)?;
disks.push(disk, GFP_KERNEL)?;
}
@@ -111,10 +126,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))
@@ -127,15 +149,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> {
- Ok(())
+ pin_init!(Pdu {
+ timer <- kernel::time::hrtimer::HrTimer::new(),
+ })
}

#[inline(always)]
@@ -143,6 +193,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