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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
|
// SPDX-License-Identifier: GPL-2.0 OR MIT
use super::*;
/// The actual data that gets threaded through the callbacks.
struct SmData<'a, 'ctx, T: DriverGpuVm> {
gpuvm: &'a mut UniqueRefGpuVm<T>,
user_context: &'a mut T::SmContext<'ctx>,
}
/// Adds an extra field to `SmData` for `sm_map()` callbacks.
///
/// # Invariants
///
/// `self.vm_bo.gpuvm() == self.sm_data.gpuvm`.
#[repr(C)]
struct SmMapData<'a, 'ctx, T: DriverGpuVm> {
sm_data: SmData<'a, 'ctx, T>,
vm_bo: &'a GpuVmBo<T>,
}
/// The argument for [`UniqueRefGpuVm::sm_map`].
pub struct OpMapRequest<'a, 'ctx, T: DriverGpuVm> {
/// Address in GPU virtual address space.
pub addr: u64,
/// Length of mapping to create.
pub range: u64,
/// Offset in GEM object.
pub gem_offset: u64,
/// The GEM object to map.
pub vm_bo: &'a GpuVmBo<T>,
/// The user-provided context type.
pub context: &'a mut T::SmContext<'ctx>,
}
impl<'a, 'ctx, T: DriverGpuVm> OpMapRequest<'a, 'ctx, T> {
fn raw_request(&self) -> bindings::drm_gpuvm_map_req {
bindings::drm_gpuvm_map_req {
map: bindings::drm_gpuva_op_map {
va: bindings::drm_gpuva_op_map__bindgen_ty_1 {
addr: self.addr,
range: self.range,
},
gem: bindings::drm_gpuva_op_map__bindgen_ty_2 {
offset: self.gem_offset,
obj: self.vm_bo.obj().as_raw(),
},
},
}
}
}
/// Represents an `sm_step_map` operation that has not yet been completed.
pub struct OpMap<'op, T: DriverGpuVm> {
op: &'op bindings::drm_gpuva_op_map,
// Since these abstractions are designed for immediate mode, the VM BO needs to be
// pre-allocated, so we always have it available when we reach this point.
vm_bo: &'op GpuVmBo<T>,
// This ensures that 'op is invariant, so that `OpMap<'long, T>` does not
// coerce to `OpMap<'short, T>`. This ensures that the user can't return
// the wrong `OpMapped` value.
_invariant: PhantomData<*mut &'op mut T>,
}
impl<'op, T: DriverGpuVm> OpMap<'op, T> {
/// The base address of the new mapping.
pub fn addr(&self) -> u64 {
self.op.va.addr
}
/// The length of the new mapping.
pub fn length(&self) -> u64 {
self.op.va.range
}
/// The offset within the [`drm_gem_object`](DriverGpuVm::Object).
pub fn gem_offset(&self) -> u64 {
self.op.gem.offset
}
/// The [`drm_gem_object`](DriverGpuVm::Object) to map.
pub fn obj(&self) -> &T::Object {
// SAFETY: The `obj` pointer is guaranteed to be valid.
unsafe { <T::Object as IntoGEMObject>::from_raw(self.op.gem.obj) }
}
/// The [`GpuVmBo`] that the new VA will be associated with.
pub fn vm_bo(&self) -> &GpuVmBo<T> {
self.vm_bo
}
/// Use the pre-allocated VA to carry out this map operation.
pub fn insert(self, va: GpuVaAlloc<T>, va_data: impl PinInit<T::VaData>) -> OpMapped<'op, T> {
let va = va.prepare(va_data);
// SAFETY: By the type invariants we may access the interval tree.
unsafe { bindings::drm_gpuva_map(self.vm_bo.gpuvm().as_raw(), va, self.op) };
let _gpuva_guard = self.vm_bo().lock_gpuva();
// SAFETY: The va is prepared for insertion, and we hold the GEM lock.
unsafe { bindings::drm_gpuva_link(va, self.vm_bo.as_raw()) };
OpMapped {
_invariant: self._invariant,
}
}
}
/// Represents a completed [`OpMap`] operation.
pub struct OpMapped<'op, T> {
_invariant: PhantomData<*mut &'op mut T>,
}
/// Represents an `sm_step_unmap` operation that has not yet been completed.
pub struct OpUnmap<'op, T: DriverGpuVm> {
op: &'op bindings::drm_gpuva_op_unmap,
// This ensures that 'op is invariant, so that `OpUnmap<'long, T>` does not
// coerce to `OpUnmap<'short, T>`. This ensures that the user can't return the
// wrong`OpUnmapped` value.
_invariant: PhantomData<*mut &'op mut T>,
}
impl<'op, T: DriverGpuVm> OpUnmap<'op, T> {
/// Indicates whether this [`GpuVa`] is physically contiguous with the
/// original mapping request.
///
/// Optionally, if `keep` is set, drivers may keep the actual page table
/// mappings for this `drm_gpuva`, adding the missing page table entries
/// only and update the `drm_gpuvm` accordingly.
pub fn keep(&self) -> bool {
self.op.keep
}
/// The range being unmapped.
pub fn va(&self) -> &GpuVa<T> {
// SAFETY: This is a valid va. It's not the `kernel_alloc_node` because you can't unmap it,
// and it's not sparse by the `GpuVm<T>` type invariants.
unsafe { GpuVa::<T>::from_raw(self.op.va) }
}
/// Remove the VA.
pub fn remove(self) -> (OpUnmapped<'op, T>, GpuVaRemoved<T>) {
// SAFETY: The op references a valid drm_gpuva in the GPUVM.
unsafe { bindings::drm_gpuva_unmap(self.op) };
// SAFETY: The va is no longer in the interval tree so we may unlink it.
unsafe { bindings::drm_gpuva_unlink_defer(self.op.va) };
// SAFETY: We just removed this va from the `GpuVm<T>`.
let va = unsafe { GpuVaRemoved::from_raw(self.op.va) };
(
OpUnmapped {
_invariant: self._invariant,
},
va,
)
}
}
/// Represents a completed [`OpUnmap`] operation.
pub struct OpUnmapped<'op, T> {
_invariant: PhantomData<*mut &'op mut T>,
}
/// Represents an `sm_step_remap` operation that has not yet been completed.
pub struct OpRemap<'op, T: DriverGpuVm> {
op: &'op bindings::drm_gpuva_op_remap,
// This ensures that 'op is invariant, so that `OpRemap<'long, T>` does not
// coerce to `OpRemap<'short, T>`. This ensures that the user can't return the
// wrong`OpRemapped` value.
_invariant: PhantomData<*mut &'op mut T>,
}
impl<'op, T: DriverGpuVm> OpRemap<'op, T> {
/// The preceding part of a split mapping.
#[inline]
pub fn prev(&self) -> Option<&OpRemapMapData> {
// SAFETY: We checked for null, so the pointer must be valid.
NonNull::new(self.op.prev).map(|ptr| unsafe { OpRemapMapData::from_raw(ptr) })
}
/// The subsequent part of a split mapping.
#[inline]
pub fn next(&self) -> Option<&OpRemapMapData> {
// SAFETY: We checked for null, so the pointer must be valid.
NonNull::new(self.op.next).map(|ptr| unsafe { OpRemapMapData::from_raw(ptr) })
}
/// Indicates whether the `drm_gpuva` being removed is physically contiguous with the original
/// mapping request.
///
/// Optionally, if `keep` is set, drivers may keep the actual page table mappings for this
/// `drm_gpuva`, adding the missing page table entries only and update the `drm_gpuvm`
/// accordingly.
#[inline]
pub fn keep(&self) -> bool {
// SAFETY: The unmap pointer is always valid.
unsafe { (*self.op.unmap).keep }
}
/// The range being unmapped.
#[inline]
pub fn va_to_unmap(&self) -> &GpuVa<T> {
// SAFETY: This is a valid va. It's not the `kernel_alloc_node` because you can't unmap it,
// and it's not sparse by the `GpuVm<T>` type invariants.
unsafe { GpuVa::<T>::from_raw((*self.op.unmap).va) }
}
/// The [`drm_gem_object`](DriverGpuVm::Object) whose VA is being remapped.
#[inline]
pub fn obj(&self) -> &T::Object {
self.va_to_unmap().obj()
}
/// The [`GpuVmBo`] that is being remapped.
#[inline]
pub fn vm_bo(&self) -> &GpuVmBo<T> {
self.va_to_unmap().vm_bo()
}
/// Update the GPUVM to perform the remapping.
pub fn remap(
self,
va_alloc: [GpuVaAlloc<T>; 2],
prev_data: impl PinInit<T::VaData>,
next_data: impl PinInit<T::VaData>,
) -> (OpRemapped<'op, T>, OpRemapRet<T>) {
let [va1, va2] = va_alloc;
let mut unused_va = None;
let mut prev_ptr = ptr::null_mut();
let mut next_ptr = ptr::null_mut();
if self.prev().is_some() {
prev_ptr = va1.prepare(prev_data);
} else {
unused_va = Some(va1);
}
if self.next().is_some() {
next_ptr = va2.prepare(next_data);
} else {
unused_va = Some(va2);
}
// SAFETY: the pointers are non-null when required
unsafe { bindings::drm_gpuva_remap(prev_ptr, next_ptr, self.op) };
let gpuva_guard = self.vm_bo().lock_gpuva();
if !prev_ptr.is_null() {
// SAFETY: The prev_ptr is a valid drm_gpuva prepared for insertion. The vm_bo is still
// valid as the not-yet-unlinked gpuva holds a refcount on the vm_bo.
unsafe { bindings::drm_gpuva_link(prev_ptr, self.vm_bo().as_raw()) };
}
if !next_ptr.is_null() {
// SAFETY: The next_ptr is a valid drm_gpuva prepared for insertion. The vm_bo is still
// valid as the not-yet-unlinked gpuva holds a refcount on the vm_bo.
unsafe { bindings::drm_gpuva_link(next_ptr, self.vm_bo().as_raw()) };
}
drop(gpuva_guard);
// SAFETY: The va is no longer in the interval tree so we may unlink it.
unsafe { bindings::drm_gpuva_unlink_defer((*self.op.unmap).va) };
(
OpRemapped {
_invariant: self._invariant,
},
OpRemapRet {
// SAFETY: We just removed this va from the `GpuVm<T>`.
unmapped_va: unsafe { GpuVaRemoved::from_raw((*self.op.unmap).va) },
unused_va,
},
)
}
}
/// Part of an [`OpRemap`] that represents a new mapping.
#[repr(transparent)]
pub struct OpRemapMapData(bindings::drm_gpuva_op_map);
impl OpRemapMapData {
/// # Safety
/// Must reference a valid `drm_gpuva_op_map` for duration of `'a`.
unsafe fn from_raw<'a>(ptr: NonNull<bindings::drm_gpuva_op_map>) -> &'a Self {
// SAFETY: ok per safety requirements
unsafe { ptr.cast().as_ref() }
}
/// The base address of the new mapping.
pub fn addr(&self) -> u64 {
self.0.va.addr
}
/// The length of the new mapping.
pub fn length(&self) -> u64 {
self.0.va.range
}
/// The offset within the [`drm_gem_object`](DriverGpuVm::Object).
pub fn gem_offset(&self) -> u64 {
self.0.gem.offset
}
}
/// Struct containing objects removed or not used by [`OpRemap::remap`].
pub struct OpRemapRet<T: DriverGpuVm> {
/// The `drm_gpuva` that was removed.
pub unmapped_va: GpuVaRemoved<T>,
/// If the remap did not split the region into two pieces, then the unused `drm_gpuva` is
/// returned here.
pub unused_va: Option<GpuVaAlloc<T>>,
}
/// Represents a completed [`OpRemap`] operation.
pub struct OpRemapped<'op, T> {
_invariant: PhantomData<*mut &'op mut T>,
}
impl<T: DriverGpuVm> UniqueRefGpuVm<T> {
/// Create a mapping, removing or remapping anything that overlaps.
///
/// Internally calls the [`DriverGpuVm`] callbacks similar to [`Self::sm_unmap`], except that
/// the [`DriverGpuVm::sm_step_map`] is called once to create the requested mapping.
#[inline]
pub fn sm_map(&mut self, req: OpMapRequest<'_, '_, T>) -> Result {
if req.vm_bo.gpuvm() != &**self {
return Err(EINVAL);
}
let gpuvm = self.as_raw();
let raw_req = req.raw_request();
// INVARIANT: Checked above that `vm_bo.gpuvm() == self`.
let mut p = SmMapData {
sm_data: SmData {
gpuvm: self,
user_context: req.context,
},
vm_bo: req.vm_bo,
};
// SAFETY:
// * raw_request() creates a valid request.
// * The private data is valid to be interpreted as both SmData and SmMapData since the
// first field of SmMapData is SmData.
to_result(unsafe {
bindings::drm_gpuvm_sm_map(gpuvm, (&raw mut p).cast(), &raw const raw_req)
})
}
/// Remove any mappings in the given region.
///
/// Internally calls [`DriverGpuVm::sm_step_unmap`] for ranges entirely contained within the
/// given range, and [`DriverGpuVm::sm_step_remap`] for ranges that overlap with the range.
#[inline]
pub fn sm_unmap(&mut self, addr: u64, length: u64, context: &mut T::SmContext<'_>) -> Result {
let gpuvm = self.as_raw();
let mut p = SmData {
gpuvm: self,
user_context: context,
};
// SAFETY:
// * raw_request() creates a valid request.
// * The private data is a valid SmData.
to_result(unsafe { bindings::drm_gpuvm_sm_unmap(gpuvm, (&raw mut p).cast(), addr, length) })
}
}
impl<T: DriverGpuVm> GpuVm<T> {
/// # Safety
/// Must be called from `sm_map` with a pointer to `SmMapData`.
pub(super) unsafe extern "C" fn sm_step_map(
op: *mut bindings::drm_gpuva_op,
p: *mut c_void,
) -> c_int {
// SAFETY: If we reach `sm_step_map` then we were called from `sm_map` which always passes
// an `SmMapData` as private data.
let p = unsafe { &mut *p.cast::<SmMapData<'_, '_, T>>() };
let op = OpMap {
// SAFETY: sm_step_map is called with a map operation.
op: unsafe { &(*op).__bindgen_anon_1.map },
vm_bo: p.vm_bo,
_invariant: PhantomData,
};
match p
.sm_data
.gpuvm
.data()
.sm_step_map(op, p.sm_data.user_context)
{
Ok(OpMapped { .. }) => 0,
Err(err) => err.to_errno(),
}
}
/// # Safety
/// Must be called from `sm_map` or `sm_unmap` with a pointer to `SmMapData` or `SmData`.
pub(super) unsafe extern "C" fn sm_step_unmap(
op: *mut bindings::drm_gpuva_op,
p: *mut c_void,
) -> c_int {
// SAFETY: The caller provides a pointer that can be treated as `SmData`.
let p = unsafe { &mut *p.cast::<SmData<'_, '_, T>>() };
let op = OpUnmap {
// SAFETY: sm_step_unmap is called with an unmap operation.
op: unsafe { &(*op).__bindgen_anon_1.unmap },
_invariant: PhantomData,
};
match p.gpuvm.data().sm_step_unmap(op, p.user_context) {
Ok(OpUnmapped { .. }) => 0,
Err(err) => err.to_errno(),
}
}
/// # Safety
/// Must be called from `sm_map` or `sm_unmap` with a pointer to `SmMapData` or `SmData`.
pub(super) unsafe extern "C" fn sm_step_remap(
op: *mut bindings::drm_gpuva_op,
p: *mut c_void,
) -> c_int {
// SAFETY: The caller provides a pointer that can be treated as `SmData`.
let p = unsafe { &mut *p.cast::<SmData<'_, '_, T>>() };
let op = OpRemap {
// SAFETY: sm_step_remap is called with a remap operation.
op: unsafe { &(*op).__bindgen_anon_1.remap },
_invariant: PhantomData,
};
match p.gpuvm.data().sm_step_remap(op, p.user_context) {
Ok(OpRemapped { .. }) => 0,
Err(err) => err.to_errno(),
}
}
}
|