All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/14] rnull: add configfs, remote completion to rnull
@ 2025-07-08 19:44 Andreas Hindborg
  2025-07-08 19:44 ` [PATCH v2 01/14] rust: str: normalize imports in `str.rs` Andreas Hindborg
                   ` (13 more replies)
  0 siblings, 14 replies; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:44 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

This series adds configuration via configfs and remote completion to
the rnull driver. The series also includes a set of changes to the
rust block device driver API: a few cleanup patches, and a few features
supporting the rnull changes.

The series moves the raw buffer formatting logic from `kernel::block`
to `kernel::string` and also improves that logic a bit.

This series is a smaller subset of the patches available in the
downstream rnull tree. I hope to minimize the delta between upstream
and downstream over the next few kernel releases.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
Changes in v2:
- Rework formatter logic. Add two new formatters that write to slices,
  one of which adds a trailing null byte.
- Reorder and split patches so that changes are more clear.
- Fix a typo in soft-irq patch summary.
- Link to v1: https://lore.kernel.org/r/20250616-rnull-up-v6-16-v1-0-a4168b8e76b2@kernel.org

---
Andreas Hindborg (14):
      rust: str: normalize imports in `str.rs`
      rust: str: introduce `BorrowFormatter`
      rust: str: introduce `NullBorrowFormatter`
      rust: block: normalize imports for `gen_disk.rs`
      rust: block: use `NullBorrowFormatter`
      rust: block: remove `RawWriter`
      rust: block: remove trait bound from `mq::Request` definition
      rust: block: add block related constants
      rnull: move driver to separate directory
      rnull: enable configuration via `configfs`
      rust: block: add `GenDisk` private data support
      rust: block: mq: fix spelling in a safety comment
      rust: block: add remote completion to `Request`
      rnull: add soft-irq completion support

 MAINTAINERS                        |   2 +-
 drivers/block/Kconfig              |  10 +-
 drivers/block/Makefile             |   4 +-
 drivers/block/rnull.rs             |  80 -----------
 drivers/block/rnull/Kconfig        |  13 ++
 drivers/block/rnull/Makefile       |   3 +
 drivers/block/rnull/configfs.rs    | 277 +++++++++++++++++++++++++++++++++++++
 drivers/block/rnull/rnull.rs       | 105 ++++++++++++++
 rust/kernel/block.rs               |  12 ++
 rust/kernel/block/mq.rs            |  14 +-
 rust/kernel/block/mq/gen_disk.rs   |  50 +++++--
 rust/kernel/block/mq/operations.rs |  65 +++++++--
 rust/kernel/block/mq/raw_writer.rs |  55 --------
 rust/kernel/block/mq/request.rs    |  21 ++-
 rust/kernel/str.rs                 |  95 ++++++++++++-
 15 files changed, 622 insertions(+), 184 deletions(-)
---
base-commit: e04c78d86a9699d136910cfc0bdcf01087e3267e
change-id: 20250616-rnull-up-v6-16-b4571e28feee

Best regards,
-- 
Andreas Hindborg <a.hindborg@kernel.org>



^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v2 01/14] rust: str: normalize imports in `str.rs`
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
@ 2025-07-08 19:44 ` Andreas Hindborg
  2025-07-09 13:26   ` Alice Ryhl
  2025-07-08 19:44 ` [PATCH v2 02/14] rust: str: introduce `BorrowFormatter` Andreas Hindborg
                   ` (12 subsequent siblings)
  13 siblings, 1 reply; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:44 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

Clean up imports in `str.rs`. This makes future code manipulation more
manageable.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 rust/kernel/str.rs | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index a927db8e079c3..488b0e97004ee 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -3,10 +3,11 @@
 //! String representations.
 
 use crate::alloc::{flags::*, AllocError, KVec};
-use core::fmt::{self, Write};
-use core::ops::{self, Deref, DerefMut, Index};
-
 use crate::prelude::*;
