|
| 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 | +} |
0 commit comments