kernel/block/mq/operations.rs
1// SPDX-License-Identifier: GPL-2.0
2
3//! This module provides an interface for blk-mq drivers to implement.
4//!
5//! C header: [`include/linux/blk-mq.h`](srctree/include/linux/blk-mq.h)
6
7use crate::{
8 bindings,
9 block::mq::{
10 request::RequestDataWrapper,
11 Request, //
12 },
13 error::from_result,
14 prelude::*,
15 sync::{
16 aref::ARef,
17 Refcount, //
18 },
19 types::ForeignOwnable, //
20};
21use core::marker::PhantomData;
22
23type ForeignBorrowed<'a, T> = <T as ForeignOwnable>::Borrowed<'a>;
24
25/// Implement this trait to interface blk-mq as block devices.
26///
27/// To implement a block device driver, implement this trait as described in the
28/// [module level documentation]. The kernel will use the implementation of the
29/// functions defined in this trait to interface a block device driver. Note:
30/// There is no need for an exit_request() implementation, because the `drop`
31/// implementation of the [`Request`] type will be invoked by automatically by
32/// the C/Rust glue logic.
33///
34/// [module level documentation]: kernel::block::mq
35#[macros::vtable]
36pub trait Operations: Sized {
37 /// Data associated with the `struct request_queue` that is allocated for
38 /// the `GenDisk` associated with this `Operations` implementation.
39 type QueueData: ForeignOwnable + Sync;
40
41 /// Called by the kernel to queue a request with the driver. If `is_last` is
42 /// `false`, the driver is allowed to defer committing the request.
43 fn queue_rq(
44 queue_data: ForeignBorrowed<'_, Self::QueueData>,
45 rq: ARef<Request<Self>>,
46 is_last: bool,
47 ) -> Result;
48
49 /// Called by the kernel to indicate that queued requests should be submitted.
50 fn commit_rqs(queue_data: ForeignBorrowed<'_, Self::QueueData>);
51
52 /// Called by the kernel when the request is completed.
53 fn complete(rq: ARef<Request<Self>>);
54
55 /// Called by the kernel to poll the device for completed requests. Only
56 /// used for poll queues.
57 fn poll() -> bool {
58 build_error!(crate::error::VTABLE_DEFAULT_ERROR)
59 }
60}
61
62/// A vtable for blk-mq to interact with a block device driver.
63///
64/// A `bindings::blk_mq_ops` vtable is constructed from pointers to the `extern
65/// "C"` functions of this struct, exposed through the `OperationsVTable::VTABLE`.
66///
67/// For general documentation of these methods, see the kernel source
68/// documentation related to `struct blk_mq_operations` in
69/// [`include/linux/blk-mq.h`].
70///
71/// [`include/linux/blk-mq.h`]: srctree/include/linux/blk-mq.h
72pub(crate) struct OperationsVTable<T: Operations>(PhantomData<T>);
73
74impl<T: Operations> OperationsVTable<T> {
75 /// This function is called by the C kernel. A pointer to this function is
76 /// installed in the `blk_mq_ops` vtable for the driver.
77 ///
78 /// # Safety
79 ///
80 /// - The caller of this function must ensure that the pointee of `bd` is
81 /// valid for reads for the duration of this function.
82 /// - This function must be called for an initialized and live `hctx`. That
83 /// is, `Self::init_hctx_callback` was called and
84 /// `Self::exit_hctx_callback()` was not yet called.
85 /// - `(*bd).rq` must point to an initialized and live `bindings:request`.
86 /// That is, `Self::init_request_callback` was called but
87 /// `Self::exit_request_callback` was not yet called for the request.
88 /// - `(*bd).rq` must be owned by the driver. That is, the block layer must
89 /// promise to not access the request until the driver calls
90 /// `bindings::blk_mq_end_request` for the request.
91 unsafe extern "C" fn queue_rq_callback(
92 hctx: *mut bindings::blk_mq_hw_ctx,
93 bd: *const bindings::blk_mq_queue_data,
94 ) -> bindings::blk_status_t {
95 // SAFETY: `bd.rq` is valid as required by the safety requirement for
96 // this function.
97 let request = unsafe { &*(*bd).rq.cast::<Request<T>>() };
98
99 // One refcount for the ARef, one for being in flight
100 request.wrapper_ref().refcount().set(2);
101
102 // SAFETY:
103 // - We own a refcount that we took above. We pass that to `ARef`.
104 // - By the safety requirements of this function, `request` is a valid
105 // `struct request` and the private data is properly initialized.
106 // - `rq` will be alive until `blk_mq_end_request` is called and is
107 // reference counted by `ARef` until then.
108 let rq = unsafe { Request::aref_from_raw((*bd).rq) };
109
110 // SAFETY: `hctx` is valid as required by this function.
111 let queue_data = unsafe { (*(*hctx).queue).queuedata };
112
113 // SAFETY: `queue.queuedata` was created by `GenDiskBuilder::build` with
114 // a call to `ForeignOwnable::into_foreign` to create `queuedata`.
115 // `ForeignOwnable::from_foreign` is only called when the tagset is
116 // dropped, which happens after we are dropped.
117 let queue_data = unsafe { T::QueueData::borrow(queue_data) };
118
119 // SAFETY: We have exclusive access and we just set the refcount above.
120 unsafe { Request::start_unchecked(&rq) };
121
122 let ret = T::queue_rq(
123 queue_data,
124 rq,
125 // SAFETY: `bd` is valid as required by the safety requirement for
126 // this function.
127 unsafe { (*bd).last },
128 );
129
130 if let Err(e) = ret {
131 e.to_blk_status()
132 } else {
133 bindings::BLK_STS_OK as bindings::blk_status_t
134 }
135 }
136
137 /// This function is called by the C kernel. A pointer to this function is
138 /// installed in the `blk_mq_ops` vtable for the driver.
139 ///
140 /// # Safety
141 ///
142 /// This function may only be called by blk-mq C infrastructure. The caller
143 /// must ensure that `hctx` is valid.
144 unsafe extern "C" fn commit_rqs_callback(hctx: *mut bindings::blk_mq_hw_ctx) {
145 // SAFETY: `hctx` is valid as required by this function.
146 let queue_data = unsafe { (*(*hctx).queue).queuedata };
147
148 // SAFETY: `queue.queuedata` was created by `GenDisk::try_new()` with a
149 // call to `ForeignOwnable::into_foreign()` to create `queuedata`.
150 // `ForeignOwnable::from_foreign()` is only called when the tagset is
151 // dropped, which happens after we are dropped.
152 let queue_data = unsafe { T::QueueData::borrow(queue_data) };
153 T::commit_rqs(queue_data)
154 }
155
156 /// This function is called by the C kernel. A pointer to this function is
157 /// installed in the `blk_mq_ops` vtable for the driver.
158 ///
159 /// # Safety
160 ///
161 /// This function may only be called by blk-mq C infrastructure. `rq` must
162 /// point to a valid request that has been marked as completed. The pointee
163 /// of `rq` must be valid for write for the duration of this function.
164 unsafe extern "C" fn complete_callback(rq: *mut bindings::request) {
165 // SAFETY: This function can only be dispatched through
166 // `Request::complete`. We leaked a refcount then which we pick back up
167 // now.
168 let aref = unsafe { Request::aref_from_raw(rq) };
169 T::complete(aref);
170 }
171
172 /// This function is called by the C kernel. A pointer to this function is
173 /// installed in the `blk_mq_ops` vtable for the driver.
174 ///
175 /// # Safety
176 ///
177 /// This function may only be called by blk-mq C infrastructure.
178 unsafe extern "C" fn poll_callback(
179 _hctx: *mut bindings::blk_mq_hw_ctx,
180 _iob: *mut bindings::io_comp_batch,
181 ) -> crate::ffi::c_int {
182 T::poll().into()
183 }
184
185 /// This function is called by the C kernel. A pointer to this function is
186 /// installed in the `blk_mq_ops` vtable for the driver.
187 ///
188 /// # Safety
189 ///
190 /// This function may only be called by blk-mq C infrastructure. This
191 /// function may only be called once before `exit_hctx_callback` is called
192 /// for the same context.
193 unsafe extern "C" fn init_hctx_callback(
194 _hctx: *mut bindings::blk_mq_hw_ctx,
195 _tagset_data: *mut crate::ffi::c_void,
196 _hctx_idx: crate::ffi::c_uint,
197 ) -> crate::ffi::c_int {
198 from_result(|| Ok(0))
199 }
200
201 /// This function is called by the C kernel. A pointer to this function is
202 /// installed in the `blk_mq_ops` vtable for the driver.
203 ///
204 /// # Safety
205 ///
206 /// This function may only be called by blk-mq C infrastructure.
207 unsafe extern "C" fn exit_hctx_callback(
208 _hctx: *mut bindings::blk_mq_hw_ctx,
209 _hctx_idx: crate::ffi::c_uint,
210 ) {
211 }
212
213 /// This function is called by the C kernel. A pointer to this function is
214 /// installed in the `blk_mq_ops` vtable for the driver.
215 ///
216 /// # Safety
217 ///
218 /// - This function may only be called by blk-mq C infrastructure.
219 /// - `_set` must point to an initialized `TagSet<T>`.
220 /// - `rq` must point to an initialized `bindings::request`.
221 /// - The allocation pointed to by `rq` must be at the size of `Request`
222 /// plus the size of `RequestDataWrapper`.
223 unsafe extern "C" fn init_request_callback(
224 _set: *mut bindings::blk_mq_tag_set,
225 rq: *mut bindings::request,
226 _hctx_idx: crate::ffi::c_uint,
227 _numa_node: crate::ffi::c_int,
228 ) -> crate::ffi::c_int {
229 from_result(|| {
230 // SAFETY: By the safety requirements of this function, `rq` points
231 // to a valid allocation.
232 let pdu = unsafe { Request::wrapper_ptr(rq.cast::<Request<T>>()) };
233
234 // SAFETY: The refcount field is allocated but not initialized, so
235 // it is valid for writes.
236 unsafe { RequestDataWrapper::refcount_ptr(pdu.as_ptr()).write(Refcount::new(0)) };
237
238 Ok(0)
239 })
240 }
241
242 /// This function is called by the C kernel. A pointer to this function is
243 /// installed in the `blk_mq_ops` vtable for the driver.
244 ///
245 /// # Safety
246 ///
247 /// - This function may only be called by blk-mq C infrastructure.
248 /// - `_set` must point to an initialized `TagSet<T>`.
249 /// - `rq` must point to an initialized and valid `Request`.
250 unsafe extern "C" fn exit_request_callback(
251 _set: *mut bindings::blk_mq_tag_set,
252 rq: *mut bindings::request,
253 _hctx_idx: crate::ffi::c_uint,
254 ) {
255 // SAFETY: The tagset invariants guarantee that all requests are allocated with extra memory
256 // for the request data.
257 let pdu = unsafe { bindings::blk_mq_rq_to_pdu(rq) }.cast::<RequestDataWrapper>();
258
259 // SAFETY: `pdu` is valid for read and write and is properly initialised.
260 unsafe { core::ptr::drop_in_place(pdu) };
261 }
262
263 const VTABLE: bindings::blk_mq_ops = bindings::blk_mq_ops {
264 queue_rq: Some(Self::queue_rq_callback),
265 queue_rqs: None,
266 commit_rqs: Some(Self::commit_rqs_callback),
267 get_budget: None,
268 put_budget: None,
269 set_rq_budget_token: None,
270 get_rq_budget_token: None,
271 timeout: None,
272 poll: if T::HAS_POLL {
273 Some(Self::poll_callback)
274 } else {
275 None
276 },
277 complete: Some(Self::complete_callback),
278 init_hctx: Some(Self::init_hctx_callback),
279 exit_hctx: Some(Self::exit_hctx_callback),
280 init_request: Some(Self::init_request_callback),
281 exit_request: Some(Self::exit_request_callback),
282 cleanup_rq: None,
283 busy: None,
284 map_queues: None,
285 #[cfg(CONFIG_BLK_DEBUG_FS)]
286 show_rq: None,
287 };
288
289 pub(crate) const fn build() -> &'static bindings::blk_mq_ops {
290 &Self::VTABLE
291 }
292}