Add comprehensive self-tests for the MM subsystem that run during driver probe when CONFIG_NOVA_MM_SELFTESTS is enabled (default disabled). These result in testing the Vmm, buddy, bar1 and pramin all of which should function correctly for the tests to pass.
Signed-off-by: Joel Fernandes <[email protected]> --- drivers/gpu/nova-core/Kconfig | 10 ++ drivers/gpu/nova-core/driver.rs | 2 + drivers/gpu/nova-core/gpu.rs | 43 ++++++++ drivers/gpu/nova-core/gsp/commands.rs | 1 - drivers/gpu/nova-core/mm/bar_user.rs | 141 ++++++++++++++++++++++++++ 5 files changed, 196 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/nova-core/Kconfig b/drivers/gpu/nova-core/Kconfig index 809485167aff..257bca5aa0ef 100644 --- a/drivers/gpu/nova-core/Kconfig +++ b/drivers/gpu/nova-core/Kconfig @@ -15,3 +15,13 @@ config NOVA_CORE This driver is work in progress and may not be functional. If M is selected, the module will be called nova_core. + +config NOVA_MM_SELFTESTS + bool "Memory management self-tests" + depends on NOVA_CORE + help + Enable self-tests for the memory management subsystem. When enabled, + tests are run during GPU probe to verify page table walking and + BAR1 virtual memory mapping functionality. + + This is a testing option and is default-disabled. diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs index d8b2e967ba4c..7d0d09939835 100644 --- a/drivers/gpu/nova-core/driver.rs +++ b/drivers/gpu/nova-core/driver.rs @@ -92,6 +92,8 @@ fn probe(pdev: &pci::Device<Core>, _info: &Self::IdInfo) -> impl PinInit<Self, E Ok(try_pin_init!(Self { gpu <- Gpu::new(pdev, bar.clone(), bar.access(pdev.as_ref())?), + // Run optional GPU selftests. + _: { gpu.run_selftests(pdev)? }, _reg <- auxiliary::Registration::new( pdev.as_ref(), c"nova-drm", diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs index 91ec7f7910e9..938828508f2c 100644 --- a/drivers/gpu/nova-core/gpu.rs +++ b/drivers/gpu/nova-core/gpu.rs @@ -318,4 +318,47 @@ pub(crate) fn unbind(&self, dev: &device::Device<device::Core>) { .inspect(|bar| self.sysmem_flush.unregister(bar)) .is_err()); } + + /// Run selftests on the constructed [`Gpu`]. + pub(crate) fn run_selftests( + mut self: Pin<&mut Self>, + pdev: &pci::Device<device::Bound>, + ) -> Result { + self.as_mut().run_mm_selftest(pdev)?; + Ok(()) + } + + fn run_mm_selftest(mut self: Pin<&mut Self>, pdev: &pci::Device<device::Bound>) -> Result { + #[cfg(CONFIG_NOVA_MM_SELFTESTS)] + { + use crate::driver::BAR1_SIZE; + use crate::mm::pagetable::MmuVersion; + use kernel::c_str; + + let bar1 = Arc::pin_init( + pdev.iomap_region_sized::<BAR1_SIZE>(1, c_str!("nova-core/bar1")), + GFP_KERNEL, + )?; + let bar1_access = bar1.access(pdev.as_ref())?; + + // Use projection to access non-pinned fields. + let proj = self.as_mut().project(); + let bar1_pde_base = proj.gsp_static_info.bar1_pde_base(); + let mm = proj.mm; + let mmu_version = MmuVersion::from(proj.spec.chipset.arch()); + + crate::mm::bar_user::run_self_test( + pdev.as_ref(), + mm, + bar1_access, + bar1_pde_base, + mmu_version, + )?; + } + + // Suppress unused warnings when selftests disabled. + let _ = &mut self; + let _ = pdev; + Ok(()) + } } diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs index 7b5025cba106..311f65f8367b 100644 --- a/drivers/gpu/nova-core/gsp/commands.rs +++ b/drivers/gpu/nova-core/gsp/commands.rs @@ -232,7 +232,6 @@ pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> { } /// Returns the BAR1 Page Directory Entry base address. - #[expect(dead_code)] pub(crate) fn bar1_pde_base(&self) -> u64 { self.bar1_pde_base } diff --git a/drivers/gpu/nova-core/mm/bar_user.rs b/drivers/gpu/nova-core/mm/bar_user.rs index 288dec0ae920..e19906d5bcc6 100644 --- a/drivers/gpu/nova-core/mm/bar_user.rs +++ b/drivers/gpu/nova-core/mm/bar_user.rs @@ -193,3 +193,144 @@ fn drop(&mut self) { } } } + +/// Run MM subsystem self-tests during probe. +/// +/// Tests page table infrastructure and BAR1 MMIO access using the BAR1 +/// address space initialized by GSP-RM. Uses the GpuMm's buddy allocator +/// to allocate page tables and test pages as needed. +#[cfg(CONFIG_NOVA_MM_SELFTESTS)] +pub(crate) fn run_self_test( + dev: &kernel::device::Device, + mm: &mut GpuMm, + bar1: &crate::driver::Bar1, + bar1_pdb: u64, + mmu_version: MmuVersion, +) -> Result { + use crate::mm::vmm::Vmm; + use crate::mm::PAGE_SIZE; + use kernel::gpu::buddy::BuddyFlags; + use kernel::gpu::buddy::GpuBuddyAllocParams; + use kernel::sizes::{ + SZ_4K, + SZ_64K, // + }; + + // Self-tests only support MMU v2 (Turing/Ampere/Ada). + if mmu_version != MmuVersion::V2 { + dev_info!( + dev, + "MM: Skipping self-tests for MMU {:?} (only V2 supported)\n", + mmu_version + ); + return Ok(()); + } + + // Test patterns - distinct values to detect stale reads. + const PATTERN_PRAMIN: u32 = 0xDEAD_BEEF; + const PATTERN_BAR1: u32 = 0xCAFE_BABE; + + dev_info!(dev, "MM: Starting self-test...\n"); + + let pdb_addr = VramAddress::new(bar1_pdb); + + // Phase 1: Check if page tables are in VRAM (accessible via PRAMIN). + { + use crate::mm::pagetable::ver2::Pde; + use crate::mm::pagetable::AperturePde; + + // Read PDB[0] to check the aperture of the first L1 pointer. + let pdb_entry_raw = mm.pramin().try_read64(pdb_addr.raw())?; + let pdb_entry = Pde::new(pdb_entry_raw); + + if !pdb_entry.is_valid() { + dev_info!(dev, "MM: Self-test SKIPPED - no valid page tables\n"); + return Ok(()); + } + + if pdb_entry.aperture() != AperturePde::VideoMemory { + dev_info!(dev, "MM: Self-test SKIPPED - requires VRAM-based page tables\n"); + return Ok(()); + } + } + + // Phase 2: Allocate a test page from the buddy allocator. + let alloc_params = GpuBuddyAllocParams { + start_range_address: 0, + end_range_address: 0, + size_bytes: SZ_4K as u64, + min_block_size_bytes: SZ_4K as u64, + buddy_flags: BuddyFlags::try_new(0)?, + }; + + let test_page_blocks = mm.buddy().alloc_blocks(alloc_params)?; + let test_vram_offset = test_page_blocks.iter().next().ok_or(ENOMEM)?.offset(); + let test_vram = VramAddress::new(test_vram_offset); + let test_pfn = Pfn::from(test_vram); + + // Use VFN 8 (offset 0x8000) for the test mapping. + // This is within the BAR1 aperture and will trigger page table allocation. + let test_vfn = Vfn::new(8u64); + + // Create a VMM of size 64K to track virtual memory mappings. + let mut vmm = Vmm::new(pdb_addr, MmuVersion::V2, SZ_64K as u64)?; + + // Phase 3+4: Create mapping using `GpuMm` and `Vmm`. + vmm.map_page(mm, test_vfn, test_pfn, true)?; + + // Phase 5: Test the mapping. + // Pre-compute test addresses for each access path. + // Use distinct offsets within the page for read (0x100) and write (0x200) tests. + let bar1_base_offset = test_vfn.raw() as usize * PAGE_SIZE; + let bar1_read_offset: usize = bar1_base_offset + 0x100; + let bar1_write_offset: usize = bar1_base_offset + 0x200; + let vram_read_addr: usize = test_vram.raw() + 0x100; + let vram_write_addr: usize = test_vram.raw() + 0x200; + + // Test 1: Write via PRAMIN, read via BAR1. + mm.pramin().try_write32(vram_read_addr, PATTERN_PRAMIN)?; + + // Read back via BAR1 aperture. + let bar1_value = bar1.try_read32(bar1_read_offset)?; + + let test1_passed = if bar1_value == PATTERN_PRAMIN { + true + } else { + dev_err!( + dev, + "MM: Test 1 FAILED - Expected {:#010x}, got {:#010x}\n", + PATTERN_PRAMIN, + bar1_value + ); + false + }; + + // Test 2: Write via BAR1, read via PRAMIN. + bar1.try_write32(PATTERN_BAR1, bar1_write_offset)?; + + // Read back via PRAMIN. + let pramin_value = mm.pramin().try_read32(vram_write_addr)?; + + let test2_passed = if pramin_value == PATTERN_BAR1 { + true + } else { + dev_err!( + dev, + "MM: Test 2 FAILED - Expected {:#010x}, got {:#010x}\n", + PATTERN_BAR1, + pramin_value + ); + false + }; + + // Phase 6: Cleanup - invalidate PTE. + vmm.unmap_page(mm, test_vfn)?; + + if test1_passed && test2_passed { + dev_info!(dev, "MM: All self-tests PASSED\n"); + Ok(()) + } else { + dev_err!(dev, "MM: Self-tests FAILED\n"); + Err(EIO) + } +} -- 2.34.1
