Skip to contentSkip to conversation

Rust block device driver API and null block driver

PATCH Superseded Andreas Hindborg 6 participants 20 messages Jun 1, 2024 – Jun 10, 2024

Subsystems (3)

v4 of 6 Superseded Latest is v6 →

lore
Share & tools
lore
#0 lore raw

v4 cover Rust block device driver API and null block driver

From: Andreas Hindborg <[email protected]>

Hi!

A few more fixes based on input from Benno. Earlier version here [1].

Changes from v3:

- mq: rewrite atomic_relaxed_op_unless in terms of atomicu64::fetch_update
- mq: fix docs for atomic_relaxed_op_unless
- mq: rewrite initialization of TagSet to be more idiomatic
- rnull: add a comment for error path
- rnull: move a ? operator

Best regards,
Andreas Hindborg

Andreas Hindborg (3):
rust: block: introduce kernel::block::mq module
rust: block: add rnull, Rust null_blk implementation
MAINTAINERS: add entry for Rust block device driver API

 MAINTAINERS                        |  14 ++
 drivers/block/Kconfig              |   9 ++
 drivers/block/Makefile             |   3 +
 drivers/block/rnull.rs             |  81 ++++++++++
 rust/bindings/bindings_helper.h    |   2 +
 rust/helpers.c                     |  16 ++
 rust/kernel/block.rs               |   5 +
 rust/kernel/block/mq.rs            |  97 ++++++++++++
 rust/kernel/block/mq/gen_disk.rs   | 222 ++++++++++++++++++++++++++
 rust/kernel/block/mq/operations.rs | 233 ++++++++++++++++++++++++++++
 rust/kernel/block/mq/raw_writer.rs |  55 +++++++
 rust/kernel/block/mq/request.rs    | 241 +++++++++++++++++++++++++++++
 rust/kernel/block/mq/tag_set.rs    |  85 ++++++++++
 rust/kernel/error.rs               |   6 +
 rust/kernel/lib.rs                 |   2 +
 15 files changed, 1071 insertions(+)
 create mode 100644 drivers/block/rnull.rs
 create mode 100644 rust/kernel/block.rs
 create mode 100644 rust/kernel/block/mq.rs
 create mode 100644 rust/kernel/block/mq/gen_disk.rs
 create mode 100644 rust/kernel/block/mq/operations.rs
 create mode 100644 rust/kernel/block/mq/raw_writer.rs
 create mode 100644 rust/kernel/block/mq/request.rs
 create mode 100644 rust/kernel/block/mq/tag_set.rs

base-commit: 1613e604df0cd359cf2a7fbd9be7a0bcfacfabd0

Signature · 1 line
2.45.1
#1 lore raw

PATCH v4 1/3 rust: block: introduce `kernel::block::mq` module

From: Andreas Hindborg <[email protected]>

Add initial abstractions for working with blk-mq.

This patch is a maintained, refactored subset of code originally published by Wedson Almeida Filho <[email protected]> [1].

[1] https://github.com/wedsonaf/linux/tree/f2cfd2fe0e2ca4e90994f96afe268bbd4382a891/rust/kernel/blk/mq.rs

Cc Wedson Almeida Filho <[email protected]>
---
 rust/bindings/bindings_helper.h    |   2 +
 rust/helpers.c                     |  16 ++
 rust/kernel/block.rs               |   5 +
 rust/kernel/block/mq.rs            |  97 ++++++++++++
 rust/kernel/block/mq/gen_disk.rs   | 222 ++++++++++++++++++++++++++
 rust/kernel/block/mq/operations.rs | 233 ++++++++++++++++++++++++++++
 rust/kernel/block/mq/raw_writer.rs |  55 +++++++
 rust/kernel/block/mq/request.rs    | 241 +++++++++++++++++++++++++++++
 rust/kernel/block/mq/tag_set.rs    |  85 ++++++++++
 rust/kernel/error.rs               |   6 +
 rust/kernel/lib.rs                 |   2 +
 11 files changed, 964 insertions(+)
 create mode 100644 rust/kernel/block.rs
 create mode 100644 rust/kernel/block/mq.rs
 create mode 100644 rust/kernel/block/mq/gen_disk.rs
 create mode 100644 rust/kernel/block/mq/operations.rs
 create mode 100644 rust/kernel/block/mq/raw_writer.rs
 create mode 100644 rust/kernel/block/mq/request.rs
 create mode 100644 rust/kernel/block/mq/tag_set.rs
rust/bindings/bindings_helper.h +2 -0
index ddb5644d4fd9..68f937f8374f 100644
@@ -7,6 +7,8 @@
  */
 
 #include <kunit/test.h>
+#include <linux/blk_types.h>
+#include <linux/blk-mq.h>
 #include <linux/errname.h>
 #include <linux/ethtool.h>
 #include <linux/jiffies.h>
rust/helpers.c +16 -0
index 2c37a0f5d7a8..3df5217fb2ff 100644
@@ -186,3 +186,19 @@ static_assert(
 	__alignof__(size_t) == __alignof__(uintptr_t),
 	"Rust code expects C `size_t` to match Rust `usize`"
 );
+
+// This will soon be moved to a separate file, so no need to merge with above.
+#include <linux/blk-mq.h>
+#include <linux/blkdev.h>
+
+void *rust_helper_blk_mq_rq_to_pdu(struct request *rq)
+{
+	return blk_mq_rq_to_pdu(rq);
+}
+EXPORT_SYMBOL_GPL(rust_helper_blk_mq_rq_to_pdu);
+
+struct request *rust_helper_blk_mq_rq_from_pdu(void *pdu)
+{
+	return blk_mq_rq_from_pdu(pdu);
+}
+EXPORT_SYMBOL_GPL(rust_helper_blk_mq_rq_from_pdu);
rust/kernel/block.rs new file +5 -0
new file mode 100644
index 000000000000..150f710efe5b
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Types for working with the block layer.
+
+pub mod mq;
rust/kernel/block/mq.rs new file +97 -0

97 lines not on this page · raw message

rust/kernel/block/mq/gen_disk.rs new file +222 -0

222 lines not on this page · raw message

rust/kernel/block/mq/operations.rs new file +233 -0

233 lines not on this page · raw message

rust/kernel/block/mq/raw_writer.rs new file +55 -0

55 lines not on this page · raw message

rust/kernel/block/mq/request.rs new file +241 -0

241 lines not on this page · raw message

rust/kernel/block/mq/tag_set.rs new file +85 -0

85 lines not on this page · raw message

rust/kernel/error.rs +6 -0
index 55280ae9fe40..145f5c397009 100644
@@ -126,6 +126,12 @@ pub fn to_errno(self) -> core::ffi::c_int {
         self.0
     }
 
