* [PATCH v2 11/83] block: rnull: add timer completion mode
From: Andreas Hindborg @ 2026-06-09 19:07 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
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 | 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
^ permalink raw reply related
* [PATCH v2 54/83] block: rust: add `map_queues` support
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux, Andreas Hindborg
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
From: Andreas Hindborg <a.hindborg@samsung.com>
Add support for the `map_queues` callback to the Rust block layer
bindings. This callback allows drivers to customize the mapping between
CPUs and hardware queues.
The callback receives a mutable reference to the `TagSet`, and drivers
can use the `TagSet::update_maps` method to configure the mappings for
each queue type.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/block/mq/operations.rs | 29 +++++++++++++++++++++++++++--
rust/kernel/block/mq/tag_set.rs | 13 +++++++++++++
2 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/rust/kernel/block/mq/operations.rs b/rust/kernel/block/mq/operations.rs
index 71d4192d627f..8a418bf0f3ba 100644
--- a/rust/kernel/block/mq/operations.rs
+++ b/rust/kernel/block/mq/operations.rs
@@ -12,7 +12,8 @@
gen_disk::GenDiskRef,
request::RequestDataWrapper,
IdleRequest,
- Request, //
+ Request,
+ TagSet, //
},
},
error::{
@@ -126,6 +127,11 @@ fn report_zones(
) -> Result<u32> {
Err(ENOTSUPP)
}
+
+ /// Called by the kernel to map submission queues to CPU cores.
+ fn map_queues(_tag_set: &TagSet<Self>) {
+ build_error!(crate::error::VTABLE_DEFAULT_ERROR)
+ }
}
/// A vtable for blk-mq to interact with a block device driver.
@@ -418,6 +424,21 @@ impl<T: Operations> OperationsVTable<T> {
})
}
+ /// 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. `tag_set`
+ /// must be a pointer to a valid and initialized `TagSet<T>`. The pointee
+ /// must be valid for use as a reference at least the duration of this call.
+ unsafe extern "C" fn map_queues_callback(tag_set: *mut bindings::blk_mq_tag_set) {
+ // SAFETY: The safety requirements of this function satiesfies the
+ // requirements of `TagSet::from_ptr`.
+ let tag_set = unsafe { TagSet::from_ptr(tag_set) };
+ T::map_queues(tag_set);
+ }
+
const VTABLE: bindings::blk_mq_ops = bindings::blk_mq_ops {
queue_rq: Some(Self::queue_rq_callback),
queue_rqs: None,
@@ -439,7 +460,11 @@ impl<T: Operations> OperationsVTable<T> {
exit_request: Some(Self::exit_request_callback),
cleanup_rq: None,
busy: None,
- map_queues: None,
+ map_queues: if T::HAS_MAP_QUEUES {
+ Some(Self::map_queues_callback)
+ } else {
+ None
+ },
#[cfg(CONFIG_BLK_DEBUG_FS)]
show_rq: None,
};
diff --git a/rust/kernel/block/mq/tag_set.rs b/rust/kernel/block/mq/tag_set.rs
index 157c47f64334..d3e93ad98b6e 100644
--- a/rust/kernel/block/mq/tag_set.rs
+++ b/rust/kernel/block/mq/tag_set.rs
@@ -116,6 +116,19 @@ pub fn flags(&self) -> Flags {
let flags_raw = unsafe { (*this).flags };
Flags::try_from(flags_raw).expect("Expected valid flags from C struct")
}
+
+ /// Create a `TagSet<T>` from a raw pointer.
+ ///
+ /// # Safety
+ ///
+ /// `ptr` must be a pointer to a valid and initialized `TagSet<T>`. There
+ /// may be no other mutable references to the tag set. The pointee must be
+ /// live and valid at least for the duration of the returned lifetime `'a`.
+ pub(crate) unsafe fn from_ptr<'a>(ptr: *mut bindings::blk_mq_tag_set) -> &'a Self {
+ // SAFETY: By the safety requirements of this function, `ptr` is valid
+ // for use as a reference for the duration of `'a`.
+ unsafe { &*(ptr.cast::<Self>()) }
+ }
}
#[pinned_drop]
--
2.51.2
^ permalink raw reply related
* [PATCH v2 06/83] block: rust: fix generation of bindings to `BLK_STS_.*`
From: Andreas Hindborg @ 2026-06-09 19:07 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Bindgen generates constants for CPP integer literals as u32. The
`blk_status_t` type is defined as `u8` but the variants of the type are
defined as integer literals via CPP macros. Thus the defined variants of
the type are not of the same type as the type itself.
Prevent bindgen from emitting generated bindings for the `BLK_STS_.*`
defines and instead define constants manually in `bindings_helper.h`
Also remove casts that are no longer necessary.
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/bindgen_parameters | 6 ++++++
rust/bindings/bindings_helper.h | 19 +++++++++++++++++++
rust/kernel/block/mq/operations.rs | 17 +++++++++++++----
3 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/rust/bindgen_parameters b/rust/bindgen_parameters
index 6f02d9720ad2..128731e84775 100644
--- a/rust/bindgen_parameters
+++ b/rust/bindgen_parameters
@@ -5,6 +5,12 @@
--blocklist-type __kernel_s?size_t
--blocklist-type __kernel_ptrdiff_t
+# Bindgen cannot extract values from the `((__force blk_status_t)N)`
+# CPP-macro form used by most of these and emits the few it can extract
+# as `u32`. Block them entirely; the `RUST_CONST_HELPER_BLK_STS_*`
+# definitions in `bindings_helper.h` expose them as `blk_status_t`.
+--blocklist-item BLK_STS_.*
+
--opaque-type xregs_state
--opaque-type desc_struct
--opaque-type arch_lbr_state
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 9da216faad51..b1fb3afee4ca 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -119,6 +119,25 @@ const gfp_t RUST_CONST_HELPER___GFP_ZERO = __GFP_ZERO;
const gfp_t RUST_CONST_HELPER___GFP_HIGHMEM = ___GFP_HIGHMEM;
const gfp_t RUST_CONST_HELPER___GFP_NOWARN = ___GFP_NOWARN;
const blk_features_t RUST_CONST_HELPER_BLK_FEAT_ROTATIONAL = BLK_FEAT_ROTATIONAL;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_OK = BLK_STS_OK;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_NOTSUPP = BLK_STS_NOTSUPP;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_TIMEOUT = BLK_STS_TIMEOUT;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_NOSPC = BLK_STS_NOSPC;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_TRANSPORT = BLK_STS_TRANSPORT;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_TARGET = BLK_STS_TARGET;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_RESV_CONFLICT = BLK_STS_RESV_CONFLICT;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_MEDIUM = BLK_STS_MEDIUM;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_PROTECTION = BLK_STS_PROTECTION;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_RESOURCE = BLK_STS_RESOURCE;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_IOERR = BLK_STS_IOERR;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_DM_REQUEUE = BLK_STS_DM_REQUEUE;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_AGAIN = BLK_STS_AGAIN;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_DEV_RESOURCE = BLK_STS_DEV_RESOURCE;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_ZONE_OPEN_RESOURCE = BLK_STS_ZONE_OPEN_RESOURCE;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_ZONE_ACTIVE_RESOURCE = BLK_STS_ZONE_ACTIVE_RESOURCE;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_OFFLINE = BLK_STS_OFFLINE;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_DURATION_LIMIT = BLK_STS_DURATION_LIMIT;
+const blk_status_t RUST_CONST_HELPER_BLK_STS_INVAL = BLK_STS_INVAL;
const fop_flags_t RUST_CONST_HELPER_FOP_UNSIGNED_OFFSET = FOP_UNSIGNED_OFFSET;
const xa_mark_t RUST_CONST_HELPER_XA_PRESENT = XA_PRESENT;
diff --git a/rust/kernel/block/mq/operations.rs b/rust/kernel/block/mq/operations.rs
index 89029f468f44..6b2fcd76372e 100644
--- a/rust/kernel/block/mq/operations.rs
+++ b/rust/kernel/block/mq/operations.rs
@@ -6,10 +6,19 @@
use crate::{
bindings,
- block::mq::{request::RequestDataWrapper, Request},
- error::{from_result, Result},
+ block::mq::{
+ request::RequestDataWrapper,
+ Request, //
+ },
+ error::{
+ from_result,
+ Result, //
+ },
prelude::*,
- sync::{aref::ARef, Refcount},
+ sync::{
+ aref::ARef,
+ Refcount, //
+ },
types::ForeignOwnable,
};
use core::marker::PhantomData;
@@ -124,7 +133,7 @@ impl<T: Operations> OperationsVTable<T> {
if let Err(e) = ret {
e.to_blk_status()
} else {
- bindings::BLK_STS_OK as bindings::blk_status_t
+ bindings::BLK_STS_OK
}
}
--
2.51.2
^ permalink raw reply related
* [PATCH v2 44/83] block: rnull: add bandwidth limiting
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add bandwidth limiting support to rnull via the `mbps` configfs
attribute. When set to a non-zero value, the driver limits I/O
throughput to the specified rate in megabytes per second.
The implementation uses a token bucket algorithm to enforce the rate
limit, delaying request completion when the limit is exceeded.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/configfs.rs | 7 ++-
drivers/block/rnull/rnull.rs | 111 +++++++++++++++++++++++++++++++++++-----
2 files changed, 105 insertions(+), 13 deletions(-)
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index 4df0b748596a..59217d75f46b 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -104,6 +104,7 @@ fn make_group(
badblocks_once: 13,
badblocks_partial_io: 14,
cache_size_mib: 15,
+ mbps: 16,
],
};
@@ -135,6 +136,7 @@ fn make_group(
GFP_KERNEL
)?,
cache_size_mib: 0,
+ mbps: 0,
}),
}),
core::iter::empty(),
@@ -209,6 +211,7 @@ struct DeviceConfigInner {
bad_blocks_partial_io: bool,
cache_size_mib: u64,
disk_storage: Arc<DiskStorage>,
+ mbps: u32,
}
#[vtable]
@@ -248,6 +251,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
bad_blocks_once: guard.bad_blocks_once,
bad_blocks_partial_io: guard.bad_blocks_partial_io,
storage: guard.disk_storage.clone(),
+ bandwidth_limit: u64::from(guard.mbps) * 2u64.pow(20),
})?);
guard.powered = true;
} else if guard.powered && !power_op {
@@ -259,7 +263,6 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
}
}
-// DiskStorage::new(cache_size_mib << 20, block_size as usize),
configfs_simple_field!(DeviceConfig, 1, block_size, u32, check GenDiskBuilder::validate_block_size);
configfs_simple_bool_field!(DeviceConfig, 2, rotational);
configfs_simple_field!(DeviceConfig, 3, capacity_mib, u64);
@@ -417,3 +420,5 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
Ok(())
})
);
+
+configfs_simple_field!(DeviceConfig, 16, mbps, u32);
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 6ceba23a4d3e..1dda8d717b95 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -25,7 +25,8 @@
self,
gen_disk::{
self,
- GenDisk, //
+ GenDisk,
+ GenDiskRef, //
},
Operations,
TagSet, //
@@ -37,25 +38,32 @@
Result, //
},
ffi,
+ impl_has_hr_timer,
memalloc_scope,
new_mutex,
new_spinlock,
pr_info,
prelude::*,
+ revocable::Revocable,
str::CString,
sync::{
aref::ARef,
atomic::{
ordering,
Atomic, //
- }, //
+ },
Arc,
+ ArcBorrow,
Mutex,
+ SetOnce,
SpinLock,
- SpinLockGuard,
+ SpinLockGuard, //
},
time::{
hrtimer::{
+ self,
+ ArcHrTimerHandle,
+ HrTimer,
HrTimerCallback,
HrTimerCallbackContext,
HrTimerPointer,
@@ -127,6 +135,10 @@
default: false,
description: "No IO scheduler",
},
+ mbps: u32 {
+ default: 0,
+ description: "Max bandwidth in MiB/s. 0 means no limit.",
+ },
},
}
@@ -172,6 +184,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
bad_blocks_once: false,
bad_blocks_partial_io: false,
storage: Arc::pin_init(DiskStorage::new(0, block_size as usize), GFP_KERNEL)?,
+ bandwidth_limit: u64::from(module_parameters::mbps.value()) * 2u64.pow(20),
})?;
disks.push(disk, GFP_KERNEL)?;
}
@@ -202,6 +215,7 @@ struct NullBlkOptions<'a> {
bad_blocks_once: bool,
bad_blocks_partial_io: bool,
storage: Arc<DiskStorage>,
+ bandwidth_limit: u64,
}
#[pin_data]
@@ -214,9 +228,18 @@ struct NullBlkDevice {
bad_blocks: Arc<BadBlocks>,
bad_blocks_once: bool,
bad_blocks_partial_io: bool,
+ bandwidth_limit: u64,
+ #[pin]
+ bandwidth_timer: HrTimer<Self>,
+ bandwidth_bytes: Atomic<u64>,
+ #[pin]
+ bandwidth_timer_handle: SpinLock<Option<ArcHrTimerHandle<Self>>>,
+ disk: SetOnce<Arc<Revocable<GenDiskRef<Self>>>>,
}
impl NullBlkDevice {
+ const BANDWIDTH_TIMER_INTERVAL: Delta = Delta::from_millis(20);
+
fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
let NullBlkOptions {
name,
@@ -234,6 +257,7 @@ fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
bad_blocks_once,
bad_blocks_partial_io,
storage,
+ bandwidth_limit,
} = options;
let mut flags = mq::tag_set::Flags::default();
@@ -268,7 +292,7 @@ fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
GFP_KERNEL,
)?;
- let queue_data = Box::try_pin_init(
+ let queue_data = Arc::try_pin_init(
try_pin_init!(Self {
storage,
irq_mode,
@@ -278,6 +302,11 @@ fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
bad_blocks,
bad_blocks_once,
bad_blocks_partial_io,
+ bandwidth_limit: bandwidth_limit / 50,
+ bandwidth_timer <- HrTimer::new(),
+ bandwidth_bytes: Atomic::new(0),
+ bandwidth_timer_handle <- new_spinlock!(None),
+ disk: SetOnce::new(),
}),
GFP_KERNEL,
)?;
@@ -294,7 +323,10 @@ fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
.max_hw_discard_sectors(ffi::c_uint::MAX >> block::SECTOR_SHIFT);
}
- builder.build(fmt!("{}", name.to_str()?), tagset, queue_data)
+ let disk = builder.build(fmt!("{}", name.to_str()?), tagset, queue_data)?;
+ let queue_data: ArcBorrow<'_, Self> = disk.queue_data();
+ queue_data.disk.populate(disk.get_ref());
+ Ok(disk)
}
fn sheaf_size() -> usize {
@@ -522,6 +554,36 @@ fn end_request(rq: Owned<mq::Request<Self>>) {
}
}
+impl_has_hr_timer! {
+ impl HasHrTimer<Self> for NullBlkDevice {
+ mode: hrtimer::RelativeHardMode<kernel::time::Monotonic>,
+ field: self.bandwidth_timer,
+ }
+}
+
+impl HrTimerCallback for NullBlkDevice {
+ type Pointer<'a> = Arc<Self>;
+
+ fn run(
+ this: ArcBorrow<'_, Self>,
+ mut context: HrTimerCallbackContext<'_, Self>,
+ ) -> HrTimerRestart {
+ if this.bandwidth_bytes.load(ordering::Relaxed) == 0 {
+ return HrTimerRestart::NoRestart;
+ }
+
+ this.disk.as_ref().map(|disk| {
+ disk.try_access()
+ .map(|disk| disk.queue().start_stopped_hw_queues_async())
+ });
+
+ this.bandwidth_bytes.store(0, ordering::Relaxed);
+
+ context.forward_now(Self::BANDWIDTH_TIMER_INTERVAL);
+ HrTimerRestart::Restart
+ }
+}
+
struct HwQueueContext {
page: Option<KBox<disk_storage::NullBlockPage>>,
}
@@ -529,7 +591,7 @@ struct HwQueueContext {
#[pin_data]
struct Pdu {
#[pin]
- timer: kernel::time::hrtimer::HrTimer<Self>,
+ timer: HrTimer<Self>,
error: Atomic<u32>,
}
@@ -578,14 +640,14 @@ fn align_down<T>(value: T, to: T) -> T
#[vtable]
impl Operations for NullBlkDevice {
- type QueueData = Pin<KBox<Self>>;
+ type QueueData = Arc<Self>;
type RequestData = Pdu;
type TagSetData = ();
type HwData = Pin<KBox<SpinLock<HwQueueContext>>>;
fn new_request_data() -> impl PinInit<Self::RequestData> {
pin_init!(Pdu {
- timer <- kernel::time::hrtimer::HrTimer::new(),
+ timer <- HrTimer::new(),
error: Atomic::new(0),
})
}
@@ -593,14 +655,39 @@ fn new_request_data() -> impl PinInit<Self::RequestData> {
#[inline(always)]
fn queue_rq(
hw_data: Pin<&SpinLock<HwQueueContext>>,
- this: Pin<&Self>,
+ this: ArcBorrow<'_, Self>,
rq: Owned<mq::IdleRequest<Self>>,
_is_last: bool,
) -> BlkResult {
- let mut rq = rq.start();
let mut sectors = rq.sectors();
- Self::handle_bad_blocks(this.get_ref(), &mut rq, &mut sectors)?;
+ if this.bandwidth_limit != 0 {
+ if !this.bandwidth_timer.active() {
+ drop(this.bandwidth_timer_handle.lock().take());
+ let arc: Arc<_> = this.into();
+ *this.bandwidth_timer_handle.lock() =
+ Some(arc.start(Self::BANDWIDTH_TIMER_INTERVAL));
+ }
+
+ if this
+ .bandwidth_bytes
+ .fetch_add(u64::from(rq.bytes()), ordering::Relaxed)
+ + u64::from(rq.bytes())
+ > this.bandwidth_limit
+ {
+ rq.queue().stop_hw_queues();
+ if this.bandwidth_bytes.load(ordering::Relaxed) <= this.bandwidth_limit {
+ rq.queue().start_stopped_hw_queues_async();
+ }
+
+ return Err(kernel::block::error::code::BLK_STS_DEV_RESOURCE);
+ }
+ }
+
+ let mut rq = rq.start();
+
+ use core::ops::Deref;
+ Self::handle_bad_blocks(this.deref(), &mut rq, &mut sectors)?;
if this.memory_backed {
memalloc_scope!(let _noio: NoIo);
@@ -623,7 +710,7 @@ fn queue_rq(
Ok(())
}
- fn commit_rqs(_hw_data: Pin<&SpinLock<HwQueueContext>>, _queue_data: Pin<&Self>) {}
+ fn commit_rqs(_hw_data: Pin<&SpinLock<HwQueueContext>>, _queue_data: ArcBorrow<'_, Self>) {}
fn init_hctx(_tagset_data: (), _hctx_idx: u32) -> Result<Self::HwData> {
KBox::pin_init(new_spinlock!(HwQueueContext { page: None }), GFP_KERNEL)
--
2.51.2
^ permalink raw reply related
* [PATCH v2 21/83] block: rust: add Request::sectors() method
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add a new method to get the size of a request in number of sectors.
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/block/mq/request.rs | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/rust/kernel/block/mq/request.rs b/rust/kernel/block/mq/request.rs
index 19bdf17de166..54fe580b7b42 100644
--- a/rust/kernel/block/mq/request.rs
+++ b/rust/kernel/block/mq/request.rs
@@ -183,6 +183,13 @@ pub fn sector(&self) -> usize {
unsafe { (*self.0.get()).__sector as usize }
}
+ /// Get the size of the request in number of sectors.
+ #[inline(always)]
+ pub fn sectors(&self) -> usize {
+ // SAFETY: By type invariant of `Self`, `self.0` is valid and live.
+ (unsafe { (*self.0.get()).__data_len as usize }) >> crate::block::SECTOR_SHIFT
+ }
+
/// Return a pointer to the [`RequestDataWrapper`] stored in the private area
/// of the request structure.
///
--
2.51.2
^ permalink raw reply related
* [PATCH v2 77/83] block: rnull: add fault injection support
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add fault injection support to rnull using the kernel fault injection
infrastructure. When enabled via `CONFIG_FAULT_INJECTION`, users can
inject failures into I/O requests through the standard fault injection
debugfs interface.
The fault injection point is exposed as a configfs default group,
allowing per-device fault injection configuration.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/Kconfig | 11 ++++
drivers/block/rnull/configfs.rs | 57 ++++++++++++++++++-
drivers/block/rnull/rnull.rs | 121 +++++++++++++++++++++++++++++++++++++---
3 files changed, 180 insertions(+), 9 deletions(-)
diff --git a/drivers/block/rnull/Kconfig b/drivers/block/rnull/Kconfig
index 7bc5b376c128..1ade5d8c1799 100644
--- a/drivers/block/rnull/Kconfig
+++ b/drivers/block/rnull/Kconfig
@@ -11,3 +11,14 @@ config BLK_DEV_RUST_NULL
devices that can be configured via various configuration options.
If unsure, say N.
+
+config BLK_DEV_RUST_NULL_FAULT_INJECTION
+ bool "Support fault injection for Rust Null test block driver"
+ depends on BLK_DEV_RUST_NULL && FAULT_INJECTION_CONFIGFS
+ help
+ Enable fault injection support for the Rust null block driver. This
+ allows injecting errors into block I/O operations for testing error
+ handling paths and verifying system resilience. Fault injection is
+ configured through configfs alongside the null block device settings.
+
+ If unsure, say N.
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index d9246b9150f4..eaa7617e5ffa 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -48,6 +48,9 @@
mod macros;
+#[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+use kernel::fault_injection::FaultConfig;
+
pub(crate) fn subsystem(
shared_tag_set: Arc<TagSet<NullBlkDevice>>,
) -> impl PinInit<kernel::configfs::Subsystem<Config>, Error> {
@@ -132,10 +135,44 @@ fn make_group(
],
};
+ use kernel::configfs::CDefaultGroup;
+
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ let mut default_groups: KVec<Arc<dyn CDefaultGroup>> = KVec::new();
+
+ #[cfg(not(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION))]
+ let default_groups: KVec<Arc<dyn CDefaultGroup>> = KVec::new();
+
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ let timeout_inject = Arc::pin_init(
+ kernel::fault_injection::FaultConfig::new(c"timeout_inject"),
+ GFP_KERNEL,
+ )?;
+
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ let requeue_inject = Arc::pin_init(
+ kernel::fault_injection::FaultConfig::new(c"requeue_inject"),
+ GFP_KERNEL,
+ )?;
+
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ let init_hctx_inject = Arc::pin_init(
+ kernel::fault_injection::FaultConfig::new(c"init_hctx_fault_inject"),
+ GFP_KERNEL,
+ )?;
+
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ {
+ default_groups.push(timeout_inject.clone(), GFP_KERNEL)?;
+ default_groups.push(requeue_inject.clone(), GFP_KERNEL)?;
+ default_groups.push(init_hctx_inject.clone(), GFP_KERNEL)?;
+ }
+
let block_size = 4096;
Ok(configfs::Group::new(
name.try_into()?,
item_type,
+ // default_groups,
// TODO: cannot coerce new_mutex!() to impl PinInit<_, Error>, so put mutex inside
try_pin_init!(DeviceConfig {
data <- new_mutex!(DeviceConfigInner {
@@ -176,9 +213,15 @@ fn make_group(
zone_max_active: 0,
zone_append_max_sectors: u32::MAX,
fua: true,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ timeout_inject,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ requeue_inject,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ init_hctx_inject,
}),
}),
- core::iter::empty(),
+ default_groups,
))
}
}
@@ -263,6 +306,12 @@ struct DeviceConfigInner {
zone_max_active: u32,
zone_append_max_sectors: u32,
fua: bool,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ timeout_inject: Arc<FaultConfig>,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ requeue_inject: Arc<FaultConfig>,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ init_hctx_inject: Arc<FaultConfig>,
}
#[vtable]
@@ -320,6 +369,8 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
memory_backed: guard.memory_backed,
no_sched: guard.no_sched,
hw_queue_depth: guard.hw_queue_depth,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ init_hctx_inject: guard.init_hctx_inject.clone(),
},
zoned: guard.zoned,
zone_size_mib: guard.zone_size_mib,
@@ -329,6 +380,10 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
zone_max_active: guard.zone_max_active,
zone_append_max_sectors: guard.zone_append_max_sectors,
forced_unit_access: guard.fua,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ requeue_inject: guard.requeue_inject.clone(),
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ timeout_inject: guard.timeout_inject.clone(),
})?);
guard.powered = true;
} else if guard.powered && !power_op {
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 8e17b2b17a66..f909360ec70d 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -40,6 +40,7 @@
IoCompletionBatch,
Operations,
RequestList,
+ RequestTimeoutStatus,
TagSet, //
},
SECTOR_SHIFT,
@@ -90,6 +91,9 @@
};
use util::*;
+#[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+use kernel::fault_injection::FaultConfig;
+
module! {
type: NullBlkModule,
name: "rnull_mod",
@@ -203,6 +207,8 @@
},
}
+// TODO: Fault inject via params - requires module_params string support.
+
#[pin_data]
struct NullBlkModule {
#[pin]
@@ -241,6 +247,11 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
memory_backed,
no_sched,
hw_queue_depth,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ init_hctx_inject: Arc::pin_init(
+ FaultConfig::new(c"init_hctx_fault_inject"),
+ GFP_KERNEL,
+ )?,
})?;
let mut disks = KVec::new();
@@ -278,6 +289,11 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
memory_backed,
no_sched,
hw_queue_depth,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ init_hctx_inject: Arc::pin_init(
+ FaultConfig::new(c"init_hctx_fault_inject"),
+ GFP_KERNEL,
+ )?,
},
zoned: module_parameters::zoned.value(),
zone_size_mib: module_parameters::zone_size.value(),
@@ -287,6 +303,10 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
zone_max_active: module_parameters::zone_max_active.value(),
zone_append_max_sectors: module_parameters::zone_append_max_sectors.value(),
forced_unit_access: module_parameters::fua.value(),
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ requeue_inject: Arc::pin_init(FaultConfig::new(c"requeue_inject"), GFP_KERNEL)?,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ timeout_inject: Arc::pin_init(FaultConfig::new(c"timeout_inject"), GFP_KERNEL)?,
})?;
disks.push(disk, GFP_KERNEL)?;
}
@@ -328,6 +348,10 @@ struct NullBlkOptions<'a> {
#[cfg_attr(not(CONFIG_BLK_DEV_ZONED), allow(dead_code))]
zone_append_max_sectors: u32,
forced_unit_access: bool,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ requeue_inject: Arc<FaultConfig>,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ timeout_inject: Arc<FaultConfig>,
}
#[pin_data]
@@ -350,6 +374,12 @@ struct NullBlkDevice {
#[cfg(CONFIG_BLK_DEV_ZONED)]
#[pin]
zoned: zoned::ZoneOptions,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ requeue_inject: Arc<FaultConfig>,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ requeue_selector: kernel::sync::atomic::Atomic<u64>,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ timeout_inject: Arc<FaultConfig>,
}
struct TagSetOptions {
@@ -359,6 +389,8 @@ struct TagSetOptions {
memory_backed: bool,
no_sched: bool,
hw_queue_depth: u32,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ init_hctx_inject: Arc<FaultConfig>,
}
impl NullBlkDevice {
@@ -372,6 +404,8 @@ fn build_tag_set(options: TagSetOptions) -> Result<Arc<TagSet<Self>>> {
memory_backed,
no_sched,
hw_queue_depth,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ init_hctx_inject,
} = options;
if home_node > kernel::numa::num_online_nodes().try_into()? {
@@ -404,6 +438,8 @@ fn build_tag_set(options: TagSetOptions) -> Result<Arc<TagSet<Self>>> {
NullBlkTagsetData {
queue_depth: hw_queue_depth,
queue_config,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ init_hctx_inject,
},
GFP_KERNEL,
)?,
@@ -446,6 +482,11 @@ fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
#[cfg_attr(not(CONFIG_BLK_DEV_ZONED), allow(unused_variables))]
zone_append_max_sectors,
forced_unit_access,
+
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ requeue_inject,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ timeout_inject,
} = options;
let memory_backed = tag_set.memory_backed;
@@ -491,6 +532,12 @@ fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
zone_max_active,
zone_append_max_sectors,
})?,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ requeue_inject,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ requeue_selector: Atomic::new(0),
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ timeout_inject,
}),
GFP_KERNEL,
)?;
@@ -733,7 +780,9 @@ fn handle_bad_blocks(&self, rq: &mut Owned<mq::Request<Self>>, sectors: &mut u32
badblocks::BlockStatus::None => {}
badblocks::BlockStatus::Acknowledged(mut range)
| badblocks::BlockStatus::Unacknowledged(mut range) => {
- rq.data_ref().error.store(1, ordering::Relaxed);
+ rq.data_ref()
+ .error
+ .store(block::error::code::BLK_STS_IOERR.into(), ordering::Relaxed);
if self.bad_blocks_once {
self.bad_blocks.set_good(range.clone())?;
@@ -783,6 +832,22 @@ fn queue_rq_internal(
rq: Owned<mq::IdleRequest<Self>>,
_is_last: bool,
) -> Result<(), QueueRequestError> {
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ if rq.queue_data().requeue_inject.should_fail(1) {
+ if rq
+ .queue_data()
+ .requeue_selector
+ .fetch_add(1, ordering::Relaxed)
+ & 1
+ == 0
+ {
+ return Err(QueueRequestError { request: rq });
+ } else {
+ rq.requeue(true);
+ return Ok(());
+ }
+ }
+
if this.bandwidth_limit != 0 {
if !this.bandwidth_timer.active() {
drop(this.bandwidth_timer_handle.lock().take());
@@ -808,6 +873,12 @@ fn queue_rq_internal(
let mut rq = rq.start();
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ if rq.queue_data().timeout_inject.should_fail(1) {
+ rq.data_ref().fake_timeout.store(1, ordering::Relaxed);
+ return Ok(());
+ }
+
if rq.command() == mq::Command::Flush {
if this.memory_backed {
this.storage.flush(&hw_data);
@@ -831,12 +902,13 @@ fn queue_rq_internal(
Ok(())
})();
- if let Err(e) = status {
- // Do not overwrite existing error. We do not care whether this write fails.
- let _ = rq
- .data_ref()
- .error
- .cmpxchg(0, e.to_errno(), ordering::Relaxed);
+ if status.is_err() {
+ // Do not overwrite existing error.
+ let _ = rq.data_ref().error.cmpxchg(
+ 0,
+ kernel::block::error::code::BLK_STS_IOERR.into(),
+ ordering::Relaxed,
+ );
}
if rq.is_poll() {
@@ -914,7 +986,8 @@ struct HwQueueContext {
struct Pdu {
#[pin]
timer: HrTimer<Self>,
- error: Atomic<i32>,
+ error: Atomic<u32>,
+ fake_timeout: Atomic<u32>,
}
impl HrTimerCallback for Pdu {
@@ -939,6 +1012,8 @@ impl HasHrTimer<Self> for Pdu {
struct NullBlkTagsetData {
queue_depth: u32,
queue_config: Arc<Mutex<QueueConfig>>,
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ init_hctx_inject: Arc<FaultConfig>,
}
#[vtable]
@@ -952,6 +1027,7 @@ fn new_request_data() -> impl PinInit<Self::RequestData> {
pin_init!(Pdu {
timer <- HrTimer::new(),
error: Atomic::new(0),
+ fake_timeout: Atomic::new(0),
})
}
@@ -1006,6 +1082,11 @@ fn poll(
}
fn init_hctx(tagset_data: &NullBlkTagsetData, _hctx_idx: u32) -> Result<Self::HwData> {
+ #[cfg(CONFIG_BLK_DEV_RUST_NULL_FAULT_INJECTION)]
+ if tagset_data.init_hctx_inject.should_fail(1) {
+ return Err(EFAULT);
+ }
+
KBox::pin_init(
new_spinlock!(HwQueueContext {
page: None,
@@ -1067,4 +1148,28 @@ fn map_queues(tag_set: Pin<&mut TagSet<Self>>) {
})
.unwrap()
}
+
+ fn request_timeout(tag_set: &TagSet<Self>, qid: u32, tag: u32) -> RequestTimeoutStatus {
+ if let Some(request) = tag_set.tag_to_rq(qid, tag) {
+ pr_info!("Request timed out\n");
+ // Only fail requests that are faking timeouts. Requests that time
+ // out due to memory pressure will be completed normally.
+ if request.data_ref().fake_timeout.load(ordering::Relaxed) != 0 {
+ request.data_ref().error.store(
+ block::error::code::BLK_STS_TIMEOUT.into(),
+ ordering::Relaxed,
+ );
+ request.data_ref().fake_timeout.store(0, ordering::Relaxed);
+
+ if let Ok(request) = OwnableRefCounted::try_from_shared(request) {
+ Self::end_request(request);
+ return RequestTimeoutStatus::Completed;
+ }
+ kernel::pr_warn_once!("Timed out request could not be completed\n");
+ }
+ } else {
+ kernel::pr_warn_once!("Timed out request referenced in timeout handler\n");
+ }
+ RequestTimeoutStatus::RetryLater
+ }
}
--
2.51.2
^ permalink raw reply related
* [PATCH v2 73/83] block: rust: add `TagSet::tag_to_rq`
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add a way for block device drivers to obtain a `Request` from a tag. This
is backed by the C `blk_mq_tag_to_rq` but with added checks to ensure
memory safety.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/helpers/blk.c | 6 ++++
rust/kernel/block/mq/tag_set.rs | 66 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 71 insertions(+), 1 deletion(-)
diff --git a/rust/helpers/blk.c b/rust/helpers/blk.c
index 422289d617ae..1f3e5c661096 100644
--- a/rust/helpers/blk.c
+++ b/rust/helpers/blk.c
@@ -53,3 +53,9 @@ __rust_helper struct request *rust_helper_rq_list_peek(struct rq_list *rl)
{
return rq_list_peek(rl);
}
+
+__rust_helper struct request *
+rust_helper_blk_mq_tag_to_rq(struct blk_mq_tags *tags, unsigned int tag)
+{
+ return blk_mq_tag_to_rq(tags, tag);
+}
diff --git a/rust/kernel/block/mq/tag_set.rs b/rust/kernel/block/mq/tag_set.rs
index e89c76987b54..66b6a30a9e66 100644
--- a/rust/kernel/block/mq/tag_set.rs
+++ b/rust/kernel/block/mq/tag_set.rs
@@ -6,7 +6,6 @@
use crate::{
alloc::NumaNode,
- bindings,
block::mq::{
operations::OperationsVTable,
request::RequestDataWrapper,
@@ -17,7 +16,9 @@
Result, //
},
prelude::*,
+ sync::atomic::ordering,
types::{
+ ARef,
ForeignOwnable,
Opaque, //
},
@@ -39,6 +40,8 @@
Flags, //
};
+use super::Request;
+
/// 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.
@@ -193,6 +196,67 @@ pub fn data(&self) -> <T::TagSetData as ForeignOwnable>::Borrowed<'_> {
// converted back with `from_foreign` while `&self` is live.
unsafe { T::TagSetData::borrow(ptr) }
}
+
+ /// Obtain a shared reference to a request.
+ ///
+ /// This method will hang if the request is not owned by the driver, or if
+ /// the driver holds an [`Ownable<Request>`] reference to the request.
+ pub fn tag_to_rq(&self, qid: u32, tag: u32) -> Option<ARef<Request<T>>> {
+ if qid >= self.hw_queue_count() {
+ kernel::pr_warn_once!("Invalid queue id: {qid}\n");
+ return None;
+ }
+
+ // SAFETY: We checked that `qid` is within bounds.
+ let tags = unsafe { *(*self.inner.get()).tags.add(qid as usize) };
+
+ // SAFETY: We checked `qid` for overflow above, so `tags` is valid.
+ let rq_ptr = unsafe { bindings::blk_mq_tag_to_rq(tags, tag) };
+ if rq_ptr.is_null() {
+ None
+ } else {
+ // SAFETY: if `rq_ptr`is not null, it is a valid request pointer.
+ let refcount_ptr = unsafe {
+ RequestDataWrapper::refcount_ptr(
+ Request::wrapper_ptr(rq_ptr.cast::<Request<T>>()).as_ptr(),
+ )
+ };
+
+ // SAFETY: The refcount was initialized in `init_request_callback` and is never
+ // referenced mutably.
+ let refcount_ref = unsafe { &*refcount_ptr };
+
+ let atomic_ref = refcount_ref.as_atomic();
+
+ // It is possible for an interrupt to arrive faster than the last
+ // change to the refcount, so retry if the refcount is not what we
+ // think it should be.
+ loop {
+ // Load acquire to sync with store release of `Owned<Request>`
+ // being destroyed (prevent mutable access overlapping shared
+ // access).
+ let prev = atomic_ref.load(ordering::Acquire);
+
+ if prev >= 1 {
+ // Store relaxed as no other operations need to happen strictly
+ // before or after the increment.
+ match atomic_ref.cmpxchg(prev, prev + 1, ordering::Relaxed) {
+ Ok(_) => break,
+ // NOTE: We cannot use the load part of a failed cmpxchg as it is always
+ // relaxed.
+ Err(_) => continue,
+ }
+ } else {
+ // We are probably waiting to observe a refcount increment.
+ core::hint::spin_loop();
+ continue;
+ };
+ }
+
+ // SAFETY: We checked above that `rq_ptr` is valid for use as an `ARef`.
+ Some(unsafe { Request::aref_from_raw(rq_ptr) })
+ }
+ }
}
#[pinned_drop]
--
2.51.2
^ permalink raw reply related
* [PATCH v2 38/83] block: rust: introduce an idle type state for `Request`
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Block device drivers need to invoke `blk_mq_start_request` on a request to
indicate that they have started processing the request. This function may
only be called once after a request has been issued to a driver. For Rust
block device drivers, the Rust abstractions handle this call. However, in
some situations a driver may want to control when a request is started.
Thus, expose the start method to Rust block device drivers.
To ensure the method is not called more than once, introduce a type state
for `Request`. Requests are issued as `IdleRequest` and transition to
`Request` when the `start` method is called.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/rnull.rs | 3 +-
rust/kernel/block/mq.rs | 5 +-
rust/kernel/block/mq/operations.rs | 15 ++--
rust/kernel/block/mq/request.rs | 149 +++++++++++++++++++++++++++++++------
4 files changed, 137 insertions(+), 35 deletions(-)
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index fd9b770965a6..bb8c4df08218 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -593,9 +593,10 @@ fn new_request_data() -> impl PinInit<Self::RequestData> {
fn queue_rq(
hw_data: Pin<&SpinLock<HwQueueContext>>,
this: Pin<&Self>,
- mut rq: Owned<mq::Request<Self>>,
+ rq: Owned<mq::IdleRequest<Self>>,
_is_last: bool,
) -> Result {
+ let mut rq = rq.start();
let mut sectors = rq.sectors();
Self::handle_bad_blocks(this.get_ref(), &mut rq, &mut sectors)?;
diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index b095cc7f51ce..77e3593e8626 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -88,10 +88,10 @@
//! fn queue_rq(
//! _hw_data: (),
//! _queue_data: (),
-//! rq: Owned<Request<Self>>,
+//! rq: Owned<IdleRequest<Self>>,
//! _is_last: bool
//! ) -> Result {
-//! rq.end_ok();
+//! rq.start().end_ok();
//! Ok(())
//! }
//!
@@ -131,6 +131,7 @@
pub use operations::Operations;
pub use request::{
+ IdleRequest,
Request,
RequestTimerHandle, //
};
diff --git a/rust/kernel/block/mq/operations.rs b/rust/kernel/block/mq/operations.rs
index 1b20df25d6df..01917ef213d1 100644
--- a/rust/kernel/block/mq/operations.rs
+++ b/rust/kernel/block/mq/operations.rs
@@ -8,6 +8,7 @@
bindings,
block::mq::{
request::RequestDataWrapper,
+ IdleRequest,
Request, //
},
error::{
@@ -25,10 +26,7 @@
Owned, //
},
};
-use core::{
- marker::PhantomData,
- ptr::NonNull, //
-};
+use core::marker::PhantomData;
use pin_init::PinInit;
type ForeignBorrowed<'a, T> = <T as ForeignOwnable>::Borrowed<'a>;
@@ -82,7 +80,7 @@ pub trait Operations: Sized {
fn queue_rq(
hw_data: ForeignBorrowed<'_, Self::HwData>,
queue_data: ForeignBorrowed<'_, Self::QueueData>,
- rq: Owned<Request<Self>>,
+ rq: Owned<IdleRequest<Self>>,
is_last: bool,
) -> Result;
@@ -154,14 +152,14 @@ impl<T: Operations> OperationsVTable<T> {
== 0
);
+ // INVARIANT: By C API contract, `bd.rq` has not been started yet.
// SAFETY:
// - By API contract, we own the request.
// - By the safety requirements of this function, `request` is a valid
// `struct request` and the private data is properly initialized.
// - `rq` will be alive until `blk_mq_end_request` is called and is
// reference counted by until then.
- let mut rq =
- unsafe { Owned::from_raw(NonNull::<Request<T>>::new_unchecked((*bd).rq.cast())) };
+ let rq = unsafe { IdleRequest::from_raw((*bd).rq) };
// SAFETY: The safety requirement for this function ensure that `hctx`
// is valid and that `driver_data` was produced by a call to
@@ -177,9 +175,6 @@ impl<T: Operations> OperationsVTable<T> {
// dropped, which happens after we are dropped.
let queue_data = unsafe { T::QueueData::borrow(queue_data) };
- // SAFETY: We have exclusive access and we just set the refcount above.
- unsafe { rq.start_unchecked() };
-
let ret = T::queue_rq(
hw_data,
queue_data,
diff --git a/rust/kernel/block/mq/request.rs b/rust/kernel/block/mq/request.rs
index c06907dfe5b5..f94e9c2181d0 100644
--- a/rust/kernel/block/mq/request.rs
+++ b/rust/kernel/block/mq/request.rs
@@ -24,6 +24,7 @@
HrTimerPointer, //
},
types::{
+ ForeignOwnable,
Opaque,
Ownable,
OwnableRefCounted,
@@ -33,6 +34,7 @@
use core::{
ffi::c_void,
marker::PhantomData,
+ ops::Deref,
pin::Pin,
ptr::NonNull, //
};
@@ -42,6 +44,104 @@
BioIterator, //
};
+/// A [`Request`] that a driver has not yet begun to process.
+///
+/// A driver can convert an `IdleRequest` to a [`Request`] by calling [`IdleRequest::start`].
+///
+/// # Invariants
+///
+/// - This request has not been started yet.
+#[repr(transparent)]
+pub struct IdleRequest<T>(RequestInner<T>);
+
+impl<T: Operations> IdleRequest<T> {
+ /// Mark the request as processing.
+ ///
+ /// This converts the [`IdleRequest`] into a [`Request`].
+ pub fn start(self: Owned<Self>) -> Owned<Request<T>> {
+ // SAFETY: By type invariant `self.0.0` is a valid request. Because we have an `Owned<_>`,
+ // the refcount is zero.
+ let mut request = unsafe { Request::from_raw(self.0 .0.get()) };
+
+ debug_assert!(
+ request
+ .wrapper_ref()
+ .refcount()
+ .as_atomic()
+ .load(ordering::Acquire)
+ == 0
+ );
+
+ // SAFETY: We have exclusive access and the refcount is 0. By type invariant `request` was
+ // not started yet.
+ unsafe { request.start_unchecked() };
+
+ request
+ }
+
+ /// Create a [`Self`] from a raw request pointer.
+ ///
+ /// # Safety
+ ///
+ /// - The request pointed to by `ptr` must satisfythe invariants of both [`Request`] and
+ /// [`Self`].
+ /// - The refcount of the request pointed to by `ptr` must be 0.
+ pub(crate) unsafe fn from_raw(ptr: *mut bindings::request) -> Owned<Self> {
+ // SAFETY: By function safety requirements, `ptr` is valid for use as an `IdleRequest`.
+ unsafe { Owned::from_raw(NonNull::<Self>::new_unchecked(ptr.cast())) }
+ }
+}
+
+impl<T: Operations> Ownable for IdleRequest<T> {
+ // The `release` implementation leaks the `IdleRequest`, which is a valid state for a
+ // [`Request`] with refcount 0.
+ unsafe fn release(&mut self) {}
+}
+
+impl<T: Operations> Deref for IdleRequest<T> {
+ type Target = RequestInner<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+pub struct RequestInner<T>(Opaque<bindings::request>, PhantomData<T>);
+
+impl<T: Operations> RequestInner<T> {
+ /// Get the command identifier for the request
+ pub fn command(&self) -> u32 {
+ // SAFETY: By C API contract and type invariant, `cmd_flags` is valid for read
+ unsafe { (*self.0.get()).cmd_flags & ((1 << bindings::REQ_OP_BITS) - 1) }
+ }
+
+ /// Get the target sector for the request.
+ #[inline(always)]
+ pub fn sector(&self) -> u64 {
+ // SAFETY: By type invariant of `Self`, `self.0` is valid and live.
+ unsafe { (*self.0.get()).__sector }
+ }
+
+ /// Get the size of the request in number of sectors.
+ #[inline(always)]
+ pub fn sectors(&self) -> u32 {
+ self.bytes() >> crate::block::SECTOR_SHIFT
+ }
+
+ /// Get the size of the request in bytes.
+ #[inline(always)]
+ pub fn bytes(&self) -> u32 {
+ // SAFETY: By type invariant of `Self`, `self.0` is valid and live.
+ unsafe { (*self.0.get()).__data_len }
+ }
+
+ /// Borrow the queue data from the request queue associated with this request.
+ pub fn queue_data(&self) -> <T::QueueData as ForeignOwnable>::Borrowed<'_> {
+ // SAFETY: By type invariants of `Request`, `self.0` is a valid request.
+ unsafe { T::QueueData::borrow((*(*self.0.get()).q).queuedata) }
+ }
+}
+
/// A wrapper around a blk-mq [`struct request`]. This represents an IO request.
///
/// # Lifetime
@@ -96,9 +196,28 @@
/// [`struct request`]: srctree/include/linux/blk-mq.h
///
#[repr(transparent)]
-pub struct Request<T>(Opaque<bindings::request>, PhantomData<T>);
+pub struct Request<T>(RequestInner<T>);
+
+impl<T: Operations> Deref for Request<T> {
+ type Target = RequestInner<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
impl<T: Operations> Request<T> {
+ /// Create a `Owned<Request>` from a request pointer.
+ ///
+ /// # Safety
+ ///
+ /// - `ptr` must satisfy invariants of `Request`.
+ /// - The refcount of the request pointed to by `ptr` must be 0.
+ pub(crate) unsafe fn from_raw(ptr: *mut bindings::request) -> Owned<Self> {
+ // SAFETY: By function safety requirements, `ptr` is valid for use as `Owned<Request>`.
+ unsafe { Owned::from_raw(NonNull::<Self>::new_unchecked(ptr.cast())) }
+ }
+
/// Create an [`ARef<Request>`] from a [`struct request`] pointer.
///
/// # Safety
@@ -120,7 +239,7 @@ pub(crate) unsafe fn aref_from_raw(ptr: *mut bindings::request) -> ARef<Self> {
pub fn command(&self) -> u32 {
use core::ops::BitAnd;
// SAFETY: By C API contract and type invariant, `cmd_flags` is valid for read
- unsafe { (*self.0.get()).cmd_flags }.bitand((1u32 << bindings::REQ_OP_BITS) - 1)
+ unsafe { (*self.0 .0.get()).cmd_flags }.bitand((1u32 << bindings::REQ_OP_BITS) - 1)
}
/// Complete the request by scheduling `Operations::complete` for
@@ -145,7 +264,7 @@ pub fn complete(this: ARef<Self>) {
pub fn bio(&self) -> Option<&Bio> {
// SAFETY: By type invariant of `Self`, `self.0` is valid and the deref
// is safe.
- let ptr = unsafe { (*self.0.get()).bio };
+ let ptr = unsafe { (*self.0 .0.get()).bio };
// SAFETY: By C API contract, if `bio` is not null it will have a
// positive refcount at least for the duration of the lifetime of
// `&self`.
@@ -157,7 +276,7 @@ pub fn bio(&self) -> Option<&Bio> {
pub fn bio_mut(self: Pin<&mut Self>) -> Option<Pin<&mut Bio>> {
// SAFETY: By type invariant of `Self`, `self.0` is valid and the deref
// is safe.
- let ptr = unsafe { (*self.0.get()).bio };
+ let ptr = unsafe { (*self.0 .0.get()).bio };
// SAFETY: By C API contract, if `bio` is not null it will have a
// positive refcount at least for the duration of the lifetime of
// `&mut self`.
@@ -171,25 +290,11 @@ pub fn bio_iter_mut<'a>(self: &'a mut Owned<Self>) -> BioIterator<'a> {
// `NonNull::new` will return `None` if the pointer is null.
BioIterator {
// SAFETY: By type invariant `self.0` is a valid `struct request`.
- bio: NonNull::new(unsafe { (*self.0.get()).bio.cast() }),
+ bio: NonNull::new(unsafe { (*self.0 .0.get()).bio.cast() }),
_p: PhantomData,
}
}
- /// Get the target sector for the request.
- #[inline(always)]
- pub fn sector(&self) -> u64 {
- // SAFETY: By type invariant of `Self`, `self.0` is valid and live.
- unsafe { (*self.0.get()).__sector }
- }
-
- /// Get the size of the request in number of sectors.
- #[inline(always)]
- pub fn sectors(&self) -> u32 {
- // SAFETY: By type invariant of `Self`, `self.0` is valid and live.
- (unsafe { (*self.0.get()).__data_len }) >> crate::block::SECTOR_SHIFT
- }
-
/// Return a pointer to the [`RequestDataWrapper`] stored in the private area
/// of the request structure.
///
@@ -328,10 +433,10 @@ impl<T: Operations> Owned<Request<T>> {
/// `self.wrapper_ref().refcount() == 0`.
///
/// This can only be called once in the request life cycle.
- pub(crate) unsafe fn start_unchecked(&mut self) {
+ pub unsafe fn start_unchecked(&mut self) {
// SAFETY: By type invariant, `self.0` is a valid `struct request` and
// we have exclusive access.
- unsafe { bindings::blk_mq_start_request(self.0.get()) };
+ unsafe { bindings::blk_mq_start_request(self.0 .0.get()) };
}
/// Notify the block layer that the request has been completed without errors.
@@ -341,7 +446,7 @@ pub fn end_ok(self) {
/// Notify the block layer that the request has been completed.
pub fn end(self, status: u8) {
- let request_ptr = self.0.get().cast();
+ let request_ptr = self.0 .0.get().cast();
core::mem::forget(self);
// SAFETY: By type invariant, `this.0` was a valid `struct request`. The
// existence of `self` guarantees that there are no `ARef`s pointing to
--
2.51.2
^ permalink raw reply related
* [PATCH v2 68/83] block: rust: add an abstraction for `struct rq_list`
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add the `RequestList` type as a safe wrapper around the C `struct
rq_list`. This type provides methods to iterate over and manipulate
lists of block requests, which is needed for implementing the
`queue_rqs` callback.
The abstraction includes methods for popping requests from the list,
checking if the list is empty, and peeking at the head request.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/helpers/blk.c | 26 ++++++++
rust/kernel/block/mq.rs | 2 +
rust/kernel/block/mq/request_list.rs | 119 +++++++++++++++++++++++++++++++++++
3 files changed, 147 insertions(+)
diff --git a/rust/helpers/blk.c b/rust/helpers/blk.c
index 500e3c6fd951..422289d617ae 100644
--- a/rust/helpers/blk.c
+++ b/rust/helpers/blk.c
@@ -27,3 +27,29 @@ bool rust_helper_blk_mq_add_to_batch(struct request *req,
{
return blk_mq_add_to_batch(req, iob, is_error, complete);
}
+
+__rust_helper struct request *rust_helper_rq_list_pop(struct rq_list *rl)
+{
+ return rq_list_pop(rl);
+}
+
+__rust_helper int rust_helper_rq_list_empty(const struct rq_list *rl)
+{
+ return rq_list_empty(rl);
+}
+
+__rust_helper void rust_helper_rq_list_add_tail(struct rq_list *rl,
+ struct request *rq)
+{
+ rq_list_add_tail(rl, rq);
+}
+
+__rust_helper void rust_helper_rq_list_init(struct rq_list *rl)
+{
+ rq_list_init(rl);
+}
+
+__rust_helper struct request *rust_helper_rq_list_peek(struct rq_list *rl)
+{
+ return rq_list_peek(rl);
+}
diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index 7c346be843e1..e8f0d03f2ff7 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -129,6 +129,7 @@
pub mod gen_disk;
mod operations;
mod request;
+mod request_list;
mod request_queue;
pub mod tag_set;
@@ -148,6 +149,7 @@
Request,
RequestTimerHandle, //
};
+pub use request_list::RequestList;
pub use request_queue::RequestQueue;
pub use tag_set::{
QueueType,
diff --git a/rust/kernel/block/mq/request_list.rs b/rust/kernel/block/mq/request_list.rs
new file mode 100644
index 000000000000..82e6005126f7
--- /dev/null
+++ b/rust/kernel/block/mq/request_list.rs
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use core::marker::PhantomData;
+
+use crate::{
+ owned::Owned,
+ types::Opaque, //
+};
+
+use super::{
+ IdleRequest,
+ Operations, //
+};
+
+/// A list of [`Request`].
+///
+/// # INVARIANTS
+///
+/// - `self.inner` is always a valid list, meaning the `next` and `prev`
+/// pointers point to valid requests, or are both null.
+/// - All requests in the list are valid for use as `IdleRequest<T>`.
+#[repr(transparent)]
+pub struct RequestList<T: Operations> {
+ inner: Opaque<bindings::rq_list>,
+ _p: PhantomData<T>,
+}
+
+impl<T: Operations> RequestList<T> {
+ /// Create a new [`RequestList`].
+ pub fn new() -> Self {
+ let this = Self {
+ inner: Opaque::zeroed(),
+ _p: PhantomData,
+ };
+
+ // NOTE: We are actually good to go, but we call the C initializer for forward
+ // compatibility.
+ // SAFETY: `this.inner` is a valid allocation for use as `bindings::rq_list!.
+ unsafe { bindings::rq_list_init(this.inner.get()) }
+
+ //INVARIANT: `self.inner` was initialized above and is empty.
+ this
+ }
+
+ /// Create a mutable reference to a [`RequestList`] from a raw pointer.
+ ///
+ /// # SAFETY
+ /// - The list pointed to by `ptr` must satisfy the invariants of `Self`.
+ /// - The list pointed to by `ptr` must remain valid for use as a mutable reference for the
+ /// duration of `'a`.
+ pub unsafe fn from_raw<'a>(ptr: *mut bindings::rq_list) -> &'a mut Self {
+ // SAFETY:
+ // - RequestList is transparent.
+ // - By function safety requirements, `ptr` is valid for us as a mutable reference.
+ unsafe { &mut (*ptr.cast()) }
+ }
+
+ /// Check if the list is empty.
+ pub fn empty(&self) -> bool {
+ // SAFETY: By type invariant, self.inner is valid.
+ let ret = unsafe { bindings::rq_list_empty(self.inner.get()) };
+ ret != 0
+ }
+
+ /// Pop a request from the list.
+ ///
+ /// Returns [`None`] if the list is empty.
+ pub fn pop(&mut self) -> Option<Owned<IdleRequest<T>>> {
+ // SAFETY: By type invariant `self.inner` is a valid list.
+ let ptr = unsafe { bindings::rq_list_pop(self.inner.get()) };
+
+ if !ptr.is_null() {
+ // SAFETY: If `rq_list_pop` returns a non-null pointer, it points to a valid request. By
+ // type invariant all requests in this list are valid for use as `IdleRequest`.
+ Some(unsafe { IdleRequest::from_raw(ptr) })
+ } else {
+ None
+ }
+ }
+
+ /// Push a request on the tail of the list.
+ pub fn push_tail(&mut self, rq: Owned<IdleRequest<T>>) {
+ let ptr = rq.as_raw();
+ core::mem::forget(rq);
+ // INVARIANT: rq is an `IdleRequest<T>`.
+ // SAFETY: By type invariant, `self.inner` is a valid list.
+ unsafe { bindings::rq_list_add_tail(self.inner.get(), ptr) };
+ }
+
+ /// Peek at the head of the list.
+ ///
+ /// Returns a null pointer if the list is empty.
+ pub fn peek_raw(&self) -> *mut bindings::request {
+ // SAFETY: By type invariant, `self.inner` is a valid list.
+ unsafe { bindings::rq_list_peek(self.inner.get()) }
+ }
+}
+
+impl<T: Operations> Default for RequestList<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<T: Operations> Drop for RequestList<T> {
+ fn drop(&mut self) {
+ while let Some(rq) = self.pop() {
+ drop(rq)
+ }
+ }
+}
+
+impl<T: Operations> Iterator for &mut RequestList<T> {
+ type Item = Owned<IdleRequest<T>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.pop()
+ }
+}
--
2.51.2
^ permalink raw reply related
* [PATCH v2 57/83] block: rust: add accessors to `TagSet`
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add `hw_queue_count()` to query the number of hardware queues and
`data()` to borrow the private tag set data associated with a `TagSet`.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/block/mq/tag_set.rs | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/rust/kernel/block/mq/tag_set.rs b/rust/kernel/block/mq/tag_set.rs
index e62dfd267fd9..858c1b952b00 100644
--- a/rust/kernel/block/mq/tag_set.rs
+++ b/rust/kernel/block/mq/tag_set.rs
@@ -4,8 +4,6 @@
//!
//! C header: [`include/linux/blk-mq.h`](srctree/include/linux/blk-mq.h)
-use core::pin::Pin;
-
use crate::{
alloc::NumaNode,
bindings,
@@ -26,7 +24,8 @@
};
use core::{
convert::TryInto,
- marker::PhantomData, //
+ marker::PhantomData,
+ pin::Pin, //
};
use pin_init::{
pin_data,
@@ -164,6 +163,22 @@ pub fn update_maps(self: Pin<&mut Self>, mut cb: impl FnMut(QueueMap)) -> Result
Ok(())
}
+
+ /// Return the number of hardware queues for this tag set.
+ pub fn hw_queue_count(&self) -> u32 {
+ // SAFETY: By type invariant, `self.inner` is valid.
+ unsafe { (*self.inner.get()).nr_hw_queues }
+ }
+
+ /// Borrow the [`T::TagSetData`] associated with this tag set.
+ pub fn data(&self) -> <T::TagSetData as ForeignOwnable>::Borrowed<'_> {
+ // SAFETY: By type invariant, `self.inner` is valid.
+ let ptr = unsafe { (*self.inner.get()).driver_data };
+
+ // SAFETY: `ptr` was created by `into_foreign` during initialization and the target is not
+ // converted back with `from_foreign` while `&self` is live.
+ unsafe { T::TagSetData::borrow(ptr) }
+ }
}
#[pinned_drop]
--
2.51.2
^ permalink raw reply related
* [PATCH v2 40/83] block: rust: add a method to get the request queue for a request
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add a method to `Request` for obtaining the associated `RequestQueue`.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/block/mq/request.rs | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/rust/kernel/block/mq/request.rs b/rust/kernel/block/mq/request.rs
index f94e9c2181d0..a05df2351c2c 100644
--- a/rust/kernel/block/mq/request.rs
+++ b/rust/kernel/block/mq/request.rs
@@ -39,6 +39,7 @@
ptr::NonNull, //
};
+use super::RequestQueue;
use crate::block::bio::{
Bio,
BioIterator, //
@@ -140,6 +141,12 @@ pub fn queue_data(&self) -> <T::QueueData as ForeignOwnable>::Borrowed<'_> {
// SAFETY: By type invariants of `Request`, `self.0` is a valid request.
unsafe { T::QueueData::borrow((*(*self.0.get()).q).queuedata) }
}
+
+ /// Get the request queue associated with this request.
+ pub fn queue(&self) -> &RequestQueue<T> {
+ // SAFETY: By type invariant, self.0 is guaranteed to be valid.
+ unsafe { RequestQueue::from_raw((*self.0.get()).q) }
+ }
}
/// A wrapper around a blk-mq [`struct request`]. This represents an IO request.
--
2.51.2
^ permalink raw reply related
* [PATCH v2 63/83] block: rust: add `Segment::copy_to_page_limit`
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add a method to `block::mq::bio::Segment` to copy a bounded amount of bytes
to a page.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/block/bio/vec.rs | 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/block/bio/vec.rs b/rust/kernel/block/bio/vec.rs
index 61d83a07397f..82e89a1d17c3 100644
--- a/rust/kernel/block/bio/vec.rs
+++ b/rust/kernel/block/bio/vec.rs
@@ -102,13 +102,38 @@ pub fn truncate(&mut self, new_len: u32) {
/// Returns the number of bytes copied.
#[inline(always)]
pub fn copy_to_page(&mut self, dst_page: Pin<&mut SafePage>, dst_offset: usize) -> usize {
+ self.copy_to_page_limit(dst_page, dst_offset, 0)
+ }
+
+ /// Copy data of this segment into `dst_page`.
+ ///
+ /// Copies at most `limit` bytes of data from the current offset to the next page boundary. That
+ /// is `PAGE_SIZE - (self.offeset() % PAGE_SIZE)` bytes of data. Data is placed at offset
+ /// `self.offset()` in the target page. This call will advance offset and reduce length of
+ /// `self`.
+ ///
+ /// If `limit` is zero it is ignored.
+ ///
+ /// Returns the number of bytes copied.
+ #[inline(always)]
+ pub fn copy_to_page_limit(
+ &mut self,
+ dst_page: Pin<&mut SafePage>,
+ dst_offset: usize,
+ limit: usize,
+ ) -> usize {
// SAFETY: We are not moving out of `dst_page`.
let dst_page = unsafe { Pin::into_inner_unchecked(dst_page) };
let src_offset = self.offset() % PAGE_SIZE;
debug_assert!(dst_offset <= PAGE_SIZE);
- let length = (PAGE_SIZE - src_offset)
+ let mut length = (PAGE_SIZE - src_offset)
.min(self.len() as usize)
.min(PAGE_SIZE - dst_offset);
+
+ if limit > 0 {
+ length = length.min(limit);
+ }
+
let page_idx = self.offset() / PAGE_SIZE;
// SAFETY: self.bio_vec is valid and thus bv_page must be a valid
--
2.51.2
^ permalink raw reply related
* [PATCH v2 64/83] block: rnull: add fua support
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add Forced Unit Access (FUA) support to rnull. When enabled via the `fua`
configfs attribute, the driver advertises FUA capability and handles FUA
requests by bypassing the volatile cache in the write path.
FUA support requires memory backing and write cache to be enabled.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/configfs.rs | 5 ++++
drivers/block/rnull/disk_storage.rs | 22 +++++++++++++----
drivers/block/rnull/disk_storage/page.rs | 1 +
drivers/block/rnull/rnull.rs | 41 ++++++++++++++++++++++++++------
4 files changed, 58 insertions(+), 11 deletions(-)
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index 0637c1e0ab22..8195d645ecc6 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -128,6 +128,7 @@ fn make_group(
zone_max_active: 25,
zone_append_max_sectors: 26,
poll_queues: 27,
+ fua: 28,
],
};
@@ -169,6 +170,7 @@ fn make_group(
zone_max_active: 0,
zone_append_max_sectors: u32::MAX,
poll_queues: 0,
+ fua: true,
}),
}),
core::iter::empty(),
@@ -256,6 +258,7 @@ struct DeviceConfigInner {
zone_max_active: u32,
zone_append_max_sectors: u32,
poll_queues: u32,
+ fua: bool,
}
#[vtable]
@@ -322,6 +325,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
zone_max_open: guard.zone_max_open,
zone_max_active: guard.zone_max_active,
zone_append_max_sectors: guard.zone_append_max_sectors,
+ forced_unit_access: guard.fua,
})?);
guard.powered = true;
} else if guard.powered && !power_op {
@@ -515,3 +519,4 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
}
})
);
+configfs_simple_bool_field!(DeviceConfig, 28, fua);
diff --git a/drivers/block/rnull/disk_storage.rs b/drivers/block/rnull/disk_storage.rs
index 7667830bd616..4a9bf480221f 100644
--- a/drivers/block/rnull/disk_storage.rs
+++ b/drivers/block/rnull/disk_storage.rs
@@ -92,6 +92,10 @@ pub(crate) fn flush(&self, hw_data: &Pin<&SpinLock<HwQueueContext>>) -> Result {
let mut access = self.access(&mut tree_guard, &mut hw_data_guard, None);
access.flush()
}
+
+ pub(crate) fn cache_enabled(&self) -> bool {
+ self.cache_size > 0
+ }
}
pub(crate) struct DiskStorageAccess<'a, 'b, 'c> {
@@ -205,7 +209,7 @@ fn flush(&mut self) -> Result {
Ok(())
}
- fn get_cache_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> {
+ fn get_or_alloc_cache_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> {
let index = Self::to_index(sector);
match self.cache_guard.entry(index) {
@@ -239,6 +243,12 @@ fn get_cache_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> {
}
}
+ pub(crate) fn get_cache_page(&mut self, sector: u64) -> Option<&mut NullBlockPage> {
+ let index = Self::to_index(sector);
+
+ self.cache_guard.get_mut(index)
+ }
+
fn get_disk_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> {
let index = Self::to_index(sector);
@@ -256,9 +266,13 @@ fn get_disk_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> {
Ok(page)
}
- pub(crate) fn get_write_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> {
- let page = if self.disk_storage.cache_size > 0 {
- self.get_cache_page(sector)?
+ pub(crate) fn get_write_page(
+ &mut self,
+ sector: u64,
+ bypass_cache: bool,
+ ) -> Result<&mut NullBlockPage> {
+ let page = if self.disk_storage.cache_size > 0 && !bypass_cache {
+ self.get_or_alloc_cache_page(sector)?
} else {
self.get_disk_page(sector)?
};
diff --git a/drivers/block/rnull/disk_storage/page.rs b/drivers/block/rnull/disk_storage/page.rs
index 88dc9a2476bd..846269d31c63 100644
--- a/drivers/block/rnull/disk_storage/page.rs
+++ b/drivers/block/rnull/disk_storage/page.rs
@@ -15,6 +15,7 @@
uapi::PAGE_SECTORS, //
};
+// TODO: Use rust bitmap
static_assert!((PAGE_SIZE >> SECTOR_SHIFT) <= 64);
pub(crate) struct NullBlockPage {
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 0695cbd07f1d..c3126b923367 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -191,6 +191,10 @@
default: 0,
description: "Number of IOPOLL submission queues.",
},
+ fua: bool {
+ default: true,
+ description: "Enable/disable FUA support when cache_size is used.",
+ },
},
}
@@ -267,6 +271,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
zone_max_open: module_parameters::zone_max_open.value(),
zone_max_active: module_parameters::zone_max_active.value(),
zone_append_max_sectors: module_parameters::zone_append_max_sectors.value(),
+ forced_unit_access: module_parameters::fua.value(),
})?;
disks.push(disk, GFP_KERNEL)?;
}
@@ -307,6 +312,7 @@ struct NullBlkOptions<'a> {
zone_max_active: u32,
#[cfg_attr(not(CONFIG_BLK_DEV_ZONED), allow(dead_code))]
zone_append_max_sectors: u32,
+ forced_unit_access: bool,
}
#[pin_data]
@@ -422,6 +428,7 @@ fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
zone_max_active,
#[cfg_attr(not(CONFIG_BLK_DEV_ZONED), allow(unused_variables))]
zone_append_max_sectors,
+ forced_unit_access,
} = options;
let memory_backed = tag_set.memory_backed;
@@ -439,9 +446,10 @@ fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
return Err(code::EINVAL);
}
+ let s = storage.clone();
let queue_data = Arc::try_pin_init(
try_pin_init!(Self {
- storage,
+ storage: s,
irq_mode,
completion_time,
memory_backed,
@@ -474,7 +482,9 @@ fn new(options: NullBlkOptions<'_>) -> Result<Arc<GenDisk<Self>>> {
.capacity_sectors(device_capacity_sectors)
.logical_block_size(block_size_bytes)?
.physical_block_size(block_size_bytes)?
- .rotational(rotational);
+ .rotational(rotational)
+ .write_cache(storage.cache_enabled())
+ .forced_unit_access(forced_unit_access && storage.cache_enabled());
#[cfg(CONFIG_BLK_DEV_ZONED)]
{
@@ -553,6 +563,7 @@ fn write<'a, 'b, 'c>(
hw_data_guard: &'b mut SpinLockGuard<'c, HwQueueContext>,
mut sector: u64,
mut segment: Segment<'_>,
+ bypass_cache: bool,
) -> Result {
let mut sheaf: Option<XArraySheaf<'_>> = None;
@@ -561,7 +572,13 @@ fn write<'a, 'b, 'c>(
let mut access = self.storage.access(tree_guard, hw_data_guard, sheaf);
- let page = access.get_write_page(sector)?;
+ if bypass_cache {
+ if let Some(page) = access.get_cache_page(sector) {
+ page.set_free(sector);
+ }
+ }
+
+ let page = access.get_write_page(sector, bypass_cache)?;
page.set_occupied(sector);
// CAST: Page offset always fits in 32 bits.
@@ -569,7 +586,11 @@ fn write<'a, 'b, 'c>(
((sector & u64::from(block::PAGE_SECTOR_MASK)) << block::SECTOR_SHIFT) as usize;
// CAST: Casting from `usize` to `u64` never overflows.
- sector += segment.copy_to_page(page.page_mut().as_pin_mut(), page_offset) as u64
+ sector += segment.copy_to_page_limit(
+ page.page_mut().as_pin_mut(),
+ page_offset,
+ self.block_size_bytes.try_into()?,
+ ) as u64
>> block::SECTOR_SHIFT;
sheaf = access.sheaf;
@@ -632,6 +653,8 @@ fn transfer(
let mut hw_data_guard = hw_data.lock();
let mut tree_guard = self.storage.lock();
+ let skip_cache = rq.flags().contains(mq::RequestFlag::ForcedUnitAccess);
+
for bio in rq.bio_iter_mut() {
let segment_iter = bio.segment_iter();
for mut segment in segment_iter {
@@ -641,9 +664,13 @@ fn transfer(
let length_sectors_allowed = segment_length_sectors.min(max_remaining_sectors);
segment.truncate(length_sectors_allowed << SECTOR_SHIFT);
match command {
- mq::Command::Write => {
- self.write(&mut tree_guard, &mut hw_data_guard, sector, segment)?
- }
+ mq::Command::Write => self.write(
+ &mut tree_guard,
+ &mut hw_data_guard,
+ sector,
+ segment,
+ skip_cache,
+ )?,
mq::Command::Read => {
self.read(&mut tree_guard, &mut hw_data_guard, sector, segment)?
}
--
2.51.2
^ permalink raw reply related
* [PATCH v2 22/83] block: rust: mq: add max_hw_discard_sectors support to GenDiskBuilder
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add support for configuring the maximum hardware discard sectors
through GenDiskBuilder. This allows block devices to specify their
discard/trim capabilities.
Setting this value to 0 (the default) indicates that discard is not
supported by the device. Non-zero values specify the maximum number
of sectors that can be discarded in a single operation.
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/block/mq/gen_disk.rs | 34 ++++++++++++++++++++++++++++++----
1 file changed, 30 insertions(+), 4 deletions(-)
diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
index b36d24382cc3..2b204b0ed49a 100644
--- a/rust/kernel/block/mq/gen_disk.rs
+++ b/rust/kernel/block/mq/gen_disk.rs
@@ -7,14 +7,27 @@
use crate::{
bindings,
- block::mq::{Operations, TagSet},
- error::{self, from_err_ptr, Result},
- fmt::{self, Write},
+ block::mq::{
+ Operations,
+ TagSet, //
+ },
+ error::{
+ self,
+ from_err_ptr,
+ Result, //
+ },
+ fmt::{
+ self,
+ Write, //
+ },
prelude::*,
static_lock_class,
str::NullTerminatedFormatter,
sync::Arc,
- types::{ForeignOwnable, ScopeGuard},
+ types::{
+ ForeignOwnable,
+ ScopeGuard, //
+ },
};
/// A builder for [`GenDisk`].
@@ -25,6 +38,7 @@ pub struct GenDiskBuilder {
logical_block_size: u32,
physical_block_size: u32,
capacity_sectors: u64,
+ max_hw_discard_sectors: u32,
}
impl Default for GenDiskBuilder {
@@ -34,6 +48,7 @@ fn default() -> Self {
logical_block_size: bindings::PAGE_SIZE as u32,
physical_block_size: bindings::PAGE_SIZE as u32,
capacity_sectors: 0,
+ max_hw_discard_sectors: 0,
}
}
}
@@ -94,6 +109,16 @@ pub fn capacity_sectors(mut self, capacity: u64) -> Self {
self
}
+ /// Set the maximum amount of sectors the underlying hardware device can
+ /// discard/trim in a single operation.
+ ///
+ /// Setting 0 (default) here will cause the disk to report discard not
+ /// supported.
+ pub fn max_hw_discard_sectors(mut self, max_hw_discard_sectors: u32) -> Self {
+ self.max_hw_discard_sectors = max_hw_discard_sectors;
+ self
+ }
+
/// Build a new `GenDisk` and add it to the VFS.
pub fn build<T: Operations>(
self,
@@ -111,6 +136,7 @@ pub fn build<T: Operations>(
lim.logical_block_size = self.logical_block_size;
lim.physical_block_size = self.physical_block_size;
+ lim.max_hw_discard_sectors = self.max_hw_discard_sectors;
if self.rotational {
lim.features = bindings::BLK_FEAT_ROTATIONAL;
}
--
2.51.2
^ permalink raw reply related
* [PATCH v2 58/83] block: rnull: add polled completion support
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add support for polled I/O completion in rnull. This feature requires
configuring poll queues via the `poll_queues` attribute.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/configfs.rs | 19 +++++-
drivers/block/rnull/rnull.rs | 133 ++++++++++++++++++++++++++++++++++++----
2 files changed, 139 insertions(+), 13 deletions(-)
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index f866595a263c..0637c1e0ab22 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -81,7 +81,7 @@ impl AttributeOperations<0> for Config {
writer.write_str(
"blocksize,size,rotational,irqmode,completion_nsec,memory_backed,\
submit_queues,use_per_node_hctx,discard,blocking,shared_tags,\
- zoned,zone_size,zone_capacity\n",
+ zoned,zone_size,zone_capacity,poll_queues\n",
)?;
Ok(writer.bytes_written())
}
@@ -127,6 +127,7 @@ fn make_group(
zone_max_open: 24,
zone_max_active: 25,
zone_append_max_sectors: 26,
+ poll_queues: 27,
],
};
@@ -167,6 +168,7 @@ fn make_group(
zone_max_open: 0,
zone_max_active: 0,
zone_append_max_sectors: u32::MAX,
+ poll_queues: 0,
}),
}),
core::iter::empty(),
@@ -253,6 +255,7 @@ struct DeviceConfigInner {
zone_max_open: u32,
zone_max_active: u32,
zone_append_max_sectors: u32,
+ poll_queues: u32,
}
#[vtable]
@@ -305,6 +308,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
shared_tag_set: guard.shared_tags.then(|| guard.shared_tag_set.clone()),
tag_set: crate::TagSetOptions {
submit_queues: guard.submit_queues,
+ poll_queues: guard.poll_queues,
home_node: guard.home_node,
blocking: guard.blocking,
memory_backed: guard.memory_backed,
@@ -498,3 +502,16 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
configfs_simple_field!(DeviceConfig, 24, zone_max_open, u32);
configfs_simple_field!(DeviceConfig, 25, zone_max_active, u32);
configfs_simple_field!(DeviceConfig, 26, zone_append_max_sectors, u32);
+configfs_simple_field!(
+ DeviceConfig,
+ 27,
+ poll_queues,
+ u32,
+ check(|value| {
+ if value > kernel::cpu::num_possible_cpus() {
+ Err(kernel::error::code::EINVAL)
+ } else {
+ Ok(())
+ }
+ })
+);
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 076493f92516..edb4ef53d6ad 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -33,6 +33,7 @@
GenDisk,
GenDiskRef, //
},
+ IoCompletionBatch,
Operations,
TagSet, //
},
@@ -186,6 +187,10 @@
default: 0,
description: "Maximum size of a zone append command (in 512B sectors). Specify 0 for no zone append.",
},
+ poll_queues: u32 {
+ default: 0,
+ description: "Number of IOPOLL submission queues.",
+ },
},
}
@@ -207,6 +212,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
} else {
module_parameters::submit_queues.value()
};
+ let poll_queues = module_parameters::poll_queues.value();
let home_node = module_parameters::home_node.value();
let blocking = module_parameters::blocking.value();
let memory_backed = module_parameters::memory_backed.value();
@@ -215,6 +221,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
let shared_tag_set = NullBlkDevice::build_tag_set(TagSetOptions {
submit_queues,
+ poll_queues,
home_node,
blocking,
memory_backed,
@@ -246,6 +253,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
.then(|| shared_tag_set.clone()),
tag_set: TagSetOptions {
submit_queues,
+ poll_queues,
home_node,
blocking,
memory_backed,
@@ -325,6 +333,7 @@ struct NullBlkDevice {
struct TagSetOptions {
submit_queues: u32,
+ poll_queues: u32,
home_node: i32,
blocking: bool,
memory_backed: bool,
@@ -338,6 +347,7 @@ impl NullBlkDevice {
fn build_tag_set(options: TagSetOptions) -> Result<Arc<TagSet<Self>>> {
let TagSetOptions {
submit_queues,
+ poll_queues,
home_node,
blocking,
memory_backed,
@@ -364,7 +374,21 @@ fn build_tag_set(options: TagSetOptions) -> Result<Arc<TagSet<Self>>> {
}
Arc::pin_init(
- TagSet::new(submit_queues, (), hw_queue_depth, 1, numa_node, flags),
+ TagSet::new(
+ submit_queues + poll_queues,
+ KBox::new(
+ NullBlkTagsetData {
+ queue_depth: hw_queue_depth,
+ submit_queue_count: submit_queues,
+ poll_queue_count: poll_queues,
+ },
+ GFP_KERNEL,
+ )?,
+ hw_queue_depth,
+ if poll_queues == 0 { 1 } else { 3 },
+ numa_node,
+ flags,
+ ),
GFP_KERNEL,
)
}
@@ -729,6 +753,7 @@ fn run(
struct HwQueueContext {
page: Option<KBox<disk_storage::NullBlockPage>>,
+ poll_queue: kernel::alloc::ringbuffer::KRingBuffer<Owned<mq::Request<NullBlkDevice>>>,
}
#[pin_data]
@@ -757,11 +782,17 @@ impl HasHrTimer<Self> for Pdu {
}
}
+struct NullBlkTagsetData {
+ queue_depth: u32,
+ submit_queue_count: u32,
+ poll_queue_count: u32,
+}
+
#[vtable]
impl Operations for NullBlkDevice {
type QueueData = Arc<Self>;
type RequestData = Pdu;
- type TagSetData = ();
+ type TagSetData = KBox<NullBlkTagsetData>;
type HwData = Pin<KBox<SpinLock<HwQueueContext>>>;
fn new_request_data() -> impl PinInit<Self::RequestData> {
@@ -777,7 +808,7 @@ fn queue_rq(
this: ArcBorrow<'_, Self>,
rq: Owned<mq::IdleRequest<Self>>,
_is_last: bool,
- _is_poll: bool,
+ is_poll: bool,
) -> BlkResult {
if this.bandwidth_limit != 0 {
if !this.bandwidth_timer.active() {
@@ -814,13 +845,29 @@ fn queue_rq(
#[cfg(not(CONFIG_BLK_DEV_ZONED))]
this.handle_regular_command(&hw_data, &mut rq)?;
- match this.irq_mode {
- IRQMode::None => Self::end_request(rq),
- IRQMode::Soft => mq::Request::complete(rq.into()),
- IRQMode::Timer => {
- OwnableRefCounted::into_shared(rq)
- .start(this.completion_time)
- .dismiss();
+ if is_poll {
+ // NOTE: We lack the ability to insert `Owned<Request>` into a
+ // `kernel::list::List`, so we use a `RingBuffer` instead. The
+ // drawback of this is that we have to allocate the space for the
+ // ring buffer during drive initialization, and we have to hold the
+ // lock protecting the list until we have processed all the requests
+ // in the list. Change to a linked list when the kernel gets this
+ // ability.
+
+ // NOTE: We are processing requests during submit rather than during
+ // poll. This is different from C driver. C driver does processing
+ // during poll.
+
+ hw_data.lock().poll_queue.push_head(rq)?;
+ } else {
+ match this.irq_mode {
+ IRQMode::None => Self::end_request(rq),
+ IRQMode::Soft => mq::Request::complete(rq.into()),
+ IRQMode::Timer => {
+ OwnableRefCounted::into_shared(rq)
+ .start(this.completion_time)
+ .dismiss();
+ }
}
}
Ok(())
@@ -828,8 +875,40 @@ fn queue_rq(
fn commit_rqs(_hw_data: Pin<&SpinLock<HwQueueContext>>, _queue_data: ArcBorrow<'_, Self>) {}
- fn init_hctx(_tagset_data: (), _hctx_idx: u32) -> Result<Self::HwData> {
- KBox::pin_init(new_spinlock!(HwQueueContext { page: None }), GFP_KERNEL)
+ fn poll(
+ hw_data: Pin<&SpinLock<HwQueueContext>>,
+ _this: ArcBorrow<'_, Self>,
+ batch: &mut IoCompletionBatch<Self>,
+ ) -> Result<bool> {
+ let mut guard = hw_data.lock();
+ let mut completed = false;
+
+ while let Some(rq) = guard.poll_queue.pop_tail() {
+ let status = rq.data_ref().error.load(ordering::Relaxed);
+ rq.data_ref().error.store(0, ordering::Relaxed);
+
+ // TODO: check error handling via status
+ if let Err(rq) = batch.add_request(rq, status != 0) {
+ Self::end_request(rq);
+ }
+
+ completed = true;
+ }
+
+ Ok(completed)
+ }
+
+ fn init_hctx(tagset_data: &NullBlkTagsetData, _hctx_idx: u32) -> Result<Self::HwData> {
+ KBox::pin_init(
+ new_spinlock!(HwQueueContext {
+ page: None,
+ poll_queue: kernel::alloc::ringbuffer::KRingBuffer::new(
+ tagset_data.queue_depth.try_into()?,
+ GFP_KERNEL,
+ )?,
+ }),
+ GFP_KERNEL,
+ )
}
fn complete(rq: ARef<mq::Request<Self>>) {
@@ -849,4 +928,34 @@ fn report_zones(
) -> Result<u32> {
Self::report_zones_internal(disk, sector, nr_zones, callback)
}
+
+ fn map_queues(tag_set: Pin<&mut TagSet<Self>>) {
+ let mut submit_queue_count = tag_set.data().submit_queue_count;
+ let mut poll_queue_count = tag_set.data().poll_queue_count;
+
+ if tag_set.hw_queue_count() != submit_queue_count + poll_queue_count {
+ pr_warn!(
+ "tag set has unexpected hardware queue count: {}\n",
+ tag_set.hw_queue_count()
+ );
+ submit_queue_count = 1;
+ poll_queue_count = 0;
+ }
+
+ let mut offset = 0;
+ tag_set
+ .update_maps(|mut qmap| {
+ use mq::QueueType::*;
+ let queue_count = match qmap.kind() {
+ Default => submit_queue_count,
+ Read => 0,
+ Poll => poll_queue_count,
+ };
+ qmap.set_queue_count(queue_count);
+ qmap.set_offset(offset);
+ offset += queue_count;
+ qmap.map_queues();
+ })
+ .unwrap()
+ }
}
--
2.51.2
^ permalink raw reply related
* [PATCH v2 59/83] block: rnull: add REQ_OP_FLUSH support
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add support for handling flush requests in rnull. When memory backing
and write cache are enabled, flush requests trigger a cache flush
operation that writes all dirty cache pages to the backing store.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/disk_storage.rs | 45 +++++++++++++++++++++++++++++++------
drivers/block/rnull/rnull.rs | 31 +++++++++++++++++--------
2 files changed, 60 insertions(+), 16 deletions(-)
diff --git a/drivers/block/rnull/disk_storage.rs b/drivers/block/rnull/disk_storage.rs
index 82de1f656f68..7667830bd616 100644
--- a/drivers/block/rnull/disk_storage.rs
+++ b/drivers/block/rnull/disk_storage.rs
@@ -85,6 +85,13 @@ pub(crate) fn discard(
remaining_bytes -= processed;
}
}
+
+ pub(crate) fn flush(&self, hw_data: &Pin<&SpinLock<HwQueueContext>>) -> Result {
+ let mut tree_guard = self.lock();
+ let mut hw_data_guard = hw_data.lock();
+ let mut access = self.access(&mut tree_guard, &mut hw_data_guard, None);
+ access.flush()
+ }
}
pub(crate) struct DiskStorageAccess<'a, 'b, 'c> {
@@ -120,18 +127,32 @@ fn to_sector(index: usize) -> u64 {
(index << block::PAGE_SECTORS_SHIFT) as u64
}
+ fn extract_cache_page(&mut self) -> Result<Option<KBox<NullBlockPage>>> {
+ Self::extract_cache_page_inner(
+ &mut self.cache_guard,
+ &mut self.disk_guard,
+ self.disk_storage,
+ self.hw_data_guard,
+ self.sheaf.as_mut(),
+ )
+ }
+
fn extract_cache_page_inner<'g>(
cache_guard: &mut xarray::Guard<'g, TreeNode>,
disk_guard: &mut xarray::Guard<'g, TreeNode>,
disk_storage: &DiskStorage,
hw_data: &mut HwQueueContext,
sheaf: Option<&mut XArraySheaf<'_>>,
- ) -> Result<KBox<NullBlockPage>> {
- let cache_entry = cache_guard
- .find_next_entry_circular(
- disk_storage.next_flush_sector.load(ordering::Relaxed) as usize
- )
- .expect("Expected to find a page in the cache");
+ ) -> Result<Option<KBox<NullBlockPage>>> {
+ let cache_entry = cache_guard.find_next_entry_circular(
+ disk_storage.next_flush_sector.load(ordering::Relaxed) as usize,
+ );
+
+ let cache_entry = if let Some(entry) = cache_entry {
+ entry
+ } else {
+ return Ok(None);
+ };
let index = cache_entry.index();
@@ -172,7 +193,16 @@ fn extract_cache_page_inner<'g>(
}
};
- Ok(page)
+ Ok(Some(page))
+ }
+
+ fn flush(&mut self) -> Result {
+ if self.disk_storage.cache_size > 0 {
+ while let Some(page) = self.extract_cache_page()? {
+ drop(page);
+ }
+ }
+ Ok(())
}
fn get_cache_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> {
@@ -197,6 +227,7 @@ fn get_cache_page(&mut self, sector: u64) -> Result<&mut NullBlockPage> {
self.hw_data_guard,
self.sheaf.as_mut(),
)?
+ .expect("Expected to find a page in the cache")
};
let xarray::Entry::Vacant(vacant_entry) = cache_guard.entry(index) else {
unreachable!("slot was vacant and we hold the lock")
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index edb4ef53d6ad..0695cbd07f1d 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -719,6 +719,18 @@ fn end_request(rq: Owned<mq::Request<Self>>) {
_ => rq.end(bindings::BLK_STS_IOERR),
}
}
+
+ fn complete_request(&self, rq: Owned<mq::Request<Self>>) {
+ match self.irq_mode {
+ IRQMode::None => Self::end_request(rq),
+ IRQMode::Soft => mq::Request::complete(rq.into()),
+ IRQMode::Timer => {
+ OwnableRefCounted::into_shared(rq)
+ .start(self.completion_time)
+ .dismiss();
+ }
+ }
+ }
}
impl_has_hr_timer! {
@@ -835,6 +847,15 @@ fn queue_rq(
let mut rq = rq.start();
+ if rq.command() == mq::Command::Flush {
+ if this.memory_backed {
+ this.storage.flush(&hw_data)?;
+ }
+ this.complete_request(rq);
+
+ return Ok(());
+ }
+
#[cfg(CONFIG_BLK_DEV_ZONED)]
if this.zoned.enabled {
this.handle_zoned_command(&hw_data, &mut rq)?;
@@ -860,15 +881,7 @@ fn queue_rq(
hw_data.lock().poll_queue.push_head(rq)?;
} else {
- match this.irq_mode {
- IRQMode::None => Self::end_request(rq),
- IRQMode::Soft => mq::Request::complete(rq.into()),
- IRQMode::Timer => {
- OwnableRefCounted::into_shared(rq)
- .start(this.completion_time)
- .dismiss();
- }
- }
+ this.complete_request(rq);
}
Ok(())
}
--
2.51.2
^ permalink raw reply related
* [PATCH v2 47/83] block: rnull: add queue depth config option
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add a configfs attribute to configure the queue depth (number of tags)
for the rnull block device.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/configfs.rs | 5 +++++
drivers/block/rnull/rnull.rs | 11 ++++++++++-
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index a84854e7c358..2dfc87dff66a 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -118,6 +118,7 @@ fn make_group(
mbps: 16,
blocking: 17,
shared_tags: 18,
+ hw_queue_depth: 19
],
};
@@ -153,6 +154,7 @@ fn make_group(
blocking: false,
shared_tags: false,
shared_tag_set: self.shared_tag_set.clone(),
+ hw_queue_depth: 64,
}),
}),
core::iter::empty(),
@@ -231,6 +233,7 @@ struct DeviceConfigInner {
blocking: bool,
shared_tags: bool,
shared_tag_set: Arc<TagSet<NullBlkDevice>>,
+ hw_queue_depth: u32,
}
#[vtable]
@@ -274,6 +277,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
blocking: guard.blocking,
memory_backed: guard.memory_backed,
no_sched: guard.no_sched,
+ hw_queue_depth: guard.hw_queue_depth,
},
})?);
guard.powered = true;
@@ -447,3 +451,4 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
configfs_simple_field!(DeviceConfig, 16, mbps, u32);
configfs_simple_bool_field!(DeviceConfig, 17, blocking);
configfs_simple_bool_field!(DeviceConfig, 18, shared_tags);
+configfs_simple_field!(DeviceConfig, 19, hw_queue_depth, u32);
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index bcf6a85f1cbc..491979daa50e 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -147,6 +147,10 @@
default: false,
description: "Share tag set between devices for blk-mq",
},
+ hw_queue_depth: u32 {
+ default: 64,
+ description: "Queue depth for each hardware queue. Default: 64",
+ },
},
}
@@ -172,6 +176,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
let blocking = module_parameters::blocking.value();
let memory_backed = module_parameters::memory_backed.value();
let no_sched = module_parameters::no_sched.value();
+ let hw_queue_depth = module_parameters::hw_queue_depth.value();
let shared_tag_set = NullBlkDevice::build_tag_set(TagSetOptions {
submit_queues,
@@ -179,6 +184,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
blocking,
memory_backed,
no_sched,
+ hw_queue_depth,
})?;
let mut disks = KVec::new();
@@ -209,6 +215,7 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
blocking,
memory_backed,
no_sched,
+ hw_queue_depth,
},
})?;
disks.push(disk, GFP_KERNEL)?;
@@ -264,6 +271,7 @@ struct TagSetOptions {
blocking: bool,
memory_backed: bool,
no_sched: bool,
+ hw_queue_depth: u32,
}
impl NullBlkDevice {
@@ -276,6 +284,7 @@ fn build_tag_set(options: TagSetOptions) -> Result<Arc<TagSet<Self>>> {
blocking,
memory_backed,
no_sched,
+ hw_queue_depth,
} = options;
if home_node > kernel::numa::num_online_nodes().try_into()? {
@@ -297,7 +306,7 @@ fn build_tag_set(options: TagSetOptions) -> Result<Arc<TagSet<Self>>> {
}
Arc::pin_init(
- TagSet::new(submit_queues, (), 256, 1, numa_node, flags),
+ TagSet::new(submit_queues, (), hw_queue_depth, 1, numa_node, flags),
GFP_KERNEL,
)
}
--
2.51.2
^ permalink raw reply related
* [PATCH v2 34/83] block: rust: add `hctx` private data support
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux, Andreas Hindborg
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
From: Andreas Hindborg <a.hindborg@samsung.com>
C block device drivers can attach private data to a hardware context
(`struct blk_mq_hw_ctx`). Add support for this feature for Rust block
device drivers via the `Operations::HwData` associated type.
The private data is created in the `init_hctx` callback and stored in
the `driver_data` field of `blk_mq_hw_ctx`. It is passed to `queue_rq`,
`commit_rqs`, and `poll` callbacks, and is released in `exit_hctx`.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/rnull.rs | 8 +++-
rust/kernel/block/mq.rs | 23 +++++++++-
rust/kernel/block/mq/operations.rs | 88 +++++++++++++++++++++++++++++++-------
3 files changed, 100 insertions(+), 19 deletions(-)
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index ad26a4a8dbbe..0c1bc2f5ae9c 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -534,6 +534,7 @@ impl Operations for NullBlkDevice {
type QueueData = Pin<KBox<QueueData>>;
type RequestData = Pdu;
type TagSetData = ();
+ type HwData = ();
fn new_request_data() -> impl PinInit<Self::RequestData> {
pin_init!(Pdu {
@@ -544,6 +545,7 @@ fn new_request_data() -> impl PinInit<Self::RequestData> {
#[inline(always)]
fn queue_rq(
+ _hw_data: (),
queue_data: Pin<&QueueData>,
mut rq: Owned<mq::Request<Self>>,
_is_last: bool,
@@ -575,7 +577,11 @@ fn queue_rq(
Ok(())
}
- fn commit_rqs(_queue_data: Pin<&QueueData>) {}
+ fn commit_rqs(_hw_data: (), _queue_data: Pin<&QueueData>) {}
+
+ fn init_hctx(_tagset_data: (), _hctx_idx: u32) -> Result {
+ Ok(())
+ }
fn complete(rq: ARef<mq::Request<Self>>) {
Self::end_request(
diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index 28cee0d60846..b095cc7f51ce 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -17,6 +17,12 @@
//! - The [`GenDisk`] type that abstracts the C type `struct gendisk`.
//! - The [`Request`] type that abstracts the C type `struct request`.
//!
+//! Many of the C types that this module abstracts allow a driver to carry
+//! private data, either embedded in the struct directly, or as a C `void*`. In
+//! these abstractions, this data is typed. The types of the data is defined by
+//! associated types in `Operations`, see [`Operations::RequestData`] for an
+//! example.
+//!
//! The kernel will interface with the block device driver by calling the method
//! implementations of the `Operations` trait.
//!
@@ -71,6 +77,7 @@
//! impl Operations for MyBlkDevice {
//! type RequestData = ();
//! type QueueData = ();
+//! type HwData = ();
//! type TagSetData = ();
//!
//! fn new_request_data(
@@ -78,12 +85,17 @@
//! Ok(())
//! }
//!
-//! fn queue_rq(_queue_data: (), rq: Owned<Request<Self>>, _is_last: bool) -> Result {
+//! fn queue_rq(
+//! _hw_data: (),
+//! _queue_data: (),
+//! rq: Owned<Request<Self>>,
+//! _is_last: bool
+//! ) -> Result {
//! rq.end_ok();
//! Ok(())
//! }
//!
-//! fn commit_rqs(_queue_data: ()) {}
+//! fn commit_rqs(_hw_data: (), _queue_data: ()) {}
//!
//! fn complete(rq: ARef<Request<Self>>) {
//! OwnableRefCounted::try_from_shared(rq)
@@ -91,6 +103,13 @@
//! .expect("Fatal error - expected to be able to end request")
//! .end_ok();
//! }
+//!
+//! fn init_hctx(
+//! _tagset_data: (),
+//! _hctx_idx: u32,
+//! ) -> Result<Self::HwData> {
+//! Ok(())
+//! }
//! }
//!
//! let tagset: Arc<TagSet<MyBlkDevice>> =
diff --git a/rust/kernel/block/mq/operations.rs b/rust/kernel/block/mq/operations.rs
index 093bb21fa1b2..1b20df25d6df 100644
--- a/rust/kernel/block/mq/operations.rs
+++ b/rust/kernel/block/mq/operations.rs
@@ -63,6 +63,13 @@ pub trait Operations: Sized {
/// the `GenDisk` associated with this `Operations` implementation.
type QueueData: ForeignOwnable + Sync;
+ /// Data associated with a dispatch queue. This is stored as a pointer in the C `struct
+ /// blk_mq_hw_ctx` that represents a hardware queue.
+ ///
+ /// Hardware contexts may be cleaned up by a thread different from the allocating thread, so
+ /// `HwData` must be `Send`.
+ type HwData: ForeignOwnable + Sync + Send;
+
/// Data associated with a `TagSet`. This is stored as a pointer in `struct
/// blk_mq_tag_set`.
type TagSetData: ForeignOwnable + Sync;
@@ -73,20 +80,30 @@ pub trait Operations: Sized {
/// Called by the kernel to queue a request with the driver. If `is_last` is
/// `false`, the driver is allowed to defer committing the request.
fn queue_rq(
+ hw_data: ForeignBorrowed<'_, Self::HwData>,
queue_data: ForeignBorrowed<'_, Self::QueueData>,
rq: Owned<Request<Self>>,
is_last: bool,
) -> Result;
/// Called by the kernel to indicate that queued requests should be submitted.
- fn commit_rqs(queue_data: ForeignBorrowed<'_, Self::QueueData>);
+ fn commit_rqs(
+ hw_data: ForeignBorrowed<'_, Self::HwData>,
+ queue_data: ForeignBorrowed<'_, Self::QueueData>,
+ );
+
+ /// Called by the kernel to allocate and initialize a driver specific hardware context data.
+ fn init_hctx(
+ tagset_data: ForeignBorrowed<'_, Self::TagSetData>,
+ hctx_idx: u32,
+ ) -> Result<Self::HwData>;
/// Called by the kernel when the request is completed.
fn complete(rq: ARef<Request<Self>>);
/// Called by the kernel to poll the device for completed requests. Only
/// used for poll queues.
- fn poll() -> bool {
+ fn poll(_hw_data: ForeignBorrowed<'_, Self::HwData>) -> bool {
build_error!(crate::error::VTABLE_DEFAULT_ERROR)
}
}
@@ -146,6 +163,11 @@ impl<T: Operations> OperationsVTable<T> {
let mut rq =
unsafe { Owned::from_raw(NonNull::<Request<T>>::new_unchecked((*bd).rq.cast())) };
+ // SAFETY: The safety requirement for this function ensure that `hctx`
+ // is valid and that `driver_data` was produced by a call to
+ // `into_foreign` in `Self::init_hctx_callback`.
+ let hw_data = unsafe { T::HwData::borrow((*hctx).driver_data) };
+
// SAFETY: `hctx` is valid as required by this function.
let queue_data = unsafe { (*(*hctx).queue).queuedata };
@@ -159,6 +181,7 @@ impl<T: Operations> OperationsVTable<T> {
unsafe { rq.start_unchecked() };
let ret = T::queue_rq(
+ hw_data,
queue_data,
rq,
// SAFETY: `bd` is valid as required by the safety requirement for
@@ -181,6 +204,10 @@ impl<T: Operations> OperationsVTable<T> {
/// This function may only be called by blk-mq C infrastructure. The caller
/// must ensure that `hctx` is valid.
unsafe extern "C" fn commit_rqs_callback(hctx: *mut bindings::blk_mq_hw_ctx) {
+ // SAFETY: `driver_data` was installed by us in `init_hctx_callback` as
+ // the result of a call to `into_foreign`.
+ let hw_data = unsafe { T::HwData::borrow((*hctx).driver_data) };
+
// SAFETY: `hctx` is valid as required by this function.
let queue_data = unsafe { (*(*hctx).queue).queuedata };
@@ -189,7 +216,7 @@ impl<T: Operations> OperationsVTable<T> {
// `ForeignOwnable::from_foreign()` is only called when the tagset is
// dropped, which happens after we are dropped.
let queue_data = unsafe { T::QueueData::borrow(queue_data) };
- T::commit_rqs(queue_data)
+ T::commit_rqs(hw_data, queue_data)
}
/// This function is called by the C kernel. A pointer to this function is
@@ -213,12 +240,18 @@ impl<T: Operations> OperationsVTable<T> {
///
/// # Safety
///
- /// This function may only be called by blk-mq C infrastructure.
+ /// This function may only be called by blk-mq C infrastructure. `hctx` must
+ /// be a pointer to a valid and aligned `struct blk_mq_hw_ctx` that was
+ /// previously initialized by a call to `init_hctx_callback`.
unsafe extern "C" fn poll_callback(
- _hctx: *mut bindings::blk_mq_hw_ctx,
+ hctx: *mut bindings::blk_mq_hw_ctx,
_iob: *mut bindings::io_comp_batch,
) -> crate::ffi::c_int {
- T::poll().into()
+ // SAFETY: By function safety requirement, `hctx` was initialized by
+ // `init_hctx_callback` and thus `driver_data` came from a call to
+ // `into_foreign`.
+ let hw_data = unsafe { T::HwData::borrow((*hctx).driver_data) };
+ T::poll(hw_data).into()
}
/// This function is called by the C kernel. A pointer to this function is
@@ -226,15 +259,29 @@ impl<T: Operations> OperationsVTable<T> {
///
/// # Safety
///
- /// This function may only be called by blk-mq C infrastructure. This
- /// function may only be called once before `exit_hctx_callback` is called
- /// for the same context.
+ /// This function may only be called by blk-mq C infrastructure.
+ /// `tagset_data` must be initialized by the initializer returned by
+ /// `TagSet::try_new` as part of tag set initialization. `hctx` must be a
+ /// pointer to a valid `blk_mq_hw_ctx` where the `driver_data` field was not
+ /// yet initialized. This function may only be called once before
+ /// `exit_hctx_callback` is called for the same context.
unsafe extern "C" fn init_hctx_callback(
- _hctx: *mut bindings::blk_mq_hw_ctx,
- _tagset_data: *mut crate::ffi::c_void,
- _hctx_idx: crate::ffi::c_uint,
- ) -> crate::ffi::c_int {
- from_result(|| Ok(0))
+ hctx: *mut bindings::blk_mq_hw_ctx,
+ tagset_data: *mut c_void,
+ hctx_idx: c_uint,
+ ) -> c_int {
+ from_result(|| {
+ // SAFETY: By the safety requirements of this function,
+ // `tagset_data` came from a call to `into_foreign` when the
+ // `TagSet` was initialized.
+ let tagset_data = unsafe { T::TagSetData::borrow(tagset_data) };
+ let data = T::init_hctx(tagset_data, hctx_idx)?;
+
+ // SAFETY: by the safety requirements of this function, `hctx` is
+ // valid for write
+ unsafe { (*hctx).driver_data = data.into_foreign().cast() };
+ Ok(0)
+ })
}
/// This function is called by the C kernel. A pointer to this function is
@@ -242,11 +289,20 @@ impl<T: Operations> OperationsVTable<T> {
///
/// # Safety
///
- /// This function may only be called by blk-mq C infrastructure.
+ /// This function may only be called by blk-mq C infrastructure. `hctx` must
+ /// be a valid pointer that was previously initialized by a call to
+ /// `init_hctx_callback`. This function may be called only once after
+ /// `init_hctx_callback` was called.
unsafe extern "C" fn exit_hctx_callback(
- _hctx: *mut bindings::blk_mq_hw_ctx,
+ hctx: *mut bindings::blk_mq_hw_ctx,
_hctx_idx: crate::ffi::c_uint,
) {
+ // SAFETY: By the safety requirements of this function, `hctx` is valid for read.
+ let ptr = unsafe { (*hctx).driver_data };
+
+ // SAFETY: By the safety requirements of this function, `ptr` came from
+ // a call to `into_foreign` in `init_hctx_callback`
+ unsafe { T::HwData::from_foreign(ptr) };
}
/// This function is called by the C kernel. A pointer to this function is
--
2.51.2
^ permalink raw reply related
* [PATCH v2 72/83] block: rust: add a debug assert for refcounts
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add a debug assertion in `ARef<Request>::dismiss` to verify that the
request refcount is at least two when an `ARef<Request>` exists. This
helps catch reference counting bugs during development.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/block/mq/request.rs | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/rust/kernel/block/mq/request.rs b/rust/kernel/block/mq/request.rs
index 9c451583e75d..05b167dfc6c6 100644
--- a/rust/kernel/block/mq/request.rs
+++ b/rust/kernel/block/mq/request.rs
@@ -619,9 +619,20 @@ impl<T> RequestTimerHandle<T>
pub fn dismiss(mut self) {
let inner = core::ptr::from_mut(&mut self.inner);
+ debug_assert!(
+ self.inner
+ .wrapper_ref()
+ .refcount()
+ .as_atomic()
+ .load(ordering::Relaxed)
+ >= 2,
+ "Request refcount must be at least two when an ARef<Request> exist"
+ );
+
// SAFETY: `inner` is valid for reads and writes, is properly aligned and nonnull. We have
// exclusive access to `inner` and we do not access `inner` after this call.
unsafe { core::ptr::drop_in_place(inner) };
+
core::mem::forget(self);
}
}
--
2.51.2
^ permalink raw reply related
* [PATCH v2 60/83] block: rust: add request flags abstraction
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add the `Flag` enum and `Flags` type as Rust abstractions for the C
`REQ_*` request flags. These flags modify how block I/O requests are
processed, including sync behavior, priority hints, and integrity
settings.
Also add a `flags()` method to `Request` to retrieve the flags for a
given request.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/bindings/bindings_helper.h | 21 ++++++++++++
rust/kernel/block/mq.rs | 2 ++
rust/kernel/block/mq/request.rs | 12 +++++++
rust/kernel/block/mq/request/flag.rs | 65 ++++++++++++++++++++++++++++++++++++
4 files changed, 100 insertions(+)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 2a69c17bf271..7acda3ae9725 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -140,6 +140,27 @@ const blk_status_t RUST_CONST_HELPER_BLK_STS_OFFLINE = BLK_STS_OFFLINE;
const blk_status_t RUST_CONST_HELPER_BLK_STS_DURATION_LIMIT = BLK_STS_DURATION_LIMIT;
const blk_status_t RUST_CONST_HELPER_BLK_STS_INVAL = BLK_STS_INVAL;
const blk_features_t RUST_CONST_HELPER_BLK_FEAT_ZONED = BLK_FEAT_ZONED;
+const blk_opf_t RUST_CONST_HELPER_REQ_FAILFAST_DEV = REQ_FAILFAST_DEV;
+const blk_opf_t RUST_CONST_HELPER_REQ_FAILFAST_TRANSPORT = REQ_FAILFAST_TRANSPORT;
+const blk_opf_t RUST_CONST_HELPER_REQ_FAILFAST_DRIVER = REQ_FAILFAST_DRIVER;
+const blk_opf_t RUST_CONST_HELPER_REQ_SYNC = REQ_SYNC;
+const blk_opf_t RUST_CONST_HELPER_REQ_META = REQ_META;
+const blk_opf_t RUST_CONST_HELPER_REQ_PRIO = REQ_PRIO;
+const blk_opf_t RUST_CONST_HELPER_REQ_NOMERGE = REQ_NOMERGE;
+const blk_opf_t RUST_CONST_HELPER_REQ_IDLE = REQ_IDLE;
+const blk_opf_t RUST_CONST_HELPER_REQ_INTEGRITY = REQ_INTEGRITY;
+const blk_opf_t RUST_CONST_HELPER_REQ_FUA = REQ_FUA;
+const blk_opf_t RUST_CONST_HELPER_REQ_PREFLUSH = REQ_PREFLUSH;
+const blk_opf_t RUST_CONST_HELPER_REQ_RAHEAD = REQ_RAHEAD;
+const blk_opf_t RUST_CONST_HELPER_REQ_BACKGROUND = REQ_BACKGROUND;
+const blk_opf_t RUST_CONST_HELPER_REQ_NOWAIT = REQ_NOWAIT;
+const blk_opf_t RUST_CONST_HELPER_REQ_POLLED = REQ_POLLED;
+const blk_opf_t RUST_CONST_HELPER_REQ_ALLOC_CACHE = REQ_ALLOC_CACHE;
+const blk_opf_t RUST_CONST_HELPER_REQ_SWAP = REQ_SWAP;
+const blk_opf_t RUST_CONST_HELPER_REQ_DRV = REQ_DRV;
+const blk_opf_t RUST_CONST_HELPER_REQ_FS_PRIVATE = REQ_FS_PRIVATE;
+const blk_opf_t RUST_CONST_HELPER_REQ_ATOMIC = REQ_ATOMIC;
+const blk_opf_t RUST_CONST_HELPER_REQ_NOUNMAP = REQ_NOUNMAP;
const fop_flags_t RUST_CONST_HELPER_FOP_UNSIGNED_OFFSET = FOP_UNSIGNED_OFFSET;
const xa_mark_t RUST_CONST_HELPER_XA_PRESENT = XA_PRESENT;
diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index 23bf95136bc1..9bad95d79230 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -137,6 +137,8 @@
};
pub use request::{
Command,
+ Flag as RequestFlag,
+ Flags as RequestFlags,
IdleRequest,
Request,
RequestTimerHandle, //
diff --git a/rust/kernel/block/mq/request.rs b/rust/kernel/block/mq/request.rs
index dbe657a80324..84f8b2c17f85 100644
--- a/rust/kernel/block/mq/request.rs
+++ b/rust/kernel/block/mq/request.rs
@@ -48,6 +48,12 @@
mod command;
pub use command::Command;
+mod flag;
+pub use flag::{
+ Flag,
+ Flags, //
+};
+
/// A [`Request`] that a driver has not yet begun to process.
///
/// A driver can convert an `IdleRequest` to a [`Request`] by calling [`IdleRequest::start`].
@@ -125,6 +131,12 @@ pub fn command(&self) -> Command {
unsafe { Command::from_raw(self.command_raw()) }
}
+ pub fn flags(&self) -> Flags {
+ // SAFETY: By C API contract and type invariant, `cmd_flags` is valid for read
+ let flags = unsafe { (*self.0.get()).cmd_flags & !((1 << bindings::REQ_OP_BITS) - 1) };
+ Flags::try_from(flags).expect("Request should have valid flags")
+ }
+
/// Get the target sector for the request.
#[inline(always)]
pub fn sector(&self) -> u64 {
diff --git a/rust/kernel/block/mq/request/flag.rs b/rust/kernel/block/mq/request/flag.rs
new file mode 100644
index 000000000000..01f249269803
--- /dev/null
+++ b/rust/kernel/block/mq/request/flag.rs
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+use crate::{
+ bindings,
+ impl_flags, //
+};
+
+impl_flags! {
+ /// A set of request flags.
+ ///
+ /// This type wraps the C `REQ_*` flags and allows combining multiple flags
+ /// together. These flags modify how a block I/O request is processed.
+ #[derive(Debug, Clone, Default, Copy, PartialEq, Eq)]
+ pub struct Flags(u32);
+
+ /// Individual request flags for block I/O operations.
+ ///
+ /// These flags correspond to the C `REQ_*` defines in `linux/blk_types.h`
+ /// and are used to modify the behavior of block I/O requests.
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ pub enum Flag {
+ /// No driver retries on device errors.
+ FailfastDev = bindings::REQ_FAILFAST_DEV,
+ /// No driver retries on transport errors.
+ FailfastTransport = bindings::REQ_FAILFAST_TRANSPORT,
+ /// No driver retries on driver errors.
+ FailfastDriver = bindings::REQ_FAILFAST_DRIVER,
+ /// Request is synchronous (sync write or read).
+ Sync = bindings::REQ_SYNC,
+ /// Metadata I/O request.
+ Meta = bindings::REQ_META,
+ /// Boost priority in CFQ scheduler.
+ Priority = bindings::REQ_PRIO,
+ /// Don't merge this request with others.
+ NoMerge = bindings::REQ_NOMERGE,
+ /// Anticipate more I/O after this one.
+ Idle = bindings::REQ_IDLE,
+ /// I/O includes block integrity payload.
+ Integrity = bindings::REQ_INTEGRITY,
+ /// Forced unit access - data must be written to persistent storage
+ /// before command completion is signaled.
+ ForcedUnitAccess = bindings::REQ_FUA,
+ /// Request a cache flush before this operation.
+ Preflush = bindings::REQ_PREFLUSH,
+ /// Read ahead request, can fail anytime.
+ ReadAhead = bindings::REQ_RAHEAD,
+ /// Background I/O operation.
+ Background = bindings::REQ_BACKGROUND,
+ /// Don't wait if the request would block.
+ NoWait = bindings::REQ_NOWAIT,
+ /// Caller polls for completion using `bio_poll`.
+ Polled = bindings::REQ_POLLED,
+ /// Allocate I/O from cache if available.
+ AllocCache = bindings::REQ_ALLOC_CACHE,
+ /// Swap I/O operation.
+ Swap = bindings::REQ_SWAP,
+ /// Reserved for driver use.
+ Driver = bindings::REQ_DRV,
+ /// Reserved for file system (submitter) use.
+ FsPrivate = bindings::REQ_FS_PRIVATE,
+ /// Atomic write operation.
+ Atomic = bindings::REQ_ATOMIC,
+ /// Do not free blocks when zeroing (for write zeroes operations).
+ NoUnmap = bindings::REQ_NOUNMAP,
+ }
+}
--
2.51.2
^ permalink raw reply related
* [PATCH v2 19/83] block: rust: allow specifying home node when constructing `TagSet`
From: Andreas Hindborg @ 2026-06-09 19:07 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add a `numa_node` parameter to `TagSet::new` to specify the home NUMA
node for tag set allocations. This allows drivers to optimize memory
placement for NUMA systems.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/rnull.rs | 11 ++++++++++-
rust/kernel/block/mq.rs | 5 ++++-
rust/kernel/block/mq/tag_set.rs | 4 +++-
3 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 30de022146ec..6323327d4a5a 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -174,7 +174,16 @@ fn new(options: NullBlkOptions<'_>) -> Result<GenDisk<Self>> {
mq::tag_set::Flags::default()
};
- let tagset = Arc::pin_init(TagSet::new(submit_queues, 256, 1, flags), GFP_KERNEL)?;
+ let tagset = Arc::pin_init(
+ TagSet::new(
+ submit_queues,
+ 256,
+ 1,
+ kernel::alloc::NumaNode::NO_NODE,
+ flags,
+ ),
+ GFP_KERNEL,
+ )?;
let queue_data = Box::pin_init(
pin_init!(QueueData {
diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index e556b3bb1191..bac15b509d90 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -57,6 +57,7 @@
//!
//! ```rust
//! use kernel::{
+//! alloc::NumaNode,
//! block::mq::{self, *},
//! new_mutex,
//! prelude::*,
@@ -92,7 +93,9 @@
//! }
//!
//! let tagset: Arc<TagSet<MyBlkDevice>> =
-//! Arc::pin_init(TagSet::new(1, 256, 1, mq::tag_set::Flags::default()), GFP_KERNEL)?;
+//! Arc::pin_init(
+//! TagSet::new(1, 256, 1, NumaNode::NO_NODE, mq::tag_set::Flags::default()),
+//! GFP_KERNEL)?;
//! let mut disk = gen_disk::GenDiskBuilder::new()
//! .capacity_sectors(4096)
//! .build(fmt!("myblk"), tagset, ())?;
diff --git a/rust/kernel/block/mq/tag_set.rs b/rust/kernel/block/mq/tag_set.rs
index 5b1a5bcc978d..d6d104adf4aa 100644
--- a/rust/kernel/block/mq/tag_set.rs
+++ b/rust/kernel/block/mq/tag_set.rs
@@ -7,6 +7,7 @@
use core::pin::Pin;
use crate::{
+ alloc::NumaNode,
bindings,
block::mq::{
operations::OperationsVTable,
@@ -57,6 +58,7 @@ pub fn new(
nr_hw_queues: u32,
num_tags: u32,
num_maps: u32,
+ numa_node: NumaNode,
flags: Flags,
) -> impl PinInit<Self, error::Error> {
let tag_set: bindings::blk_mq_tag_set = pin_init::zeroed();
@@ -67,7 +69,7 @@ pub fn new(
ops: OperationsVTable::<T>::build(),
nr_hw_queues,
timeout: 0, // 0 means default which is 30Hz in C
- numa_node: bindings::NUMA_NO_NODE,
+ numa_node: numa_node.id(),
queue_depth: num_tags,
cmd_size,
flags: flags.into(),
--
2.51.2
^ permalink raw reply related
* [PATCH v2 67/83] block: rnull: add an option to change the number of hardware queues
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add a feature to rnull that allows changing the number of simulated
hardware queues during device operation.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/configfs.rs | 117 ++++++++++++++++++++++++++--------------
drivers/block/rnull/rnull.rs | 46 ++++++++++------
2 files changed, 108 insertions(+), 55 deletions(-)
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index 8195d645ecc6..d9246b9150f4 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -148,7 +148,13 @@ fn make_group(
completion_time: time::Delta::ZERO,
name: name.try_into()?,
memory_backed: false,
- submit_queues: 1,
+ queue_config: Arc::pin_init(
+ new_mutex!(QueueConfig {
+ submit_queues: 1,
+ poll_queues: 0
+ }),
+ GFP_KERNEL
+ )?,
home_node: bindings::NUMA_NO_NODE,
discard: false,
no_sched: false,
@@ -169,7 +175,6 @@ fn make_group(
zone_max_open: 0,
zone_max_active: 0,
zone_append_max_sectors: u32::MAX,
- poll_queues: 0,
fua: true,
}),
}),
@@ -236,7 +241,7 @@ struct DeviceConfigInner {
completion_time: time::Delta,
disk: Option<Arc<GenDisk<NullBlkDevice>>>,
memory_backed: bool,
- submit_queues: u32,
+ queue_config: Arc<Mutex<QueueConfig>>,
home_node: i32,
discard: bool,
no_sched: bool,
@@ -257,7 +262,6 @@ struct DeviceConfigInner {
zone_max_open: u32,
zone_max_active: u32,
zone_append_max_sectors: u32,
- poll_queues: u32,
fua: bool,
}
@@ -310,9 +314,8 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
bandwidth_limit: u64::from(guard.mbps) * 2u64.pow(20),
shared_tag_set: guard.shared_tags.then(|| guard.shared_tag_set.clone()),
tag_set: crate::TagSetOptions {
- submit_queues: guard.submit_queues,
- poll_queues: guard.poll_queues,
home_node: guard.home_node,
+ queue_config: guard.queue_config.clone(),
blocking: guard.blocking,
memory_backed: guard.memory_backed,
no_sched: guard.no_sched,
@@ -337,9 +340,17 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
}
}
-configfs_simple_field!(DeviceConfig, 1,
- block_size, u32,
- check GenDiskBuilder::<NullBlkDevice>::validate_block_size
+pub(crate) struct QueueConfig {
+ pub(crate) submit_queues: u32,
+ pub(crate) poll_queues: u32,
+}
+
+configfs_simple_field!(
+ DeviceConfig,
+ 1,
+ block_size,
+ u32,
+ check GenDiskBuilder::<NullBlkDevice>::validate_block_size
);
configfs_simple_bool_field!(DeviceConfig, 2, rotational);
configfs_simple_field!(DeviceConfig, 3, capacity_mib, u64);
@@ -363,38 +374,44 @@ fn from_str(s: &str) -> Result<Self> {
configfs_simple_bool_field!(DeviceConfig, 6, memory_backed);
-#[vtable]
-impl configfs::AttributeOperations<7> for DeviceConfig {
- type Data = DeviceConfig;
-
- fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
- let mut writer = kernel::str::Formatter::new(page);
- writer.write_fmt(fmt!("{}\n", this.data.lock().submit_queues))?;
- Ok(writer.bytes_written())
- }
+configfs_attribute! {
+ DeviceConfig,
+ 7,
+ show: |this, page| show_field(this.data.lock().queue_config.lock().submit_queues, page),
+ store: |this,page| {
+ let config_guard = this.data.lock();
+ let mut queue_config = config_guard.queue_config.lock();
- fn store(this: &DeviceConfig, page: &[u8]) -> Result {
- if this.data.lock().powered {
- return Err(EBUSY);
+ let text = core::str::from_utf8(page)?.trim();
+ let value = text.parse().map_err(|_| EINVAL)?;
+ if value > kernel::cpu::num_possible_cpus() {
+ return Err(kernel::error::code::EINVAL)
}
- let text = core::str::from_utf8(page)?.trim();
- let value = text
- .parse::<u32>()
- .map_err(|_| kernel::error::code::EINVAL)?;
+ let old_submit_queues = queue_config.submit_queues;
+ queue_config.submit_queues = value;
+ let total_queue_count = queue_config.submit_queues + queue_config.poll_queues;
+
+ let disk = config_guard.disk.clone();
+
+ drop(queue_config);
+ drop(config_guard);
- if value == 0 || value > kernel::cpu::num_possible_cpus() {
- return Err(kernel::error::code::EINVAL);
+ if let Some(disk) = &disk {
+ if let Err(e) = disk.tag_set().update_hw_queue_count(total_queue_count) {
+ this.data.lock().queue_config.lock().submit_queues = old_submit_queues;
+ return Err(e);
+ }
}
- this.data.lock().submit_queues = value;
Ok(())
- }
+ },
}
configfs_attribute!(DeviceConfig, 8,
show: |this, page| show_field(
- this.data.lock().submit_queues == kernel::numa::num_online_nodes(), page
+ this.data.lock().queue_config.lock().submit_queues == kernel::numa::num_online_nodes(),
+ page
),
store: |this, page| store_with_power_check(this, page, |data, page| {
let value = core::str::from_utf8(page)?
@@ -404,7 +421,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
!= 0;
if value {
- data.submit_queues = kernel::numa::num_online_nodes();
+ data.queue_config.lock().submit_queues = kernel::numa::num_online_nodes();
}
Ok(())
})
@@ -506,17 +523,37 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
configfs_simple_field!(DeviceConfig, 24, zone_max_open, u32);
configfs_simple_field!(DeviceConfig, 25, zone_max_active, u32);
configfs_simple_field!(DeviceConfig, 26, zone_append_max_sectors, u32);
-configfs_simple_field!(
+configfs_attribute! {
DeviceConfig,
27,
- poll_queues,
- u32,
- check(|value| {
+ show: |this, page| show_field(this.data.lock().queue_config.lock().poll_queues, page),
+ store: |this,page| {
+ let config_guard = this.data.lock();
+ let mut queue_config = config_guard.queue_config.lock();
+
+ let text = core::str::from_utf8(page)?.trim();
+ let value = text.parse().map_err(|_| EINVAL)?;
if value > kernel::cpu::num_possible_cpus() {
- Err(kernel::error::code::EINVAL)
- } else {
- Ok(())
+ return Err(kernel::error::code::EINVAL)
}
- })
-);
+
+ let old_poll_queues = queue_config.poll_queues;
+ queue_config.poll_queues = value;
+ let total_queue_count = queue_config.submit_queues + queue_config.poll_queues;
+
+ let disk = config_guard.disk.clone();
+
+ drop(queue_config);
+ drop(config_guard);
+
+ if let Some(disk) = &disk {
+ if let Err(e) = disk.tag_set().update_hw_queue_count(total_queue_count) {
+ this.data.lock().queue_config.lock().poll_queues = old_poll_queues;
+ return Err(e);
+ }
+ }
+
+ Ok(())
+ },
+}
configfs_simple_bool_field!(DeviceConfig, 28, fua);
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index c3126b923367..6653db5c069b 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -10,7 +10,10 @@
#[cfg(CONFIG_BLK_DEV_ZONED)]
mod zoned;
-use configfs::IRQMode;
+use configfs::{
+ IRQMode,
+ QueueConfig, //
+};
use disk_storage::{
DiskStorage,
NullBlockPage,
@@ -224,9 +227,14 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
let hw_queue_depth = module_parameters::hw_queue_depth.value();
let shared_tag_set = NullBlkDevice::build_tag_set(TagSetOptions {
- submit_queues,
- poll_queues,
home_node,
+ queue_config: Arc::pin_init(
+ new_mutex!(QueueConfig {
+ submit_queues,
+ poll_queues,
+ }),
+ GFP_KERNEL,
+ )?,
blocking,
memory_backed,
no_sched,
@@ -256,9 +264,14 @@ fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
.value()
.then(|| shared_tag_set.clone()),
tag_set: TagSetOptions {
- submit_queues,
- poll_queues,
home_node,
+ queue_config: Arc::pin_init(
+ new_mutex!(QueueConfig {
+ submit_queues,
+ poll_queues,
+ }),
+ GFP_KERNEL,
+ )?,
blocking,
memory_backed,
no_sched,
@@ -338,9 +351,8 @@ struct NullBlkDevice {
}
struct TagSetOptions {
- submit_queues: u32,
- poll_queues: u32,
home_node: i32,
+ queue_config: Arc<Mutex<QueueConfig>>,
blocking: bool,
memory_backed: bool,
no_sched: bool,
@@ -352,9 +364,8 @@ impl NullBlkDevice {
fn build_tag_set(options: TagSetOptions) -> Result<Arc<TagSet<Self>>> {
let TagSetOptions {
- submit_queues,
- poll_queues,
home_node,
+ queue_config,
blocking,
memory_backed,
no_sched,
@@ -379,14 +390,18 @@ fn build_tag_set(options: TagSetOptions) -> Result<Arc<TagSet<Self>>> {
flags |= mq::tag_set::Flag::NoDefaultScheduler;
}
+ let queue_config_guard = queue_config.lock();
+ let submit_queues = queue_config_guard.submit_queues;
+ let poll_queues = queue_config_guard.poll_queues;
+ drop(queue_config_guard);
+
Arc::pin_init(
TagSet::new(
submit_queues + poll_queues,
KBox::new(
NullBlkTagsetData {
queue_depth: hw_queue_depth,
- submit_queue_count: submit_queues,
- poll_queue_count: poll_queues,
+ queue_config,
},
GFP_KERNEL,
)?,
@@ -823,8 +838,7 @@ impl HasHrTimer<Self> for Pdu {
struct NullBlkTagsetData {
queue_depth: u32,
- submit_queue_count: u32,
- poll_queue_count: u32,
+ queue_config: Arc<Mutex<QueueConfig>>,
}
#[vtable]
@@ -970,8 +984,10 @@ fn report_zones(
}
fn map_queues(tag_set: Pin<&mut TagSet<Self>>) {
- let mut submit_queue_count = tag_set.data().submit_queue_count;
- let mut poll_queue_count = tag_set.data().poll_queue_count;
+ let queue_config = tag_set.data().queue_config.lock();
+ let mut submit_queue_count = queue_config.submit_queues;
+ let mut poll_queue_count = queue_config.poll_queues;
+ drop(queue_config);
if tag_set.hw_queue_count() != submit_queue_count + poll_queue_count {
pr_warn!(
--
2.51.2
^ permalink raw reply related
* [PATCH v2 42/83] block: rust: require `queue_rq` to return a `BlkResult`
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Change the return type of `Operations::queue_rq` from `Result` to
`BlkResult`. This ensures that drivers return proper block layer status
codes that can be translated to the appropriate `blk_status_t` value.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
drivers/block/rnull/rnull.rs | 3 ++-
rust/kernel/block/mq.rs | 4 ++--
rust/kernel/block/mq/operations.rs | 13 ++++++++-----
3 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index bb8c4df08218..6ceba23a4d3e 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -20,6 +20,7 @@
BadBlocks, //
},
bio::Segment,
+ error::BlkResult,
mq::{
self,
gen_disk::{
@@ -595,7 +596,7 @@ fn queue_rq(
this: Pin<&Self>,
rq: Owned<mq::IdleRequest<Self>>,
_is_last: bool,
- ) -> Result {
+ ) -> BlkResult {
let mut rq = rq.start();
let mut sectors = rq.sectors();
diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index e89eb394001f..503623267b19 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -64,7 +64,7 @@
//! ```rust
//! use kernel::{
//! alloc::NumaNode,
-//! block::mq::{self, *},
+//! block::{error::BlkResult, mq::{self, *}},
//! new_mutex,
//! prelude::*,
//! sync::{aref::ARef, Arc, Mutex},
@@ -90,7 +90,7 @@
//! _queue_data: (),
//! rq: Owned<IdleRequest<Self>>,
//! _is_last: bool
-//! ) -> Result {
+//! ) -> BlkResult {
//! rq.start().end_ok();
//! Ok(())
//! }
diff --git a/rust/kernel/block/mq/operations.rs b/rust/kernel/block/mq/operations.rs
index 01917ef213d1..b9a2bf6592b3 100644
--- a/rust/kernel/block/mq/operations.rs
+++ b/rust/kernel/block/mq/operations.rs
@@ -6,10 +6,13 @@
use crate::{
bindings,
- block::mq::{
- request::RequestDataWrapper,
- IdleRequest,
- Request, //
+ block::{
+ error::BlkResult,
+ mq::{
+ request::RequestDataWrapper,
+ IdleRequest,
+ Request, //
+ },
},
error::{
from_result,
@@ -82,7 +85,7 @@ fn queue_rq(
queue_data: ForeignBorrowed<'_, Self::QueueData>,
rq: Owned<IdleRequest<Self>>,
is_last: bool,
- ) -> Result;
+ ) -> BlkResult;
/// Called by the kernel to indicate that queued requests should be submitted.
fn commit_rqs(
--
2.51.2
^ permalink raw reply related
* [PATCH v2 62/83] block: rust: allow setting write cache and FUA flags for `GenDisk`
From: Andreas Hindborg @ 2026-06-09 19:08 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
Add methods to `GenDiskBuilder` for enabling the write cache and FUA
feature flags. These flags are set in the `queue_limits` structure
when building the disk.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/block/mq/gen_disk.rs | 29 +++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
index eedba691e167..5367ca92b7aa 100644
--- a/rust/kernel/block/mq/gen_disk.rs
+++ b/rust/kernel/block/mq/gen_disk.rs
@@ -9,6 +9,7 @@
bindings,
block::mq::{
operations::OperationsVTable,
+ Feature,
Operations,
RequestQueue,
TagSet, //
@@ -55,6 +56,8 @@ pub struct GenDiskBuilder<T> {
zone_size_sectors: u32,
#[cfg(CONFIG_BLK_DEV_ZONED)]
zone_append_max_sectors: u32,
+ write_cache: bool,
+ forced_unit_access: bool,
_p: PhantomData<T>,
}
@@ -72,6 +75,8 @@ fn default() -> Self {
zone_size_sectors: 0,
#[cfg(CONFIG_BLK_DEV_ZONED)]
zone_append_max_sectors: 0,
+ write_cache: false,
+ forced_unit_access: false,
_p: PhantomData,
}
}
@@ -164,6 +169,18 @@ pub fn zone_append_max(mut self, sectors: u32) -> Self {
self
}
+ /// Declare that this device supports forced unit access.
+ pub fn forced_unit_access(mut self, enable: bool) -> Self {
+ self.forced_unit_access = enable;
+ self
+ }
+
+ /// Declare that this device has a write-back cache.
+ pub fn write_cache(mut self, enable: bool) -> Self {
+ self.write_cache = enable;
+ self
+ }
+
/// Build a new `GenDisk` and add it to the VFS.
pub fn build(
self,
@@ -183,7 +200,7 @@ pub fn build(
lim.physical_block_size = self.physical_block_size;
lim.max_hw_discard_sectors = self.max_hw_discard_sectors;
if self.rotational {
- lim.features |= bindings::BLK_FEAT_ROTATIONAL;
+ lim.features = Feature::Rotational.into();
}
#[cfg(CONFIG_BLK_DEV_ZONED)]
@@ -192,11 +209,19 @@ pub fn build(
return Err(error::code::EINVAL);
}
- lim.features |= bindings::BLK_FEAT_ZONED;
+ lim.features |= Feature::Zoned;
lim.chunk_sectors = self.zone_size_sectors;
lim.max_hw_zone_append_sectors = self.zone_append_max_sectors;
}
+ if self.write_cache {
+ lim.features |= Feature::WriteCache;
+ }
+
+ if self.forced_unit_access {
+ lim.features |= Feature::ForcedUnitAccess;
+ }
+
// SAFETY: `tagset.raw_tag_set()` points to a valid and initialized tag set
let gendisk = from_err_ptr(unsafe {
bindings::__blk_mq_alloc_disk(
--
2.51.2
^ permalink raw reply related
* [PATCH v2 09/83] block: rust: document the lifetime of `Request`
From: Andreas Hindborg @ 2026-06-09 19:07 UTC (permalink / raw)
To: Liam R. Howlett, Alice Ryhl, Anna-Maria Behnsen, Benno Lossin,
Björn Roy Baron, Boqun Feng, Danilo Krummrich,
FUJITA Tomonori, Frederic Weisbecker, Gary Guo, Jens Axboe,
John Stultz, Lorenzo Stoakes, Lyude Paul, Miguel Ojeda,
Stephen Boyd, Thomas Gleixner, Trevor Gross, Liam R. Howlett,
Boqun Feng, Lorenzo Stoakes
Cc: Andreas Hindborg, linux-block, linux-kernel, linux-mm,
rust-for-linux
In-Reply-To: <20260609-rnull-v6-19-rc5-send-v2-0-82c7404542e2@kernel.org>
The `struct request` objects backing a `Request` are not allocated and
freed for each IO. Instead, a fixed pool of requests is allocated when
the tag set is initialized, and each request is reused to service many
distinct IO operations over the lifetime of the request queue. It is
easy to assume from the existing documentation that a request, and in
particular its private data, is fresh for each IO.
Add a `Lifetime` section to the `Request` documentation describing this
reuse and its consequence for the lifetime of the request private data.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/block/mq/request.rs | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/rust/kernel/block/mq/request.rs b/rust/kernel/block/mq/request.rs
index 1882d697dcf3..a6e757d8755d 100644
--- a/rust/kernel/block/mq/request.rs
+++ b/rust/kernel/block/mq/request.rs
@@ -29,6 +29,24 @@
/// A wrapper around a blk-mq [`struct request`]. This represents an IO request.
///
+/// # Lifetime
+///
+/// The [`struct request`] backing a [`Request`] is not allocated and freed for
+/// each IO. Instead, a fixed pool of requests is allocated up front when the
+/// [`TagSet`](crate::block::mq::TagSet) is initialized, with one request per
+/// available tag. A single request allocation is then reused to service many
+/// distinct IO operations over the lifetime of the request queue: when the
+/// block layer needs to process an IO, it assigns a free tag and hands the
+/// driver the associated request, and once that IO completes the request is
+/// returned to the pool to later be handed out again for an unrelated IO.
+///
+/// The private data area of the request, which holds the driver defined
+/// [`Operations::RequestData`], shares this lifetime. It is initialized once
+/// when the request pool is allocated and dropped once when the pool is torn
+/// down - not once per IO. As a result, [`Operations::RequestData`] persists
+/// across the many IO operations that reuse the same request, and a driver must
+/// not assume that it is reset to a fresh value at the start of each IO.
+///
/// # Implementation details
///
/// There are three states for a request that the Rust bindings care about:
--
2.51.2
^ 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