+use core::{
+    fmt::{self, Write},
+    ops::{self, Deref, DerefMut, Index},
+};
 
 /// Byte string without UTF-8 validity guarantee.
 #[repr(transparent)]

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 02/14] rust: str: introduce `BorrowFormatter`
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
  2025-07-08 19:44 ` [PATCH v2 01/14] rust: str: normalize imports in `str.rs` Andreas Hindborg
@ 2025-07-08 19:44 ` Andreas Hindborg
  2025-07-09 13:14   ` Alice Ryhl
  2025-07-08 19:44 ` [PATCH v2 03/14] rust: str: introduce `NullBorrowFormatter` Andreas Hindborg
                   ` (11 subsequent siblings)
  13 siblings, 1 reply; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:44 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

Add `BorrowFormatter`, a formatter that writes to an array or slice buffer.
This formatter is backed by the existing `Formatter`.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 rust/kernel/str.rs | 36 +++++++++++++++++++++++++++++++++---
 1 file changed, 33 insertions(+), 3 deletions(-)

diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index 488b0e97004ee..78b2f95eb3171 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -6,6 +6,7 @@
 use crate::prelude::*;
 use core::{
     fmt::{self, Write},
+    marker::PhantomData,
     ops::{self, Deref, DerefMut, Index},
 };
 
@@ -702,7 +703,7 @@ fn test_bstr_debug() -> Result {
 ///
 /// The memory region between `pos` (inclusive) and `end` (exclusive) is valid for writes if `pos`
 /// is less than `end`.
-pub(crate) struct RawFormatter {
+pub struct RawFormatter {
     // Use `usize` to use `saturating_*` functions.
     beg: usize,
     pos: usize,
@@ -760,7 +761,7 @@ pub(crate) fn pos(&self) -> *mut u8 {
     }
 
     /// Returns the number of bytes written to the formatter.
-    pub(crate) fn bytes_written(&self) -> usize {
+    pub fn bytes_written(&self) -> usize {
         self.pos - self.beg
     }
 }
@@ -794,7 +795,7 @@ fn write_str(&mut self, s: &str) -> fmt::Result {
 /// Allows formatting of [`fmt::Arguments`] into a raw buffer.
 ///
 /// Fails if callers attempt to write more than will fit in the buffer.
-pub(crate) struct Formatter(RawFormatter);
+pub struct Formatter(RawFormatter);
 
 impl Formatter {
     /// Creates a new instance of [`Formatter`] with the given buffer.
@@ -830,6 +831,35 @@ fn write_str(&mut self, s: &str) -> fmt::Result {
     }
 }
 
+/// A mutable reference to a byte buffer where a string can be written into.
+pub struct BorrowFormatter<'a>(Formatter, PhantomData<&'a mut ()>);
+
+impl<'a> BorrowFormatter<'a> {
+    /// Create a new [`Self`] instance.
+    pub fn new(buffer: &'a mut [u8]) -> Result<BorrowFormatter<'a>> {
+        Ok(Self(
+            // SAFETY: `buffer` is valid for writes for the entire length for
+            // the lifetime of `Self`.
+            unsafe { Formatter::from_buffer(buffer.as_mut_ptr(), buffer.len()) },
+            PhantomData,
+        ))
+    }
+}
+
+impl Deref for BorrowFormatter<'_> {
+    type Target = Formatter;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for BorrowFormatter<'_> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
 /// An owned string that is guaranteed to have exactly one `NUL` byte, which is at the end.
 ///
 /// Used for interoperability with kernel APIs that take C strings.

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 03/14] rust: str: introduce `NullBorrowFormatter`
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
  2025-07-08 19:44 ` [PATCH v2 01/14] rust: str: normalize imports in `str.rs` Andreas Hindborg
  2025-07-08 19:44 ` [PATCH v2 02/14] rust: str: introduce `BorrowFormatter` Andreas Hindborg
@ 2025-07-08 19:44 ` Andreas Hindborg
  2025-07-09 13:23   ` Alice Ryhl
  2025-07-08 19:44 ` [PATCH v2 04/14] rust: block: normalize imports for `gen_disk.rs` Andreas Hindborg
                   ` (10 subsequent siblings)
  13 siblings, 1 reply; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:44 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

Add `NullBorrowFormatter`, a formatter that writes a null terminated string
to an array or slice buffer. Because this type needs to manage the trailing
null marker, the existing formatters cannot be used to implement this type.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 rust/kernel/str.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 59 insertions(+)

diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index 78b2f95eb3171..05d79cf40c201 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -860,6 +860,65 @@ fn deref_mut(&mut self) -> &mut Self::Target {
     }
 }
 
+/// A mutable reference to a byte buffer where a string can be written into.
+///
+/// The buffer will be automatically null terminated after the last written character.
+///
+/// # Invariants
+///
+/// `buffer` is always null terminated.
+pub(crate) struct NullBorrowFormatter<'a> {
+    buffer: &'a mut [u8],
+    pos: usize,
+}
+
+impl<'a> NullBorrowFormatter<'a> {
+    /// Create a new [`Self`] instance.
+    pub(crate) fn new(buffer: &'a mut [u8]) -> Result<NullBorrowFormatter<'a>> {
+        *(buffer.first_mut().ok_or(EINVAL)?) = 0;
+
+        // INVARIANT: We null terminated the buffer above.
+        Ok(Self { buffer, pos: 0 })
+    }
+
+    #[expect(dead_code)]
+    pub(crate) fn from_array<const N: usize>(
+        a: &'a mut [crate::ffi::c_char; N],
+    ) -> Result<NullBorrowFormatter<'a>> {
+        Self::new(
+            // SAFETY: the buffer of `a` is valid for read and write as `u8` for
+            // at least `N` bytes.
+            unsafe { core::slice::from_raw_parts_mut(a.as_mut_ptr().cast::<u8>(), N) },
+        )
+    }
+
+    /// Return the position of the write pointer in the underlying buffer.
+    #[expect(dead_code)]
+    pub(crate) fn pos(&self) -> usize {
+        self.pos
+    }
+}
+
+impl Write for NullBorrowFormatter<'_> {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        let bytes = s.as_bytes();
+        let len = bytes.len();
+
+        // We want space for a null terminator
+        if self.pos + len > self.buffer.len() - 1 {
+            return Err(fmt::Error);
+        }
+
+        self.buffer[self.pos..self.pos + len].copy_from_slice(bytes);
+        self.pos += len;
+
+        // INVARIANT: The buffer is null terminated.
+        self.buffer[self.pos] = 0;
+
+        Ok(())
+    }
+}
+
 /// An owned string that is guaranteed to have exactly one `NUL` byte, which is at the end.
 ///
 /// Used for interoperability with kernel APIs that take C strings.

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 04/14] rust: block: normalize imports for `gen_disk.rs`
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
                   ` (2 preceding siblings ...)
  2025-07-08 19:44 ` [PATCH v2 03/14] rust: str: introduce `NullBorrowFormatter` Andreas Hindborg
@ 2025-07-08 19:44 ` Andreas Hindborg
  2025-07-09 13:26   ` Alice Ryhl
  2025-07-08 19:45 ` [PATCH v2 05/14] rust: block: use `NullBorrowFormatter` Andreas Hindborg
                   ` (9 subsequent siblings)
  13 siblings, 1 reply; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:44 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

Clean up the import statements in `gen_disk.rs` to make the code easier to
maintain.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 rust/kernel/block/mq/gen_disk.rs | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
index cd54cd64ea887..679ee1bb21950 100644
--- a/rust/kernel/block/mq/gen_disk.rs
+++ b/rust/kernel/block/mq/gen_disk.rs
@@ -5,9 +5,13 @@
 //! C header: [`include/linux/blkdev.h`](srctree/include/linux/blkdev.h)
 //! C header: [`include/linux/blk_mq.h`](srctree/include/linux/blk_mq.h)
 
-use crate::block::mq::{raw_writer::RawWriter, Operations, TagSet};
-use crate::{bindings, error::from_err_ptr, error::Result, sync::Arc};
-use crate::{error, static_lock_class};
+use crate::{
+    bindings,
+    block::mq::{raw_writer::RawWriter, Operations, TagSet},
+    error::{self, from_err_ptr, Result},
+    static_lock_class,
+    sync::Arc,
+};
 use core::fmt::{self, Write};
 
 /// A builder for [`GenDisk`].

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 05/14] rust: block: use `NullBorrowFormatter`
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
                   ` (3 preceding siblings ...)
  2025-07-08 19:44 ` [PATCH v2 04/14] rust: block: normalize imports for `gen_disk.rs` Andreas Hindborg
@ 2025-07-08 19:45 ` Andreas Hindborg
  2025-07-09 13:25   ` Alice Ryhl
  2025-07-08 19:45 ` [PATCH v2 06/14] rust: block: remove `RawWriter` Andreas Hindborg
                   ` (8 subsequent siblings)
  13 siblings, 1 reply; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:45 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

Use the new `NullBorrowFormatter` to write the name of a `GenDisk` to the
name buffer. This new formatter automatically adds a trailing null marker
after the written characters, so we don't need to append that at the call
site any longer.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 rust/kernel/block/mq/gen_disk.rs   | 8 ++++----
 rust/kernel/block/mq/raw_writer.rs | 1 +
 rust/kernel/str.rs                 | 7 -------
 3 files changed, 5 insertions(+), 11 deletions(-)

diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
index 679ee1bb21950..e0e42f7028276 100644
--- a/rust/kernel/block/mq/gen_disk.rs
+++ b/rust/kernel/block/mq/gen_disk.rs
@@ -7,9 +7,10 @@
 
 use crate::{
     bindings,
-    block::mq::{raw_writer::RawWriter, Operations, TagSet},
+    block::mq::{Operations, TagSet},
     error::{self, from_err_ptr, Result},
     static_lock_class,
+    str::NullBorrowFormatter,
     sync::Arc,
 };
 use core::fmt::{self, Write};
@@ -143,14 +144,13 @@ pub fn build<T: Operations>(
         // SAFETY: `gendisk` is a valid pointer as we initialized it above
         unsafe { (*gendisk).fops = &TABLE };
 
-        let mut raw_writer = RawWriter::from_array(
+        let mut writer = NullBorrowFormatter::from_array(
             // SAFETY: `gendisk` points to a valid and initialized instance. We
             // have exclusive access, since the disk is not added to the VFS
             // yet.
             unsafe { &mut (*gendisk).disk_name },
         )?;
-        raw_writer.write_fmt(name)?;
-        raw_writer.write_char('\0')?;
+        writer.write_fmt(name)?;
 
         // SAFETY: `gendisk` points to a valid and initialized instance of
         // `struct gendisk`. `set_capacity` takes a lock to synchronize this
diff --git a/rust/kernel/block/mq/raw_writer.rs b/rust/kernel/block/mq/raw_writer.rs
index 7e2159e4f6a6f..0aef55703e71d 100644
--- a/rust/kernel/block/mq/raw_writer.rs
+++ b/rust/kernel/block/mq/raw_writer.rs
@@ -24,6 +24,7 @@ fn new(buffer: &'a mut [u8]) -> Result<RawWriter<'a>> {
         Ok(Self { buffer, pos: 0 })
     }
 
+    #[expect(dead_code)]
     pub(crate) fn from_array<const N: usize>(
         a: &'a mut [crate::ffi::c_char; N],
     ) -> Result<RawWriter<'a>> {
diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index 05d79cf40c201..4140b4af64e50 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -881,7 +881,6 @@ pub(crate) fn new(buffer: &'a mut [u8]) -> Result<NullBorrowFormatter<'a>> {
         Ok(Self { buffer, pos: 0 })
     }
 
-    #[expect(dead_code)]
     pub(crate) fn from_array<const N: usize>(
         a: &'a mut [crate::ffi::c_char; N],
     ) -> Result<NullBorrowFormatter<'a>> {
@@ -891,12 +890,6 @@ pub(crate) fn from_array<const N: usize>(
             unsafe { core::slice::from_raw_parts_mut(a.as_mut_ptr().cast::<u8>(), N) },
         )
     }
-
-    /// Return the position of the write pointer in the underlying buffer.
-    #[expect(dead_code)]
-    pub(crate) fn pos(&self) -> usize {
-        self.pos
-    }
 }
 
 impl Write for NullBorrowFormatter<'_> {

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 06/14] rust: block: remove `RawWriter`
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
                   ` (4 preceding siblings ...)
  2025-07-08 19:45 ` [PATCH v2 05/14] rust: block: use `NullBorrowFormatter` Andreas Hindborg
@ 2025-07-08 19:45 ` Andreas Hindborg
  2025-07-08 19:45 ` [PATCH v2 07/14] rust: block: remove trait bound from `mq::Request` definition Andreas Hindborg
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:45 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

`RawWriter` is now dead code, so remove it.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 rust/kernel/block/mq.rs            |  1 -
 rust/kernel/block/mq/raw_writer.rs | 56 --------------------------------------
 2 files changed, 57 deletions(-)

diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index fb0f393c1cea6..faa3ccb5a49a8 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -89,7 +89,6 @@
 
 pub mod gen_disk;
 mod operations;
-mod raw_writer;
 mod request;
 mod tag_set;
 
diff --git a/rust/kernel/block/mq/raw_writer.rs b/rust/kernel/block/mq/raw_writer.rs
deleted file mode 100644
index 0aef55703e71d..0000000000000
--- a/rust/kernel/block/mq/raw_writer.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-use core::fmt::{self, Write};
-
-use crate::error::Result;
-use crate::prelude::EINVAL;
-
-/// A mutable reference to a byte buffer where a string can be written into.
-///
-/// # Invariants
-///
-/// `buffer` is always null terminated.
-pub(crate) struct RawWriter<'a> {
-    buffer: &'a mut [u8],
-    pos: usize,
-}
-
-impl<'a> RawWriter<'a> {
-    /// Create a new `RawWriter` instance.
-    fn new(buffer: &'a mut [u8]) -> Result<RawWriter<'a>> {
-        *(buffer.last_mut().ok_or(EINVAL)?) = 0;
-
-        // INVARIANT: We null terminated the buffer above.
-        Ok(Self { buffer, pos: 0 })
-    }
-
-    #[expect(dead_code)]
-    pub(crate) fn from_array<const N: usize>(
-        a: &'a mut [crate::ffi::c_char; N],
-    ) -> Result<RawWriter<'a>> {
-        Self::new(
-            // SAFETY: the buffer of `a` is valid for read and write as `u8` for
-            // at least `N` bytes.
-            unsafe { core::slice::from_raw_parts_mut(a.as_mut_ptr().cast::<u8>(), N) },
-        )
-    }
-}
-
-impl Write for RawWriter<'_> {
-    fn write_str(&mut self, s: &str) -> fmt::Result {
-        let bytes = s.as_bytes();
-        let len = bytes.len();
-
-        // We do not want to overwrite our null terminator
-        if self.pos + len > self.buffer.len() - 1 {
-            return Err(fmt::Error);
-        }
-
-        // INVARIANT: We are not overwriting the last byte
-        self.buffer[self.pos..self.pos + len].copy_from_slice(bytes);
-
-        self.pos += len;
-
-        Ok(())
-    }
-}

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 07/14] rust: block: remove trait bound from `mq::Request` definition
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
                   ` (5 preceding siblings ...)
  2025-07-08 19:45 ` [PATCH v2 06/14] rust: block: remove `RawWriter` Andreas Hindborg
@ 2025-07-08 19:45 ` Andreas Hindborg
  2025-07-09 13:25   ` Alice Ryhl
  2025-07-08 19:45 ` [PATCH v2 08/14] rust: block: add block related constants Andreas Hindborg
                   ` (6 subsequent siblings)
  13 siblings, 1 reply; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:45 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

Remove the trait bound `T:Operations` from `mq::Request`. The bound is not
required, so remove it to reduce complexity.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 rust/kernel/block/mq/request.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/rust/kernel/block/mq/request.rs b/rust/kernel/block/mq/request.rs
index 4a5b7ec914efa..2d14a6261a313 100644
--- a/rust/kernel/block/mq/request.rs
+++ b/rust/kernel/block/mq/request.rs
@@ -53,7 +53,7 @@
 /// [`struct request`]: srctree/include/linux/blk-mq.h
 ///
 #[repr(transparent)]
-pub struct Request<T: Operations>(Opaque<bindings::request>, PhantomData<T>);
+pub struct Request<T>(Opaque<bindings::request>, PhantomData<T>);
 
 impl<T: Operations> Request<T> {
     /// Create an [`ARef<Request>`] from a [`struct request`] pointer.

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 08/14] rust: block: add block related constants
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
                   ` (6 preceding siblings ...)
  2025-07-08 19:45 ` [PATCH v2 07/14] rust: block: remove trait bound from `mq::Request` definition Andreas Hindborg
@ 2025-07-08 19:45 ` Andreas Hindborg
  2025-07-08 19:45 ` [PATCH v2 09/14] rnull: move driver to separate directory Andreas Hindborg
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:45 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

Add a few block subsystem constants to the rust `kernel::block` name space.
This makes it easier to access the constants from rust code.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 rust/kernel/block.rs | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/rust/kernel/block.rs b/rust/kernel/block.rs
index 150f710efe5b4..7461adf4d7e06 100644
--- a/rust/kernel/block.rs
+++ b/rust/kernel/block.rs
@@ -3,3 +3,15 @@
 //! Types for working with the block layer.
 
 pub mod mq;
+
+/// Bit mask for masking out [`SECTOR_SIZE`]
+pub const SECTOR_MASK: u32 = bindings::SECTOR_MASK;
+
+/// Sectors are size `1 << SECTOR_SHIFT`.
+pub const SECTOR_SHIFT: u32 = bindings::SECTOR_SHIFT;
+
+/// Size of a sector.
+pub const SECTOR_SIZE: u32 = bindings::SECTOR_SIZE;
+
+/// Power of two difference in size of a page and size of a sector.
+pub const PAGE_SECTORS_SHIFT: u32 = bindings::PAGE_SECTORS_SHIFT;

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 09/14] rnull: move driver to separate directory
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
                   ` (7 preceding siblings ...)
  2025-07-08 19:45 ` [PATCH v2 08/14] rust: block: add block related constants Andreas Hindborg
@ 2025-07-08 19:45 ` Andreas Hindborg
  2025-07-08 19:45 ` [PATCH v2 10/14] rnull: enable configuration via `configfs` Andreas Hindborg
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:45 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

The rust null block driver is about to gain some additional modules. Rather
than pollute the current directory, move the driver to a subdirectory.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 MAINTAINERS                        |  2 +-
 drivers/block/Kconfig              | 10 +---------
 drivers/block/Makefile             |  4 +---
 drivers/block/rnull/Kconfig        | 13 +++++++++++++
 drivers/block/rnull/Makefile       |  3 +++
 drivers/block/{ => rnull}/rnull.rs |  0
 6 files changed, 19 insertions(+), 13 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 0c1d245bf7b84..29b14aec3559f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4246,7 +4246,7 @@ W:	https://rust-for-linux.com
 B:	https://github.com/Rust-for-Linux/linux/issues
 C:	https://rust-for-linux.zulipchat.com/#narrow/stream/Block
 T:	git https://github.com/Rust-for-Linux/linux.git rust-block-next
-F:	drivers/block/rnull.rs
+F:	drivers/block/rnull/
 F:	rust/kernel/block.rs
 F:	rust/kernel/block/
 
diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index 0f70e2374e7fa..6b50dbc0495ba 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -17,6 +17,7 @@ menuconfig BLK_DEV
 if BLK_DEV
 
 source "drivers/block/null_blk/Kconfig"
+source "drivers/block/rnull/Kconfig"
 
 config BLK_DEV_FD
 	tristate "Normal floppy disk support"
@@ -354,15 +355,6 @@ config VIRTIO_BLK
 	  This is the virtual block driver for virtio.  It can be used with
           QEMU based VMMs (like KVM or Xen).  Say Y or M.
 
-config BLK_DEV_RUST_NULL
-	tristate "Rust null block driver (Experimental)"
-	depends on RUST
-	help
-	  This is the Rust implementation of the null block driver. For now it
-	  is only a minimal stub.
-
-	  If unsure, say N.
-
 config BLK_DEV_RBD
 	tristate "Rados block device (RBD)"
 	depends on INET && BLOCK
diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index 097707aca7255..aba3e93d5014b 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -9,9 +9,6 @@
 # needed for trace events
 ccflags-y				+= -I$(src)
 
-obj-$(CONFIG_BLK_DEV_RUST_NULL) += rnull_mod.o
-rnull_mod-y := rnull.o
-
 obj-$(CONFIG_MAC_FLOPPY)	+= swim3.o
 obj-$(CONFIG_BLK_DEV_SWIM)	+= swim_mod.o
 obj-$(CONFIG_BLK_DEV_FD)	+= floppy.o
@@ -39,6 +36,7 @@ obj-$(CONFIG_ZRAM) += zram/
 obj-$(CONFIG_BLK_DEV_RNBD)	+= rnbd/
 
 obj-$(CONFIG_BLK_DEV_NULL_BLK)	+= null_blk/
+obj-$(CONFIG_BLK_DEV_RUST_NULL) += rnull/
 
 obj-$(CONFIG_BLK_DEV_UBLK)			+= ublk_drv.o
 obj-$(CONFIG_BLK_DEV_ZONED_LOOP) += zloop.o
diff --git a/drivers/block/rnull/Kconfig b/drivers/block/rnull/Kconfig
new file mode 100644
index 0000000000000..6dc5aff96bf4e
--- /dev/null
+++ b/drivers/block/rnull/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Rust null block device driver configuration
+
+config BLK_DEV_RUST_NULL
+	tristate "Rust null block driver (Experimental)"
+	depends on RUST
+	help
+	  This is the Rust implementation of the null block driver. Like
+	  the C version, the driver allows the user to create virutal block
+	  devices that can be configured via various configuration options.
+
+	  If unsure, say N.
diff --git a/drivers/block/rnull/Makefile b/drivers/block/rnull/Makefile
new file mode 100644
index 0000000000000..11cfa5e615dcf
--- /dev/null
+++ b/drivers/block/rnull/Makefile
@@ -0,0 +1,3 @@
+
+obj-$(CONFIG_BLK_DEV_RUST_NULL) += rnull_mod.o
+rnull_mod-y := rnull.o
diff --git a/drivers/block/rnull.rs b/drivers/block/rnull/rnull.rs
similarity index 100%
rename from drivers/block/rnull.rs
rename to drivers/block/rnull/rnull.rs

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 10/14] rnull: enable configuration via `configfs`
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
                   ` (8 preceding siblings ...)
  2025-07-08 19:45 ` [PATCH v2 09/14] rnull: move driver to separate directory Andreas Hindborg
@ 2025-07-08 19:45 ` Andreas Hindborg
  2025-07-08 19:45 ` [PATCH v2 11/14] rust: block: add `GenDisk` private data support Andreas Hindborg
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:45 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

Allow rust null block devices to be configured and instantiated via
`configfs`.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 drivers/block/rnull/Kconfig      |   2 +-
 drivers/block/rnull/configfs.rs  | 220 +++++++++++++++++++++++++++++++++++++++
 drivers/block/rnull/rnull.rs     |  58 ++++++-----
 rust/kernel/block/mq/gen_disk.rs |   2 +-
 4 files changed, 253 insertions(+), 29 deletions(-)

diff --git a/drivers/block/rnull/Kconfig b/drivers/block/rnull/Kconfig
index 6dc5aff96bf4e..7bc5b376c128b 100644
--- a/drivers/block/rnull/Kconfig
+++ b/drivers/block/rnull/Kconfig
@@ -4,7 +4,7 @@
 
 config BLK_DEV_RUST_NULL
 	tristate "Rust null block driver (Experimental)"
-	depends on RUST
+	depends on RUST && CONFIGFS_FS
 	help
 	  This is the Rust implementation of the null block driver. Like
 	  the C version, the driver allows the user to create virutal block
diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
new file mode 100644
index 0000000000000..e7bd2b8f1e905
--- /dev/null
+++ b/drivers/block/rnull/configfs.rs
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use super::{NullBlkDevice, THIS_MODULE};
+use core::fmt::Write;
+use kernel::{
+    block::mq::gen_disk::{GenDisk, GenDiskBuilder},
+    c_str,
+    configfs::{self, AttributeOperations},
+    configfs_attrs, new_mutex,
+    page::PAGE_SIZE,
+    prelude::*,
+    str::CString,
+    sync::Mutex,
+};
+use pin_init::PinInit;
+
+pub(crate) fn subsystem() -> impl PinInit<kernel::configfs::Subsystem<Config>, Error> {
+    let item_type = configfs_attrs! {
+        container: configfs::Subsystem<Config>,
+        data: Config,
+        child: DeviceConfig,
+        attributes: [
+            features: 0,
+        ],
+    };
+
+    kernel::configfs::Subsystem::new(c_str!("rnull"), item_type, try_pin_init!(Config {}))
+}
+
+#[pin_data]
+pub(crate) struct Config {}
+
+#[vtable]
+impl AttributeOperations<0> for Config {
+    type Data = Config;
+
+    fn show(_this: &Config, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
+        let mut writer = kernel::str::BorrowFormatter::new(page)?;
+        writer.write_str("blocksize,size,rotational\n")?;
+        Ok(writer.bytes_written())
+    }
+}
+
+#[vtable]
+impl configfs::GroupOperations for Config {
+    type Child = DeviceConfig;
+
+    fn make_group(
+        &self,
+        name: &CStr,
+    ) -> Result<impl PinInit<configfs::Group<DeviceConfig>, Error>> {
+        let item_type = configfs_attrs! {
+            container: configfs::Group<DeviceConfig>,
+            data: DeviceConfig,
+            attributes: [
+                // Named for compatibility with C null_blk
+                power: 0,
+                blocksize: 1,
+                rotational: 2,
+                size: 3,
+            ],
+        };
+
+        Ok(configfs::Group::new(
+            name.try_into()?,
+            item_type,
+            // TODO: cannot coerce new_mutex!() to impl PinInit<_, Error>, so put mutex inside
+            try_pin_init!( DeviceConfig {
+                data <- new_mutex!( DeviceConfigInner {
+                    powered: false,
+                    block_size: 4096,
+                    rotational: false,
+                    disk: None,
+                    capacity_mib: 4096,
+                    name: name.try_into()?,
+                }),
+            }),
+        ))
+    }
+}
+
+#[pin_data]
+pub(crate) struct DeviceConfig {
+    #[pin]
+    data: Mutex<DeviceConfigInner>,
+}
+
+#[pin_data]
+struct DeviceConfigInner {
+    powered: bool,
+    name: CString,
+    block_size: u32,
+    rotational: bool,
+    capacity_mib: u64,
+    disk: Option<GenDisk<NullBlkDevice>>,
+}
+
+#[vtable]
+impl configfs::AttributeOperations<0> for DeviceConfig {
+    type Data = DeviceConfig;
+
+    fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
+        let mut writer = kernel::str::BorrowFormatter::new(page)?;
+
+        if this.data.lock().powered {
+            writer.write_fmt(fmt!("1\n"))?;
+        } else {
+            writer.write_fmt(fmt!("0\n"))?;
+        }
+
+        Ok(writer.bytes_written())
+    }
+
+    fn store(this: &DeviceConfig, page: &[u8]) -> Result {
+        let power_op: bool = core::str::from_utf8(page)?
+            .trim()
+            .parse::<u8>()
+            .map_err(|_| kernel::error::code::EINVAL)?
+            != 0;
+
+        let mut guard = this.data.lock();
+
+        if !guard.powered && power_op {
+            guard.disk = Some(NullBlkDevice::new(
+                &guard.name,
+                guard.block_size,
+                guard.rotational,
+                guard.capacity_mib,
+            )?);
+            guard.powered = true;
+        } else if guard.powered && !power_op {
+            drop(guard.disk.take());
+            guard.powered = false;
+        }
+
+        Ok(())
+    }
+}
+
+#[vtable]
+impl configfs::AttributeOperations<1> for DeviceConfig {
+    type Data = DeviceConfig;
+
+    fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
+        let mut writer = kernel::str::BorrowFormatter::new(page)?;
+        writer.write_fmt(fmt!("{}\n", this.data.lock().block_size))?;
+        Ok(writer.bytes_written())
+    }
+
+    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::<u32>()
+            .map_err(|_| kernel::error::code::EINVAL)?;
+
+        GenDiskBuilder::validate_block_size(value)?;
+        this.data.lock().block_size = value;
+        Ok(())
+    }
+}
+
+#[vtable]
+impl configfs::AttributeOperations<2> for DeviceConfig {
+    type Data = DeviceConfig;
+
+    fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
+        let mut writer = kernel::str::BorrowFormatter::new(page)?;
+
+        if this.data.lock().rotational {
+            writer.write_fmt(fmt!("1\n"))?;
+        } else {
+            writer.write_fmt(fmt!("0\n"))?;
+        }
+
+        Ok(writer.bytes_written())
+    }
+
+    fn store(this: &DeviceConfig, page: &[u8]) -> Result {
+        if this.data.lock().powered {
+            return Err(EBUSY);
+        }
+
+        this.data.lock().rotational = core::str::from_utf8(page)?
+            .trim()
+            .parse::<u8>()
+            .map_err(|_| kernel::error::code::EINVAL)?
+            != 0;
+
+        Ok(())
+    }
+}
+
+#[vtable]
+impl configfs::AttributeOperations<3> for DeviceConfig {
+    type Data = DeviceConfig;
+
+    fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
+        let mut writer = kernel::str::BorrowFormatter::new(page)?;
+        writer.write_fmt(fmt!("{}\n", this.data.lock().capacity_mib))?;
+        Ok(writer.bytes_written())
+    }
+
+    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::<u64>()
+            .map_err(|_| kernel::error::code::EINVAL)?;
+
+        this.data.lock().capacity_mib = value;
+        Ok(())
+    }
+}
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index d07e76ae2c13f..d09bc77861e4e 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -1,28 +1,26 @@
 // SPDX-License-Identifier: GPL-2.0
 
 //! This is a Rust implementation of the C null block driver.
-//!
-//! Supported features:
-//!
-//! - blk-mq interface
-//! - direct completion
-//! - block size 4k
-//!
-//! The driver is not configurable.
+
+mod configfs;
 
 use kernel::{
     alloc::flags,
-    block::mq::{
+    block::{
         self,
-        gen_disk::{self, GenDisk},
-        Operations, TagSet,
+        mq::{
+            self,
+            gen_disk::{self, GenDisk},
+            Operations, TagSet,
+        },
     },
     error::Result,
-    new_mutex, pr_info,
+    pr_info,
     prelude::*,
-    sync::{Arc, Mutex},
+    sync::Arc,
     types::ARef,
 };
+use pin_init::PinInit;
 
 module! {
     type: NullBlkModule,
@@ -35,33 +33,39 @@
 #[pin_data]
 struct NullBlkModule {
     #[pin]
-    _disk: Mutex<GenDisk<NullBlkDevice>>,
+    configfs_subsystem: kernel::configfs::Subsystem<configfs::Config>,
 }
 
 impl kernel::InPlaceModule for NullBlkModule {
     fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
         pr_info!("Rust null_blk loaded\n");
 
-        // Use a immediately-called closure as a stable `try` block
-        let disk = /* try */ (|| {
-            let tagset = Arc::pin_init(TagSet::new(1, 256, 1), flags::GFP_KERNEL)?;
-
-            gen_disk::GenDiskBuilder::new()
-                .capacity_sectors(4096 << 11)
-                .logical_block_size(4096)?
-                .physical_block_size(4096)?
-                .rotational(false)
-                .build(format_args!("rnullb{}", 0), tagset)
-        })();
-
         try_pin_init!(Self {
-            _disk <- new_mutex!(disk?, "nullb:disk"),
+            configfs_subsystem <- configfs::subsystem(),
         })
     }
 }
 
 struct NullBlkDevice;
 
+impl NullBlkDevice {
+    fn new(
+        name: &CStr,
+        block_size: u32,
+        rotational: bool,
+        capacity_mib: u64,
+    ) -> Result<GenDisk<Self>> {
+        let tagset = Arc::pin_init(TagSet::new(1, 256, 1), flags::GFP_KERNEL)?;
+
+        gen_disk::GenDiskBuilder::new()
+            .capacity_sectors(capacity_mib << (20 - block::SECTOR_SHIFT))
+            .logical_block_size(block_size)?
+            .physical_block_size(block_size)?
+            .rotational(rotational)
+            .build(fmt!("{}", name.to_str()?), tagset)
+    }
+}
+
 #[vtable]
 impl Operations for NullBlkDevice {
     #[inline(always)]
diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
index e0e42f7028276..6cf0f3d411b4f 100644
--- a/rust/kernel/block/mq/gen_disk.rs
+++ b/rust/kernel/block/mq/gen_disk.rs
@@ -50,7 +50,7 @@ pub fn rotational(mut self, rotational: bool) -> Self {
 
     /// Validate block size by verifying that it is between 512 and `PAGE_SIZE`,
     /// and that it is a power of two.
-    fn validate_block_size(size: u32) -> Result {
+    pub fn validate_block_size(size: u32) -> Result {
         if !(512..=bindings::PAGE_SIZE as u32).contains(&size) || !size.is_power_of_two() {
             Err(error::code::EINVAL)
         } else {

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 11/14] rust: block: add `GenDisk` private data support
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
                   ` (9 preceding siblings ...)
  2025-07-08 19:45 ` [PATCH v2 10/14] rnull: enable configuration via `configfs` Andreas Hindborg
@ 2025-07-08 19:45 ` Andreas Hindborg
  2025-07-08 19:45 ` [PATCH v2 12/14] rust: block: mq: fix spelling in a safety comment Andreas Hindborg
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:45 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

Allow users of the rust block device driver API to install private data in
the `GenDisk` structure.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 drivers/block/rnull/rnull.rs       |  8 ++++---
 rust/kernel/block/mq.rs            |  7 +++---
 rust/kernel/block/mq/gen_disk.rs   | 32 ++++++++++++++++++++++----
 rust/kernel/block/mq/operations.rs | 46 ++++++++++++++++++++++++++++++--------
 4 files changed, 74 insertions(+), 19 deletions(-)

diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index d09bc77861e4e..a012c59ecb3c9 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -62,14 +62,16 @@ fn new(
             .logical_block_size(block_size)?
             .physical_block_size(block_size)?
             .rotational(rotational)
-            .build(fmt!("{}", name.to_str()?), tagset)
+            .build(fmt!("{}", name.to_str()?), tagset, ())
     }
 }
 
 #[vtable]
 impl Operations for NullBlkDevice {
+    type QueueData = ();
+
     #[inline(always)]
-    fn queue_rq(rq: ARef<mq::Request<Self>>, _is_last: bool) -> Result {
+    fn queue_rq(_queue_data: (), rq: ARef<mq::Request<Self>>, _is_last: bool) -> Result {
         mq::Request::end_ok(rq)
             .map_err(|_e| kernel::error::code::EIO)
             // We take no refcounts on the request, so we expect to be able to
@@ -80,5 +82,5 @@ fn queue_rq(rq: ARef<mq::Request<Self>>, _is_last: bool) -> Result {
         Ok(())
     }
 
-    fn commit_rqs() {}
+    fn commit_rqs(_queue_data: ()) {}
 }
diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index faa3ccb5a49a8..34b7425fa94d8 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -69,20 +69,21 @@
 //!
 //! #[vtable]
 //! impl Operations for MyBlkDevice {
+//!     type QueueData = ();
 //!
-//!     fn queue_rq(rq: ARef<Request<Self>>, _is_last: bool) -> Result {
+//!     fn queue_rq(_queue_data: (), rq: ARef<Request<Self>>, _is_last: bool) -> Result {
 //!         Request::end_ok(rq);
 //!         Ok(())
 //!     }
 //!
-//!     fn commit_rqs() {}
+//!     fn commit_rqs(_queue_data: ()) {}
 //! }
 //!
 //! let tagset: Arc<TagSet<MyBlkDevice>> =
 //!     Arc::pin_init(TagSet::new(1, 256, 1), flags::GFP_KERNEL)?;
 //! let mut disk = gen_disk::GenDiskBuilder::new()
 //!     .capacity_sectors(4096)
-//!     .build(format_args!("myblk"), tagset)?;
+//!     .build(format_args!("myblk"), tagset, ())?;
 //!
 //! # Ok::<(), kernel::error::Error>(())
 //! ```
diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
index 6cf0f3d411b4f..4ab658cf811eb 100644
--- a/rust/kernel/block/mq/gen_disk.rs
+++ b/rust/kernel/block/mq/gen_disk.rs
@@ -12,6 +12,7 @@
     static_lock_class,
     str::NullBorrowFormatter,
     sync::Arc,
+    types::{ForeignOwnable, ScopeGuard},
 };
 use core::fmt::{self, Write};
 
@@ -97,7 +98,14 @@ pub fn build<T: Operations>(
         self,
         name: fmt::Arguments<'_>,
         tagset: Arc<TagSet<T>>,
+        queue_data: T::QueueData,
     ) -> Result<GenDisk<T>> {
+        let data = queue_data.into_foreign();
+        let recover_data = ScopeGuard::new(|| {
+            // SAFETY: T::QueueData was created by the call to `into_foreign()` above
+            unsafe { T::QueueData::from_foreign(data) };
+        });
+
         // SAFETY: `bindings::queue_limits` contain only fields that are valid when zeroed.
         let mut lim: bindings::queue_limits = unsafe { core::mem::zeroed() };
 
@@ -112,7 +120,7 @@ pub fn build<T: Operations>(
             bindings::__blk_mq_alloc_disk(
                 tagset.raw_tag_set(),
                 &mut lim,
-                core::ptr::null_mut(),
+                data.cast(),
                 static_lock_class!().as_ptr(),
             )
         })?;