+    #[cfg(CONFIG_BLOCK)]
+    pub(crate) fn to_blk_status(self) -> bindings::blk_status_t {
+        // SAFETY: `self.0` is a valid error due to its invariant.
+        unsafe { bindings::errno_to_blk_status(self.0) }
+    }
+
     /// Returns the error encoded as a pointer.
     #[allow(dead_code)]
     pub(crate) fn to_ptr<T>(self) -> *mut T {
rust/kernel/lib.rs +2 -0
index fbd91a48ff8b..2cf7c6b6f66b 100644
@@ -27,6 +27,8 @@
 extern crate self as kernel;
 
 pub mod alloc;
+#[cfg(CONFIG_BLOCK)]
+pub mod block;
 mod build_assert;
 pub mod error;
 pub mod init;
Signature · 1 line
2.45.1
#2 lore raw

Re: [PATCH v4 1/3] rust: block: introduce `kernel::block::mq` module

rust/kernel/block/mq/gen_disk.rs line 18

Quoted 6 lines · Andreas Hindborg
rust/kernel/block/mq/gen_disk.rs new file +6 -0
+/// A generic block device.
+///
+/// # Invariants
+///
+///  - `gendisk` must always point to an initialized and valid `struct gendisk`.
+pub struct GenDisk<T: Operations, S: GenDiskState = Added> {

I am curious, do you need the type state for this struct? AFAIU you are only using it to configure the GenDisk, so could you also use a config struct that is given to GenDisk::new. That way we can avoid the extra traits and generic argument.

Since there are so many options, a builder config struct might be a good idea.

Quoted 31 lines
rust/kernel/block/mq/gen_disk.rs new file +31 -0
+    tagset: Arc<TagSet<T>>,
+    gendisk: *mut bindings::gendisk,
+    _phantom: core::marker::PhantomData<S>,
+}
+
+// SAFETY: `GenDisk` is an owned pointer to a `struct gendisk` and an `Arc` to a
+// `TagSet` It is safe to send this to other threads as long as T is Send.
+unsafe impl<T: Operations + Send, S: GenDiskState> Send for GenDisk<T, S> {}
+
+/// Disks in this state are allocated and initialized, but are not yet
+/// accessible from the kernel VFS.
+pub enum Initialized {}
+
+/// Disks in this state have been attached to the kernel VFS and may receive IO
+/// requests.
+pub enum Added {}
+
+mod seal {
+    pub trait Sealed {}
+}
+
+/// Typestate representing states of a `GenDisk`.
+///
+/// This trait cannot be implemented by downstream crates.
+pub trait GenDiskState: seal::Sealed {
+    /// Set to true if [`GenDisk`] should call `del_gendisk` on drop.
+    const DELETE_ON_DROP: bool;
+}
+
+impl seal::Sealed for Initialized {}
+impl GenDiskState for Initialized {

rust/kernel/block/mq/gen_disk.rs line 59

Quoted 10 lines
rust/kernel/block/mq/gen_disk.rs new file +10 -0
+    const DELETE_ON_DROP: bool = false;
+}
+impl seal::Sealed for Added {}
+impl GenDiskState for Added {
+    const DELETE_ON_DROP: bool = true;
+}
+
+impl<T: Operations> GenDisk<T, Initialized> {
+    /// Try to create a new `GenDisk`.
+    pub fn try_new(tagset: Arc<TagSet<T>>) -> Result<Self> {

Since there is no non-try new function, I think we should name this function just new.

Quoted 31 lines
rust/kernel/block/mq/gen_disk.rs new file +31 -0
+        let lock_class_key = crate::sync::LockClassKey::new();
+
+        // SAFETY: `tagset.raw_tag_set()` points to a valid and initialized tag set
+        let gendisk = from_err_ptr(unsafe {
+            bindings::__blk_mq_alloc_disk(
+                tagset.raw_tag_set(),
+                core::ptr::null_mut(), // TODO: We can pass queue limits right here
+                core::ptr::null_mut(),
+                lock_class_key.as_ptr(),
+            )
+        })?;
+
+        const TABLE: bindings::block_device_operations = bindings::block_device_operations {
+            submit_bio: None,
+            open: None,
+            release: None,
+            ioctl: None,
+            compat_ioctl: None,
+            check_events: None,
+            unlock_native_capacity: None,
+            getgeo: None,
+            set_read_only: None,
+            swap_slot_free_notify: None,
+            report_zones: None,
+            devnode: None,
+            alternative_gpt_sector: None,
+            get_unique_id: None,
+            // TODO: Set to THIS_MODULE. Waiting for const_refs_to_static feature to
+            // be merged (unstable in rustc 1.78 which is staged for linux 6.10)
+            // https://github.com/rust-lang/rust/issues/119618
+            owner: core::ptr::null_mut(),

rust/kernel/block/mq/gen_disk.rs line 100

Quoted 10 lines
rust/kernel/block/mq/gen_disk.rs new file +10 -0
+            pr_ops: core::ptr::null_mut(),
+            free_disk: None,
+            poll_bio: None,
+        };
+
+        // SAFETY: gendisk is a valid pointer as we initialized it above
+        unsafe { (*gendisk).fops = &TABLE };
+
+        // INVARIANT: `gendisk` was initialized above.
+        // INVARIANT: `gendisk.queue.queue_data` is set to `data` in the call to

There is no data in the mentioned call above.

Quoted 8 lines
rust/kernel/block/mq/gen_disk.rs new file +8 -0
+        // `__blk_mq_alloc_disk` above.
+        Ok(GenDisk {
+            tagset,
+            gendisk,
+            _phantom: PhantomData,
+        })
+    }
+

[...]

rust/kernel/block/mq/operations.rs line 61

Quoted 8 lines
rust/kernel/block/mq/operations.rs new file +8 -0
+impl<T: Operations> OperationsVTable<T> {
+    /// This function is called by the C kernel. A pointer to this function is
+    /// installed in the `blk_mq_ops` vtable for the driver.
+    ///
+    /// # Safety
+    ///
+    /// - The caller of this function must ensure `bd` is valid
+    ///   and initialized. The pointees must outlive this function.

Until when do the pointees have to be alive? "must outlive this function" could also be the case if the pointees die immediately after this function returns.

rust/kernel/block/mq/operations.rs line 65

Quoted 4 lines
rust/kernel/block/mq/operations.rs new file +4 -0
+    /// - This function must not be called with a `hctx` for which
+    ///   `Self::exit_hctx_callback()` has been called.
+    /// - (*bd).rq must point to a valid `bindings:request` for which
+    ///   `OperationsVTable<T>::init_request_callback` was called

Missing . at the end.

Quoted 5 lines
rust/kernel/block/mq/operations.rs new file +5 -0
+    unsafe extern "C" fn queue_rq_callback(
+        _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

rust/kernel/block/mq/operations.rs line 80

Quoted 10 lines
rust/kernel/block/mq/operations.rs new file +10 -0
+        // this function.
+        let request = unsafe { &*(*bd).rq.cast::<Request<T>>() };
+
+        // One refcount for the ARef, one for being in flight
+        request.wrapper_ref().refcount().store(2, Ordering::Relaxed);
+
+        // SAFETY: We own a refcount that we took above. We pass that to `ARef`.
+        // By the safety requirements of this function, `request` is a valid
+        // `struct request` and the private data is properly initialized.
+        let rq = unsafe { Request::aref_from_raw((*bd).rq) };

I think that you need to require that the request is alive at least until blk_mq_end_request is called for the request (since at that point all ARefs will be gone).
Also if this is not guaranteed, the safety requirements of AlwaysRefCounted are violated (since the object can just disappear even if it has refcount > 0 [the refcount refers to the Rust refcount in the RequestDataWrapper, not the one in C]).

Quoted 45 lines
rust/kernel/block/mq/operations.rs new file +45 -0
+
+        // SAFETY: We have exclusive access and we just set the refcount above.
+        unsafe { Request::start_unchecked(&rq) };
+
+        let ret = T::queue_rq(
+            rq,
+            // SAFETY: `bd` is valid as required by the safety requirement for this function.
+            unsafe { (*bd).last },
+        );
+
+        if let Err(e) = ret {
+            e.to_blk_status()
+        } else {
+            bindings::BLK_STS_OK as _
+        }
+    }
+
+    /// 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 commit_rqs_callback(_hctx: *mut bindings::blk_mq_hw_ctx) {
+        T::commit_rqs()
+    }
+
+    /// This function is called by the C kernel. It is not currently
+    /// implemented, and there is no way to exercise this code path.
+    ///
+    /// # Safety
+    ///
+    /// This function may only be called by blk-mq C infrastructure.
+    unsafe extern "C" fn complete_callback(_rq: *mut bindings::request) {}
+
+    /// 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 poll_callback(
+        _hctx: *mut bindings::blk_mq_hw_ctx,
+        _iob: *mut bindings::io_comp_batch,
+    ) -> core::ffi::c_int {

rust/kernel/block/mq/operations.rs line 135

Quoted 10 lines
rust/kernel/block/mq/operations.rs new file +10 -0
+        T::poll().into()
+    }
+
+    /// 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. This
+    /// function may only be called onece before `exit_hctx_callback` is called

Typo: "onece"

Quoted 27 lines
rust/kernel/block/mq/operations.rs new file +27 -0
+    /// for the same context.
+    unsafe extern "C" fn init_hctx_callback(
+        _hctx: *mut bindings::blk_mq_hw_ctx,
+        _tagset_data: *mut core::ffi::c_void,
+        _hctx_idx: core::ffi::c_uint,
+    ) -> core::ffi::c_int {
+        from_result(|| Ok(0))
+    }
+
+    /// 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 exit_hctx_callback(
+        _hctx: *mut bindings::blk_mq_hw_ctx,
+        _hctx_idx: core::ffi::c_uint,
+    ) {
+    }
+
+    /// 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. `set` must

rust/kernel/block/mq/operations.rs line 172

Quoted 10 lines
rust/kernel/block/mq/operations.rs new file +10 -0
+    /// point to an initialized `TagSet<T>`.
+    unsafe extern "C" fn init_request_callback(
+        _set: *mut bindings::blk_mq_tag_set,
+        rq: *mut bindings::request,
+        _hctx_idx: core::ffi::c_uint,
+        _numa_node: core::ffi::c_uint,
+    ) -> core::ffi::c_int {
+        from_result(|| {
+            // SAFETY: The `blk_mq_tag_set` invariants guarantee that all
+            // requests are allocated with extra memory for the request data.

What guarantees that the right amount of memory has been allocated? AFAIU that is guaranteed by the TagSet (but there is no invariant).

Quoted 9 lines
rust/kernel/block/mq/operations.rs new file +9 -0
+            let pdu = unsafe { bindings::blk_mq_rq_to_pdu(rq) }.cast::<RequestDataWrapper>();
+
+            // SAFETY: The refcount field is allocated but not initialized, this
+            // valid for write.
+            unsafe { RequestDataWrapper::refcount_ptr(pdu).write(AtomicU64::new(0)) };
+
+            Ok(0)
+        })
+    }

[...]

rust/kernel/block/mq/request.rs line 82

