Skip to content

Commit d343424

Browse files
committed
rust: gpuvm: add GpuVmCore::sm_unmap()
Add the entrypoint for unmapping ranges in the GPUVM, and provide callbacks and VA types for the implementation. Co-developed-by: Asahi Lina <lina+kernel@asahilina.net> Signed-off-by: Asahi Lina <lina+kernel@asahilina.net> Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com> Signed-off-by: Alice Ryhl <aliceryhl@google.com>
1 parent d590578 commit d343424

4 files changed

Lines changed: 306 additions & 5 deletions

File tree

rust/kernel/drm/gpuvm/mod.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use kernel::{
1818
bindings,
1919
drm,
2020
drm::gem::IntoGEMObject,
21+
error::to_result,
2122
prelude::*,
2223
sync::aref::{
2324
ARef,
@@ -28,6 +29,7 @@ use kernel::{
2829

2930
use core::{
3031
cell::UnsafeCell,
32+
marker::PhantomData,
3133
mem::{
3234
ManuallyDrop,
3335
MaybeUninit, //
@@ -43,12 +45,15 @@ use core::{
4345
}, //
4446
};
4547

46-
mod va;
47-
pub use self::va::*;
48+
mod sm_ops;
49+
pub use self::sm_ops::*;
4850

4951
mod vm_bo;
5052
pub use self::vm_bo::*;
5153

54+
mod va;
55+
pub use self::va::*;
56+
5257
/// A DRM GPU VA manager.
5358
///
5459
/// This object is refcounted, but the locations of mapped ranges may only be accessed or changed
@@ -104,8 +109,8 @@ impl<T: DriverGpuVm> GpuVm<T> {
104109
vm_bo_free: GpuVmBo::<T>::FREE_FN,
105110
vm_bo_validate: None,
106111
sm_step_map: None,
107-
sm_step_unmap: None,
108-
sm_step_remap: None,
112+
sm_step_unmap: Some(Self::sm_step_unmap),
113+
sm_step_remap: Some(Self::sm_step_remap),
109114
}
110115
}
111116

@@ -257,6 +262,23 @@ pub trait DriverGpuVm: Sized + Send {
257262

258263
/// Data stored with each [`struct drm_gpuvm_bo`](struct@GpuVmBo).
259264
type VmBoData;
265+
266+
/// The private data passed to callbacks.
267+
type SmContext<'ctx>;
268+
269+
/// Indicates that an existing mapping should be removed.
270+
fn sm_step_unmap<'op, 'ctx>(
271+
&mut self,
272+
op: OpUnmap<'op, Self>,
273+
context: &mut Self::SmContext<'ctx>,
274+
) -> Result<OpUnmapped<'op, Self>, Error>;
275+
276+
/// Indicates that an existing mapping should be split up.
277+
fn sm_step_remap<'op, 'ctx>(
278+
&mut self,
279+
op: OpRemap<'op, Self>,
280+
context: &mut Self::SmContext<'ctx>,
281+
) -> Result<OpRemapped<'op, Self>, Error>;
260282
}
261283

262284
/// The core of the DRM GPU VA manager.

rust/kernel/drm/gpuvm/sm_ops.rs

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
// SPDX-License-Identifier: GPL-2.0 OR MIT
2+
3+
use super::*;
4+
5+
/// The actual data that gets threaded through the callbacks.
6+
struct SmData<'a, 'ctx, T: DriverGpuVm> {
7+
gpuvm: &'a mut UniqueRefGpuVm<T>,
8+
user_context: &'a mut T::SmContext<'ctx>,
9+
}
10+
11+
/// Represents an `sm_step_unmap` operation that has not yet been completed.
12+
pub struct OpUnmap<'op, T: DriverGpuVm> {
13+
op: &'op bindings::drm_gpuva_op_unmap,
14+
// This ensures that 'op is invariant, so that `OpUnmap<'long, T>` does not
15+
// coerce to `OpUnmap<'short, T>`. This ensures that the user can't return the
16+
// wrong`OpUnmapped` value.
17+
_invariant: PhantomData<*mut &'op mut T>,
18+
}
19+
20+
impl<'op, T: DriverGpuVm> OpUnmap<'op, T> {
21+
/// Indicates whether this [`GpuVa`] is physically contiguous with the
22+
/// original mapping request.
23+
///
24+
/// Optionally, if `keep` is set, drivers may keep the actual page table
25+
/// mappings for this `drm_gpuva`, adding the missing page table entries
26+
/// only and update the `drm_gpuvm` accordingly.
27+
pub fn keep(&self) -> bool {
28+
self.op.keep
29+
}
30+
31+
/// The range being unmapped.
32+
pub fn va(&self) -> &GpuVa<T> {
33+
// SAFETY: This is a valid va. It's not the `kernel_alloc_node` because you can't unmap it,
34+
// and it's not sparse by the `GpuVm<T>` type invariants.
35+
unsafe { GpuVa::<T>::from_raw(self.op.va) }
36+
}
37+
38+
/// Remove the VA.
39+
pub fn remove(self) -> (OpUnmapped<'op, T>, GpuVaRemoved<T>) {
40+
// SAFETY: The op references a valid drm_gpuva in the GPUVM.
41+
unsafe { bindings::drm_gpuva_unmap(self.op) };
42+
// SAFETY: The va is no longer in the interval tree so we may unlink it.
43+
unsafe { bindings::drm_gpuva_unlink_defer(self.op.va) };
44+
45+
// SAFETY: We just removed this va from the `GpuVm<T>`.
46+
let va = unsafe { GpuVaRemoved::from_raw(self.op.va) };
47+
48+
(
49+
OpUnmapped {
50+
_invariant: self._invariant,
51+
},
52+
va,
53+
)
54+
}
55+
}
56+
57+
/// Represents a completed [`OpUnmap`] operation.
58+
pub struct OpUnmapped<'op, T> {
59+
_invariant: PhantomData<*mut &'op mut T>,
60+
}
61+
62+
/// Represents an `sm_step_remap` operation that has not yet been completed.
63+
pub struct OpRemap<'op, T: DriverGpuVm> {
64+
op: &'op bindings::drm_gpuva_op_remap,
65+
// This ensures that 'op is invariant, so that `OpRemap<'long, T>` does not
66+
// coerce to `OpRemap<'short, T>`. This ensures that the user can't return the
67+
// wrong`OpRemapped` value.
68+
_invariant: PhantomData<*mut &'op mut T>,
69+
}
70+
71+
impl<'op, T: DriverGpuVm> OpRemap<'op, T> {
72+
/// The preceding part of a split mapping.
73+
#[inline]
74+
pub fn prev(&self) -> Option<&OpRemapMapData> {
75+
// SAFETY: We checked for null, so the pointer must be valid.
76+
NonNull::new(self.op.prev).map(|ptr| unsafe { OpRemapMapData::from_raw(ptr) })
77+
}
78+
79+
/// The subsequent part of a split mapping.
80+
#[inline]
81+
pub fn next(&self) -> Option<&OpRemapMapData> {
82+
// SAFETY: We checked for null, so the pointer must be valid.
83+
NonNull::new(self.op.next).map(|ptr| unsafe { OpRemapMapData::from_raw(ptr) })
84+
}
85+
86+
/// Indicates whether the `drm_gpuva` being removed is physically contiguous with the original
87+
/// mapping request.
88+
///
89+
/// Optionally, if `keep` is set, drivers may keep the actual page table mappings for this
90+
/// `drm_gpuva`, adding the missing page table entries only and update the `drm_gpuvm`
91+
/// accordingly.
92+
#[inline]
93+
pub fn keep(&self) -> bool {
94+
// SAFETY: The unmap pointer is always valid.
95+
unsafe { (*self.op.unmap).keep }
96+
}
97+
98+
/// The range being unmapped.
99+
#[inline]
100+
pub fn va_to_unmap(&self) -> &GpuVa<T> {
101+
// SAFETY: This is a valid va. It's not the `kernel_alloc_node` because you can't unmap it,
102+
// and it's not sparse by the `GpuVm<T>` type invariants.
103+
unsafe { GpuVa::<T>::from_raw((*self.op.unmap).va) }
104+
}
105+
106+
/// The [`drm_gem_object`](DriverGpuVm::Object) whose VA is being remapped.
107+
#[inline]
108+
pub fn obj(&self) -> &T::Object {
109+
self.va_to_unmap().obj()
110+
}
111+
112+
/// The [`GpuVmBo`] that is being remapped.
113+
#[inline]
114+
pub fn vm_bo(&self) -> &GpuVmBo<T> {
115+
self.va_to_unmap().vm_bo()
116+
}
117+
118+
/// Update the GPUVM to perform the remapping.
119+
pub fn remap(
120+
self,
121+
va_alloc: [GpuVaAlloc<T>; 2],
122+
prev_data: impl PinInit<T::VaData>,
123+
next_data: impl PinInit<T::VaData>,
124+
) -> (OpRemapped<'op, T>, OpRemapRet<T>) {
125+
let [va1, va2] = va_alloc;
126+
127+
let mut unused_va = None;
128+
let mut prev_ptr = ptr::null_mut();
129+
let mut next_ptr = ptr::null_mut();
130+
if self.prev().is_some() {
131+
prev_ptr = va1.prepare(prev_data);
132+
} else {
133+
unused_va = Some(va1);
134+
}
135+
if self.next().is_some() {
136+
next_ptr = va2.prepare(next_data);
137+
} else {
138+
unused_va = Some(va2);
139+
}
140+
141+
// SAFETY: the pointers are non-null when required
142+
unsafe { bindings::drm_gpuva_remap(prev_ptr, next_ptr, self.op) };
143+
144+
let gpuva_guard = self.vm_bo().lock_gpuva();
145+
if !prev_ptr.is_null() {
146+
// SAFETY: The prev_ptr is a valid drm_gpuva prepared for insertion. The vm_bo is still
147+
// valid as the not-yet-unlinked gpuva holds a refcount on the vm_bo.
148+
unsafe { bindings::drm_gpuva_link(prev_ptr, self.vm_bo().as_raw()) };
149+
}
150+
if !next_ptr.is_null() {
151+
// SAFETY: The next_ptr is a valid drm_gpuva prepared for insertion. The vm_bo is still
152+
// valid as the not-yet-unlinked gpuva holds a refcount on the vm_bo.
153+
unsafe { bindings::drm_gpuva_link(next_ptr, self.vm_bo().as_raw()) };
154+
}
155+
drop(gpuva_guard);
156+
157+
// SAFETY: The va is no longer in the interval tree so we may unlink it.
158+
unsafe { bindings::drm_gpuva_unlink_defer((*self.op.unmap).va) };
159+
160+
(
161+
OpRemapped {
162+
_invariant: self._invariant,
163+
},
164+
OpRemapRet {
165+
// SAFETY: We just removed this va from the `GpuVm<T>`.
166+
unmapped_va: unsafe { GpuVaRemoved::from_raw((*self.op.unmap).va) },
167+
unused_va,
168+
},
169+
)
170+
}
171+
}
172+
173+
/// Part of an [`OpRemap`] that represents a new mapping.
174+
#[repr(transparent)]
175+
pub struct OpRemapMapData(bindings::drm_gpuva_op_map);
176+
177+
impl OpRemapMapData {
178+
/// # Safety
179+
/// Must reference a valid `drm_gpuva_op_map` for duration of `'a`.
180+
unsafe fn from_raw<'a>(ptr: NonNull<bindings::drm_gpuva_op_map>) -> &'a Self {
181+
// SAFETY: ok per safety requirements
182+
unsafe { ptr.cast().as_ref() }
183+
}
184+
185+
/// The base address of the new mapping.
186+
pub fn addr(&self) -> u64 {
187+
self.0.va.addr
188+
}
189+
190+
/// The length of the new mapping.
191+
pub fn length(&self) -> u64 {
192+
self.0.va.range
193+
}
194+
195+
/// The offset within the [`drm_gem_object`](DriverGpuVm::Object).
196+
pub fn gem_offset(&self) -> u64 {
197+
self.0.gem.offset
198+
}
199+
}
200+
201+
/// Struct containing objects removed or not used by [`OpRemap::remap`].
202+
pub struct OpRemapRet<T: DriverGpuVm> {
203+
/// The `drm_gpuva` that was removed.
204+
pub unmapped_va: GpuVaRemoved<T>,
205+
/// If the remap did not split the region into two pieces, then the unused `drm_gpuva` is
206+
/// returned here.
207+
pub unused_va: Option<GpuVaAlloc<T>>,
208+
}
209+
210+
/// Represents a completed [`OpRemap`] operation.
211+
pub struct OpRemapped<'op, T> {
212+
_invariant: PhantomData<*mut &'op mut T>,
213+
}
214+
215+
impl<T: DriverGpuVm> UniqueRefGpuVm<T> {
216+
/// Remove any mappings in the given region.
217+
///
218+
/// Internally calls [`DriverGpuVm::sm_step_unmap`] for ranges entirely contained within the
219+
/// given range, and [`DriverGpuVm::sm_step_remap`] for ranges that overlap with the range.
220+
#[inline]
221+
pub fn sm_unmap(&mut self, addr: u64, length: u64, context: &mut T::SmContext<'_>) -> Result {
222+
let gpuvm = self.as_raw();
223+
let mut p = SmData {
224+
gpuvm: self,
225+
user_context: context,
226+
};
227+
// SAFETY:
228+
// * raw_request() creates a valid request.
229+
// * The private data is valid to be interpreted as SmData.
230+
to_result(unsafe { bindings::drm_gpuvm_sm_unmap(gpuvm, (&raw mut p).cast(), addr, length) })
231+
}
232+
}
233+
234+
impl<T: DriverGpuVm> GpuVm<T> {
235+
/// # Safety
236+
/// Must be called from `sm_unmap` with a pointer to `SmData`.
237+
pub(super) unsafe extern "C" fn sm_step_unmap(
238+
op: *mut bindings::drm_gpuva_op,
239+
p: *mut c_void,
240+
) -> c_int {
241+
// SAFETY: The caller provides a pointer to `SmData`.
242+
let p = unsafe { &mut *p.cast::<SmData<'_, '_, T>>() };
243+
let op = OpUnmap {
244+
// SAFETY: sm_step_unmap is called with an unmap operation.
245+
op: unsafe { &(*op).__bindgen_anon_1.unmap },
246+
_invariant: PhantomData,
247+
};
248+
match p.gpuvm.data().sm_step_unmap(op, p.user_context) {
249+
Ok(OpUnmapped { .. }) => 0,
250+
Err(err) => err.to_errno(),
251+
}
252+
}
253+
254+
/// # Safety
255+
/// Must be called from `sm_unmap` with a pointer to `SmData`.
256+
pub(super) unsafe extern "C" fn sm_step_remap(
257+
op: *mut bindings::drm_gpuva_op,
258+
p: *mut c_void,
259+
) -> c_int {
260+
// SAFETY: The caller provides a pointer to `SmData`.
261+
let p = unsafe { &mut *p.cast::<SmData<'_, '_, T>>() };
262+
let op = OpRemap {
263+
// SAFETY: sm_step_remap is called with a remap operation.
264+
op: unsafe { &(*op).__bindgen_anon_1.remap },
265+
_invariant: PhantomData,
266+
};
267+
match p.gpuvm.data().sm_step_remap(op, p.user_context) {
268+
Ok(OpRemapped { .. }) => 0,
269+
Err(err) => err.to_errno(),
270+
}
271+
}
272+
}

rust/kernel/drm/gpuvm/va.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// SPDX-License-Identifier: GPL-2.0 OR MIT
22

3-
#![expect(dead_code)]
43
use super::*;
54

65
/// Represents that a range of a GEM object is mapped in this [`GpuVm`] instance.

rust/kernel/drm/gpuvm/vm_bo.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ impl<T: DriverGpuVm> GpuVmBo<T> {
144144
pub fn data(&self) -> &T::VmBoData {
145145
&self.data
146146
}
147+
148+
pub(super) fn lock_gpuva(&self) -> crate::sync::MutexGuard<'_, ()> {
149+
// SAFETY: The GEM object is valid.
150+
let ptr = unsafe { &raw mut (*self.obj().as_raw()).gpuva.lock };
151+
// SAFETY: The GEM object is valid, so the mutex is properly initialized.
152+
let mutex = unsafe { crate::sync::Mutex::from_raw(ptr) };
153+
mutex.lock()
154+
}
147155
}
148156

149157
/// A pre-allocated [`GpuVmBo`] object.

0 commit comments

Comments
 (0)