@@ -165,8 +173,12 @@ pub fn build<T: Operations>(
             },
         )?;
 
+        recover_data.dismiss();
+
         // INVARIANT: `gendisk` was initialized above.
         // INVARIANT: `gendisk` was added to the VFS via `device_add_disk` above.
+        // INVARIANT: `gendisk.queue.queue_data` is set to `data` in the call to
+        // `__blk_mq_alloc_disk` above.
         Ok(GenDisk {
             _tagset: tagset,
             gendisk,
@@ -178,9 +190,10 @@ pub fn build<T: Operations>(
 ///
 /// # Invariants
 ///
-/// - `gendisk` must always point to an initialized and valid `struct gendisk`.
-/// - `gendisk` was added to the VFS through a call to
-///   `bindings::device_add_disk`.
+///  - `gendisk` must always point to an initialized and valid `struct gendisk`.
+///  - `gendisk` was added to the VFS through a call to
+///    `bindings::device_add_disk`.
+///  - `self.gendisk.queue.queuedata` is initialized by a call to `ForeignOwnable::into_foreign`.
 pub struct GenDisk<T: Operations> {
     _tagset: Arc<TagSet<T>>,
     gendisk: *mut bindings::gendisk,
@@ -192,9 +205,20 @@ unsafe impl<T: Operations + Send> Send for GenDisk<T> {}
 
 impl<T: Operations> Drop for GenDisk<T> {
     fn drop(&mut self) {
+        // SAFETY: By type invariant of `Self`, `self.gendisk` points to a valid
+        // and initialized instance of `struct gendisk`, and, `queuedata` was
+        // initialized with the result of a call to
+        // `ForeignOwnable::into_foreign`.
+        let queue_data = unsafe { (*(*self.gendisk).queue).queuedata };
+
         // SAFETY: By type invariant, `self.gendisk` points to a valid and
         // initialized instance of `struct gendisk`, and it was previously added
         // to the VFS.
         unsafe { bindings::del_gendisk(self.gendisk) };
+
+        // SAFETY: `queue.queuedata` was created by `GenDiskBuilder::build` with
+        // a call to `ForeignOwnable::into_foreign` to create `queuedata`.
+        // `ForeignOwnable::from_foreign` is only called here.
+        let _queue_data = unsafe { T::QueueData::from_foreign(queue_data.cast()) };
     }
 }
diff --git a/rust/kernel/block/mq/operations.rs b/rust/kernel/block/mq/operations.rs
index 864ff379dc918..c50959d5517bc 100644
--- a/rust/kernel/block/mq/operations.rs
+++ b/rust/kernel/block/mq/operations.rs
@@ -6,14 +6,15 @@
 
 use crate::{
     bindings,
-    block::mq::request::RequestDataWrapper,
-    block::mq::Request,
+    block::mq::{request::RequestDataWrapper, Request},
     error::{from_result, Result},
     prelude::*,
-    types::ARef,
+    types::{ARef, ForeignOwnable},
 };
 use core::{marker::PhantomData, sync::atomic::AtomicU64, sync::atomic::Ordering};
 
+type ForeignBorrowed<'a, T> = <T as ForeignOwnable>::Borrowed<'a>;
+
 /// Implement this trait to interface blk-mq as block devices.
 ///
 /// To implement a block device driver, implement this trait as described in the
@@ -26,12 +27,20 @@
 /// [module level documentation]: kernel::block::mq
 #[macros::vtable]
 pub trait Operations: Sized {
+    /// Data associated with the `struct request_queue` that is allocated for
+    /// the `GenDisk` associated with this `Operations` implementation.
+    type QueueData: ForeignOwnable;
+
     /// 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(rq: ARef<Request<Self>>, is_last: bool) -> Result;
+    fn queue_rq(
+        queue_data: ForeignBorrowed<'_, Self::QueueData>,
+        rq: ARef<Request<Self>>,
+        is_last: bool,
+    ) -> Result;
 
     /// Called by the kernel to indicate that queued requests should be submitted.
-    fn commit_rqs();
+    fn commit_rqs(queue_data: ForeignBorrowed<'_, Self::QueueData>);
 
     /// Called by the kernel to poll the device for completed requests. Only
     /// used for poll queues.
@@ -70,7 +79,7 @@ impl<T: Operations> OperationsVTable<T> {
     ///   promise to not access the request until the driver calls
     ///   `bindings::blk_mq_end_request` for the request.
     unsafe extern "C" fn queue_rq_callback(
-        _hctx: *mut bindings::blk_mq_hw_ctx,
+        hctx: *mut bindings::blk_mq_hw_ctx,
         bd: *const bindings::blk_mq_queue_data,
     ) -> bindings::blk_status_t {
         // SAFETY: `bd.rq` is valid as required by the safety requirement for
@@ -88,10 +97,20 @@ impl<T: Operations> OperationsVTable<T> {
         //    reference counted by `ARef` until then.
         let rq = unsafe { Request::aref_from_raw((*bd).rq) };
 
+        // SAFETY: `hctx` is valid as required by this function.
+        let queue_data = unsafe { (*(*hctx).queue).queuedata };
+
+        // SAFETY: `queue.queuedata` was created by `GenDisk::try_new()` with a
+        // call to `ForeignOwnable::into_pointer()` to create `queuedata`.
+        // `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.cast()) };
+
         // SAFETY: We have exclusive access and we just set the refcount above.
         unsafe { Request::start_unchecked(&rq) };
 
         let ret = T::queue_rq(
+            queue_data,
             rq,
             // SAFETY: `bd` is valid as required by the safety requirement for
             // this function.
@@ -110,9 +129,18 @@ impl<T: Operations> OperationsVTable<T> {
     ///
     /// # Safety
     ///
-    /// This function may only be called by blk-mq C infrastructure.
-    unsafe extern "C" fn commit_rqs_callback(_hctx: *mut bindings::blk_mq_hw_ctx) {
-        T::commit_rqs()
+    /// 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: `hctx` is valid as required by this function.
+        let queue_data = unsafe { (*(*hctx).queue).queuedata };
+
+        // SAFETY: `queue.queuedata` was created by `GenDisk::try_new()` with a
+        // call to `ForeignOwnable::into_pointer()` to create `queuedata`.
+        // `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.cast()) };
+        T::commit_rqs(queue_data)
     }
 
     /// This function is called by the C kernel. It is not currently

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 12/14] rust: block: mq: fix spelling in a safety comment
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
                   ` (10 preceding siblings ...)
  2025-07-08 19:45 ` [PATCH v2 11/14] rust: block: add `GenDisk` private data support Andreas Hindborg
@ 2025-07-08 19:45 ` Andreas Hindborg
  2025-07-08 19:45 ` [PATCH v2 13/14] rust: block: add remote completion to `Request` Andreas Hindborg
  2025-07-08 19:45 ` [PATCH v2 14/14] rnull: add soft-irq completion support Andreas Hindborg
  13 siblings, 0 replies; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:45 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

Add code block quotes to a safety comment.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 rust/kernel/block/mq/request.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/rust/kernel/block/mq/request.rs b/rust/kernel/block/mq/request.rs
index 2d14a6261a313..873d00db74dd5 100644
--- a/rust/kernel/block/mq/request.rs
+++ b/rust/kernel/block/mq/request.rs
@@ -143,7 +143,7 @@ pub(crate) unsafe fn wrapper_ptr(this: *mut Self) -> NonNull<RequestDataWrapper>
         // valid allocation.
         let wrapper_ptr =
             unsafe { bindings::blk_mq_rq_to_pdu(request_ptr).cast::<RequestDataWrapper>() };
-        // SAFETY: By C API contract, wrapper_ptr points to a valid allocation
+        // SAFETY: By C API contract, `wrapper_ptr` points to a valid allocation
         // and is not null.
         unsafe { NonNull::new_unchecked(wrapper_ptr) }
     }

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 13/14] rust: block: add remote completion to `Request`
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
                   ` (11 preceding siblings ...)
  2025-07-08 19:45 ` [PATCH v2 12/14] rust: block: mq: fix spelling in a safety comment Andreas Hindborg
@ 2025-07-08 19:45 ` Andreas Hindborg
  2025-07-08 19:45 ` [PATCH v2 14/14] rnull: add soft-irq completion support Andreas Hindborg
  13 siblings, 0 replies; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:45 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

Allow users of rust block device driver API to schedule completion of
requests via `blk_mq_complete_request_remote`.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 drivers/block/rnull/rnull.rs       |  9 +++++++++
 rust/kernel/block/mq.rs            |  6 ++++++
 rust/kernel/block/mq/operations.rs | 19 +++++++++++++++----
 rust/kernel/block/mq/request.rs    | 17 +++++++++++++++++
 4 files changed, 47 insertions(+), 4 deletions(-)

diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index a012c59ecb3c9..371786be7f476 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -83,4 +83,13 @@ fn queue_rq(_queue_data: (), rq: ARef<mq::Request<Self>>, _is_last: bool) -> Res
     }
 
     fn commit_rqs(_queue_data: ()) {}
+
+    fn complete(rq: ARef<mq::Request<Self>>) {
+        mq::Request::end_ok(rq)
+            .map_err(|_e| kernel::error::code::EIO)
+            // We take no refcounts on the request, so we expect to be able to
+            // end the request. The request reference must be unique at this
+            // point, and so `end_ok` cannot fail.
+            .expect("Fatal error - expected to be able to end request");
+    }
 }
diff --git a/rust/kernel/block/mq.rs b/rust/kernel/block/mq.rs
index 34b7425fa94d8..551ef38efea25 100644
--- a/rust/kernel/block/mq.rs
+++ b/rust/kernel/block/mq.rs
@@ -77,6 +77,12 @@
 //!     }
 //!
 //!     fn commit_rqs(_queue_data: ()) {}
+//!
+//!     fn complete(rq: ARef<Request<Self>>) {
+//!         Request::end_ok(rq)
+//!             .map_err(|_e| kernel::error::code::EIO)
+//!             .expect("Fatal error - expected to be able to end request");
+//!     }
 //! }
 //!
 //! let tagset: Arc<TagSet<MyBlkDevice>> =
diff --git a/rust/kernel/block/mq/operations.rs b/rust/kernel/block/mq/operations.rs
index c50959d5517bc..b6b26cebd4f55 100644
--- a/rust/kernel/block/mq/operations.rs
+++ b/rust/kernel/block/mq/operations.rs
@@ -42,6 +42,9 @@ fn queue_rq(
     /// Called by the kernel to indicate that queued requests should be submitted.
     fn commit_rqs(queue_data: ForeignBorrowed<'_, Self::QueueData>);
 
+    /// 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 {
@@ -143,13 +146,21 @@ impl<T: Operations> OperationsVTable<T> {
         T::commit_rqs(queue_data)
     }
 
-    /// This function is called by the C kernel. It is not currently
-    /// implemented, and there is no way to exercise this code path.
+    /// 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.
-    unsafe extern "C" fn complete_callback(_rq: *mut bindings::request) {}
+    /// This function may only be called by blk-mq C infrastructure. `rq` must
+    /// point to a valid request that has been marked as completed. The pointee
+    /// of `rq` must be valid for write for the duration of this function.
+    unsafe extern "C" fn complete_callback(rq: *mut bindings::request) {
+        // SAFETY: This function can only be dispatched through
+        // `Request::complete`. We leaked a refcount then which we pick back up
+        // now.
+        let aref = unsafe { Request::aref_from_raw(rq) };
+        T::complete(aref);
+    }
 
     /// This function is called by the C kernel. A pointer to this function is
     /// installed in the `blk_mq_ops` vtable for the driver.
diff --git a/rust/kernel/block/mq/request.rs b/rust/kernel/block/mq/request.rs
index 873d00db74dd5..244578a802ceb 100644
--- a/rust/kernel/block/mq/request.rs
+++ b/rust/kernel/block/mq/request.rs
@@ -130,6 +130,23 @@ pub fn end_ok(this: ARef<Self>) -> Result<(), ARef<Self>> {
         Ok(())
     }
 
+    /// Complete the request by scheduling `Operations::complete` for
+    /// execution.
+    ///
+    /// The function may be scheduled locally, via SoftIRQ or remotely via IPMI.
+    /// See `blk_mq_complete_request_remote` in [`blk-mq.c`] for details.
+    ///
+    /// [`blk-mq.c`]: srctree/block/blk-mq.c
+    pub fn complete(this: ARef<Self>) {
+        let ptr = ARef::into_raw(this).cast::<bindings::request>().as_ptr();
+        // SAFETY: By type invariant, `self.0` is a valid `struct request`
+        if !unsafe { bindings::blk_mq_complete_request_remote(ptr) } {
+            // SAFETY: We released a refcount above that we can reclaim here.
+            let this = unsafe { Request::aref_from_raw(ptr) };
+            T::complete(this);
+        }
+    }
+
     /// Return a pointer to the [`RequestDataWrapper`] stored in the private area
     /// of the request structure.
     ///

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2 14/14] rnull: add soft-irq completion support
  2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
                   ` (12 preceding siblings ...)
  2025-07-08 19:45 ` [PATCH v2 13/14] rust: block: add remote completion to `Request` Andreas Hindborg
@ 2025-07-08 19:45 ` Andreas Hindborg
  13 siblings, 0 replies; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-08 19:45 UTC (permalink / raw)
  To: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Jens Axboe
  Cc: linux-block, rust-for-linux, linux-kernel, Andreas Hindborg

rnull currently only supports direct completion. Add option for completing
requests across CPU nodes via soft IRQ or IPI.

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

diff --git a/drivers/block/rnull/configfs.rs b/drivers/block/rnull/configfs.rs
index e7bd2b8f1e905..19bc7278546ca 100644
--- a/drivers/block/rnull/configfs.rs
+++ b/drivers/block/rnull/configfs.rs
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
 use super::{NullBlkDevice, THIS_MODULE};
-use core::fmt::Write;
+use core::fmt::{Display, Write};
 use kernel::{
     block::mq::gen_disk::{GenDisk, GenDiskBuilder},
     c_str,
@@ -36,7 +36,7 @@ impl AttributeOperations<0> for Config {
 
     fn show(_this: &Config, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
         let mut writer = kernel::str::BorrowFormatter::new(page)?;
-        writer.write_str("blocksize,size,rotational\n")?;
+        writer.write_str("blocksize,size,rotational,irqmode\n")?;
         Ok(writer.bytes_written())
     }
 }
@@ -58,6 +58,7 @@ fn make_group(
                 blocksize: 1,
                 rotational: 2,
                 size: 3,
+                irqmode: 4,
             ],
         };
 
@@ -72,6 +73,7 @@ fn make_group(
                     rotational: false,
                     disk: None,
                     capacity_mib: 4096,
+                    irq_mode: IRQMode::None,
                     name: name.try_into()?,
                 }),
             }),
@@ -79,6 +81,34 @@ fn make_group(
     }
 }
 
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum IRQMode {
+    None,
+    Soft,
+}
+
+impl TryFrom<u8> for IRQMode {
+    type Error = kernel::error::Error;
+
+    fn try_from(value: u8) -> Result<Self> {
+        match value {
+            0 => Ok(Self::None),
+            1 => Ok(Self::Soft),
+            _ => Err(kernel::error::code::EINVAL),
+        }
+    }
+}
+
+impl Display for IRQMode {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            Self::None => f.write_str("0")?,
+            Self::Soft => f.write_str("1")?,
+        }
+        Ok(())
+    }
+}
+
 #[pin_data]
 pub(crate) struct DeviceConfig {
     #[pin]
@@ -92,6 +122,7 @@ struct DeviceConfigInner {
     block_size: u32,
     rotational: bool,
     capacity_mib: u64,
+    irq_mode: IRQMode,
     disk: Option<GenDisk<NullBlkDevice>>,
 }
 
@@ -126,6 +157,7 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
                 guard.block_size,
                 guard.rotational,
                 guard.capacity_mib,
+                guard.irq_mode,
             )?);
             guard.powered = true;
         } else if guard.powered && !power_op {
@@ -218,3 +250,28 @@ fn store(this: &DeviceConfig, page: &[u8]) -> Result {
         Ok(())
     }
 }
+
+#[vtable]
+impl configfs::AttributeOperations<4> for DeviceConfig {
+    type Data = DeviceConfig;
+
+    fn show(this: &DeviceConfig, page: &mut [u8; PAGE_SIZE]) -> Result<usize> {
+        let mut writer = kernel::str::BorrowFormatter::new(page)?;
+        writer.write_fmt(fmt!("{}\n", this.data.lock().irq_mode))?;
+        Ok(writer.bytes_written())
+    }
+
+    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::<u8>()
+            .map_err(|_| kernel::error::code::EINVAL)?;
+
+        this.data.lock().irq_mode = IRQMode::try_from(value)?;
+        Ok(())
+    }
+}
diff --git a/drivers/block/rnull/rnull.rs b/drivers/block/rnull/rnull.rs
index 371786be7f476..85b1509a31065 100644
--- a/drivers/block/rnull/rnull.rs
+++ b/drivers/block/rnull/rnull.rs
@@ -4,6 +4,7 @@
 
 mod configfs;
 
+use configfs::IRQMode;
 use kernel::{
     alloc::flags,
     block::{
@@ -54,35 +55,44 @@ fn new(
         block_size: u32,
         rotational: bool,
         capacity_mib: u64,
+        irq_mode: IRQMode,
     ) -> Result<GenDisk<Self>> {
         let tagset = Arc::pin_init(TagSet::new(1, 256, 1), flags::GFP_KERNEL)?;
 
+        let queue_data = Box::new(QueueData { irq_mode }, flags::GFP_KERNEL)?;
+
         gen_disk::GenDiskBuilder::new()
             .capacity_sectors(capacity_mib << (20 - block::SECTOR_SHIFT))
             .logical_block_size(block_size)?
             .physical_block_size(block_size)?
             .rotational(rotational)
-            .build(fmt!("{}", name.to_str()?), tagset, ())
+            .build(fmt!("{}", name.to_str()?), tagset, queue_data)
     }
 }
 
+struct QueueData {
+    irq_mode: IRQMode,
+}
+
 #[vtable]
 impl Operations for NullBlkDevice {
-    type QueueData = ();
+    type QueueData = KBox<QueueData>;
 
     #[inline(always)]
-    fn queue_rq(_queue_data: (), rq: ARef<mq::Request<Self>>, _is_last: bool) -> Result {
-        mq::Request::end_ok(rq)
-            .map_err(|_e| kernel::error::code::EIO)
-            // We take no refcounts on the request, so we expect to be able to
-            // end the request. The request reference must be unique at this
-            // point, and so `end_ok` cannot fail.
-            .expect("Fatal error - expected to be able to end request");
-
+    fn queue_rq(queue_data: &QueueData, rq: ARef<mq::Request<Self>>, _is_last: bool) -> Result {
+        match queue_data.irq_mode {
+            IRQMode::None => mq::Request::end_ok(rq)
+                .map_err(|_e| kernel::error::code::EIO)
+                // We take no refcounts on the request, so we expect to be able to
+                // end the request. The request reference must be unique at this
+                // point, and so `end_ok` cannot fail.
+                .expect("Fatal error - expected to be able to end request"),
+            IRQMode::Soft => mq::Request::complete(rq),
+        }
         Ok(())
     }
 
-    fn commit_rqs(_queue_data: ()) {}
+    fn commit_rqs(_queue_data: &QueueData) {}
 
     fn complete(rq: ARef<mq::Request<Self>>) {
         mq::Request::end_ok(rq)

-- 
2.47.2



^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 02/14] rust: str: introduce `BorrowFormatter`
  2025-07-08 19:44 ` [PATCH v2 02/14] rust: str: introduce `BorrowFormatter` Andreas Hindborg
@ 2025-07-09 13:14   ` Alice Ryhl
  2025-07-09 15:11     ` Andreas Hindborg
  0 siblings, 1 reply; 27+ messages in thread
From: Alice Ryhl @ 2025-07-09 13:14 UTC (permalink / raw)
  To: Andreas Hindborg
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

On Tue, Jul 08, 2025 at 09:44:57PM +0200, Andreas Hindborg wrote:
> Add `BorrowFormatter`, a formatter that writes to an array or slice buffer.
> This formatter is backed by the existing `Formatter`.
> 
> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>

I don't think we need a separate BorrowFormatter. We can instead add a
lifetime to Formatter and give it a safe constructor from a mutable
slice.

Alice

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 03/14] rust: str: introduce `NullBorrowFormatter`
  2025-07-08 19:44 ` [PATCH v2 03/14] rust: str: introduce `NullBorrowFormatter` Andreas Hindborg
@ 2025-07-09 13:23   ` Alice Ryhl
  2025-07-09 15:49     ` Andreas Hindborg
  0 siblings, 1 reply; 27+ messages in thread
From: Alice Ryhl @ 2025-07-09 13:23 UTC (permalink / raw)
  To: Andreas Hindborg
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

On Tue, Jul 08, 2025 at 09:44:58PM +0200, Andreas Hindborg wrote:
> Add `NullBorrowFormatter`, a formatter that writes a null terminated string
> to an array or slice buffer. Because this type needs to manage the trailing
> null marker, the existing formatters cannot be used to implement this type.
> 
> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
> ---
>  rust/kernel/str.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 59 insertions(+)
> 
> diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
> index 78b2f95eb3171..05d79cf40c201 100644
> --- a/rust/kernel/str.rs
> +++ b/rust/kernel/str.rs
> @@ -860,6 +860,65 @@ fn deref_mut(&mut self) -> &mut Self::Target {
>      }
>  }
>  
> +/// A mutable reference to a byte buffer where a string can be written into.
> +///
> +/// The buffer will be automatically null terminated after the last written character.
> +///
> +/// # Invariants
> +///
> +/// `buffer` is always null terminated.
> +pub(crate) struct NullBorrowFormatter<'a> {
> +    buffer: &'a mut [u8],
> +    pos: usize,
> +}

Do you need `pos`? Often I see this kind of code subslice `buffer`
instead.

> +impl<'a> NullBorrowFormatter<'a> {
> +    /// Create a new [`Self`] instance.
> +    pub(crate) fn new(buffer: &'a mut [u8]) -> Result<NullBorrowFormatter<'a>> {
> +        *(buffer.first_mut().ok_or(EINVAL)?) = 0;
> +
> +        // INVARIANT: We null terminated the buffer above.
> +        Ok(Self { buffer, pos: 0 })
> +    }

I would probably just use an Option for this constructor.

> +    #[expect(dead_code)]
> +    pub(crate) fn from_array<const N: usize>(
> +        a: &'a mut [crate::ffi::c_char; N],
> +    ) -> Result<NullBorrowFormatter<'a>> {
> +        Self::new(
> +            // SAFETY: the buffer of `a` is valid for read and write as `u8` for
> +            // at least `N` bytes.
> +            unsafe { core::slice::from_raw_parts_mut(a.as_mut_ptr().cast::<u8>(), N) },
> +        )
> +    }

Arrays automatically coerce to slices, so I don't think this is
necessary. You can just call `new`.

> +    /// Return the position of the write pointer in the underlying buffer.
> +    #[expect(dead_code)]
> +    pub(crate) fn pos(&self) -> usize {
> +        self.pos
> +    }

You delete this function in one of the later patches, so it makes more
sense not to add it.

> +}
> +
> +impl Write for NullBorrowFormatter<'_> {
> +    fn write_str(&mut self, s: &str) -> fmt::Result {
> +        let bytes = s.as_bytes();
> +        let len = bytes.len();
> +
> +        // We want space for a null terminator
> +        if self.pos + len > self.buffer.len() - 1 {

Integer overflow?

> +            return Err(fmt::Error);
> +        }
> +
> +        self.buffer[self.pos..self.pos + len].copy_from_slice(bytes);
> +        self.pos += len;
> +
> +        // INVARIANT: The buffer is null terminated.
> +        self.buffer[self.pos] = 0;
> +
> +        Ok(())
> +    }
> +}
> +
>  /// An owned string that is guaranteed to have exactly one `NUL` byte, which is at the end.
>  ///
>  /// Used for interoperability with kernel APIs that take C strings.
> 
> -- 
> 2.47.2
> 
> 

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 05/14] rust: block: use `NullBorrowFormatter`
  2025-07-08 19:45 ` [PATCH v2 05/14] rust: block: use `NullBorrowFormatter` Andreas Hindborg
@ 2025-07-09 13:25   ` Alice Ryhl
  2025-07-11  9:29     ` Andreas Hindborg
  0 siblings, 1 reply; 27+ messages in thread
From: Alice Ryhl @ 2025-07-09 13:25 UTC (permalink / raw)
  To: Andreas Hindborg
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

On Tue, Jul 08, 2025 at 09:45:00PM +0200, Andreas Hindborg wrote:
> Use the new `NullBorrowFormatter` to write the name of a `GenDisk` to the
> name buffer. This new formatter automatically adds a trailing null marker
> after the written characters, so we don't need to append that at the call
> site any longer.
> 
> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
> ---
>  rust/kernel/block/mq/gen_disk.rs   | 8 ++++----
>  rust/kernel/block/mq/raw_writer.rs | 1 +
>  rust/kernel/str.rs                 | 7 -------
>  3 files changed, 5 insertions(+), 11 deletions(-)
> 
> diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
> index 679ee1bb21950..e0e42f7028276 100644
> --- a/rust/kernel/block/mq/gen_disk.rs
> +++ b/rust/kernel/block/mq/gen_disk.rs
> @@ -7,9 +7,10 @@
>  
>  use crate::{
>      bindings,
> -    block::mq::{raw_writer::RawWriter, Operations, TagSet},
> +    block::mq::{Operations, TagSet},
>      error::{self, from_err_ptr, Result},
>      static_lock_class,
> +    str::NullBorrowFormatter,
>      sync::Arc,
>  };
>  use core::fmt::{self, Write};
> @@ -143,14 +144,13 @@ pub fn build<T: Operations>(
>          // SAFETY: `gendisk` is a valid pointer as we initialized it above
>          unsafe { (*gendisk).fops = &TABLE };
>  
> -        let mut raw_writer = RawWriter::from_array(
> +        let mut writer = NullBorrowFormatter::from_array(
>              // SAFETY: `gendisk` points to a valid and initialized instance. We
>              // have exclusive access, since the disk is not added to the VFS
>              // yet.
>              unsafe { &mut (*gendisk).disk_name },
>          )?;
> -        raw_writer.write_fmt(name)?;
> -        raw_writer.write_char('\0')?;
> +        writer.write_fmt(name)?;

Although this is nicer than the existing code, I wonder if it should
just be a function rather than a whole NullBorrowFormatter struct? Take
a slice and a fmt::Arguments and write it with a nul-terminator. Do you
need anything more complex than what you have here?

Alice

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 07/14] rust: block: remove trait bound from `mq::Request` definition
  2025-07-08 19:45 ` [PATCH v2 07/14] rust: block: remove trait bound from `mq::Request` definition Andreas Hindborg