Quoted 13 lines
rust/kernel/block/mq/request.rs new file +13 -0
+    /// Notify the block layer that a request is going to be processed now.
+    ///
+    /// The block layer uses this hook to do proper initializations such as
+    /// starting the timeout timer. It is a requirement that block device
+    /// drivers call this function when starting to process a request.
+    ///
+    /// # Safety
+    ///
+    /// The caller must have exclusive ownership of `self`, that is
+    /// `self.wrapper_ref().refcount() == 2`.
+    pub(crate) unsafe fn start_unchecked(this: &ARef<Self>) {
+        // SAFETY: By type invariant, `self.0` is a valid `struct request`. By
+        // existence of `&mut self` we have exclusive access.

We don't have a &mut self. But the safety requirements ask for a unique ARef.

Quoted 17 lines
rust/kernel/block/mq/request.rs new file +17 -0
+        unsafe { bindings::blk_mq_start_request(this.0.get()) };
+    }
+
+    fn try_set_end(this: ARef<Self>) -> Result<ARef<Self>, ARef<Self>> {
+        // We can race with `TagSet::tag_to_rq`
+        match this.wrapper_ref().refcount().compare_exchange(
+            2,
+            0,
+            Ordering::Relaxed,
+            Ordering::Relaxed,
+        ) {
+            Err(_old) => Err(this),
+            Ok(_) => Ok(this),
+        }
+    }
+
+    /// Notify the block layer that the request has been completed without errors.

rust/kernel/block/mq/request.rs line 109

Quoted 10 lines
rust/kernel/block/mq/request.rs new file +10 -0
+    ///
+    /// This function will return `Err` if `this` is not the only `ARef`
+    /// referencing the request.
+    pub fn end_ok(this: ARef<Self>) -> Result<(), ARef<Self>> {
+        let this = Self::try_set_end(this)?;
+        let request_ptr = this.0.get();
+        core::mem::forget(this);
+
+        // SAFETY: By type invariant, `self.0` is a valid `struct request`. By
+        // existence of `&mut self` we have exclusive access.

Same here, but in this case, the ARef is unique, since you called try_set_end. You could make it a # Guarantee of try_set_end: "If Ok(aref) is returned, then the aref is unique."

Quoted 6 lines
rust/kernel/block/mq/request.rs new file +6 -0
+        unsafe { bindings::blk_mq_end_request(request_ptr, bindings::BLK_STS_OK as _) };
+
+        Ok(())
+    }
+
+    /// Return a pointer to the `RequestDataWrapper` stored in the private area

rust/kernel/block/mq/request.rs line 125

Quoted 10 lines
rust/kernel/block/mq/request.rs new file +10 -0
+    /// of the request structure.
+    ///
+    /// # Safety
+    ///
+    /// - `this` must point to a valid allocation.
+    pub(crate) unsafe fn wrapper_ptr(this: *mut Self) -> NonNull<RequestDataWrapper> {
+        let request_ptr = this.cast::<bindings::request>();
+        let wrapper_ptr =
+            // SAFETY: By safety requirements for this function, `this` is a
+            // valid allocation.

Formatting: move the safety comment above the let.

---
Cheers,
Benno

Quoted 5 lines
rust/kernel/block/mq/request.rs new file +5 -0
+            unsafe { bindings::blk_mq_rq_to_pdu(request_ptr).cast::<RequestDataWrapper>() };
+        // SAFETY: By C api contract, wrapper_ptr points to a valid allocation
+        // and is not null.
+        unsafe { NonNull::new_unchecked(wrapper_ptr) }
+    }

[...]

#3 lore raw

Re: [PATCH v4 1/3] rust: block: introduce `kernel::block::mq` module

Quoted 13 lines · Benno Lossin

rust/kernel/block/mq/gen_disk.rs line 18

Quoted 6 lines · Andreas Hindborg
rust/kernel/block/mq/gen_disk.rs new file +6 -0
+/// A generic block device.
+///
+/// # Invariants
+///
+///  - `gendisk` must always point to an initialized and valid `struct gendisk`.
+pub struct GenDisk<T: Operations, S: GenDiskState = Added> {

I am curious, do you need the type state for this struct? AFAIU you are only using it to configure the GenDisk, so could you also use a config struct that is given to GenDisk::new. That way we can avoid the extra traits and generic argument.

Quoted 2 lines

Since there are so many options, a builder config struct might be a good idea.

I agree, let's do a builder. That would actually make some things a bit simpler.

Quoted 37 lines
Quoted 37 lines
rust/kernel/block/mq/gen_disk.rs new file +36 -0
+    tagset: Arc<TagSet<T>>,
+    gendisk: *mut bindings::gendisk,
+    _phantom: core::marker::PhantomData<S>,
+}
+
+// SAFETY: `GenDisk` is an owned pointer to a `struct gendisk` and an `Arc` to a
+// `TagSet` It is safe to send this to other threads as long as T is Send.
+unsafe impl<T: Operations + Send, S: GenDiskState> Send for GenDisk<T, S> {}
+
+/// Disks in this state are allocated and initialized, but are not yet
+/// accessible from the kernel VFS.
+pub enum Initialized {}
+
+/// Disks in this state have been attached to the kernel VFS and may receive IO
+/// requests.
+pub enum Added {}
+
+mod seal {
+    pub trait Sealed {}
+}
+
+/// Typestate representing states of a `GenDisk`.
+///
+/// This trait cannot be implemented by downstream crates.
+pub trait GenDiskState: seal::Sealed {
+    /// Set to true if [`GenDisk`] should call `del_gendisk` on drop.
+    const DELETE_ON_DROP: bool;
+}
+
+impl seal::Sealed for Initialized {}
+impl GenDiskState for Initialized {
+    const DELETE_ON_DROP: bool = false;
+}
+impl seal::Sealed for Added {}
+impl GenDiskState for Added {
+    const DELETE_ON_DROP: bool = true;
Quoted 8 lines

rust/kernel/block/mq/gen_disk.rs line 59

Quoted 5 lines
rust/kernel/block/mq/gen_disk.rs new file +5 -0
+}
+
+impl<T: Operations> GenDisk<T, Initialized> {
+    /// Try to create a new `GenDisk`.
+    pub fn try_new(tagset: Arc<TagSet<T>>) -> Result<Self> {

Since there is no non-try new function, I think we should name this function just new.

Right, I am still getting used to the new naming scheme. Do you know if it is documented anywhere?

Quoted 36 lines
Quoted 36 lines
rust/kernel/block/mq/gen_disk.rs new file +35 -0
+        let lock_class_key = crate::sync::LockClassKey::new();
+
+        // SAFETY: `tagset.raw_tag_set()` points to a valid and initialized tag set
+        let gendisk = from_err_ptr(unsafe {
+            bindings::__blk_mq_alloc_disk(
+                tagset.raw_tag_set(),
+                core::ptr::null_mut(), // TODO: We can pass queue limits right here
+                core::ptr::null_mut(),
+                lock_class_key.as_ptr(),
+            )
+        })?;
+
+        const TABLE: bindings::block_device_operations = bindings::block_device_operations {
+            submit_bio: None,
+            open: None,
+            release: None,
+            ioctl: None,
+            compat_ioctl: None,
+            check_events: None,
+            unlock_native_capacity: None,
+            getgeo: None,
+            set_read_only: None,
+            swap_slot_free_notify: None,
+            report_zones: None,
+            devnode: None,
+            alternative_gpt_sector: None,
+            get_unique_id: None,
+            // TODO: Set to THIS_MODULE. Waiting for const_refs_to_static feature to
+            // be merged (unstable in rustc 1.78 which is staged for linux 6.10)
+            // https://github.com/rust-lang/rust/issues/119618
+            owner: core::ptr::null_mut(),
+            pr_ops: core::ptr::null_mut(),
+            free_disk: None,
+            poll_bio: None,
+        };
Quoted 8 lines

rust/kernel/block/mq/gen_disk.rs line 100

Quoted 6 lines
rust/kernel/block/mq/gen_disk.rs new file +6 -0
+
+        // SAFETY: gendisk is a valid pointer as we initialized it above
+        unsafe { (*gendisk).fops = &TABLE };
+
+        // INVARIANT: `gendisk` was initialized above.
+        // INVARIANT: `gendisk.queue.queue_data` is set to `data` in the call to

There is no data in the mentioned call above.

Thanks, I'll move the comment to the patch it belongs in 👍

Quoted 21 lines
Quoted 9 lines
rust/kernel/block/mq/gen_disk.rs new file +8 -0
+        // `__blk_mq_alloc_disk` above.
+        Ok(GenDisk {
+            tagset,
+            gendisk,
+            _phantom: PhantomData,
+        })
+    }
+

[...]

rust/kernel/block/mq/operations.rs line 61

Quoted 8 lines
rust/kernel/block/mq/operations.rs new file +8 -0
+impl<T: Operations> OperationsVTable<T> {
+    /// This function is called by the C kernel. A pointer to this function is
+    /// installed in the `blk_mq_ops` vtable for the driver.
+    ///
+    /// # Safety
+    ///
+    /// - The caller of this function must ensure `bd` is valid
+    ///   and initialized. The pointees must outlive this function.
Quoted 3 lines

Until when do the pointees have to be alive? "must outlive this function" could also be the case if the pointees die immediately after this function returns.

It should not be plural. What I intended to communicate is that what bd points to must be valid for read for the duration of the function call. I think that is what "The pointee must outlive this function" states? Although when we talk about lifetime of an object pointed to by a pointer, I am not sure about the correct way to word this. Do we talk about the lifetime of the pointer or the lifetime of the pointed to object (the pointee). We should not use the same wording for the pointer and the pointee.

How about:

    /// - The caller of this function must ensure that the pointee of `bd` is
    ///   valid for read for the duration of this function.
Quoted 7 lines

rust/kernel/block/mq/operations.rs line 65

Quoted 5 lines
rust/kernel/block/mq/operations.rs new file +4 -0
+    /// - This function must not be called with a `hctx` for which
+    ///   `Self::exit_hctx_callback()` has been called.
+    /// - (*bd).rq must point to a valid `bindings:request` for which
+    ///   `OperationsVTable<T>::init_request_callback` was called

Missing . at the end.

Thanks.

Quoted 17 lines
Quoted 6 lines
rust/kernel/block/mq/operations.rs new file +5 -0
+    unsafe extern "C" fn queue_rq_callback(
+        _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

rust/kernel/block/mq/operations.rs line 80

Quoted 10 lines
rust/kernel/block/mq/operations.rs new file +10 -0
+        // this function.
+        let request = unsafe { &*(*bd).rq.cast::<Request<T>>() };
+
+        // One refcount for the ARef, one for being in flight
+        request.wrapper_ref().refcount().store(2, Ordering::Relaxed);
+
+        // SAFETY: We own a refcount that we took above. We pass that to `ARef`.
+        // By the safety requirements of this function, `request` is a valid
+        // `struct request` and the private data is properly initialized.
+        let rq = unsafe { Request::aref_from_raw((*bd).rq) };
Quoted 7 lines

I think that you need to require that the request is alive at least until blk_mq_end_request is called for the request (since at that point all ARefs will be gone).
Also if this is not guaranteed, the safety requirements of AlwaysRefCounted are violated (since the object can just disappear even if it has refcount > 0 [the refcount refers to the Rust refcount in the RequestDataWrapper, not the one in C]).

Yea, for the last invariant of Request:

/// * self is reference counted by atomic modification of
/// self.wrapper_ref().refcount().

I will add this to the safety comment at the call site:

// - rq will be alive until blk_mq_end_request is called and is
// reference counted by ARef until then.

Quoted 50 lines
Quoted 50 lines
rust/kernel/block/mq/operations.rs new file +49 -0
+
+        // SAFETY: We have exclusive access and we just set the refcount above.
+        unsafe { Request::start_unchecked(&rq) };
+
+        let ret = T::queue_rq(
+            rq,
+            // SAFETY: `bd` is valid as required by the safety requirement for this function.
+            unsafe { (*bd).last },
+        );
+
+        if let Err(e) = ret {
+            e.to_blk_status()
+        } else {
+            bindings::BLK_STS_OK as _
+        }
+    }
+
+    /// 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 commit_rqs_callback(_hctx: *mut bindings::blk_mq_hw_ctx) {
+        T::commit_rqs()
+    }
+
+    /// This function is called by the C kernel. It is not currently
+    /// implemented, and there is no way to exercise this code path.
+    ///
+    /// # Safety
+    ///
+    /// This function may only be called by blk-mq C infrastructure.
+    unsafe extern "C" fn complete_callback(_rq: *mut bindings::request) {}
+
+    /// 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 poll_callback(
+        _hctx: *mut bindings::blk_mq_hw_ctx,
+        _iob: *mut bindings::io_comp_batch,
+    ) -> core::ffi::c_int {
+        T::poll().into()
+    }
+
+    /// This function is called by the C kernel. A pointer to this function is
Quoted 8 lines

