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 | 72 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/rust/helpers/blk.c b/rust/helpers/blk.c index 0db567dfc5d53..bb77af6dca9c8 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 47a162360e628..e7d7217da8922 100644 --- a/rust/kernel/block/mq/tag_set.rs +++ b/rust/kernel/block/mq/tag_set.rs @@ -5,12 +5,12 @@ //! C header: [`include/linux/blk-mq.h`](srctree/include/linux/blk-mq.h) use crate::{ - bindings, block::mq::{operations::OperationsVTable, request::RequestDataWrapper, Operations}, error::{self, Result}, - prelude::*, - try_pin_init, - types::{ForeignOwnable, Opaque}, + pr_warn, + prelude::{ENOMEM, *}, + sync::atomic::ordering, + types::{ARef, ForeignOwnable, Opaque}, }; use core::{convert::TryInto, marker::PhantomData, pin::Pin}; use pin_init::{pin_data, pinned_drop, PinInit}; @@ -19,6 +19,8 @@ pub use flags::Flag; pub use flags::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. @@ -166,6 +168,68 @@ 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() { + // TODO: Use pr_warn_once! + pr_warn!("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