@ 2025-07-09 13:25   ` Alice Ryhl
  0 siblings, 0 replies; 27+ messages in thread
From: Alice Ryhl @ 2025-07-09 13:25 UTC (permalink / raw)
  To: Andreas Hindborg
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

On Tue, Jul 08, 2025 at 09:45:02PM +0200, Andreas Hindborg wrote:
> Remove the trait bound `T:Operations` from `mq::Request`. The bound is not
> required, so remove it to reduce complexity.
> 
> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>

Reviewed-by: Alice Ryhl <aliceryhl@google.com>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 04/14] rust: block: normalize imports for `gen_disk.rs`
  2025-07-08 19:44 ` [PATCH v2 04/14] rust: block: normalize imports for `gen_disk.rs` Andreas Hindborg
@ 2025-07-09 13:26   ` Alice Ryhl
  0 siblings, 0 replies; 27+ messages in thread
From: Alice Ryhl @ 2025-07-09 13:26 UTC (permalink / raw)
  To: Andreas Hindborg
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

On Tue, Jul 08, 2025 at 09:44:59PM +0200, Andreas Hindborg wrote:
> Clean up the import statements in `gen_disk.rs` to make the code easier to
> maintain.
> 
> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>

Reviewed-by: Alice Ryhl <aliceryhl@google.com>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 01/14] rust: str: normalize imports in `str.rs`
  2025-07-08 19:44 ` [PATCH v2 01/14] rust: str: normalize imports in `str.rs` Andreas Hindborg