rust/kernel/block/mq/operations.rs line 135

Quoted 6 lines
rust/kernel/block/mq/operations.rs new file +6 -0
+    /// installed in the `blk_mq_ops` vtable for the driver.
+    ///
+    /// # Safety
+    ///
+    /// This function may only be called by blk-mq C infrastructure. This
+    /// function may only be called onece before `exit_hctx_callback` is called

Typo: "onece"

Since you keep finding typos in my patches, I took this morning to fix my spelling setup in Emacs. It was a deep rabbit hole, but I think I got it now 🤞

Quoted 33 lines
Quoted 33 lines
rust/kernel/block/mq/operations.rs new file +32 -0
+    /// for the same context.
+    unsafe extern "C" fn init_hctx_callback(
+        _hctx: *mut bindings::blk_mq_hw_ctx,
+        _tagset_data: *mut core::ffi::c_void,
+        _hctx_idx: core::ffi::c_uint,
+    ) -> core::ffi::c_int {
+        from_result(|| Ok(0))
+    }
+
+    /// 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 exit_hctx_callback(
+        _hctx: *mut bindings::blk_mq_hw_ctx,
+        _hctx_idx: core::ffi::c_uint,
+    ) {
+    }
+
+    /// 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. `set` must
+    /// point to an initialized `TagSet<T>`.
+    unsafe extern "C" fn init_request_callback(
+        _set: *mut bindings::blk_mq_tag_set,
+        rq: *mut bindings::request,
+        _hctx_idx: core::ffi::c_uint,
Quoted 8 lines

rust/kernel/block/mq/operations.rs line 172

Quoted 5 lines
rust/kernel/block/mq/operations.rs new file +5 -0
+        _numa_node: core::ffi::c_uint,
+    ) -> core::ffi::c_int {
+        from_result(|| {
+            // SAFETY: The `blk_mq_tag_set` invariants guarantee that all
+            // requests are allocated with extra memory for the request data.

What guarantees that the right amount of memory has been allocated? AFAIU that is guaranteed by the TagSet (but there is no invariant).

It is by C API contract. TagSet::try_new (now new) writes cmd_size into the struct blk_mq_tag_set. That is picked up by blk_mq_alloc_tag_set` to allocate the right amount of space for each request.

The invariant here is on the C type. Perhaps the wording is wrong. I am not exactly sure how to express this. How about this:

            // SAFETY: We instructed `blk_mq_alloc_tag_set` to allocate requests
            // with extra memory for the request data when we called it in
            // `TagSet::new`.
Quoted 27 lines
Quoted 10 lines
rust/kernel/block/mq/operations.rs new file +9 -0
+            let pdu = unsafe { bindings::blk_mq_rq_to_pdu(rq) }.cast::<RequestDataWrapper>();
+
+            // SAFETY: The refcount field is allocated but not initialized, this
+            // valid for write.
+            unsafe { RequestDataWrapper::refcount_ptr(pdu).write(AtomicU64::new(0)) };
+
+            Ok(0)
+        })
+    }

[...]

rust/kernel/block/mq/request.rs line 82

Quoted 13 lines
rust/kernel/block/mq/request.rs new file +13 -0
+    /// Notify the block layer that a request is going to be processed now.
+    ///
+    /// The block layer uses this hook to do proper initializations such as
+    /// starting the timeout timer. It is a requirement that block device
+    /// drivers call this function when starting to process a request.
+    ///
+    /// # Safety
+    ///
+    /// The caller must have exclusive ownership of `self`, that is
+    /// `self.wrapper_ref().refcount() == 2`.
+    pub(crate) unsafe fn start_unchecked(this: &ARef<Self>) {
+        // SAFETY: By type invariant, `self.0` is a valid `struct request`. By
+        // existence of `&mut self` we have exclusive access.
Quoted 2 lines

We don't have a &mut self. But the safety requirements ask for a unique ARef.

Thanks, I'll rephrase to:

        // SAFETY: By type invariant, `self.0` is a valid `struct request` and
        // we have exclusive access.
Quoted 24 lines
Quoted 24 lines
rust/kernel/block/mq/request.rs new file +23 -0
+        unsafe { bindings::blk_mq_start_request(this.0.get()) };
+    }
+
+    fn try_set_end(this: ARef<Self>) -> Result<ARef<Self>, ARef<Self>> {
+        // We can race with `TagSet::tag_to_rq`
+        match this.wrapper_ref().refcount().compare_exchange(
+            2,
+            0,
+            Ordering::Relaxed,
+            Ordering::Relaxed,
+        ) {
+            Err(_old) => Err(this),
+            Ok(_) => Ok(this),
+        }
+    }
+
+    /// Notify the block layer that the request has been completed without errors.
+    ///
+    /// This function will return `Err` if `this` is not the only `ARef`
+    /// referencing the request.
+    pub fn end_ok(this: ARef<Self>) -> Result<(), ARef<Self>> {
+        let this = Self::try_set_end(this)?;
+        let request_ptr = this.0.get();
Quoted 8 lines

rust/kernel/block/mq/request.rs line 109

Quoted 4 lines
rust/kernel/block/mq/request.rs new file +4 -0
+        core::mem::forget(this);
+
+        // SAFETY: By type invariant, `self.0` is a valid `struct request`. By
+        // existence of `&mut self` we have exclusive access.

Same here, but in this case, the ARef is unique, since you called try_set_end. You could make it a # Guarantee of try_set_end: "If Ok(aref) is returned, then the aref is unique."

Makes sense. I have not seen # Guarantee used anywhere. Do you have a link for that use?

Quoted 11 lines
Quoted 11 lines
rust/kernel/block/mq/request.rs new file +10 -0
+        unsafe { bindings::blk_mq_end_request(request_ptr, bindings::BLK_STS_OK as _) };
+
+        Ok(())
+    }
+
+    /// Return a pointer to the `RequestDataWrapper` stored in the private area
+    /// of the request structure.
+    ///
+    /// # Safety
+    ///
Quoted 8 lines

rust/kernel/block/mq/request.rs line 125

Quoted 6 lines
rust/kernel/block/mq/request.rs new file +6 -0
+    /// - `this` must point to a valid allocation.
+    pub(crate) unsafe fn wrapper_ptr(this: *mut Self) -> NonNull<RequestDataWrapper> {
+        let request_ptr = this.cast::<bindings::request>();
+        let wrapper_ptr =
+            // SAFETY: By safety requirements for this function, `this` is a
+            // valid allocation.

Formatting: move the safety comment above the let.

Thanks.

I'll send a new version shortly.

BR Andreas

#4 lore raw

Re: [PATCH v4 1/3] rust: block: introduce `kernel::block::mq` module

Quoted 18 lines · Andreas Hindborg
Quoted 16 lines · Benno Lossin

rust/kernel/block/mq/gen_disk.rs line 59

Quoted 12 lines · Andreas Hindborg
rust/kernel/block/mq/gen_disk.rs new file +12 -0
+impl seal::Sealed for Initialized {}
+impl GenDiskState for Initialized {
+    const DELETE_ON_DROP: bool = false;
+}
+impl seal::Sealed for Added {}
+impl GenDiskState for Added {
+    const DELETE_ON_DROP: bool = true;
+}
+
+impl<T: Operations> GenDisk<T, Initialized> {
+    /// Try to create a new `GenDisk`.
+    pub fn try_new(tagset: Arc<TagSet<T>>) -> Result<Self> {

Since there is no non-try new function, I think we should name this function just new.

Quoted 2 lines

Right, I am still getting used to the new naming scheme. Do you know if it is documented anywhere?

I don't think it is documented, it might only be a verbal convention at the moment. Although [1] is suggesting new for general constructors. Since this is the only constructor, one could argue that the recommendation is to use new (which I personally find a good idea).

[1]: https://rust-lang.github.io/api-guidelines/naming.html

[...]

Quoted 22 lines
Quoted 9 lines

rust/kernel/block/mq/operations.rs line 61

Quoted 8 lines
rust/kernel/block/mq/operations.rs new file +8 -0
+impl<T: Operations> OperationsVTable<T> {
+    /// This function is called by the C kernel. A pointer to this function is
+    /// installed in the `blk_mq_ops` vtable for the driver.
+    ///
+    /// # Safety
+    ///
+    /// - The caller of this function must ensure `bd` is valid
+    ///   and initialized. The pointees must outlive this function.
Quoted 3 lines

Until when do the pointees have to be alive? "must outlive this function" could also be the case if the pointees die immediately after this function returns.

It should not be plural. What I intended to communicate is that what bd points to must be valid for read for the duration of the function call. I think that is what "The pointee must outlive this function" states? Although when we talk about lifetime of an object pointed to by a pointer, I am not sure about the correct way to word this. Do we talk about the lifetime of the pointer or the lifetime of the pointed to object (the pointee). We should not use the same wording for the pointer and the pointee.

Quoted 4 lines

How about:

    /// - The caller of this function must ensure that the pointee of `bd` is
    ///   valid for read for the duration of this function.

But this is not enough for it to be sound, right? You create an ARef from bd.rq, which potentially lives forever. You somehow need to require that the pointer bd stays valid for reads and (synchronized) writes until the request is ended (probably via blk_mq_end_request).

Quoted 36 lines
Quoted 6 lines

rust/kernel/block/mq/operations.rs line 65

Quoted 4 lines
rust/kernel/block/mq/operations.rs new file +4 -0
+    /// - This function must not be called with a `hctx` for which
+    ///   `Self::exit_hctx_callback()` has been called.
+    /// - (*bd).rq must point to a valid `bindings:request` for which
+    ///   `OperationsVTable<T>::init_request_callback` was called

Missing . at the end.

Thanks.

Quoted 17 lines
Quoted 6 lines
rust/kernel/block/mq/operations.rs new file +5 -0
+    unsafe extern "C" fn queue_rq_callback(
+        _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

rust/kernel/block/mq/operations.rs line 80

Quoted 10 lines
rust/kernel/block/mq/operations.rs new file +10 -0
+        // this function.
+        let request = unsafe { &*(*bd).rq.cast::<Request<T>>() };
+
+        // One refcount for the ARef, one for being in flight
+        request.wrapper_ref().refcount().store(2, Ordering::Relaxed);
+
+        // SAFETY: We own a refcount that we took above. We pass that to `ARef`.
+        // By the safety requirements of this function, `request` is a valid
+        // `struct request` and the private data is properly initialized.
+        let rq = unsafe { Request::aref_from_raw((*bd).rq) };
Quoted 7 lines

I think that you need to require that the request is alive at least until blk_mq_end_request is called for the request (since at that point all ARefs will be gone).
Also if this is not guaranteed, the safety requirements of AlwaysRefCounted are violated (since the object can just disappear even if it has refcount > 0 [the refcount refers to the Rust refcount in the RequestDataWrapper, not the one in C]).

Yea, for the last invariant of Request:

Quoted 7 lines

/// * self is reference counted by atomic modification of
/// self.wrapper_ref().refcount().

I will add this to the safety comment at the call site:

// - rq will be alive until blk_mq_end_request is called and is
// reference counted by ARef until then.

Seems like you already want to use this here :)

[...]

Quoted 6 lines
Quoted 6 lines

rust/kernel/block/mq/operations.rs line 162

Quoted 6 lines
rust/kernel/block/mq/operations.rs new file +6 -0
+    /// 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. `set` must

set doesn't exist (_set does), you are also not using this requirement.

Quoted 18 lines
Quoted 11 lines

rust/kernel/block/mq/operations.rs line 172

Quoted 10 lines
rust/kernel/block/mq/operations.rs new file +10 -0
+    /// point to an initialized `TagSet<T>`.
+    unsafe extern "C" fn init_request_callback(
+        _set: *mut bindings::blk_mq_tag_set,
+        rq: *mut bindings::request,
+        _hctx_idx: core::ffi::c_uint,
+        _numa_node: core::ffi::c_uint,
+    ) -> core::ffi::c_int {
+        from_result(|| {
+            // SAFETY: The `blk_mq_tag_set` invariants guarantee that all
+            // requests are allocated with extra memory for the request data.
Quoted 2 lines

What guarantees that the right amount of memory has been allocated? AFAIU that is guaranteed by the TagSet (but there is no invariant).

It is by C API contract. TagSet::try_new (now new) writes cmd_size into the struct blk_mq_tag_set. That is picked up by blk_mq_alloc_tag_set` to allocate the right amount of space for each request.

Quoted 6 lines

The invariant here is on the C type. Perhaps the wording is wrong. I am not exactly sure how to express this. How about this:

            // SAFETY: We instructed `blk_mq_alloc_tag_set` to allocate requests
            // with extra memory for the request data when we called it in
            // `TagSet::new`.

I think you need a safety requirement on the function: rq points to a valid Request. Then you could just use Request::wrapper_ptr instead of the line below.

Quoted 67 lines
Quoted 26 lines
Quoted 9 lines
rust/kernel/block/mq/operations.rs new file +9 -0
+            let pdu = unsafe { bindings::blk_mq_rq_to_pdu(rq) }.cast::<RequestDataWrapper>();
+
+            // SAFETY: The refcount field is allocated but not initialized, this
+            // valid for write.
+            unsafe { RequestDataWrapper::refcount_ptr(pdu).write(AtomicU64::new(0)) };
+
+            Ok(0)
+        })
+    }

