1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
|
// SPDX-License-Identifier: GPL-2.0 OR MIT
#![cfg(CONFIG_RUST_DRM_GPUVM)]
//! DRM GPUVM in immediate mode
//!
//! Rust abstractions for using GPUVM in immediate mode. This is when the GPUVM state is updated
//! during `run_job()`, i.e., in the DMA fence signalling critical path, to ensure that the GPUVM
//! and the GPU's virtual address space has the same state at all times.
//!
//! C header: [`include/drm/drm_gpuvm.h`](srctree/include/drm/drm_gpuvm.h)
use kernel::{
alloc::{
AllocError,
Flags as AllocFlags, //
},
bindings,
drm,
drm::gem::IntoGEMObject,
error::to_result,
prelude::*,
sync::aref::{
ARef,
AlwaysRefCounted, //
},
types::Opaque, //
};
use core::{
cell::UnsafeCell,
marker::PhantomData,
mem::{
ManuallyDrop,
MaybeUninit, //
},
ops::{
Deref,
DerefMut,
Range, //
},
ptr::{
self,
NonNull, //
}, //
};
mod sm_ops;
pub use self::sm_ops::*;
mod vm_bo;
pub use self::vm_bo::*;
mod va;
pub use self::va::*;
/// A DRM GPU VA manager.
///
/// This object is refcounted, but the locations of mapped ranges may only be accessed or changed
/// via the special unique handle [`UniqueRefGpuVm`].
///
/// # Invariants
///
/// * Stored in an allocation managed by the refcount in `self.vm`.
/// * Access to `data` and the gpuvm interval tree is controlled via the [`UniqueRefGpuVm`] type.
/// * Does not contain any sparse [`GpuVa<T>`] instances.
#[pin_data]
pub struct GpuVm<T: DriverGpuVm> {
#[pin]
vm: Opaque<bindings::drm_gpuvm>,
/// Accessed only through the [`UniqueRefGpuVm`] reference.
data: UnsafeCell<T>,
}
// SAFETY: The GPUVM api does not assume that it is tied to a specific thread. The destructor will
// drop the `data` field, which is okay because it is guaranteed `Send` by the `DriverGpuVm` trait.
unsafe impl<T: DriverGpuVm> Send for GpuVm<T> {}
// SAFETY: The GPUVM api is designed to allow &self methods to be called in parallel.
unsafe impl<T: DriverGpuVm> Sync for GpuVm<T> {}
// SAFETY: By type invariants, the allocation is managed by the refcount in `self.vm`.
unsafe impl<T: DriverGpuVm> AlwaysRefCounted for GpuVm<T> {
fn inc_ref(&self) {
// SAFETY: By type invariants, the allocation is managed by the refcount in `self.vm`.
unsafe { bindings::drm_gpuvm_get(self.vm.get()) };
}
unsafe fn dec_ref(obj: NonNull<Self>) {
// SAFETY: By type invariants, the allocation is managed by the refcount in `self.vm`.
unsafe { bindings::drm_gpuvm_put((*obj.as_ptr()).vm.get()) };
}
}
impl<T: DriverGpuVm> PartialEq for GpuVm<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
core::ptr::eq(self.as_raw(), other.as_raw())
}
}
impl<T: DriverGpuVm> Eq for GpuVm<T> {}
impl<T: DriverGpuVm> GpuVm<T> {
const fn vtable() -> &'static bindings::drm_gpuvm_ops {
&bindings::drm_gpuvm_ops {
vm_free: Some(Self::vm_free),
op_alloc: None,
op_free: None,
vm_bo_alloc: GpuVmBo::<T>::ALLOC_FN,
vm_bo_free: GpuVmBo::<T>::FREE_FN,
vm_bo_validate: None,
sm_step_map: Some(Self::sm_step_map),
sm_step_unmap: Some(Self::sm_step_unmap),
sm_step_remap: Some(Self::sm_step_remap),
}
}
/// Creates a GPUVM instance.
#[expect(clippy::new_ret_no_self)]
pub fn new<E>(
name: &'static CStr,
dev: &drm::Device<T::Driver>,
r_obj: &T::Object,
range: Range<u64>,
reserve_range: Range<u64>,
data: T,
) -> Result<UniqueRefGpuVm<T>, E>
where
E: From<AllocError>,
E: From<core::convert::Infallible>,
{
let obj = KBox::try_pin_init::<E>(
try_pin_init!(Self {
data: UnsafeCell::new(data),
vm <- Opaque::ffi_init(|vm| {
// SAFETY: These arguments are valid. `vm` is valid until refcount drops to
// zero. The `vm` is zeroed before calling this method by `__GFP_ZERO` flag
// below.
unsafe {
bindings::drm_gpuvm_init(
vm,
name.as_char_ptr(),
bindings::drm_gpuvm_flags_DRM_GPUVM_IMMEDIATE_MODE
| bindings::drm_gpuvm_flags_DRM_GPUVM_RESV_PROTECTED,
dev.as_raw(),
r_obj.as_raw(),
range.start,
range.end - range.start,
reserve_range.start,
reserve_range.end - reserve_range.start,
const { Self::vtable() },
)
}
}),
}? E),
GFP_KERNEL | __GFP_ZERO,
)?;
// SAFETY: This transfers the initial refcount to the ARef.
let aref = unsafe {
ARef::from_raw(NonNull::new_unchecked(KBox::into_raw(
Pin::into_inner_unchecked(obj),
)))
};
// INVARIANT: This reference is unique.
Ok(UniqueRefGpuVm(aref))
}
/// Access this [`GpuVm`] from a raw pointer.
///
/// # Safety
///
/// The pointer must reference the `struct drm_gpuvm` in a valid [`GpuVm<T>`] that remains
/// valid for at least `'a`.
#[inline]
pub unsafe fn from_raw<'a>(ptr: *mut bindings::drm_gpuvm) -> &'a Self {
// SAFETY: Caller passes a pointer to the `drm_gpuvm` in a `GpuVm<T>`. Caller ensures the
// pointer is valid for 'a.
unsafe { &*kernel::container_of!(Opaque::cast_from(ptr), Self, vm) }
}
/// Returns a raw pointer to the embedded `struct drm_gpuvm`.
#[inline]
pub fn as_raw(&self) -> *mut bindings::drm_gpuvm {
self.vm.get()
}
/// The start of the VA space.
#[inline]
pub fn va_start(&self) -> u64 {
// SAFETY: The `mm_start` field is immutable.
unsafe { (*self.as_raw()).mm_start }
}
/// The length of the GPU's virtual address space.
#[inline]
pub fn va_length(&self) -> u64 {
// SAFETY: The `mm_range` field is immutable.
unsafe { (*self.as_raw()).mm_range }
}
/// Returns the range of the GPU virtual address space.
#[inline]
pub fn va_range(&self) -> Range<u64> {
let start = self.va_start();
// OVERFLOW: This reconstructs the Range<u64> passed to the constructor, so it won't fail.
let end = start + self.va_length();
Range { start, end }
}
/// Get or create the [`GpuVmBo`] for this gem object.
#[inline]
pub fn obtain(
&self,
obj: &T::Object,
data: impl PinInit<T::VmBoData>,
) -> Result<ARef<GpuVmBo<T>>, AllocError> {
Ok(GpuVmBoAlloc::new(self, obj, data)?.obtain())
}
/// Clean up buffer objects that are no longer used.
#[inline]
pub fn deferred_cleanup(&self) {
// SAFETY: This GPUVM uses immediate mode.
unsafe { bindings::drm_gpuvm_bo_deferred_cleanup(self.as_raw()) }
}
/// Check if this GEM object is an external object for this GPUVM.
#[inline]
pub fn is_extobj(&self, obj: &T::Object) -> bool {
// SAFETY: We may call this with any GPUVM and GEM object.
unsafe { bindings::drm_gpuvm_is_extobj(self.as_raw(), obj.as_raw()) }
}
/// Free this GPUVM.
///
/// # Safety
///
/// Called when refcount hits zero.
unsafe extern "C" fn vm_free(me: *mut bindings::drm_gpuvm) {
// SAFETY: Caller passes a pointer to the `drm_gpuvm` in a `GpuVm<T>`.
let me = unsafe { kernel::container_of!(Opaque::cast_from(me), Self, vm).cast_mut() };
// SAFETY: By type invariants we can free it when refcount hits zero.
drop(unsafe { KBox::from_raw(me) })
}
#[inline]
fn raw_resv(&self) -> *mut bindings::dma_resv {
// SAFETY: `r_obj` is immutable and valid for duration of GPUVM.
unsafe { (*(*self.as_raw()).r_obj).resv }
}
}
/// The manager for a GPUVM.
pub trait DriverGpuVm: Sized + Send {
/// Parent `Driver` for this object.
type Driver: drm::Driver<Object = Self::Object>;
/// The kind of GEM object stored in this GPUVM.
type Object: IntoGEMObject;
/// Data stored with each [`struct drm_gpuva`](struct@GpuVa).
type VaData;
/// Data stored with each [`struct drm_gpuvm_bo`](struct@GpuVmBo).
type VmBoData;
/// The private data passed to callbacks.
type SmContext<'ctx>;
/// Indicates that a new mapping should be created.
fn sm_step_map<'op, 'ctx>(
&mut self,
op: OpMap<'op, Self>,
context: &mut Self::SmContext<'ctx>,
) -> Result<OpMapped<'op, Self>, Error>;
/// Indicates that an existing mapping should be removed.
fn sm_step_unmap<'op, 'ctx>(
&mut self,
op: OpUnmap<'op, Self>,
context: &mut Self::SmContext<'ctx>,
) -> Result<OpUnmapped<'op, Self>, Error>;
/// Indicates that an existing mapping should be split up.
fn sm_step_remap<'op, 'ctx>(
&mut self,
op: OpRemap<'op, Self>,
context: &mut Self::SmContext<'ctx>,
) -> Result<OpRemapped<'op, Self>, Error>;
}
/// The core of the DRM GPU VA manager.
///
/// This object is a unique reference to the VM that can access the interval tree and the Rust
/// `data` field.
///
/// # Invariants
///
/// Each `GpuVm` instance has at most one `UniqueRefGpuVm` reference.
pub struct UniqueRefGpuVm<T: DriverGpuVm>(ARef<GpuVm<T>>);
// SAFETY: The GPUVM api is designed to allow &self methods to be called in parallel, and
// concurrent access to `data` is safe due to the `T: Sync` requirement.
unsafe impl<T: DriverGpuVm + Sync> Sync for UniqueRefGpuVm<T> {}
impl<T: DriverGpuVm> UniqueRefGpuVm<T> {
/// Access the data owned by this `UniqueRefGpuVm` immutably.
#[inline]
pub fn data_ref(&self) -> &T {
// SAFETY: By the type invariants we may access `data`.
unsafe { &*self.0.data.get() }
}
/// Access the data owned by this `UniqueRefGpuVm` mutably.
#[inline]
pub fn data(&mut self) -> &mut T {
// SAFETY: By the type invariants we may access `data`.
unsafe { &mut *self.0.data.get() }
}
}
impl<T: DriverGpuVm> Deref for UniqueRefGpuVm<T> {
type Target = GpuVm<T>;
#[inline]
fn deref(&self) -> &GpuVm<T> {
&self.0
}
}
|