@ 2025-07-09 13:26   ` Alice Ryhl
  0 siblings, 0 replies; 27+ messages in thread
From: Alice Ryhl @ 2025-07-09 13:26 UTC (permalink / raw)
  To: Andreas Hindborg
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

On Tue, Jul 08, 2025 at 09:44:56PM +0200, Andreas Hindborg wrote:
> Clean up imports in `str.rs`. This makes future code manipulation more
> manageable.
> 
> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>

Reviewed-by: Alice Ryhl <aliceryhl@google.com>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 02/14] rust: str: introduce `BorrowFormatter`
  2025-07-09 13:14   ` Alice Ryhl
@ 2025-07-09 15:11     ` Andreas Hindborg
  0 siblings, 0 replies; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-09 15:11 UTC (permalink / raw)
  To: Alice Ryhl
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

"Alice Ryhl" <aliceryhl@google.com> writes:

> On Tue, Jul 08, 2025 at 09:44:57PM +0200, Andreas Hindborg wrote:
>> Add `BorrowFormatter`, a formatter that writes to an array or slice buffer.
>> This formatter is backed by the existing `Formatter`.
>>
>> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
>
> I don't think we need a separate BorrowFormatter. We can instead add a
> lifetime to Formatter and give it a safe constructor from a mutable
> slice.

Ah, good idea.


Best regards,
Andreas Hindborg




^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 03/14] rust: str: introduce `NullBorrowFormatter`
  2025-07-09 13:23   ` Alice Ryhl