[...]

rust/kernel/block/mq/request.rs line 82

Quoted 13 lines
rust/kernel/block/mq/request.rs new file +13 -0
+    /// Notify the block layer that a request is going to be processed now.
+    ///
+    /// The block layer uses this hook to do proper initializations such as
+    /// starting the timeout timer. It is a requirement that block device
+    /// drivers call this function when starting to process a request.
+    ///
+    /// # Safety
+    ///
+    /// The caller must have exclusive ownership of `self`, that is
+    /// `self.wrapper_ref().refcount() == 2`.
+    pub(crate) unsafe fn start_unchecked(this: &ARef<Self>) {
+        // SAFETY: By type invariant, `self.0` is a valid `struct request`. By
+        // existence of `&mut self` we have exclusive access.
Quoted 2 lines

We don't have a &mut self. But the safety requirements ask for a unique ARef.

Thanks, I'll rephrase to:

        // SAFETY: By type invariant, `self.0` is a valid `struct request` and
        // we have exclusive access.
Quoted 24 lines
Quoted 24 lines
rust/kernel/block/mq/request.rs new file +23 -0
+        unsafe { bindings::blk_mq_start_request(this.0.get()) };
+    }
+
+    fn try_set_end(this: ARef<Self>) -> Result<ARef<Self>, ARef<Self>> {
+        // We can race with `TagSet::tag_to_rq`
+        match this.wrapper_ref().refcount().compare_exchange(
+            2,
+            0,
+            Ordering::Relaxed,
+            Ordering::Relaxed,
+        ) {
+            Err(_old) => Err(this),
+            Ok(_) => Ok(this),
+        }
+    }
+
+    /// Notify the block layer that the request has been completed without errors.
+    ///
+    /// This function will return `Err` if `this` is not the only `ARef`
+    /// referencing the request.
+    pub fn end_ok(this: ARef<Self>) -> Result<(), ARef<Self>> {
+        let this = Self::try_set_end(this)?;
+        let request_ptr = this.0.get();
Quoted 8 lines

rust/kernel/block/mq/request.rs line 109

Quoted 4 lines
rust/kernel/block/mq/request.rs new file +4 -0
+        core::mem::forget(this);
+
+        // SAFETY: By type invariant, `self.0` is a valid `struct request`. By
+        // existence of `&mut self` we have exclusive access.

Same here, but in this case, the ARef is unique, since you called try_set_end. You could make it a # Guarantee of try_set_end: "If Ok(aref) is returned, then the aref is unique."

Quoted 1 line

Makes sense. I have not seen # Guarantee used anywhere. Do you have a link for that use?

Alice used it a couple of times, eg in [2]. I plan on putting it in the safety standard.

[2]: https://lore.kernel.org/rust-for-linux/[email protected]/

---
Cheers,
Benno

#5 lore raw

Re: [PATCH v4 1/3] rust: block: introduce `kernel::block::mq` module

Benno Lossin <[email protected]> writes:

[...]

Quoted 24 lines
Quoted 24 lines
Quoted 9 lines

rust/kernel/block/mq/operations.rs line 61

Quoted 8 lines
rust/kernel/block/mq/operations.rs new file +8 -0
+impl<T: Operations> OperationsVTable<T> {
+    /// This function is called by the C kernel. A pointer to this function is
+    /// installed in the `blk_mq_ops` vtable for the driver.
+    ///
+    /// # Safety
+    ///
+    /// - The caller of this function must ensure `bd` is valid
+    ///   and initialized. The pointees must outlive this function.
Quoted 3 lines

Until when do the pointees have to be alive? "must outlive this function" could also be the case if the pointees die immediately after this function returns.

It should not be plural. What I intended to communicate is that what bd points to must be valid for read for the duration of the function call. I think that is what "The pointee must outlive this function" states? Although when we talk about lifetime of an object pointed to by a pointer, I am not sure about the correct way to word this. Do we talk about the lifetime of the pointer or the lifetime of the pointed to object (the pointee). We should not use the same wording for the pointer and the pointee.

How about:

Quoted 7 lines
Quoted 2 lines
    /// - The caller of this function must ensure that the pointee of `bd` is
    ///   valid for read for the duration of this function.

But this is not enough for it to be sound, right? You create an ARef from bd.rq, which potentially lives forever. You somehow need to require that the pointer bd stays valid for reads and (synchronized) writes until the request is ended (probably via blk_mq_end_request).

The statement does not say anything about *((*bd).rq). *bd needs to be valid only for the duration of the function. It carries a pointer to a struct request in the rq field. The pointee of that pointer must be exclusively owned by the driver until the request is done.

Maybe like this:

# Safety

- The caller of this function must ensure that the pointee of bd is valid for read for the duration of this function.
- This function must be called for an initialized and live hctx. That is, Self::init_hctx_callback was called and Self::exit_hctx_callback() was not yet called.
- (*bd).rq must point to an initialized and live bindings:request. That is, Self::init_request_callback was called but Self::exit_request_callback was not yet called for the request.
- (*bd).rq must be owned by the driver. That is, the block layer must promise to not access the request until the driver calls bindings::blk_mq_end_request for the request.

[...]

Quoted 9 lines
Quoted 6 lines
Quoted 6 lines

rust/kernel/block/mq/operations.rs line 162

Quoted 6 lines
rust/kernel/block/mq/operations.rs new file +6 -0
+    /// 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. `set` must

set doesn't exist (_set does), you are also not using this requirement.

Would be nice if there was a way in rustdoc no name arguments explicitly.

Quoted 22 lines
Quoted 22 lines
Quoted 12 lines

rust/kernel/block/mq/operations.rs line 172

Quoted 11 lines
rust/kernel/block/mq/operations.rs new file +10 -0
+    /// point to an initialized `TagSet<T>`.
+    unsafe extern "C" fn init_request_callback(
+        _set: *mut bindings::blk_mq_tag_set,
+        rq: *mut bindings::request,
+        _hctx_idx: core::ffi::c_uint,
+        _numa_node: core::ffi::c_uint,
+    ) -> core::ffi::c_int {
+        from_result(|| {
+            // SAFETY: The `blk_mq_tag_set` invariants guarantee that all
+            // requests are allocated with extra memory for the request data.
Quoted 2 lines

What guarantees that the right amount of memory has been allocated? AFAIU that is guaranteed by the TagSet (but there is no invariant).

It is by C API contract. TagSet::try_new (now new) writes cmd_size into the struct blk_mq_tag_set. That is picked up by blk_mq_alloc_tag_set` to allocate the right amount of space for each request.

The invariant here is on the C type. Perhaps the wording is wrong. I am not exactly sure how to express this. How about this:

Quoted 7 lines
Quoted 3 lines
            // SAFETY: We instructed `blk_mq_alloc_tag_set` to allocate requests
            // with extra memory for the request data when we called it in
            // `TagSet::new`.

I think you need a safety requirement on the function: rq points to a valid Request. Then you could just use Request::wrapper_ptr instead of the line below.

I cannot require rq to point to a valid Request, because that would require the private data area to already be initialized as a valid RequestDataWrapper. Using the wrapper_ptr is good 👍. How is this:

    /// # Safety
    ///
    /// - This function may only be called by blk-mq C infrastructure.
    /// - `_set` must point to an initialized `TagSet<T>`.
    /// - `rq` must point to an initialized `bindings::request`.
    /// - The allocation pointed to by `rq` must be at the size of `Request`
    ///   plus the size of `RequestDataWrapper`.

BR Andreas

#6 lore raw

Re: [PATCH v4 1/3] rust: block: introduce `kernel::block::mq` module

Quoted 41 lines · Andreas Hindborg

Benno Lossin <[email protected]> writes:

[...]

Quoted 24 lines
Quoted 24 lines
Quoted 9 lines

rust/kernel/block/mq/operations.rs line 61

Quoted 8 lines
rust/kernel/block/mq/operations.rs new file +8 -0
+impl<T: Operations> OperationsVTable<T> {
+    /// This function is called by the C kernel. A pointer to this function is
+    /// installed in the `blk_mq_ops` vtable for the driver.
+    ///
+    /// # Safety
+    ///
+    /// - The caller of this function must ensure `bd` is valid
+    ///   and initialized. The pointees must outlive this function.
Quoted 3 lines

Until when do the pointees have to be alive? "must outlive this function" could also be the case if the pointees die immediately after this function returns.

It should not be plural. What I intended to communicate is that what bd points to must be valid for read for the duration of the function call. I think that is what "The pointee must outlive this function" states? Although when we talk about lifetime of an object pointed to by a pointer, I am not sure about the correct way to word this. Do we talk about the lifetime of the pointer or the lifetime of the pointed to object (the pointee). We should not use the same wording for the pointer and the pointee.

How about:

Quoted 7 lines
Quoted 2 lines
    /// - The caller of this function must ensure that the pointee of `bd` is
    ///   valid for read for the duration of this function.

But this is not enough for it to be sound, right? You create an ARef from bd.rq, which potentially lives forever. You somehow need to require that the pointer bd stays valid for reads and (synchronized) writes until the request is ended (probably via blk_mq_end_request).

The statement does not say anything about *((*bd).rq). *bd needs to be valid only for the duration of the function. It carries a pointer to a struct request in the rq field. The pointee of that pointer must be exclusively owned by the driver until the request is done.

Quoted 6 lines

Maybe like this:

# Safety

- The caller of this function must ensure that the pointee of bd is valid for read for the duration of this function.

"valid for reads"

Quoted 9 lines

- This function must be called for an initialized and live hctx. That is, Self::init_hctx_callback was called and Self::exit_hctx_callback() was not yet called.
- (*bd).rq must point to an initialized and live bindings:request. That is, Self::init_request_callback was called but Self::exit_request_callback was not yet called for the request.
- (*bd).rq must be owned by the driver. That is, the block layer must promise to not access the request until the driver calls bindings::blk_mq_end_request for the request.

Sounds good!

Quoted 50 lines

[...]

Quoted 9 lines
Quoted 6 lines
Quoted 6 lines

rust/kernel/block/mq/operations.rs line 162

Quoted 6 lines
rust/kernel/block/mq/operations.rs new file +6 -0
+    /// 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. `set` must

set doesn't exist (_set does), you are also not using this requirement.

Would be nice if there was a way in rustdoc no name arguments explicitly.

Quoted 22 lines
Quoted 22 lines
Quoted 12 lines

rust/kernel/block/mq/operations.rs line 172

Quoted 11 lines
rust/kernel/block/mq/operations.rs new file +10 -0
+    /// point to an initialized `TagSet<T>`.
+    unsafe extern "C" fn init_request_callback(
+        _set: *mut bindings::blk_mq_tag_set,
+        rq: *mut bindings::request,
+        _hctx_idx: core::ffi::c_uint,
+        _numa_node: core::ffi::c_uint,
+    ) -> core::ffi::c_int {
+        from_result(|| {
+            // SAFETY: The `blk_mq_tag_set` invariants guarantee that all
+            // requests are allocated with extra memory for the request data.
Quoted 2 lines

What guarantees that the right amount of memory has been allocated? AFAIU that is guaranteed by the TagSet (but there is no invariant).

It is by C API contract. TagSet::try_new (now new) writes cmd_size into the struct blk_mq_tag_set. That is picked up by blk_mq_alloc_tag_set` to allocate the right amount of space for each request.

The invariant here is on the C type. Perhaps the wording is wrong. I am not exactly sure how to express this. How about this:

Quoted 7 lines
Quoted 3 lines
            // SAFETY: We instructed `blk_mq_alloc_tag_set` to allocate requests
            // with extra memory for the request data when we called it in
            // `TagSet::new`.

I think you need a safety requirement on the function: rq points to a valid Request. Then you could just use Request::wrapper_ptr instead of the line below.

I cannot require rq to point to a valid Request, because that would require the private data area to already be initialized as a valid RequestDataWrapper. Using the wrapper_ptr is good 👍. How is this:

Quoted 7 lines
    /// # Safety
    ///
    /// - This function may only be called by blk-mq C infrastructure.
    /// - `_set` must point to an initialized `TagSet<T>`.
    /// - `rq` must point to an initialized `bindings::request`.
    /// - The allocation pointed to by `rq` must be at the size of `Request`
    ///   plus the size of `RequestDataWrapper`.

Also sounds good to me.

---
Cheers,
Benno

#7 lore raw

PATCH v4 2/3 rust: block: add rnull, Rust null_blk implementation

In reply to Andreas Hindborg

From: Andreas Hindborg <[email protected]>

This patch adds an initial version of the Rust null block driver.

---
 drivers/block/Kconfig  |  9 +++++
 drivers/block/Makefile |  3 ++
 drivers/block/rnull.rs | 81 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 93 insertions(+)
 create mode 100644 drivers/block/rnull.rs
drivers/block/Kconfig +9 -0
index 5b9d4aaebb81..ed209f4f2798 100644
@@ -354,6 +354,15 @@ 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
drivers/block/Makefile +3 -0
index 101612cba303..1105a2d4fdcb 100644
@@ -9,6 +9,9 @@
 # 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
drivers/block/rnull.rs new file +81 -0

81 lines not on this page · raw message

Signature · 1 line
2.45.1
#8 lore raw

Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation

drivers/block/rnull.rs line 47

Quoted 10 lines · Andreas Hindborg
drivers/block/rnull.rs new file +10 -0
+impl kernel::Module for NullBlkModule {
+    fn init(_module: &'static ThisModule) -> Result<Self> {
+        pr_info!("Rust null_blk loaded\n");
+        let tagset = Arc::pin_init(TagSet::try_new(1, 256, 1), flags::GFP_KERNEL)?;
+
+        let disk = {
+            let block_size: u16 = 4096;
+            if block_size % 512 != 0 || !(512..=4096).contains(&block_size) {
+                return Err(kernel::error::code::EINVAL);
+            }

You've set block_size to the literal 4096, then validate its value immediately after? Am I missing some way this could ever be invalid?

#9 lore raw

Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation

Quoted 14 lines · Keith Busch

drivers/block/rnull.rs line 47

Quoted 10 lines · Andreas Hindborg
drivers/block/rnull.rs new file +10 -0
+impl kernel::Module for NullBlkModule {
+    fn init(_module: &'static ThisModule) -> Result<Self> {
+        pr_info!("Rust null_blk loaded\n");
+        let tagset = Arc::pin_init(TagSet::try_new(1, 256, 1), flags::GFP_KERNEL)?;
+
+        let disk = {
+            let block_size: u16 = 4096;
+            if block_size % 512 != 0 || !(512..=4096).contains(&block_size) {
+                return Err(kernel::error::code::EINVAL);
+            }

You've set block_size to the literal 4096, then validate its value immediately after? Am I missing some way this could ever be invalid?

Good catch. It is because I have a patch in the outbound queue that allows setting the block size via a module parameter. The module parameter patch is not upstream yet. Once I have that up, I will send the patch with the block size config.

Do you think it is OK to have this redundancy? It would only be for a few cycles.

Best regards,
Andreas

#10 lore raw

Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation

Quoted 17 lines · Andreas Hindborg
Quoted 14 lines · Keith Busch

drivers/block/rnull.rs line 47

Quoted 10 lines · Andreas Hindborg
drivers/block/rnull.rs new file +10 -0
+impl kernel::Module for NullBlkModule {
+    fn init(_module: &'static ThisModule) -> Result<Self> {
+        pr_info!("Rust null_blk loaded\n");
+        let tagset = Arc::pin_init(TagSet::try_new(1, 256, 1), flags::GFP_KERNEL)?;
+
+        let disk = {
+            let block_size: u16 = 4096;
+            if block_size % 512 != 0 || !(512..=4096).contains(&block_size) {
+                return Err(kernel::error::code::EINVAL);
+            }

You've set block_size to the literal 4096, then validate its value immediately after? Am I missing some way this could ever be invalid?

Quoted 7 lines

Good catch. It is because I have a patch in the outbound queue that allows setting the block size via a module parameter. The module parameter patch is not upstream yet. Once I have that up, I will send the patch with the block size config.

Do you think it is OK to have this redundancy? It would only be for a few cycles.

It's fine, just wondering why it's there. But it also allows values like 1536 and 3584, which are not valid block sizes, so I think you want the check to be:

	if !(512..=4096).contains(&block_size) || ((block_size & (block_size - 1)) != 0)
#11 lore raw

Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation

Quoted 23 lines · Keith Busch
Quoted 22 lines · Andreas Hindborg
Quoted 14 lines · Keith Busch

drivers/block/rnull.rs line 47

Quoted 10 lines · Andreas Hindborg
drivers/block/rnull.rs new file +10 -0
+impl kernel::Module for NullBlkModule {
+    fn init(_module: &'static ThisModule) -> Result<Self> {
+        pr_info!("Rust null_blk loaded\n");
+        let tagset = Arc::pin_init(TagSet::try_new(1, 256, 1), flags::GFP_KERNEL)?;
+
+        let disk = {
+            let block_size: u16 = 4096;
+            if block_size % 512 != 0 || !(512..=4096).contains(&block_size) {
+                return Err(kernel::error::code::EINVAL);
+            }

You've set block_size to the literal 4096, then validate its value immediately after? Am I missing some way this could ever be invalid?

Good catch. It is because I have a patch in the outbound queue that allows setting the block size via a module parameter. The module parameter patch is not upstream yet. Once I have that up, I will send the patch with the block size config.

Quoted 8 lines
Quoted 2 lines

Do you think it is OK to have this redundancy? It would only be for a few cycles.

It's fine, just wondering why it's there. But it also allows values like 1536 and 3584, which are not valid block sizes, so I think you want the check to be:

	if !(512..=4096).contains(&block_size) || ((block_size & (block_size - 1)) != 0)

Right, that makes sense. I modeled it after the C null_blk validation code in null_validate_conf. It contains this:

	dev->blocksize = round_down(dev->blocksize, 512);
	dev->blocksize = clamp_t(unsigned int, dev->blocksize, 512, 4096);

That would have the same semantics, right? I guess I'll try to make a device with a 1536 block size and see what happens.

BR Andreas

#12 lore raw

Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation

Quoted 34 lines · Andreas Hindborg
Quoted 23 lines · Keith Busch
Quoted 22 lines · Andreas Hindborg
Quoted 14 lines · Keith Busch

drivers/block/rnull.rs line 47

Quoted 10 lines · Andreas Hindborg
drivers/block/rnull.rs new file +10 -0
+impl kernel::Module for NullBlkModule {
+    fn init(_module: &'static ThisModule) -> Result<Self> {
+        pr_info!("Rust null_blk loaded\n");
+        let tagset = Arc::pin_init(TagSet::try_new(1, 256, 1), flags::GFP_KERNEL)?;
+
+        let disk = {
+            let block_size: u16 = 4096;
+            if block_size % 512 != 0 || !(512..=4096).contains(&block_size) {
+                return Err(kernel::error::code::EINVAL);
+            }

You've set block_size to the literal 4096, then validate its value immediately after? Am I missing some way this could ever be invalid?

Good catch. It is because I have a patch in the outbound queue that allows setting the block size via a module parameter. The module parameter patch is not upstream yet. Once I have that up, I will send the patch with the block size config.

Quoted 8 lines
Quoted 2 lines

Do you think it is OK to have this redundancy? It would only be for a few cycles.

It's fine, just wondering why it's there. But it also allows values like 1536 and 3584, which are not valid block sizes, so I think you want the check to be:

	if !(512..=4096).contains(&block_size) || ((block_size & (block_size - 1)) != 0)
Quoted 8 lines

Right, that makes sense. I modeled it after the C null_blk validation code in null_validate_conf. It contains this:

	dev->blocksize = round_down(dev->blocksize, 512);
	dev->blocksize = clamp_t(unsigned int, dev->blocksize, 512, 4096);

That would have the same semantics, right? I guess I'll try to make a device with a 1536 block size and see what happens.

This happens:

root@debian:~# insmod /mnt/linux-build/drivers/block/null_blk/null_blk.ko bs=1536
BUG: kernel NULL pointer dereference, address: 0000000000000000
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: Oops: 0002 [#1] SMP
CPU: 2 PID: 291 Comm: insmod Not tainted 6.10.0-rc1+ #839

Probably a good idea with a better check.

BR Andreas

#13 lore raw

Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation

In reply to Keith Busch
Quoted 5 lines · Keith Busch

It's fine, just wondering why it's there. But it also allows values like 1536 and 3584, which are not valid block sizes, so I think you want the check to be:

	if !(512..=4096).contains(&block_size) || ((block_size & (block_size - 1)) != 0)

I'd drop the range check. We're pretty close to landing the bs>PS patches, so just

	if block_size & block_size - 1 != 0

should be enough of a validation. Is it considered "good style" in Rust to omit the brackets here?

#14 lore raw

Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation

Quoted 5 lines · Matthew Wilcox
Quoted 4 lines · Keith Busch

It's fine, just wondering why it's there. But it also allows values like 1536 and 3584, which are not valid block sizes, so I think you want the check to be:

Quoted 8 lines
Quoted 1 line
	if !(512..=4096).contains(&block_size) || ((block_size & (block_size - 1)) != 0)

I'd drop the range check. We're pretty close to landing the bs>PS patches, so just

	if block_size & block_size - 1 != 0

should be enough of a validation.

Is it safe to do so already? Otherwise we just remove it when it is safe, no biggie.

Quoted 2 lines

Is it considered "good style" in
Rust to omit the brackets here?

Yes, the compiler will complain if you add parenthesis here.

```rust
fn main() {
    if (true) {
        return;
    }
}
```

Building this will give you:

```text
warning: unnecessary parentheses around `if` condition
 --> src/main.rs:2:8
  |
2 |     if (true) {
  |        ^    ^
  |
  = note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
  |
2 -     if (true) {
2 +     if true {
  |

warning: `playground` (bin "playground") generated 1 warning (run `cargo fix --bin "playground"` to apply 1 suggestion)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.64s
     Running `target/debug/playground`
```

If you omit the {} block after the if it is a syntax error.

Best regards,
Andreas

#15 lore raw

Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation

In reply to Keith Busch
Quoted 23 lines · Keith Busch
Quoted 22 lines · Andreas Hindborg
Quoted 14 lines · Keith Busch

drivers/block/rnull.rs line 47

Quoted 10 lines · Andreas Hindborg
drivers/block/rnull.rs new file +10 -0
+impl kernel::Module for NullBlkModule {
+    fn init(_module: &'static ThisModule) -> Result<Self> {
+        pr_info!("Rust null_blk loaded\n");
+        let tagset = Arc::pin_init(TagSet::try_new(1, 256, 1), flags::GFP_KERNEL)?;
+
+        let disk = {
+            let block_size: u16 = 4096;
+            if block_size % 512 != 0 || !(512..=4096).contains(&block_size) {
+                return Err(kernel::error::code::EINVAL);
+            }

You've set block_size to the literal 4096, then validate its value immediately after? Am I missing some way this could ever be invalid?

Good catch. It is because I have a patch in the outbound queue that allows setting the block size via a module parameter. The module parameter patch is not upstream yet. Once I have that up, I will send the patch with the block size config.

Quoted 9 lines
Quoted 2 lines

Do you think it is OK to have this redundancy? It would only be for a few cycles.

It's fine, just wondering why it's there. But it also allows values like 1536 and 3584, which are not valid block sizes, so I think you want the check to be:

	if !(512..=4096).contains(&block_size) || ((block_size & (block_size - 1)) != 0)

Can't we overload .contains() to check only power-of-2 values?

Cheers,

Hannes

reviewer
#16 lore raw

Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation

Quoted 28 lines · Hannes Reinecke
Quoted 26 lines · Keith Busch
Quoted 24 lines · Andreas Hindborg
Quoted 14 lines · Keith Busch

drivers/block/rnull.rs line 47

Quoted 10 lines · Andreas Hindborg
drivers/block/rnull.rs new file +10 -0
+impl kernel::Module for NullBlkModule {
+    fn init(_module: &'static ThisModule) -> Result<Self> {
+        pr_info!("Rust null_blk loaded\n");
+        let tagset = Arc::pin_init(TagSet::try_new(1, 256, 1), flags::GFP_KERNEL)?;
+
+        let disk = {
+            let block_size: u16 = 4096;
+            if block_size % 512 != 0 || !(512..=4096).contains(&block_size) {
+                return Err(kernel::error::code::EINVAL);
+            }

You've set block_size to the literal 4096, then validate its value immediately after? Am I missing some way this could ever be invalid?

Good catch. It is because I have a patch in the outbound queue that allows setting the block size via a module parameter. The module parameter patch is not upstream yet. Once I have that up, I will send the patch with the block size config.

Do you think it is OK to have this redundancy? It would only be for a few cycles.

Quoted 7 lines
Quoted 6 lines

It's fine, just wondering why it's there. But it also allows values like 1536 and 3584, which are not valid block sizes, so I think you want the check to be:

      if !(512..=4096).contains(&block_size) || ((block_size & (block_size - 1)) != 0)

Can't we overload .contains() to check only power-of-2 values?

Rust integers have a method called is_power_of_two. If you need to assert that it's a power of two, you can use that.

Alice

#17 lore raw

Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation

Quoted 33 lines · Alice Ryhl
Quoted 32 lines · Hannes Reinecke
Quoted 30 lines · Keith Busch
Quoted 17 lines · Andreas Hindborg
Quoted 14 lines · Keith Busch

drivers/block/rnull.rs line 47

Quoted 10 lines · Andreas Hindborg
drivers/block/rnull.rs new file +10 -0
+impl kernel::Module for NullBlkModule {
+    fn init(_module: &'static ThisModule) -> Result<Self> {
+        pr_info!("Rust null_blk loaded\n");
+        let tagset = Arc::pin_init(TagSet::try_new(1, 256, 1), flags::GFP_KERNEL)?;
+
+        let disk = {
+            let block_size: u16 = 4096;
+            if block_size % 512 != 0 || !(512..=4096).contains(&block_size) {
+                return Err(kernel::error::code::EINVAL);
+            }

You've set block_size to the literal 4096, then validate its value immediately after? Am I missing some way this could ever be invalid?

Quoted 7 lines

Good catch. It is because I have a patch in the outbound queue that allows setting the block size via a module parameter. The module parameter patch is not upstream yet. Once I have that up, I will send the patch with the block size config.

Do you think it is OK to have this redundancy? It would only be for a few cycles.

It's fine, just wondering why it's there. But it also allows values like 1536 and 3584, which are not valid block sizes, so I think you want the check to be:

Quoted 6 lines
Quoted 3 lines
Quoted 2 lines
      if !(512..=4096).contains(&block_size) || ((block_size & (block_size - 1)) != 0)

Can't we overload .contains() to check only power-of-2 values?

Rust integers have a method called is_power_of_two. If you need to assert that it's a power of two, you can use that.

Thanks Alice, that is much easier to read 👍

BR Andreas

#18 lore raw

Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation

In reply to Hannes Reinecke
Quoted 26 lines · Hannes Reinecke
Quoted 25 lines · Keith Busch
Quoted 24 lines · Andreas Hindborg
Quoted 14 lines · Keith Busch

drivers/block/rnull.rs line 47

Quoted 10 lines · Andreas Hindborg
drivers/block/rnull.rs new file +10 -0
+impl kernel::Module for NullBlkModule {
+    fn init(_module: &'static ThisModule) -> Result<Self> {
+        pr_info!("Rust null_blk loaded\n");
+        let tagset = Arc::pin_init(TagSet::try_new(1, 256, 1), flags::GFP_KERNEL)?;
+
+        let disk = {
+            let block_size: u16 = 4096;
+            if block_size % 512 != 0 || !(512..=4096).contains(&block_size) {
+                return Err(kernel::error::code::EINVAL);
+            }

You've set block_size to the literal 4096, then validate its value immediately after? Am I missing some way this could ever be invalid?

Good catch. It is because I have a patch in the outbound queue that allows setting the block size via a module parameter. The module parameter patch is not upstream yet. Once I have that up, I will send the patch with the block size config.

Do you think it is OK to have this redundancy? It would only be for a few cycles.

Quoted 7 lines
Quoted 6 lines

It's fine, just wondering why it's there. But it also allows values like 1536 and 3584, which are not valid block sizes, so I think you want the check to be:

	if !(512..=4096).contains(&block_size) || ((block_size & (block_size - 1))

!= 0)

Can't we overload .contains() to check only power-of-2 values?

I think contains just compiles down to a simple bounds check. We have to do both the bounds check and the power-of-2 check either way.

BR Andreas

#19 lore raw

PATCH v4 3/3 MAINTAINERS: add entry for Rust block device driver API

In reply to Andreas Hindborg

From: Andreas Hindborg <[email protected]>

Add an entry for the Rust block device driver abstractions.

---
 MAINTAINERS | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
MAINTAINERS +14 -0
index d6c90161c7bf..698515b0b0b3 100644
@@ -3782,6 +3782,20 @@ F:	include/linux/blk*
 F:	kernel/trace/blktrace.c
 F:	lib/sbitmap.c
 
+BLOCK LAYER DEVICE DRIVER API [RUST]
+M:	Andreas Hindborg <[email protected]>
+R:	Boqun Feng <[email protected]>
+L:	[email protected]
+L:	[email protected]
+S:	Supported
+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:	rust/kernel/block.rs
+F:	rust/kernel/block/
+
 BLOCK2MTD DRIVER
 M:	Joern Engel <[email protected]>
 L:	[email protected]
Signature · 1 line
2.45.1