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