Add self-tests for BAR1 access during driver probe when CONFIG_NOVA_MM_SELFTESTS is enabled (default disabled). This results in testing the Vmm, GPU buddy allocator and BAR1 region all of which should function correctly for the tests to pass.
Cc: Nikola Djukic <[email protected]> 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 | 48 ++++++++ drivers/gpu/nova-core/gsp/commands.rs | 1 - drivers/gpu/nova-core/mm/bar_user.rs | 164 ++++++++++++++++++++++++++ 5 files changed, 224 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/nova-core/Kconfig b/drivers/gpu/nova-core/Kconfig index 6513007bf66f..d0d4177adb1b 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 e217e5c7cb32..f17bf1bf0b12 100644 --- a/drivers/gpu/nova-core/gpu.rs +++ b/drivers/gpu/nova-core/gpu.rs @@ -319,4 +319,52 @@ 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_selftests(pdev)?; + Ok(()) + } + + #[cfg(CONFIG_NOVA_MM_SELFTESTS)] + fn run_mm_selftests( + mut self: Pin<&mut Self>, + pdev: &pci::Device<device::Bound>, + ) -> Result { + use crate::driver::BAR1_SIZE; + + let mmu_version = MmuVersion::from(self.spec.chipset.arch()); + + // BAR1 self-tests. + let bar1 = Arc::pin_init( + pdev.iomap_region_sized::<BAR1_SIZE>(1, c"nova-core/bar1"), + GFP_KERNEL, + )?; + let bar1_access = bar1.access(pdev.as_ref())?; + + let proj = self.as_mut().project(); + let bar1_pde_base = proj.gsp_static_info.bar1_pde_base(); + let mm = proj.mm.as_ref().get_ref(); + + crate::mm::bar_user::run_self_test( + pdev.as_ref(), + mm, + bar1_access, + bar1_pde_base, + mmu_version, + )?; + + Ok(()) + } + + #[cfg(not(CONFIG_NOVA_MM_SELFTESTS))] + fn run_mm_selftests( + self: Pin<&mut Self>, + _pdev: &pci::Device<device::Bound>, + ) -> Result { + Ok(()) + } } diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs index a7778d5d9e32..bf9e3086565f 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 74119c4d365e..5edaacf4c9d0 100644 --- a/drivers/gpu/nova-core/mm/bar_user.rs +++ b/drivers/gpu/nova-core/mm/bar_user.rs @@ -152,3 +152,167 @@ fn drop(&mut self) { } } } + +/// Check if the PDB has valid, VRAM-backed page tables. +/// +/// Returns `Err(ENOENT)` if page tables are missing or not in VRAM. +#[cfg(CONFIG_NOVA_MM_SELFTESTS)] +fn check_valid_page_tables(mm: &GpuMm, pdb_addr: VramAddress) -> Result { + use crate::mm::pagetable::ver2::Pde; + use crate::mm::pagetable::AperturePde; + + let mut window = mm.pramin().window()?; + let pdb_entry_raw = window.try_read64(pdb_addr.raw())?; + let pdb_entry = Pde::new(pdb_entry_raw); + + if !pdb_entry.is_valid() { + return Err(ENOENT); + } + + if pdb_entry.aperture() != AperturePde::VideoMemory { + return Err(ENOENT); + } + + Ok(()) +} + +/// Run MM subsystem self-tests during probe. +/// +/// Tests page table infrastructure and `BAR1` MMIO access using the `BAR1` +/// address space. 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: &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 for now. + if mmu_version != MmuVersion::V2 { + dev_info!( + dev, + "MM: Skipping self-tests for MMU {:?} (only V2 supported)\n", + mmu_version + ); + return Ok(()); + } + + // Test patterns. + 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); + + // Check if initial page tables are in VRAM. + if check_valid_page_tables(mm, pdb_addr).is_err() { + dev_info!(dev, "MM: Self-test SKIPPED - no valid VRAM page tables\n"); + return Ok(()); + } + + // Setup 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 = KBox::pin_init(mm.buddy().alloc_blocks(&alloc_params), GFP_KERNEL)?; + 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); + + // Create a VMM of size 64K to track virtual memory mappings. + let mut vmm = Vmm::new(pdb_addr, MmuVersion::V2, SZ_64K as u64)?; + + // Create a test mapping. + let mapped = vmm.map_pages(mm, &[test_pfn], None, true)?; + let test_vfn = mapped.vfn_start; + + // 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. + { + let mut window = mm.pramin().window()?; + window.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 = { + let mut window = mm.pramin().window()?; + window.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 + }; + + // Cleanup - invalidate PTE. + vmm.unmap_pages(mm, mapped)?; + + // Test 3: Two-phase prepare/execute API. + let prepared = vmm.prepare_map(mm, 1, None)?; + let mapped2 = vmm.execute_map(mm, prepared, &[test_pfn], true)?; + let readback = vmm.read_mapping(mm, mapped2.vfn_start)?; + let test3_passed = if readback == Some(test_pfn) { + true + } else { + dev_err!(dev, "MM: Test 3 FAILED - Two-phase map readback mismatch\n"); + false + }; + vmm.unmap_pages(mm, mapped2)?; + + if test1_passed && test2_passed && test3_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
