From: Alvin Sun <[email protected]> The MCU and user VMs need different VA layouts. Give each a dedicated constructor: new_for_fw() builds the kernel-only 4G layout, while new_for_user() splits the address space by task_size or a user-provided size, rejecting oversized requests rather than clamping. The resulting user range is what VM_CREATE reports back as user_va_range.
Signed-off-by: Alvin Sun <[email protected]> --- drivers/gpu/drm/tyr/fw.rs | 19 +++--- drivers/gpu/drm/tyr/vm.rs | 170 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 164 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/tyr/fw.rs b/drivers/gpu/drm/tyr/fw.rs index 7edb5eff17077..d790b54e373e6 100644 --- a/drivers/gpu/drm/tyr/fw.rs +++ b/drivers/gpu/drm/tyr/fw.rs @@ -52,7 +52,6 @@ KernelBoVaAlloc, // }, gpu::GpuInfo, - mmu::Mmu, regs::{ gpu_control::{ @@ -223,10 +222,10 @@ pub(crate) fn new( mmu: ArcBorrow<'_, Mmu<'drm>>, gpu_info: &GpuInfo, ) -> Result<Firmware<'drm>> { - let vm = Vm::new(dev, ddev, mmu, gpu_info)?; + let vm = Vm::new_for_fw(dev, ddev, mmu, gpu_info)?; vm.activate()?; - let result = (|| { + let sections = (|| -> Result<KVec<Section<'drm>>> { let (fw, parsed_sections) = Self::load(dev, ddev, gpu_info)?; let mut sections = KVec::new(); for parsed in parsed_sections { @@ -256,18 +255,18 @@ pub(crate) fn new( sections.push(Section { data, mem }, GFP_KERNEL)?; } - Ok(Firmware { - iomem, - vm: vm.clone(), - sections, - }) + Ok(sections) })(); - if result.is_err() { + if sections.is_err() { vm.kill(); } - result + Ok(Firmware { + iomem, + vm, + sections: sections?, + }) } pub(crate) fn boot(&self) -> Result { diff --git a/drivers/gpu/drm/tyr/vm.rs b/drivers/gpu/drm/tyr/vm.rs index a2857820570cf..625ec7e95790f 100644 --- a/drivers/gpu/drm/tyr/vm.rs +++ b/drivers/gpu/drm/tyr/vm.rs @@ -8,6 +8,7 @@ //! mapped into hardware address space (AS) slots for GPU execution. use core::marker::PhantomData; +use core::num::NonZeroU64; use core::ops::Range; use kernel::{ @@ -44,6 +45,8 @@ new_mutex, prelude::*, sizes::{ + LargeSizeConstants, + SizeConstants, SZ_1G, SZ_2M, SZ_4K, // @@ -159,6 +162,103 @@ fn try_from(value: u32) -> Result<Self, Self::Error> { } } +/// User VA size request for a user VM. +pub(crate) enum UserVaRequest { + /// Split based on `task_size()` and the GPU VA range. + Auto, + /// Caller-specified size; construction guarantees `> 0`. + Fixed(NonZeroU64), +} + +impl UserVaRequest { + /// UAPI boundary normalization: `0` -> [`Auto`](Self::Auto). + #[expect(dead_code)] + pub(crate) fn from_uapi(v: u64) -> Self { + match NonZeroU64::new(v) { + Some(size) => Self::Fixed(size), + None => Self::Auto, + } + } +} + +/// Final user/kernel VA layout for a VM. +pub(crate) struct VmLayout { + /// Full GPU VA range covered by this VM. + pub(crate) full: Range<u64>, + /// User-accessible VA range. Empty for MCU VMs. + pub(crate) user: Range<u64>, +} + +impl VmLayout { + /// Kernel VA range, reserved for future kernel object allocation. + #[expect(dead_code)] + pub(crate) fn kernel(&self) -> Range<u64> { + self.user.end..self.full.end + } + + /// Compute a user/kernel split for a user VM from the full GPU VA range and + /// a user request. + pub(crate) fn compute(full: Range<u64>, req: UserVaRequest) -> Result<Self> { + // Minimum VA space reserved for kernel objects (heaps, ring buffers, ...). + const MIN_KERNEL_VA: u64 = u64::SZ_256M; + + if full.end <= MIN_KERNEL_VA { + pr_err!( + "Invalid VA range {:#x}..{:#x}, kernel VA min required: >{:#x}\n", + full.start, + full.end, + MIN_KERNEL_VA + ); + return Err(EINVAL); + } + + let user_max = full.end - MIN_KERNEL_VA; + + let user_end = match req { + UserVaRequest::Fixed(v) => { + let user_size = v.get(); + if user_size > user_max { + pr_err!( + "Requested user VA range {:#x} exceeds maximum {:#x}\n", + user_size, + user_max + ); + return Err(EINVAL); + } + user_size + } + UserVaRequest::Auto => { + let task_size = current!().mm().map(|mm| mm.task_size()); + let candidate = match task_size { + // `task_size()` returns usize; widen to u64 for the comparison. + Some(t) if (t as u64) < full.end => t as u64, + None | Some(_) => { + // If the range exceeds 4G, split it in two so CPU and + // GPU share the same addresses (SVM). + if full.end > u64::SZ_4G { + full.end / 2 + } else { + user_max + } + } + }; + candidate.min(user_max) + } + }; + + let delta = full.end - user_end; + // Pick a kernel VA range that's a power of two, to have a clear split. + let kernel_va_range = 1u64 << delta.ilog2(); + let kernel_va_start = full.end - kernel_va_range; + let full_start = full.start; + + Ok(Self { + full, + user: full_start..kernel_va_start, + }) + } +} + /// Arguments for a virtual memory map operation. struct VmMapArgs<'drm> { /// Access permissions and caching behavior for the mapping. @@ -334,26 +434,64 @@ pub(crate) struct Vm<'drm> { /// Non-core part of the GPUVM. Can be used for stuff that doesn't modify the /// internal mapping tree, like GpuVm::obtain() gpuvm: ARef<GpuVm<GpuVmData<'drm>>>, - /// VA range for this VM. - va_range: Range<u64>, + /// VA layout for this VM. + pub(crate) layout: VmLayout, } impl<'drm> Vm<'drm> { - /// Creates a new GPU virtual address space. + /// Creates the MCU/firmware VM. /// - /// The VM is initialized with a page table configured according to the GPU's - /// address translation capabilities and registered with the GPUVM framework. - pub(crate) fn new( + /// The MCU VM is entirely kernel-managed: it has no user-accessible range. + pub(crate) fn new_for_fw( + dev: &'drm Device<Bound>, + ddev: &TyrDrmDevice, + mmu: ArcBorrow<'_, Mmu<'drm>>, + gpu_info: &GpuInfo, + ) -> Result<Arc<Vm<'drm>>> { + // As in panthor: the CSF MCU is a Cortex-M7 and can only address 4G. + let layout = VmLayout { + full: 0..u64::SZ_4G, + user: 0..0u64, + }; + Self::new_internal(dev, ddev, mmu, gpu_info, layout) + } + + /// Creates a user VM, splitting the GPU VA range per `user_va`. + #[expect(dead_code)] + pub(crate) fn new_for_user( dev: &'drm Device<Bound>, ddev: &TyrDrmDevice, mmu: ArcBorrow<'_, Mmu<'drm>>, gpu_info: &GpuInfo, + user_va: UserVaRequest, + ) -> Result<Arc<Vm<'drm>>> { + let mmu_features = MMU_FEATURES::from_raw(gpu_info.mmu_features); + let va_bits = mmu_features.va_bits().get(); + let range = 0..(1u64 << va_bits); + + let layout = VmLayout::compute(range.clone(), user_va).inspect_err(|_| { + dev_err!( + dev, + "Failed to split GPU VA range {:#x}..{:#x} into user and kernel regions\n", + range.start, + range.end + ); + })?; + Self::new_internal(dev, ddev, mmu, gpu_info, layout) + } + + /// Initializes a VM with the given layout. + fn new_internal( + dev: &'drm Device<Bound>, + ddev: &TyrDrmDevice, + mmu: ArcBorrow<'_, Mmu<'drm>>, + gpu_info: &GpuInfo, + layout: VmLayout, ) -> Result<Arc<Vm<'drm>>> { let mmu_features = MMU_FEATURES::from_raw(gpu_info.mmu_features); let va_bits = mmu_features.va_bits().get(); let pa_bits = mmu_features.pa_bits().get(); - let range = 0..(1u64 << va_bits); let reserve_range = 0..0u64; // dummy_obj is used to initialize the GPUVM tree. @@ -365,7 +503,7 @@ pub(crate) fn new( c"Tyr::GpuVm", ddev, &*dummy_obj, - range.clone(), + layout.full.clone(), reserve_range, GpuVmData::<'drm> { _phantom: PhantomData::<&()>, @@ -385,7 +523,7 @@ pub(crate) fn new( mmu: mmu.into(), gpuvm, gpuvm_unique <- new_mutex!(gpuvm_unique), - va_range: range, + layout, }), GFP_KERNEL, )?; @@ -416,10 +554,12 @@ fn deactivate(&self) -> Result { /// Kills the VM by deactivating it and unmapping all regions. pub(crate) fn kill(&self) { - // TODO: Turn the VM into a state where it can't be used. let _ = self.deactivate(); let _ = self - .unmap_range(self.va_range.start, self.va_range.end - self.va_range.start) + .unmap_range( + self.layout.full.start, + self.layout.full.end - self.layout.full.start, + ) .inspect_err(|e| { dev_err!(self.dev, "Failed to unmap range during deactivate: {:?}", e); }); @@ -556,14 +696,14 @@ pub(crate) fn unmap_range(&self, va: u64, size: u64) -> Result { let end = va.checked_add(size).ok_or(EINVAL)?; - if va < self.va_range.start || end > self.va_range.end { + if va < self.layout.full.start || end > self.layout.full.end { dev_err!( self.dev, "Unmap range {:#x}..{:#x} exceeds VM range {:#x}..{:#x}", va, end, - self.va_range.start, - self.va_range.end + self.layout.full.start, + self.layout.full.end ); return Err(EINVAL); } @@ -573,7 +713,7 @@ pub(crate) fn unmap_range(&self, va: u64, size: u64) -> Result { region: va..end, }; - let full_vm = va == self.va_range.start && end == self.va_range.end; + let full_vm = va == self.layout.full.start && end == self.layout.full.end; let mut resources = VmOpResources { preallocated_gpuvas: if full_vm { -- 2.43.0
