Rust block device driver API and null block driver
Subsystems (3)
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
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].
--- 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -27,6 +27,8 @@extern crate selfas 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
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) } + }
[...]
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 toGenDisk::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
newfunction, I think we should name this function justnew.
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
datain 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_requestis called for the request (since at that point allARefs will be gone).
Also if this is not guaranteed, the safety requirements ofAlwaysRefCountedare violated (since the object can just disappear even if it has refcount > 0 [the refcount refers to the Rust refcount in theRequestDataWrapper, 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 uniqueARef.
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
ARefis unique, since you calledtry_set_end. You could make it a# Guaranteeoftry_set_end: "IfOk(aref)is returned, then thearefis 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
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
newfunction, I think we should name this function justnew.
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
bdpoints 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_requestis called for the request (since at that point allARefs will be gone).
Also if this is not guaranteed, the safety requirements ofAlwaysRefCountedare violated (since the object can just disappear even if it has refcount > 0 [the refcount refers to the Rust refcount in theRequestDataWrapper, not the one in C]).Yea, for the last invariant of
Request:
Quoted 7 lines
/// *
selfis reference counted by atomic modification of
/// self.wrapper_ref().refcount().I will add this to the safety comment at the call site:
// -
rqwill be alive untilblk_mq_end_requestis called and is
// reference counted byARefuntil 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(nownew) writescmd_sizeinto thestructblk_mq_tag_set. That is picked up byblk_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 uniqueARef.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
ARefis unique, since you calledtry_set_end. You could make it a# Guaranteeoftry_set_end: "IfOk(aref)is returned, then thearefis unique."
Quoted 1 line
Makes sense. I have not seen
# Guaranteeused 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
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
bdpoints 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
AReffrombd.rq, which potentially lives forever. You somehow need to require that the pointerbdstays valid for reads and (synchronized) writes until the request is ended (probably viablk_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
setdoesn't exist (_setdoes), 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(nownew) writescmd_sizeinto thestructblk_mq_tag_set. That is picked up byblk_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:
rqpoints to a validRequest. Then you could just useRequest::wrapper_ptrinstead 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
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
bdpoints 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
AReffrombd.rq, which potentially lives forever. You somehow need to require that the pointerbdstays valid for reads and (synchronized) writes until the request is ended (probably viablk_mq_end_request).The statement does not say anything about
*((*bd).rq).*bdneeds to be valid only for the duration of the function. It carries a pointer to astruct requestin therqfield. 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
bdis 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_callbackwas called andSelf::exit_hctx_callback()was not yet called.
-(*bd).rqmust point to an initialized and livebindings:request. That is,Self::init_request_callbackwas called butSelf::exit_request_callbackwas not yet called for the request.
-(*bd).rqmust be owned by the driver. That is, the block layer must promise to not access the request until the driver callsbindings::blk_mq_end_requestfor 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
setdoesn't exist (_setdoes), you are also not using this requirement.Would be nice if there was a way in
rustdocno 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(nownew) writescmd_sizeinto thestructblk_mq_tag_set. That is picked up byblk_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:
rqpoints to a validRequest. Then you could just useRequest::wrapper_ptrinstead of the line below.I cannot require
rqto point to a validRequest, because that would require the private data area to already be initialized as a validRequestDataWrapper. Using thewrapper_ptris 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
PATCH v4 2/3 rust: block: add rnull, Rust null_blk implementation
In reply to Andreas HindborgFrom: 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
@@ -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
@@ -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
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: &'staticThisModule ) ->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?
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: &'staticThisModule ) ->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_sizeto 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
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: &'staticThisModule ) ->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_sizeto 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)
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: &'staticThisModule ) ->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_sizeto 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
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: &'staticThisModule ) ->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_sizeto 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_blkvalidation code innull_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
Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation
In reply to Keith BuschQuoted 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?
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 != 0should 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
Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation
In reply to Keith BuschQuoted 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: &'staticThisModule ) ->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_sizeto 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
Alice Ryhl
reviewerRe: [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: &'staticThisModule ) ->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_sizeto 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
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: &'staticThisModule ) ->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_sizeto 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
Re: [PATCH v4 2/3] rust: block: add rnull, Rust null_blk implementation
In reply to Hannes ReineckeQuoted 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: &'staticThisModule ) ->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_sizeto 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
PATCH v4 3/3 MAINTAINERS: add entry for Rust block device driver API
In reply to Andreas HindborgFrom: Andreas Hindborg <[email protected]>
Add an entry for the Rust block device driver abstractions.
--- MAINTAINERS | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
MAINTAINERS +14 -0
@@ -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