xref: /linux/drivers/gpu/drm/tyr/vm.rs (revision 570f7e331f5febb30f1384817463c7e42b65ca7d)
1 // SPDX-License-Identifier: GPL-2.0 or MIT
2 
3 //! GPU virtual memory management using the DRM GPUVM framework.
4 //!
5 //! This module manages GPU virtual address spaces, providing memory isolation and
6 //! the illusion of owning the entire virtual address (VA) range, similar to CPU virtual memory.
7 //! Each virtual memory (VM) area is backed by ARM64 LPAE Stage 1 page tables and can be
8 //! mapped into hardware address space (AS) slots for GPU execution.
9 
10 use core::marker::PhantomData;
11 use core::ops::Range;
12 
13 use kernel::{
14     device::{
15         Bound,
16         Device, //
17     },
18     drm::{
19         gem::BaseObject,
20         gpuvm::{
21             DriverGpuVm,
22             GpuVaAlloc,
23             GpuVm,
24             GpuVmBo,
25             OpMap,
26             OpMapRequest,
27             OpMapped,
28             OpRemap,
29             OpRemapped,
30             OpUnmap,
31             OpUnmapped,
32             UniqueRefGpuVm, //
33         }, //
34     },
35     fmt,
36     impl_flags,
37     io::PhysAddr,
38     iommu::pgtable::{
39         prot,
40         IoPageTable,
41         ARM64LPAES1, //
42     },
43     new_mutex,
44     prelude::*,
45     sizes::{
46         SZ_1G,
47         SZ_2M,
48         SZ_4K, //
49     },
50     sync::{
51         aref::ARef,
52         Arc,
53         ArcBorrow,
54         Mutex, //
55     },
56     uapi, //
57 };
58 
59 use crate::{
60     driver::{
61         TyrDrmDevice,
62         TyrDrmDriver, //
63     },
64     gem,
65     gem::Bo,
66     gpu::GpuInfo,
67     mmu::{
68         address_space::VmAsData,
69         Mmu, //
70     },
71     regs::gpu_control::MMU_FEATURES,
72 };
73 
74 impl_flags!(
75     /// Flags controlling virtual memory mapping behavior.
76     ///
77     /// These flags control access permissions and caching behavior for GPU virtual
78     /// memory mappings.
79     #[derive(Debug, Clone, Default, Copy, PartialEq, Eq)]
80     pub(crate) struct VmMapFlags(u32);
81 
82     /// Individual flags that can be combined in [`VmMapFlags`].
83     #[derive(Debug, Clone, Copy, PartialEq, Eq)]
84     pub(crate) enum VmFlag {
85         /// Map as read-only.
86         Readonly = uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_MAP_READONLY as u32,
87         /// Map as non-executable.
88         Noexec = uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC as u32,
89         /// Map as uncached.
90         Uncached = uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED as u32,
91     }
92 );
93 
94 impl VmMapFlags {
95     /// Convert the flags to `pgtable::prot`.
96     fn to_prot(self) -> u32 {
97         let mut prot = 0;
98 
99         if self.contains(VmFlag::Readonly) {
100             prot |= prot::READ;
101         } else {
102             prot |= prot::READ | prot::WRITE;
103         }
104 
105         if self.contains(VmFlag::Noexec) {
106             prot |= prot::NOEXEC;
107         }
108 
109         if !self.contains(VmFlag::Uncached) {
110             prot |= prot::CACHE;
111         }
112 
113         prot
114     }
115 }
116 
117 impl fmt::Display for VmMapFlags {
118     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119         let mut first = true;
120 
121         if self.contains(VmFlag::Readonly) {
122             write!(f, "READONLY")?;
123             first = false;
124         }
125         if self.contains(VmFlag::Noexec) {
126             if !first {
127                 write!(f, " | ")?;
128             }
129             write!(f, "NOEXEC")?;
130             first = false;
131         }
132 
133         if self.contains(VmFlag::Uncached) {
134             if !first {
135                 write!(f, " | ")?;
136             }
137             write!(f, "UNCACHED")?;
138         }
139 
140         Ok(())
141     }
142 }
143 
144 impl TryFrom<u32> for VmMapFlags {
145     type Error = Error;
146 
147     fn try_from(value: u32) -> Result<Self, Self::Error> {
148         let valid = VmFlag::Readonly as u32 | VmFlag::Noexec as u32 | VmFlag::Uncached as u32;
149 
150         if value & !valid != 0 {
151             return Err(EINVAL);
152         }
153         Ok(Self(value))
154     }
155 }
156 
157 /// Arguments for a virtual memory map operation.
158 struct VmMapArgs<'drm> {
159     /// Access permissions and caching behavior for the mapping.
160     flags: VmMapFlags,
161     /// GEM buffer object registered with the GPUVM framework.
162     vm_bo: ARef<GpuVmBo<GpuVmData<'drm>>>,
163     /// Offset in bytes from the start of the buffer object.
164     bo_offset: u64,
165 }
166 
167 /// Type of virtual memory operation.
168 enum VmOpType<'drm> {
169     /// Map a GEM buffer object into the virtual address space.
170     Map(VmMapArgs<'drm>),
171     /// Unmap a region from the virtual address space.
172     Unmap,
173 }
174 
175 /// Preallocated resources needed to execute a VM operation.
176 ///
177 /// VM operations may require allocating new GPUVA objects to track mappings.
178 /// To avoid allocation failures during the operation, preallocate the
179 /// maximum number of GPUVAs that might be needed.
180 struct VmOpResources<'drm> {
181     /// Preallocated GPUVA objects for remap operations.
182     ///
183     /// Partial unmap requests or map requests overlapping existing mappings
184     /// will trigger a remap call, which needs to register up to three VA
185     /// objects (one for the new mapping, and two for the previous and next
186     /// mappings).
187     preallocated_gpuvas: [Option<GpuVaAlloc<GpuVmData<'drm>>>; 3],
188 }
189 
190 /// Request to execute a virtual memory operation.
191 struct VmOpRequest<'drm> {
192     /// Request type.
193     op_type: VmOpType<'drm>,
194 
195     /// Region of the virtual address space covered by this request.
196     region: Range<u64>,
197 }
198 
199 /// Arguments for a page table map operation.
200 struct PtMapArgs {
201     /// Memory protection flags describing allowed accesses for this mapping.
202     ///
203     /// This is directly derived from [`VmMapFlags`] via [`VmMapFlags::to_prot`].
204     prot: u32,
205 }
206 
207 /// Type of page table operation.
208 enum PtOpType {
209     /// Map pages into the page table.
210     Map(PtMapArgs),
211     /// Unmap pages from the page table.
212     Unmap,
213 }
214 
215 /// Context for updating the GPU page table.
216 ///
217 /// This context is created when beginning a page table update operation and
218 /// automatically flushes changes when dropped. It ensures that the
219 /// Memory Management Unit (MMU) state is properly managed and Translation
220 /// Lookaside Buffer (TLB) entries are flushed.
221 pub(crate) struct PtUpdateContext<'ctx, 'drm> {
222     /// Device used for DMA-mapping GEM shmem SG tables.
223     dev: &'ctx Device<Bound>,
224 
225     /// Page table.
226     pt: &'ctx IoPageTable<'drm, ARM64LPAES1>,
227 
228     /// MMU manager.
229     mmu: &'ctx Mmu<'drm>,
230 
231     /// Reference to the address space data to pass to the MMU functions.
232     as_data: &'ctx VmAsData<'drm>,
233 
234     /// Region of the virtual address space covered by this request.
235     region: Range<u64>,
236 
237     /// Operation type.
238     op_type: PtOpType,
239 
240     /// Preallocated resources that can be used when executing the request.
241     resources: &'ctx mut VmOpResources<'drm>,
242 }
243 
244 impl<'ctx, 'drm> PtUpdateContext<'ctx, 'drm> {
245     /// Creates a new page table update context.
246     ///
247     /// This prepares the MMU for a page table update.
248     /// The context will automatically flush the TLB and
249     /// complete the update when dropped.
250     fn new(
251         dev: &'ctx Device<Bound>,
252         pt: &'ctx IoPageTable<'drm, ARM64LPAES1>,
253         mmu: &'ctx Mmu<'drm>,
254         as_data: &'ctx VmAsData<'drm>,
255         region: Range<u64>,
256         op_type: PtOpType,
257         resources: &'ctx mut VmOpResources<'drm>,
258     ) -> Result<PtUpdateContext<'ctx, 'drm>> {
259         mmu.start_vm_update(as_data, &region)?;
260 
261         Ok(Self {
262             dev,
263             pt,
264             mmu,
265             as_data,
266             region,
267             op_type,
268             resources,
269         })
270     }
271 
272     /// Finds one of our pre-allocated VAs.
273     fn preallocated_gpuva(&mut self) -> Result<GpuVaAlloc<GpuVmData<'drm>>> {
274         self.resources
275             .preallocated_gpuvas
276             .iter_mut()
277             .find_map(|f| f.take())
278             .ok_or(EINVAL)
279     }
280 
281     /// Returns an unused GPUVA object to the preallocated pool.
282     /// If the pool is already full, the unused allocation is simply dropped.
283     fn return_preallocated_gpuva(&mut self, gpuva: GpuVaAlloc<GpuVmData<'drm>>) {
284         if let Some(slot) = self
285             .resources
286             .preallocated_gpuvas
287             .iter_mut()
288             .find(|slot| slot.is_none())
289         {
290             *slot = Some(gpuva);
291         }
292     }
293 }
294 
295 impl Drop for PtUpdateContext<'_, '_> {
296     fn drop(&mut self) {
297         if let Err(e) = self.mmu.end_vm_update(self.as_data) {
298             dev_err!(self.dev, "Failed to end VM update {:?}", e);
299         }
300 
301         if let Err(e) = self.mmu.flush_vm(self.as_data) {
302             dev_err!(self.dev, "Failed to flush VM {:?}", e);
303         }
304     }
305 }
306 
307 /// Driver implementation for the GPUVM framework.
308 ///
309 /// Implements [`DriverGpuVm`] to provide VM operation callbacks (map, unmap, remap)
310 /// and associated types for buffer objects, virtual addresses, and contexts.
311 pub(crate) struct GpuVmData<'drm> {
312     _phantom: PhantomData<&'drm ()>,
313 }
314 
315 /// GPU virtual address space.
316 ///
317 /// Each VM can be mapped into a hardware address space slot.
318 #[pin_data]
319 pub(crate) struct Vm<'drm> {
320     /// Data referenced by an AS when the VM is active
321     as_data: Arc<VmAsData<'drm>>,
322     /// MMU manager.
323     mmu: Arc<Mmu<'drm>>,
324     /// Parent device used for DMA mapping and page-table operations.
325     dev: &'drm Device<Bound>,
326     /// DRM GPUVM core for managing virtual address space.
327     #[pin]
328     gpuvm_unique: Mutex<UniqueRefGpuVm<GpuVmData<'drm>>>,
329     /// Non-core part of the GPUVM. Can be used for stuff that doesn't modify the
330     /// internal mapping tree, like GpuVm::obtain()
331     gpuvm: ARef<GpuVm<GpuVmData<'drm>>>,
332     /// VA range for this VM.
333     va_range: Range<u64>,
334 }
335 
336 impl<'drm> Vm<'drm> {
337     /// Creates a new GPU virtual address space.
338     ///
339     /// The VM is initialized with a page table configured according to the GPU's
340     /// address translation capabilities and registered with the GPUVM framework.
341     pub(crate) fn new(
342         dev: &'drm Device<Bound>,
343         ddev: &TyrDrmDevice,
344         mmu: ArcBorrow<'_, Mmu<'drm>>,
345         gpu_info: &GpuInfo,
346     ) -> Result<Arc<Vm<'drm>>> {
347         let mmu_features = MMU_FEATURES::from_raw(gpu_info.mmu_features);
348         let va_bits = mmu_features.va_bits().get();
349         let pa_bits = mmu_features.pa_bits().get();
350 
351         let range = 0..(1u64 << va_bits);
352         let reserve_range = 0..0u64;
353 
354         // dummy_obj is used to initialize the GPUVM tree.
355         let dummy_obj = gem::new_dummy_object(ddev).inspect_err(|e| {
356             dev_err!(dev, "Failed to create dummy GEM object: {:?}", e);
357         })?;
358 
359         let gpuvm_unique = GpuVm::new::<Error, _>(
360             c"Tyr::GpuVm",
361             ddev,
362             &*dummy_obj,
363             range.clone(),
364             reserve_range,
365             GpuVmData::<'drm> {
366                 _phantom: PhantomData::<&()>,
367             },
368         )
369         .inspect_err(|e| {
370             dev_err!(dev, "Failed to create GpuVm: {:?}", e);
371         })?;
372         let gpuvm = ARef::from(&*gpuvm_unique);
373 
374         let as_data = Arc::pin_init(VmAsData::new(&mmu, dev, va_bits, pa_bits), GFP_KERNEL)?;
375 
376         let vm = Arc::pin_init(
377             pin_init!(Self{
378                 as_data,
379                 dev,
380                 mmu: mmu.into(),
381                 gpuvm,
382                 gpuvm_unique <- new_mutex!(gpuvm_unique),
383                 va_range: range,
384             }),
385             GFP_KERNEL,
386         )?;
387 
388         Ok(vm)
389     }
390 
391     /// Returns the parent device used by this VM for DMA mapping and page-table operations.
392     pub(crate) fn dev(&self) -> &'drm Device<Bound> {
393         self.dev
394     }
395 
396     /// Activate the VM in a hardware address space slot.
397     pub(crate) fn activate(&self) -> Result {
398         self.mmu
399             .activate_vm(self.as_data.as_arc_borrow())
400             .inspect_err(|e| {
401                 dev_err!(self.dev, "Failed to activate VM: {:?}", e);
402             })
403     }
404 
405     /// Deactivate the VM by evicting it from its address space slot.
406     fn deactivate(&self) -> Result {
407         self.mmu.deactivate_vm(&self.as_data).inspect_err(|e| {
408             dev_err!(self.dev, "Failed to deactivate VM: {:?}", e);
409         })
410     }
411 
412     /// Kills the VM by deactivating it and unmapping all regions.
413     pub(crate) fn kill(&self) {
414         // TODO: Turn the VM into a state where it can't be used.
415         let _ = self.deactivate();
416         let _ = self
417             .unmap_range(self.va_range.start, self.va_range.end - self.va_range.start)
418             .inspect_err(|e| {
419                 dev_err!(self.dev, "Failed to unmap range during deactivate: {:?}", e);
420             });
421     }
422 
423     /// Executes a virtual memory operation.
424     ///
425     /// This handles both map and unmap operations by coordinating between the
426     /// GPUVM framework and the hardware page table.
427     fn exec_op<'a>(
428         &self,
429         gpuvm_unique: &mut UniqueRefGpuVm<GpuVmData<'drm>>,
430         req: VmOpRequest<'drm>,
431         resources: &'a mut VmOpResources<'drm>,
432     ) -> Result {
433         let pt = &self.as_data.page_table;
434 
435         match req.op_type {
436             VmOpType::Map(args) => {
437                 let mut pt_upd = PtUpdateContext::new(
438                     self.dev,
439                     pt,
440                     &self.mmu,
441                     &self.as_data,
442                     req.region,
443                     PtOpType::Map(PtMapArgs {
444                         prot: args.flags.to_prot(),
445                     }),
446                     resources,
447                 )?;
448 
449                 gpuvm_unique.sm_map(OpMapRequest {
450                     addr: pt_upd.region.start,
451                     range: pt_upd.region.end - pt_upd.region.start,
452                     gem_offset: args.bo_offset,
453                     vm_bo: &args.vm_bo,
454                     context: &mut pt_upd,
455                 })
456                 //PtUpdateContext drops here flushing the page table
457             }
458             VmOpType::Unmap => {
459                 let mut pt_upd = PtUpdateContext::new(
460                     self.dev,
461                     pt,
462                     &self.mmu,
463                     &self.as_data,
464                     req.region,
465                     PtOpType::Unmap,
466                     resources,
467                 )?;
468 
469                 gpuvm_unique.sm_unmap(
470                     pt_upd.region.start,
471                     pt_upd.region.end - pt_upd.region.start,
472                     &mut pt_upd,
473                 )
474                 //PtUpdateContext drops here flushing the page table
475             }
476         }
477     }
478 
479     /// Maps a GEM buffer object range into the VM at the specified virtual address.
480     ///
481     /// This creates a mapping from GPU virtual address `va` to the physical pages
482     /// backing the GEM object, starting at `bo_offset` bytes into the object and
483     /// spanning `map_size` bytes. The mapping respects the access permissions and
484     /// caching behavior specified in `flags`.
485     pub(crate) fn map_bo_range(
486         &self,
487         bo: &Bo,
488         bo_offset: u64,
489         map_size: u64,
490         va: u64,
491         flags: VmMapFlags,
492     ) -> Result {
493         if map_size == 0
494             || va % SZ_4K as u64 != 0
495             || bo_offset % SZ_4K as u64 != 0
496             || map_size % SZ_4K as u64 != 0
497         {
498             return Err(EINVAL);
499         }
500 
501         let bo_size = u64::try_from(bo.size()).map_err(|_| EOVERFLOW)?;
502         let bo_end = bo_offset.checked_add(map_size).ok_or(EINVAL)?;
503 
504         if bo_end > bo_size {
505             dev_err!(
506                 self.dev,
507                 "BO mapping range {:#x}..{:#x} exceeds BO size {:#x}",
508                 bo_offset,
509                 bo_end,
510                 bo_size
511             );
512             return Err(EINVAL);
513         }
514 
515         let va_end: u64 = va.checked_add(map_size).ok_or(EINVAL)?;
516 
517         let req = VmOpRequest {
518             op_type: VmOpType::Map(VmMapArgs {
519                 vm_bo: self.gpuvm.obtain(bo, ())?,
520                 flags,
521                 bo_offset,
522             }),
523             region: va..va_end,
524         };
525         let mut resources = VmOpResources {
526             preallocated_gpuvas: [
527                 Some(GpuVaAlloc::<GpuVmData<'drm>>::new(GFP_KERNEL)?),
528                 Some(GpuVaAlloc::<GpuVmData<'drm>>::new(GFP_KERNEL)?),
529                 Some(GpuVaAlloc::<GpuVmData<'drm>>::new(GFP_KERNEL)?),
530             ],
531         };
532         let result = {
533             let mut gpuvm_unique = self.gpuvm_unique.lock();
534             self.exec_op(gpuvm_unique.as_mut().get_mut(), req, &mut resources)
535         };
536         // We flush the defer cleanup list now. Things will be different in
537         // the asynchronous VM_BIND path, where we want the cleanup to
538         // happen outside the DMA signalling path.
539         self.gpuvm.deferred_cleanup();
540         result
541     }
542 
543     /// Unmaps a virtual address range from the VM.
544     ///
545     /// This removes any existing mappings in the specified range, freeing the
546     /// virtual address space for reuse.
547     pub(crate) fn unmap_range(&self, va: u64, size: u64) -> Result {
548         if size == 0 || va % SZ_4K as u64 != 0 || size % SZ_4K as u64 != 0 {
549             return Err(EINVAL);
550         }
551 
552         let end = va.checked_add(size).ok_or(EINVAL)?;
553 
554         if va < self.va_range.start || end > self.va_range.end {
555             dev_err!(
556                 self.dev,
557                 "Unmap range {:#x}..{:#x} exceeds VM range {:#x}..{:#x}",
558                 va,
559                 end,
560                 self.va_range.start,
561                 self.va_range.end
562             );
563             return Err(EINVAL);
564         }
565 
566         let req = VmOpRequest {
567             op_type: VmOpType::Unmap,
568             region: va..end,
569         };
570 
571         let full_vm = va == self.va_range.start && end == self.va_range.end;
572 
573         let mut resources = VmOpResources {
574             preallocated_gpuvas: if full_vm {
575                 // Unmapping the entire VM cannot split an existing mapping,
576                 // so no GPUVA objects are needed for remap operations.
577                 [None, None, None]
578             } else {
579                 [
580                     Some(GpuVaAlloc::<GpuVmData<'drm>>::new(GFP_KERNEL)?),
581                     Some(GpuVaAlloc::<GpuVmData<'drm>>::new(GFP_KERNEL)?),
582                     Some(GpuVaAlloc::<GpuVmData<'drm>>::new(GFP_KERNEL)?),
583                 ]
584             },
585         };
586         let result = {
587             let mut gpuvm_unique = self.gpuvm_unique.lock();
588             self.exec_op(gpuvm_unique.as_mut().get_mut(), req, &mut resources)
589         };
590         // We flush the defer cleanup list now. Things will be different in
591         // the asynchronous VM_BIND path, where we want the cleanup to
592         // happen outside the DMA signalling path.
593         self.gpuvm.deferred_cleanup();
594         result
595     }
596 }
597 
598 impl<'drm> DriverGpuVm for GpuVmData<'drm> {
599     type Driver = TyrDrmDriver;
600     type Object = Bo;
601     type VmBoData = ();
602     type VaData = ();
603     type SmContext<'ctx>
604         = PtUpdateContext<'ctx, 'drm>
605     where
606         Self: 'ctx;
607 
608     /// Create a new mapping.
609     fn sm_step_map<'op>(
610         &mut self,
611         op: OpMap<'op, Self>,
612         context: &mut Self::SmContext<'_>,
613     ) -> Result<OpMapped<'op, Self>, Error> {
614         let start_iova = op.addr();
615         let mut iova = start_iova;
616         let mut bytes_left_to_map = op.length();
617         let mut gem_offset = op.gem_offset();
618 
619         // Make sure that the end of the requested GEM range doesn't run past the
620         // end of the GEM buffer itself.
621         let gem_range_end = op.gem_offset().checked_add(op.length()).ok_or(EINVAL)?;
622 
623         if gem_range_end > op.obj().size() as u64 {
624             dev_err!(
625                 context.dev,
626                 "Requested GEM range ends at {} which is beyond the GEM buffer size {}",
627                 gem_range_end,
628                 op.obj().size()
629             );
630             return Err(EINVAL);
631         }
632 
633         let sgt = op.obj().sg_table(context.dev).inspect_err(|e| {
634             dev_err!(context.dev, "Failed to get sg_table: {:?}", e);
635         })?;
636         let prot = match &context.op_type {
637             PtOpType::Map(args) => args.prot,
638             _ => {
639                 return Err(EINVAL);
640             }
641         };
642 
643         for sgt_entry in sgt.iter() {
644             // Expressly convert to u64 to work with arm 32-bit builds.
645             #[allow(clippy::useless_conversion)]
646             let mut paddr = u64::from(sgt_entry.dma_address());
647             #[allow(clippy::useless_conversion)]
648             let mut sgt_entry_length = u64::from(sgt_entry.dma_len());
649 
650             if bytes_left_to_map == 0 {
651                 break;
652             }
653 
654             if gem_offset > 0 {
655                 // Skip the entire SGT entry if the gem_offset exceeds its length.
656                 let skip = u64::min(sgt_entry_length, gem_offset);
657                 paddr += skip;
658                 sgt_entry_length -= skip;
659                 gem_offset -= skip;
660             }
661 
662             if sgt_entry_length == 0 {
663                 continue;
664             }
665 
666             let len = u64::min(sgt_entry_length, bytes_left_to_map);
667 
668             let segment_mapped = match pt_map(context.dev, context.pt, iova, paddr, len, prot) {
669                 Ok(segment_mapped) => segment_mapped,
670                 Err(e) => {
671                     // clean up any successful mappings from previous SGT entries.
672                     let total_mapped = iova - start_iova;
673                     if total_mapped > 0 {
674                         let _ = pt_unmap(
675                             context.dev,
676                             context.pt,
677                             start_iova..(start_iova + total_mapped),
678                         );
679                     }
680                     return Err(e);
681                 }
682             };
683 
684             bytes_left_to_map -= segment_mapped;
685             iova += segment_mapped;
686         }
687 
688         if bytes_left_to_map != 0 {
689             let total_mapped = iova - start_iova;
690 
691             if total_mapped > 0 {
692                 let _ = pt_unmap(context.dev, context.pt, start_iova..iova);
693             }
694 
695             dev_err!(
696                 context.dev,
697                 "SG table is too small for requested mapping: {} bytes remain",
698                 bytes_left_to_map
699             );
700 
701             return Err(EINVAL);
702         }
703 
704         let gpuva = context.preallocated_gpuva()?;
705         let op = op.insert(gpuva, pin_init::init_zeroed());
706 
707         Ok(op)
708     }
709 
710     /// Indicates that an existing mapping should be removed.
711     fn sm_step_unmap<'op>(
712         &mut self,
713         op: OpUnmap<'op, Self>,
714         context: &mut Self::SmContext<'_>,
715     ) -> Result<OpUnmapped<'op, Self>, Error> {
716         let start_iova = op.va().addr();
717         let length = op.va().length();
718 
719         let region = start_iova..(start_iova + length);
720         pt_unmap(context.dev, context.pt, region.clone()).inspect_err(|e| {
721             dev_err!(
722                 context.dev,
723                 "Failed to unmap region {:#x}..{:#x}: {:?}",
724                 region.start,
725                 region.end,
726                 e
727             );
728         })?;
729 
730         let (op_unmapped, _va_removed) = op.remove();
731 
732         Ok(op_unmapped)
733     }
734 
735     /// Split up an existing mapping.
736     fn sm_step_remap<'op>(
737         &mut self,
738         op: OpRemap<'op, Self>,
739         context: &mut Self::SmContext<'_>,
740     ) -> Result<OpRemapped<'op, Self>, Error> {
741         let unmap_start = if let Some(prev) = op.prev() {
742             prev.addr() + prev.length()
743         } else {
744             op.va_to_unmap().addr()
745         };
746 
747         let unmap_end = if let Some(next) = op.next() {
748             next.addr()
749         } else {
750             op.va_to_unmap().addr() + op.va_to_unmap().length()
751         };
752 
753         let unmap_length = unmap_end - unmap_start;
754 
755         if unmap_length > 0 {
756             let region = unmap_start..(unmap_start + unmap_length);
757             pt_unmap(context.dev, context.pt, region.clone()).inspect_err(|e| {
758                 dev_err!(
759                     context.dev,
760                     "Failed to unmap remap region {:#x}..{:#x}: {:?}",
761                     region.start,
762                     region.end,
763                     e
764                 );
765             })?;
766         }
767 
768         let prev_va = context.preallocated_gpuva()?;
769         let next_va = context.preallocated_gpuva()?;
770 
771         let (op_remapped, remap_ret) = op.remap(
772             [prev_va, next_va],
773             pin_init::init_zeroed(),
774             pin_init::init_zeroed(),
775         );
776 
777         if let Some(unused_va) = remap_ret.unused_va {
778             context.return_preallocated_gpuva(unused_va);
779         }
780 
781         Ok(op_remapped)
782     }
783 }
784 
785 /// This function selects the largest supported block size (currently 4KB or 2MB)
786 /// that can be used for a mapping at the given address and size, respecting alignment constraints.
787 ///
788 /// We can map multiple pages at once but we can't exceed the size of the
789 /// table entry itself. So, if mapping 4KB pages, figure out how many pages
790 /// can be mapped before we hit the 2MB boundary. Or, if mapping 2MB pages,
791 /// figure out how many pages can be mapped before hitting the 1GB boundary
792 /// Returns the page size (4KB or 2MB) and the number of pages that can be mapped at that size.
793 fn get_pgsize(addr: u64, size: u64) -> (u64, u64) {
794     // Get the distance to the next boundary of 2MB block
795     let blk_offset_2m = addr.wrapping_neg() % (SZ_2M as u64);
796 
797     // Use 4K blocks if the address is not 2MB aligned, or we have less than 2MB to map
798     if blk_offset_2m != 0 || size < SZ_2M as u64 {
799         let pgcount = if blk_offset_2m == 0 {
800             size / SZ_4K as u64
801         } else {
802             u64::min(blk_offset_2m, size) / SZ_4K as u64
803         };
804         return (SZ_4K as u64, pgcount);
805     }
806 
807     let blk_offset_1g = addr.wrapping_neg() % (SZ_1G as u64);
808     let blk_offset = if blk_offset_1g == 0 {
809         SZ_1G as u64
810     } else {
811         blk_offset_1g
812     };
813     let pgcount = u64::min(blk_offset, size) / SZ_2M as u64;
814 
815     (SZ_2M as u64, pgcount)
816 }
817 
818 /// Maps a physical address range into the page table at the specified virtual address.
819 ///
820 /// This function maps `len` bytes of physical memory starting at `paddr` to the
821 /// virtual address `iova`, using the protection flags specified in `prot`. It
822 /// automatically selects optimal page sizes to minimize page table overhead.
823 ///
824 /// If the mapping fails partway through, all successfully mapped pages are
825 /// unmapped before returning an error.
826 ///
827 /// Returns the number of bytes successfully mapped.
828 fn pt_map(
829     dev: &Device,
830     pt: &IoPageTable<'_, ARM64LPAES1>,
831     iova: u64,
832     paddr: u64,
833     len: u64,
834     prot: u32,
835 ) -> Result<u64> {
836     let mut segment_mapped = 0u64;
837     while segment_mapped < len {
838         let remaining = len - segment_mapped;
839         let curr_iova = iova + segment_mapped;
840         let curr_paddr = paddr + segment_mapped;
841 
842         let (pgsize, pgcount) = get_pgsize(curr_iova | curr_paddr, remaining);
843 
844         // On 32-bit systems, usize is only 32 bits, so check that
845         // the iova can be converted without truncation.
846         let curr_iova = match usize::try_from(curr_iova) {
847             Ok(curr_iova) => curr_iova,
848             Err(_) => {
849                 dev_err!(
850                     dev,
851                     "curr_iova {:#x} cannot be represented as usize (max {:#x})",
852                     curr_iova,
853                     usize::MAX
854                 );
855 
856                 if segment_mapped > 0 {
857                     let _ = pt_unmap(dev, pt, iova..(iova + segment_mapped));
858                 }
859 
860                 return Err(EOVERFLOW);
861             }
862         };
863 
864         // SAFETY:
865         // No other io-pgtable operation can currently access this range because Tyr holds
866         // the gpuvm_unique mutex for the entire sm_map() operation.
867         // The addresses being mapped won't overlap any existing mappings in this
868         // page table because drm_gpuvm_sm_map() checks each requested mapping and either unmaps
869         // or remaps any overlap before creating the new mapping.
870         let (mapped, result) = unsafe {
871             pt.map_pages(
872                 curr_iova,
873                 curr_paddr as PhysAddr,
874                 pgsize as usize,
875                 pgcount as usize,
876                 prot,
877                 GFP_KERNEL,
878             )
879         };
880 
881         if let Err(e) = result {
882             // If map_pages fails, mapped will be zero because the ARM LPAE backend
883             // only updates the mapped value after the entire request succeeds.
884             dev_err!(dev, "pt.map_pages failed at iova {:#x}: {:?}", curr_iova, e);
885             if segment_mapped > 0 {
886                 let _ = pt_unmap(dev, pt, iova..(iova + segment_mapped));
887             }
888             return Err(e);
889         }
890 
891         if mapped == 0 {
892             dev_err!(dev, "Failed to map any pages at iova {:#x}", curr_iova);
893             if segment_mapped > 0 {
894                 let _ = pt_unmap(dev, pt, iova..(iova + segment_mapped));
895             }
896             return Err(ENOMEM);
897         }
898 
899         segment_mapped += mapped as u64;
900     }
901 
902     Ok(segment_mapped)
903 }
904 
905 /// Unmaps a virtual address range from the page table.
906 ///
907 /// This function removes all page table entries in the specified range,
908 /// automatically handling different page sizes that may be present.
909 fn pt_unmap(dev: &Device, pt: &IoPageTable<'_, ARM64LPAES1>, range: Range<u64>) -> Result {
910     let mut iova = range.start;
911     let mut bytes_left_to_unmap = range.end - range.start;
912 
913     while bytes_left_to_unmap > 0 {
914         // It is fine to use just the iova to determine the page size
915         // because if the actual mapping was represented with smaller page sizes,
916         // (e.g. because the physical address was not 2MiB aligned)
917         // the ARM LPAE backend will notice and handle the lower-level table correctly.
918         let (pgsize, pgcount) = get_pgsize(iova, bytes_left_to_unmap);
919 
920         // On 32-bit systems, usize is only 32 bits, so check that
921         // the iova can be converted without truncation.
922         let iova_usize = usize::try_from(iova).map_err(|_| {
923             dev_err!(
924                 dev,
925                 "IOVA {:#x} cannot be represented as usize (max {:#x})",
926                 iova,
927                 usize::MAX
928             );
929             EOVERFLOW
930         })?;
931 
932         // SAFETY:
933         // No other io-pgtable operation can currently access this range because Tyr holds
934         // the gpuvm_unique mutex for the entire sm_unmap() operation.
935         // We know that this page table has one or more consecutive mappings
936         // starting at `iova` with the total size of `pgcount * pgsize` because
937         // gpuvm callbacks provide exactly the range that was previously mapped.
938         let unmapped = unsafe { pt.unmap_pages(iova_usize, pgsize as usize, pgcount as usize) };
939 
940         if unmapped == 0 {
941             dev_err!(dev, "Failed to unmap any bytes at iova {:#x}", iova_usize);
942             return Err(EINVAL);
943         }
944 
945         bytes_left_to_unmap -= unmapped as u64;
946         iova += unmapped as u64;
947     }
948 
949     Ok(())
950 }
951