Add a way for block device drivers to obtain a `Request` from a tag. This is backed by the C `blk_mq_tag_to_rq` but with added checks to ensure memory safety. Signed-off-by: Andreas Hindborg --- rust/helpers/blk.c | 6 ++++ rust/kernel/block/mq/tag_set.rs | 66 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/rust/helpers/blk.c b/rust/helpers/blk.c index 422289d617ae..1f3e5c661096 100644 --- a/rust/helpers/blk.c +++ b/rust/helpers/blk.c @@ -53,3 +53,9 @@ __rust_helper struct request *rust_helper_rq_list_peek(struct rq_list *rl) { return rq_list_peek(rl); } + +__rust_helper struct request * +rust_helper_blk_mq_tag_to_rq(struct blk_mq_tags *tags, unsigned int tag) +{ + return blk_mq_tag_to_rq(tags, tag); +} diff --git a/rust/kernel/block/mq/tag_set.rs b/rust/kernel/block/mq/tag_set.rs index e89c76987b54..66b6a30a9e66 100644 --- a/rust/kernel/block/mq/tag_set.rs +++ b/rust/kernel/block/mq/tag_set.rs @@ -6,7 +6,6 @@ use crate::{ alloc::NumaNode, - bindings, block::mq::{ operations::OperationsVTable, request::RequestDataWrapper, @@ -17,7 +16,9 @@ Result, // }, prelude::*, + sync::atomic::ordering, types::{ + ARef, ForeignOwnable, Opaque, // }, @@ -39,6 +40,8 @@ Flags, // }; +use super::Request; + /// A wrapper for the C `struct blk_mq_tag_set`. /// /// `struct blk_mq_tag_set` contains a `struct list_head` and so must be pinned. @@ -193,6 +196,67 @@ pub fn data(&self) -> ::Borrowed<'_> { // converted back with `from_foreign` while `&self` is live. unsafe { T::TagSetData::borrow(ptr) } } + + /// Obtain a shared reference to a request. + /// + /// This method will hang if the request is not owned by the driver, or if + /// the driver holds an [`Ownable`] reference to the request. + pub fn tag_to_rq(&self, qid: u32, tag: u32) -> Option>> { + if qid >= self.hw_queue_count() { + kernel::pr_warn_once!("Invalid queue id: {qid}\n"); + return None; + } + + // SAFETY: We checked that `qid` is within bounds. + let tags = unsafe { *(*self.inner.get()).tags.add(qid as usize) }; + + // SAFETY: We checked `qid` for overflow above, so `tags` is valid. + let rq_ptr = unsafe { bindings::blk_mq_tag_to_rq(tags, tag) }; + if rq_ptr.is_null() { + None + } else { + // SAFETY: if `rq_ptr`is not null, it is a valid request pointer. + let refcount_ptr = unsafe { + RequestDataWrapper::refcount_ptr( + Request::wrapper_ptr(rq_ptr.cast::>()).as_ptr(), + ) + }; + + // SAFETY: The refcount was initialized in `init_request_callback` and is never + // referenced mutably. + let refcount_ref = unsafe { &*refcount_ptr }; + + let atomic_ref = refcount_ref.as_atomic(); + + // It is possible for an interrupt to arrive faster than the last + // change to the refcount, so retry if the refcount is not what we + // think it should be. + loop { + // Load acquire to sync with store release of `Owned` + // being destroyed (prevent mutable access overlapping shared + // access). + let prev = atomic_ref.load(ordering::Acquire); + + if prev >= 1 { + // Store relaxed as no other operations need to happen strictly + // before or after the increment. + match atomic_ref.cmpxchg(prev, prev + 1, ordering::Relaxed) { + Ok(_) => break, + // NOTE: We cannot use the load part of a failed cmpxchg as it is always + // relaxed. + Err(_) => continue, + } + } else { + // We are probably waiting to observe a refcount increment. + core::hint::spin_loop(); + continue; + }; + } + + // SAFETY: We checked above that `rq_ptr` is valid for use as an `ARef`. + Some(unsafe { Request::aref_from_raw(rq_ptr) }) + } + } } #[pinned_drop] -- 2.51.2