@ 2025-07-09 15:49     ` Andreas Hindborg
  2025-07-10  8:47       ` Alice Ryhl
  0 siblings, 1 reply; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-09 15:49 UTC (permalink / raw)
  To: Alice Ryhl
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

"Alice Ryhl" <aliceryhl@google.com> writes:

> On Tue, Jul 08, 2025 at 09:44:58PM +0200, Andreas Hindborg wrote:
>> Add `NullBorrowFormatter`, a formatter that writes a null terminated string
>> to an array or slice buffer. Because this type needs to manage the trailing
>> null marker, the existing formatters cannot be used to implement this type.
>>
>> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
>> ---
>>  rust/kernel/str.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 59 insertions(+)
>>
>> diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
>> index 78b2f95eb3171..05d79cf40c201 100644
>> --- a/rust/kernel/str.rs
>> +++ b/rust/kernel/str.rs
>> @@ -860,6 +860,65 @@ fn deref_mut(&mut self) -> &mut Self::Target {
>>      }
>>  }
>>
>> +/// A mutable reference to a byte buffer where a string can be written into.
>> +///
>> +/// The buffer will be automatically null terminated after the last written character.
>> +///
>> +/// # Invariants
>> +///
>> +/// `buffer` is always null terminated.
>> +pub(crate) struct NullBorrowFormatter<'a> {
>> +    buffer: &'a mut [u8],
>> +    pos: usize,
>> +}
>
> Do you need `pos`? Often I see this kind of code subslice `buffer`
> instead.

How would that work? Can I move the start index of `buffer` in some way
without an unsafe block?

>
>> +impl<'a> NullBorrowFormatter<'a> {
>> +    /// Create a new [`Self`] instance.
>> +    pub(crate) fn new(buffer: &'a mut [u8]) -> Result<NullBorrowFormatter<'a>> {
>> +        *(buffer.first_mut().ok_or(EINVAL)?) = 0;
>> +
>> +        // INVARIANT: We null terminated the buffer above.
>> +        Ok(Self { buffer, pos: 0 })
>> +    }
>
> I would probably just use an Option for this constructor.

OK.

>
>> +    #[expect(dead_code)]
>> +    pub(crate) fn from_array<const N: usize>(
>> +        a: &'a mut [crate::ffi::c_char; N],
>> +    ) -> Result<NullBorrowFormatter<'a>> {
>> +        Self::new(
>> +            // SAFETY: the buffer of `a` is valid for read and write as `u8` for
>> +            // at least `N` bytes.
>> +            unsafe { core::slice::from_raw_parts_mut(a.as_mut_ptr().cast::<u8>(), N) },
>> +        )
>> +    }
>
> Arrays automatically coerce to slices, so I don't think this is
> necessary. You can just call `new`.

Nice!

>
>> +    /// Return the position of the write pointer in the underlying buffer.
>> +    #[expect(dead_code)]
>> +    pub(crate) fn pos(&self) -> usize {
>> +        self.pos
>> +    }
>
> You delete this function in one of the later patches, so it makes more
> sense not to add it.

Oops.

>
>> +}
>> +
>> +impl Write for NullBorrowFormatter<'_> {
>> +    fn write_str(&mut self, s: &str) -> fmt::Result {
>> +        let bytes = s.as_bytes();
>> +        let len = bytes.len();
>> +
>> +        // We want space for a null terminator
>> +        if self.pos + len > self.buffer.len() - 1 {
>
> Integer overflow?

In the subtraction? `buffer.len()` is at least 1, because of the type invariant.

Or do you mean I should do a checked add for `self.pos + len`?


Best regards,
Andreas Hindborg



^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 03/14] rust: str: introduce `NullBorrowFormatter`
  2025-07-09 15:49     ` Andreas Hindborg
@ 2025-07-10  8:47       ` Alice Ryhl
  2025-07-10 11:01         ` Andreas Hindborg
  0 siblings, 1 reply; 27+ messages in thread
From: Alice Ryhl @ 2025-07-10  8:47 UTC (permalink / raw)
  To: Andreas Hindborg
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

On Wed, Jul 09, 2025 at 05:49:57PM +0200, Andreas Hindborg wrote:
> "Alice Ryhl" <aliceryhl@google.com> writes:
> 
> > On Tue, Jul 08, 2025 at 09:44:58PM +0200, Andreas Hindborg wrote:
> >> Add `NullBorrowFormatter`, a formatter that writes a null terminated string
> >> to an array or slice buffer. Because this type needs to manage the trailing
> >> null marker, the existing formatters cannot be used to implement this type.
> >>
> >> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
> >> ---
> >>  rust/kernel/str.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> >>  1 file changed, 59 insertions(+)
> >>
> >> diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
> >> index 78b2f95eb3171..05d79cf40c201 100644
> >> --- a/rust/kernel/str.rs
> >> +++ b/rust/kernel/str.rs
> >> @@ -860,6 +860,65 @@ fn deref_mut(&mut self) -> &mut Self::Target {
> >>      }
> >>  }
> >>
> >> +/// A mutable reference to a byte buffer where a string can be written into.
> >> +///
> >> +/// The buffer will be automatically null terminated after the last written character.
> >> +///
> >> +/// # Invariants
> >> +///
> >> +/// `buffer` is always null terminated.
> >> +pub(crate) struct NullBorrowFormatter<'a> {
> >> +    buffer: &'a mut [u8],
> >> +    pos: usize,
> >> +}
> >
> > Do you need `pos`? Often I see this kind of code subslice `buffer`
> > instead.
> 
> How would that work? Can I move the start index of `buffer` in some way
> without an unsafe block?

Yes. I think this will work:

let buffer = mem::take(&mut self.buffer);
self.buffer = &mut buffer[pos..];

Temporarily storing an empty slice avoids lifetime issues.

> >> +    #[expect(dead_code)]
> >> +    pub(crate) fn from_array<const N: usize>(
> >> +        a: &'a mut [crate::ffi::c_char; N],
> >> +    ) -> Result<NullBorrowFormatter<'a>> {
> >> +        Self::new(
> >> +            // SAFETY: the buffer of `a` is valid for read and write as `u8` for
> >> +            // at least `N` bytes.
> >> +            unsafe { core::slice::from_raw_parts_mut(a.as_mut_ptr().cast::<u8>(), N) },
> >> +        )
> >> +    }
> >
> > Arrays automatically coerce to slices, so I don't think this is
> > necessary. You can just call `new`.
> 
> Nice!

I'm guessing it used to be necessary back when we used core::ffi::c_char
since &[i8;N] doesn't coerce to &[u8]. But now that we use the right
c_char definition, that isn't the case anymore.

> >> +impl Write for NullBorrowFormatter<'_> {
> >> +    fn write_str(&mut self, s: &str) -> fmt::Result {
> >> +        let bytes = s.as_bytes();
> >> +        let len = bytes.len();
> >> +
> >> +        // We want space for a null terminator
> >> +        if self.pos + len > self.buffer.len() - 1 {
> >
> > Integer overflow?
> 
> In the subtraction? `buffer.len()` is at least 1, because of the type invariant.
> 
> Or do you mean I should do a checked add for `self.pos + len`?

Ah, I guess self.pos and len are both <= the length of a slice, which is
at most isize::MAX, so the addition can't overflow an usize. Would be
good to comment this, though.

Alice

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 03/14] rust: str: introduce `NullBorrowFormatter`
  2025-07-10  8:47       ` Alice Ryhl
@ 2025-07-10 11:01         ` Andreas Hindborg
  0 siblings, 0 replies; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-10 11:01 UTC (permalink / raw)
  To: Alice Ryhl
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

"Alice Ryhl" <aliceryhl@google.com> writes:

> On Wed, Jul 09, 2025 at 05:49:57PM +0200, Andreas Hindborg wrote:
>> "Alice Ryhl" <aliceryhl@google.com> writes:
>>
>> > On Tue, Jul 08, 2025 at 09:44:58PM +0200, Andreas Hindborg wrote:
>> >> Add `NullBorrowFormatter`, a formatter that writes a null terminated string
>> >> to an array or slice buffer. Because this type needs to manage the trailing
>> >> null marker, the existing formatters cannot be used to implement this type.
>> >>
>> >> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
>> >> ---
>> >>  rust/kernel/str.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>> >>  1 file changed, 59 insertions(+)
>> >>
>> >> diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
>> >> index 78b2f95eb3171..05d79cf40c201 100644
>> >> --- a/rust/kernel/str.rs
>> >> +++ b/rust/kernel/str.rs
>> >> @@ -860,6 +860,65 @@ fn deref_mut(&mut self) -> &mut Self::Target {
>> >>      }
>> >>  }
>> >>
>> >> +/// A mutable reference to a byte buffer where a string can be written into.
>> >> +///
>> >> +/// The buffer will be automatically null terminated after the last written character.
>> >> +///
>> >> +/// # Invariants
>> >> +///
>> >> +/// `buffer` is always null terminated.
>> >> +pub(crate) struct NullBorrowFormatter<'a> {
>> >> +    buffer: &'a mut [u8],
>> >> +    pos: usize,
>> >> +}
>> >
>> > Do you need `pos`? Often I see this kind of code subslice `buffer`
>> > instead.
>>
>> How would that work? Can I move the start index of `buffer` in some way
>> without an unsafe block?
>
> Yes. I think this will work:
>
> let buffer = mem::take(&mut self.buffer);
> self.buffer = &mut buffer[pos..];
>
> Temporarily storing an empty slice avoids lifetime issues.

Ah, that is neat.


Best regards,
Andreas Hindborg




^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 05/14] rust: block: use `NullBorrowFormatter`
  2025-07-09 13:25   ` Alice Ryhl
@ 2025-07-11  9:29     ` Andreas Hindborg
  2025-07-11 10:02       ` Alice Ryhl
  0 siblings, 1 reply; 27+ messages in thread
From: Andreas Hindborg @ 2025-07-11  9:29 UTC (permalink / raw)
  To: Alice Ryhl
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

"Alice Ryhl" <aliceryhl@google.com> writes:

> On Tue, Jul 08, 2025 at 09:45:00PM +0200, Andreas Hindborg wrote:
>> Use the new `NullBorrowFormatter` to write the name of a `GenDisk` to the
>> name buffer. This new formatter automatically adds a trailing null marker
>> after the written characters, so we don't need to append that at the call
>> site any longer.
>>
>> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
>> ---
>>  rust/kernel/block/mq/gen_disk.rs   | 8 ++++----
>>  rust/kernel/block/mq/raw_writer.rs | 1 +
>>  rust/kernel/str.rs                 | 7 -------
>>  3 files changed, 5 insertions(+), 11 deletions(-)
>>
>> diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
>> index 679ee1bb21950..e0e42f7028276 100644
>> --- a/rust/kernel/block/mq/gen_disk.rs
>> +++ b/rust/kernel/block/mq/gen_disk.rs
>> @@ -7,9 +7,10 @@
>>
>>  use crate::{
>>      bindings,
>> -    block::mq::{raw_writer::RawWriter, Operations, TagSet},
>> +    block::mq::{Operations, TagSet},
>>      error::{self, from_err_ptr, Result},
>>      static_lock_class,
>> +    str::NullBorrowFormatter,
>>      sync::Arc,
>>  };
>>  use core::fmt::{self, Write};
>> @@ -143,14 +144,13 @@ pub fn build<T: Operations>(
>>          // SAFETY: `gendisk` is a valid pointer as we initialized it above
>>          unsafe { (*gendisk).fops = &TABLE };
>>
>> -        let mut raw_writer = RawWriter::from_array(
>> +        let mut writer = NullBorrowFormatter::from_array(
>>              // SAFETY: `gendisk` points to a valid and initialized instance. We
>>              // have exclusive access, since the disk is not added to the VFS
>>              // yet.
>>              unsafe { &mut (*gendisk).disk_name },
>>          )?;
>> -        raw_writer.write_fmt(name)?;
>> -        raw_writer.write_char('\0')?;
>> +        writer.write_fmt(name)?;
>
> Although this is nicer than the existing code, I wonder if it should
> just be a function rather than a whole NullBorrowFormatter struct? Take
> a slice and a fmt::Arguments and write it with a nul-terminator. Do you
> need anything more complex than what you have here?

I don't need anything more complex right now. But I think the
`NullTerminatedFormatter` could be useful anyway:

  +/// A mutable reference to a byte buffer where a string can be written into.
  +///
  +/// The buffer will be automatically null terminated after the last written character.
  +///
  +/// # Invariants
  +///
  +/// `buffer` is always null terminated.
  +pub(crate) struct NullTerminatedFormatter<'a> {
  +    buffer: &'a mut [u8],
  +}
  +
  +impl<'a> NullTerminatedFormatter<'a> {
  +    /// Create a new [`Self`] instance.
  +    pub(crate) fn new(buffer: &'a mut [u8]) -> Option<NullTerminatedFormatter<'a>> {
  +        *(buffer.first_mut()?) = 0;
  +
  +        // INVARIANT: We null terminated the buffer above.
  +        Some(Self { buffer })
  +    }
  +
  +    pub(crate) fn from_array<const N: usize>(
  +        buffer: &'a mut [crate::ffi::c_char; N],
  +    ) -> Option<NullTerminatedFormatter<'a>> {
  +        Self::new(buffer)
  +    }
  +}
  +
  +impl Write for NullTerminatedFormatter<'_> {
  +    fn write_str(&mut self, s: &str) -> fmt::Result {
  +        let bytes = s.as_bytes();
  +        let len = bytes.len();
  +
  +        // We want space for a null terminator. Buffer length is always at least 1, so no overflow.
  +        if len > self.buffer.len() - 1 {
  +            return Err(fmt::Error);
  +        }
  +
  +        let buffer = core::mem::take(&mut self.buffer);
  +        // We break the null termination invariant for a short while.
  +        buffer[..len].copy_from_slice(bytes);
  +        self.buffer = &mut buffer[len..];
  +
  +        // INVARIANT: We null terminate the buffer.
  +        self.buffer[0] = 0;
  +
  +        Ok(())
  +    }
  +}
  +

If you insist, I can write something like

  fn format_to_buffer(buffer: &mut [u8], args: fmt::Arguments) -> fmt::Result

although I am not sure I see the point of this change.


Best regards,
Andreas Hindborg



^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2 05/14] rust: block: use `NullBorrowFormatter`
  2025-07-11  9:29     ` Andreas Hindborg
@ 2025-07-11 10:02       ` Alice Ryhl
  0 siblings, 0 replies; 27+ messages in thread
