// SPDX-License-Identifier: GPL-2.0 // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. //! Memory management subsystems. #![cfg_attr(not(CONFIG_NOVA_CORE_SELFTESTS), expect(dead_code))] /// Implements `From` conversions between a frame-number type and `Bounded`. /// /// Each MMU version module should invoke this for the specific bit widths used by that version's /// PTE/PDE bitfield definitions. macro_rules! impl_frame_number_bounded { ($type:ty, $bits:literal) => { impl From> for $type { fn from(val: Bounded) -> Self { Self::new(val.get()) } } impl From<$type> for Bounded { fn from(v: $type) -> Self { Bounded::from_expr(v.raw() & ::kernel::bits::genmask_u64(0..=($bits - 1))) } } }; } /// Implements `From` conversions between [`Pfn`] and `Bounded` for bitfield interop. macro_rules! impl_pfn_bounded { ($bits:literal) => { impl_frame_number_bounded!(Pfn, $bits); }; } use core::{ fmt::LowerHex, ops, // }; use kernel::{ bitfield, fmt, gpu::buddy::{ GpuBuddy, GpuBuddyParams, // }, num::Bounded, prelude::*, ptr::{ Alignable, Alignment, // }, sizes::SZ_4K, // }; use crate::{ driver::Bar0, gpu::Chipset, // }; pub(crate) use tlb::Tlb; pub(crate) mod bar_user; mod hal; pub(super) mod pagetable; mod pramin; mod regs; pub(super) mod tlb; pub(super) mod vmm; /// GPU Memory Manager - owns all core MM components. /// /// Provides centralized ownership of memory management resources: /// - [`GpuBuddy`] allocator for VRAM page table allocation. /// - [`pramin::Pramin`] for direct VRAM access. /// - [`Tlb`] manager for translation buffer flush operations. pub(crate) struct GpuMm<'gpu> { buddy: GpuBuddy, pramin: pramin::Pramin<'gpu>, tlb: Pin>>, } impl<'gpu> GpuMm<'gpu> { /// Creates the GPU memory manager. pub(crate) fn new( bar: Bar0<'gpu>, chipset: Chipset, buddy_params: GpuBuddyParams, total_fb_end: VramAddress, ) -> Result { // PRAMIN covers all physical VRAM (including GSP-reserved areas // above the usable region, e.g. the BAR1 page directory). let vram_region = VramAddress::ZERO..total_fb_end; Ok(Self { buddy: GpuBuddy::new(buddy_params)?, pramin: pramin::Pramin::new(bar, chipset, vram_region)?, tlb: KBox::pin_init(Tlb::new(bar), GFP_KERNEL)?, }) } /// Access the [`GpuBuddy`] allocator. pub(crate) fn buddy(&self) -> &GpuBuddy { &self.buddy } /// Access the [`pramin::Pramin`]. fn pramin_mut(&mut self) -> &mut pramin::Pramin<'gpu> { &mut self.pramin } /// Access the [`Tlb`] manager. pub(crate) fn tlb(&self) -> &Tlb<'gpu> { self.tlb.as_ref().get_ref() } } /// Page size in bytes (4 KiB). pub(crate) const PAGE_SIZE: usize = SZ_4K; /// Physical VRAM address in GPU video memory. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub(crate) struct VramAddress(u64); impl VramAddress { /// The zero address. pub(crate) const ZERO: Self = Self::from_raw(0); /// Creates an address from a raw value. pub(crate) const fn from_raw(addr: u64) -> Self { Self(addr) } /// Returns the address as a raw value. pub(crate) const fn into_raw(self) -> u64 { self.0 } /// Adds `rhs` to this address, returning [`None`] on overflow. pub(crate) const fn checked_add(self, rhs: u64) -> Option { match self.into_raw().checked_add(rhs) { Some(addr) => Some(Self::from_raw(addr)), None => None, } } } impl Alignable for VramAddress { fn align_down(self, alignment: Alignment) -> Self { Self::from_raw(self.into_raw().align_down(alignment)) } fn align_up(self, alignment: Alignment) -> Option { self.into_raw().align_up(alignment).map(Self::from_raw) } } impl LowerHex for VramAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { LowerHex::fmt(&self.into_raw(), f) } } impl fmt::Debug for VramAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(fmt!("{:#x}", self)) } } impl ops::Add for VramAddress { type Output = Self; fn add(self, rhs: u64) -> Self::Output { Self::from_raw(self.into_raw() + rhs) } } impl ops::Sub for VramAddress { type Output = u64; fn sub(self, rhs: Self) -> Self::Output { self.into_raw() - rhs.into_raw() } } impl From for VramAddress { fn from(pfn: Pfn) -> Self { Self::from_raw(pfn.raw() << 12) } } bitfield! { /// Virtual address in GPU address space. pub(crate) struct VirtualAddress(u64) { /// Offset within 4KB page. 11:0 offset; /// Virtual frame number. 63:12 frame_number => Vfn; } } impl VirtualAddress { /// Create a new virtual address from a raw value. #[expect(dead_code)] pub(crate) const fn new(addr: u64) -> Self { Self::from_raw(addr) } } impl From for VirtualAddress { fn from(vfn: Vfn) -> Self { Self::zeroed().with_frame_number(vfn) } } /// Physical Frame Number. /// /// Represents a physical page in VRAM. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub(crate) struct Pfn(u64); impl Pfn { /// Create a new PFN from a frame number. pub(crate) const fn new(frame_number: u64) -> Self { Self(frame_number) } /// Get the raw frame number. pub(crate) const fn raw(self) -> u64 { self.0 } } impl From for Pfn { fn from(addr: VramAddress) -> Self { Self::new(addr.into_raw() >> 12) } } impl From for Pfn { fn from(val: u64) -> Self { Self(val) } } impl From for u64 { fn from(pfn: Pfn) -> Self { pfn.0 } } impl_pfn_bounded!(52); /// Virtual Frame Number. /// /// Represents a virtual page in GPU address space. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub(crate) struct Vfn(u64); impl Vfn { /// Create a new VFN from a frame number. pub(crate) const fn new(frame_number: u64) -> Self { Self(frame_number) } /// Get the raw frame number. pub(crate) const fn raw(self) -> u64 { self.0 } } impl From for Vfn { fn from(addr: VirtualAddress) -> Self { addr.frame_number() } } impl From for Vfn { fn from(val: u64) -> Self { Self(val) } } impl From for u64 { fn from(vfn: Vfn) -> Self { vfn.0 } } impl_frame_number_bounded!(Vfn, 52); #[cfg(CONFIG_NOVA_CORE_SELFTESTS)] pub(crate) mod selftest { use core::ops::Range; use kernel::{ device, sizes::SizeConstants, // }; use super::*; /// Run MM subsystem self-tests during probe. pub(crate) fn run( dev: &device::Device, mm: &mut GpuMm<'_>, usable_fb_regions: &[Range], bar_user: &bar_user::BarUser<'_>, bar1_pdb: u64, chipset: Chipset, ) -> Result { // VRAM span the self-tests are free to overwrite, from the chosen test base. const SELFTEST_SPAN: u64 = u64::SZ_64M; let base = usable_fb_regions.iter().find_map(|region| { // Tests rely on this being 8 byte aligned for checking misalignment handling. let base = region.start.align_up(Alignment::new::<8>())?; (base.checked_add(SELFTEST_SPAN)? <= region.end).then_some(base) }); let Some(base) = base else { dev_warn!( dev, "PRAMIN: skipping self-tests, no usable VRAM region of {:#x} bytes\n", SELFTEST_SPAN ); return Ok(()); }; pramin::selftest::run(dev, mm.pramin_mut(), VramAddress::from_raw(base))?; bar_user::run_self_test(dev, mm, bar_user, bar1_pdb, chipset) } }