From: Alice Ryhl @ 2025-07-11 10:02 UTC (permalink / raw)
  To: Andreas Hindborg
  Cc: Boqun Feng, Miguel Ojeda, Alex Gaynor, Gary Guo,
	Björn Roy Baron, Benno Lossin, Trevor Gross,
	Danilo Krummrich, Jens Axboe, linux-block, rust-for-linux,
	linux-kernel

On Fri, Jul 11, 2025 at 11:29 AM Andreas Hindborg <a.hindborg@kernel.org> wrote:
>
> "Alice Ryhl" <aliceryhl@google.com> writes:
>
> > On Tue, Jul 08, 2025 at 09:45:00PM +0200, Andreas Hindborg wrote:
> >> Use the new `NullBorrowFormatter` to write the name of a `GenDisk` to the
> >> name buffer. This new formatter automatically adds a trailing null marker
> >> after the written characters, so we don't need to append that at the call
> >> site any longer.
> >>
> >> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
> >> ---
> >>  rust/kernel/block/mq/gen_disk.rs   | 8 ++++----
> >>  rust/kernel/block/mq/raw_writer.rs | 1 +
> >>  rust/kernel/str.rs                 | 7 -------
> >>  3 files changed, 5 insertions(+), 11 deletions(-)
> >>
> >> diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
> >> index 679ee1bb21950..e0e42f7028276 100644
> >> --- a/rust/kernel/block/mq/gen_disk.rs
> >> +++ b/rust/kernel/block/mq/gen_disk.rs
> >> @@ -7,9 +7,10 @@
> >>
> >>  use crate::{
> >>      bindings,
> >> -    block::mq::{raw_writer::RawWriter, Operations, TagSet},
> >> +    block::mq::{Operations, TagSet},
> >>      error::{self, from_err_ptr, Result},
> >>      static_lock_class,
> >> +    str::NullBorrowFormatter,
> >>      sync::Arc,
> >>  };
> >>  use core::fmt::{self, Write};
> >> @@ -143,14 +144,13 @@ pub fn build<T: Operations>(
> >>          // SAFETY: `gendisk` is a valid pointer as we initialized it above
> >>          unsafe { (*gendisk).fops = &TABLE };
> >>
> >> -        let mut raw_writer = RawWriter::from_array(
> >> +        let mut writer = NullBorrowFormatter::from_array(
> >>              // SAFETY: `gendisk` points to a valid and initialized instance. We
> >>              // have exclusive access, since the disk is not added to the VFS
> >>              // yet.
> >>              unsafe { &mut (*gendisk).disk_name },
> >>          )?;
> >> -        raw_writer.write_fmt(name)?;
> >> -        raw_writer.write_char('\0')?;
> >> +        writer.write_fmt(name)?;
> >
> > Although this is nicer than the existing code, I wonder if it should
> > just be a function rather than a whole NullBorrowFormatter struct? Take
> > a slice and a fmt::Arguments and write it with a nul-terminator. Do you
> > need anything more complex than what you have here?
>
> I don't need anything more complex right now. But I think the
> `NullTerminatedFormatter` could be useful anyway:
>
>   +/// A mutable reference to a byte buffer where a string can be written into.
>   +///
>   +/// The buffer will be automatically null terminated after the last written character.
>   +///
>   +/// # Invariants
>   +///
>   +/// `buffer` is always null terminated.
>   +pub(crate) struct NullTerminatedFormatter<'a> {
>   +    buffer: &'a mut [u8],
>   +}
>   +
>   +impl<'a> NullTerminatedFormatter<'a> {
>   +    /// Create a new [`Self`] instance.
>   +    pub(crate) fn new(buffer: &'a mut [u8]) -> Option<NullTerminatedFormatter<'a>> {
>   +        *(buffer.first_mut()?) = 0;
>   +
>   +        // INVARIANT: We null terminated the buffer above.
>   +        Some(Self { buffer })
>   +    }
>   +
>   +    pub(crate) fn from_array<const N: usize>(
>   +        buffer: &'a mut [crate::ffi::c_char; N],
>   +    ) -> Option<NullTerminatedFormatter<'a>> {
>   +        Self::new(buffer)
>   +    }
>   +}
>   +
>   +impl Write for NullTerminatedFormatter<'_> {
>   +    fn write_str(&mut self, s: &str) -> fmt::Result {
>   +        let bytes = s.as_bytes();
>   +        let len = bytes.len();
>   +
>   +        // We want space for a null terminator. Buffer length is always at least 1, so no overflow.
>   +        if len > self.buffer.len() - 1 {
>   +            return Err(fmt::Error);
>   +        }
>   +
>   +        let buffer = core::mem::take(&mut self.buffer);
>   +        // We break the null termination invariant for a short while.
>   +        buffer[..len].copy_from_slice(bytes);
>   +        self.buffer = &mut buffer[len..];
>   +
>   +        // INVARIANT: We null terminate the buffer.
>   +        self.buffer[0] = 0;
>   +
>   +        Ok(())
>   +    }
>   +}
>   +
>
> If you insist, I can write something like
>
>   fn format_to_buffer(buffer: &mut [u8], args: fmt::Arguments) -> fmt::Result
>
> although I am not sure I see the point of this change.

I don't mind. I just thought it was simpler since you only need to
support a single write instead of having to support multiple writes.

Alice

^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2025-07-11 10:02 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-08 19:44 [PATCH v2 00/14] rnull: add configfs, remote completion to rnull Andreas Hindborg
2025-07-08 19:44 ` [PATCH v2 01/14] rust: str: normalize imports in `str.rs` Andreas Hindborg
2025-07-09 13:26   ` Alice Ryhl
2025-07-08 19:44 ` [PATCH v2 02/14] rust: str: introduce `BorrowFormatter` Andreas Hindborg
2025-07-09 13:14   ` Alice Ryhl
2025-07-09 15:11     ` Andreas Hindborg
2025-07-08 19:44 ` [PATCH v2 03/14] rust: str: introduce `NullBorrowFormatter` Andreas Hindborg
2025-07-09 13:23   ` Alice Ryhl
2025-07-09 15:49     ` Andreas Hindborg
2025-07-10  8:47       ` Alice Ryhl
2025-07-10 11:01         ` Andreas Hindborg
2025-07-08 19:44 ` [PATCH v2 04/14] rust: block: normalize imports for `gen_disk.rs` Andreas Hindborg
2025-07-09 13:26   ` Alice Ryhl
2025-07-08 19:45 ` [PATCH v2 05/14] rust: block: use `NullBorrowFormatter` Andreas Hindborg
2025-07-09 13:25   ` Alice Ryhl
2025-07-11  9:29     ` Andreas Hindborg
2025-07-11 10:02       ` Alice Ryhl
2025-07-08 19:45 ` [PATCH v2 06/14] rust: block: remove `RawWriter` Andreas Hindborg
2025-07-08 19:45 ` [PATCH v2 07/14] rust: block: remove trait bound from `mq::Request` definition Andreas Hindborg
2025-07-09 13:25   ` Alice Ryhl
2025-07-08 19:45 ` [PATCH v2 08/14] rust: block: add block related constants Andreas Hindborg
2025-07-08 19:45 ` [PATCH v2 09/14] rnull: move driver to separate directory Andreas Hindborg
2025-07-08 19:45 ` [PATCH v2 10/14] rnull: enable configuration via `configfs` Andreas Hindborg
2025-07-08 19:45 ` [PATCH v2 11/14] rust: block: add `GenDisk` private data support Andreas Hindborg
2025-07-08 19:45 ` [PATCH v2 12/14] rust: block: mq: fix spelling in a safety comment Andreas Hindborg
2025-07-08 19:45 ` [PATCH v2 13/14] rust: block: add remote completion to `Request` Andreas Hindborg
2025-07-08 19:45 ` [PATCH v2 14/14] rnull: add soft-irq completion support Andreas Hindborg

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.