Add self-tests for the PRAMIN aperture mechanism to verify correct operation during GPU probe. The tests validate various alignment requirements and corner cases.
The tests are default disabled and behind CONFIG_NOVA_MM_SELFTESTS. When enabled, tests run after GSP boot during probe. 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 | 9 ++ drivers/gpu/nova-core/mm.rs | 16 +++ drivers/gpu/nova-core/mm/pramin.rs | 214 +++++++++++++++++++++++++++++ 5 files changed, 251 insertions(+) diff --git a/drivers/gpu/nova-core/Kconfig b/drivers/gpu/nova-core/Kconfig index f918f69e0599..abf10e82647b 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 PRAMIN aperture access, + 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 84b0e1703150..77746d6949d7 100644 --- a/drivers/gpu/nova-core/driver.rs +++ b/drivers/gpu/nova-core/driver.rs @@ -96,6 +96,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 38544c38d660..aa047fe91054 100644 --- a/drivers/gpu/nova-core/gpu.rs +++ b/drivers/gpu/nova-core/gpu.rs @@ -342,4 +342,13 @@ 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( + self: Pin<&mut Self>, + pdev: &pci::Device<device::Bound>, + ) -> Result { + crate::mm::run_mm_selftests(pdev, &self.mm, self.spec.chipset)?; + Ok(()) + } } diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs index 5c1941d20d1b..08d74710f790 100644 --- a/drivers/gpu/nova-core/mm.rs +++ b/drivers/gpu/nova-core/mm.rs @@ -40,6 +40,7 @@ macro_rules! impl_pfn_bounded { device, devres::Devres, num::Bounded, + pci, prelude::*, sync::Arc, // }; @@ -83,6 +84,21 @@ pub(crate) fn pramin(&self) -> &pramin::Pramin { } } +/// Run MM subsystem self-tests during probe. +/// +/// No-op when `CONFIG_NOVA_MM_SELFTESTS` is not enabled. +#[cfg_attr(not(CONFIG_NOVA_MM_SELFTESTS), allow(unused_variables))] +pub(crate) fn run_mm_selftests( + pdev: &pci::Device<device::Bound>, + mm: &Arc<GpuMm>, + chipset: Chipset, +) -> Result { + #[cfg(CONFIG_NOVA_MM_SELFTESTS)] + pramin::run_self_test(pdev.as_ref(), mm.pramin(), chipset)?; + + Ok(()) +} + bitfield! { /// Physical VRAM address in GPU video memory. pub(crate) struct VramAddress(u64) { diff --git a/drivers/gpu/nova-core/mm/pramin.rs b/drivers/gpu/nova-core/mm/pramin.rs index 38758ca971be..73d516c91c15 100644 --- a/drivers/gpu/nova-core/mm/pramin.rs +++ b/drivers/gpu/nova-core/mm/pramin.rs @@ -296,3 +296,217 @@ fn compute_window( define_pramin_write!(try_write32, u32); define_pramin_write!(try_write64, u64); } + +#[cfg(CONFIG_NOVA_MM_SELFTESTS)] +mod selftest { + use super::*; + use crate::{ + mm::VramAddress, + num::IntoSafeCast, // + }; + use kernel::{ + device, + prelude::*, // + }; + + /// Offset within the VRAM region to use as the self-test area. + const SELFTEST_REGION_OFFSET: u64 = 0x1000; + + /// Test read/write at byte-aligned locations. + fn test_byte_readwrite( + dev: &kernel::device::Device, + win: &mut PraminWindow<'_>, + base: VramAddress, + ) -> Result { + for i in 0u8..4 { + let offset = base + 1 + u64::from(i); + let val = 0xA0 + i; + win.try_write8(offset, val)?; + let read_val = win.try_read8(offset)?; + if read_val != val { + dev_err!( + dev, + "PRAMIN: FAIL - offset {:#x}: wrote {:#x}, read {:#x}\n", + offset, + val, + read_val + ); + return Err(EIO); + } + } + Ok(()) + } + + /// Test writing a `u32` and reading back as individual `u8`s. + fn test_u32_as_bytes( + dev: &kernel::device::Device, + win: &mut PraminWindow<'_>, + base: VramAddress, + ) -> Result { + let offset = base + 0x10; + let val: u32 = 0xDEADBEEF; + win.try_write32(offset, val)?; + + // Read back as individual bytes (little-endian: EF BE AD DE). + let expected_bytes: [u8; 4] = [0xEF, 0xBE, 0xAD, 0xDE]; + for (i, &expected) in expected_bytes.iter().enumerate() { + let i_u64: u64 = i.into_safe_cast(); + let read_val = win.try_read8(offset + i_u64)?; + if read_val != expected { + dev_err!( + dev, + "PRAMIN: FAIL - offset {:#x}: expected {:#x}, read {:#x}\n", + offset + i_u64, + expected, + read_val + ); + return Err(EIO); + } + } + Ok(()) + } + + /// Test window repositioning across 1MB boundaries. + fn test_window_reposition( + dev: &kernel::device::Device, + win: &mut PraminWindow<'_>, + base: VramAddress, + ) -> Result { + let offset_a = base; + let offset_b = base + 0x200000; // base + 2MB (different 1MB region). + let val_a: u32 = 0x11111111; + let val_b: u32 = 0x22222222; + + win.try_write32(offset_a, val_a)?; + win.try_write32(offset_b, val_b)?; + + let read_b = win.try_read32(offset_b)?; + if read_b != val_b { + dev_err!( + dev, + "PRAMIN: FAIL - offset {:#x}: expected {:#x}, read {:#x}\n", + offset_b, + val_b, + read_b + ); + return Err(EIO); + } + + let read_a = win.try_read32(offset_a)?; + if read_a != val_a { + dev_err!( + dev, + "PRAMIN: FAIL - offset {:#x}: expected {:#x}, read {:#x}\n", + offset_a, + val_a, + read_a + ); + return Err(EIO); + } + Ok(()) + } + + /// Test that offsets outside the VRAM region are rejected. + fn test_invalid_offset( + dev: &kernel::device::Device, + win: &mut PraminWindow<'_>, + vram_end: VramAddress, + ) -> Result { + let result = win.try_read32(vram_end); + if result.is_ok() { + dev_err!( + dev, + "PRAMIN: FAIL - read at invalid offset {:#x} should have failed\n", + vram_end + ); + return Err(EIO); + } + Ok(()) + } + + /// Test that misaligned multi-byte accesses are rejected. + fn test_misaligned_access( + dev: &kernel::device::Device, + win: &mut PraminWindow<'_>, + base: VramAddress, + ) -> Result { + // `u16` at odd offset (not 2-byte aligned). + let offset_u16 = base + 0x21; + if win.try_write16(offset_u16, 0xABCD).is_ok() { + dev_err!( + dev, + "PRAMIN: FAIL - misaligned u16 write at {:#x} should have failed\n", + offset_u16 + ); + return Err(EIO); + } + + // `u32` at 2-byte-aligned (not 4-byte-aligned) offset. + let offset_u32 = base + 0x32; + if win.try_write32(offset_u32, 0x12345678).is_ok() { + dev_err!( + dev, + "PRAMIN: FAIL - misaligned u32 write at {:#x} should have failed\n", + offset_u32 + ); + return Err(EIO); + } + + // `u64` read at 4-byte-aligned (not 8-byte-aligned) offset. + let offset_u64 = base + 0x44; + if win.try_read64(offset_u64).is_ok() { + dev_err!( + dev, + "PRAMIN: FAIL - misaligned u64 read at {:#x} should have failed\n", + offset_u64 + ); + return Err(EIO); + } + Ok(()) + } + + /// Run PRAMIN self-tests during boot if self-tests are enabled. + pub(crate) fn run_self_test( + pdev: &device::Device<device::Bound>, + pramin: &Pramin, + chipset: crate::gpu::Chipset, + ) -> Result { + use crate::gpu::Architecture; + + let dev = pdev; + + // PRAMIN uses NV_PBUS_BAR0_WINDOW which is only available on pre-Hopper GPUs. + // Hopper+ uses NV_XAL_EP_BAR0_WINDOW instead, requiring a separate HAL that + // has not been implemented yet. + if !matches!( + chipset.arch(), + Architecture::Turing | Architecture::Ampere | Architecture::Ada + ) { + dev_info!( + dev, + "PRAMIN: Skipping self-tests for {:?} (only pre-Hopper supported)\n", + chipset + ); + return Ok(()); + } + + dev_info!(dev, "PRAMIN: Starting self-test...\n"); + + let vram_region = pramin.vram_region(); + let base = vram_region.start + SELFTEST_REGION_OFFSET; + let vram_end = vram_region.end; + let mut win = pramin.get_window(pdev)?; + + test_byte_readwrite(dev, &mut win, base)?; + test_u32_as_bytes(dev, &mut win, base)?; + test_window_reposition(dev, &mut win, base)?; + test_invalid_offset(dev, &mut win, vram_end)?; + test_misaligned_access(dev, &mut win, base)?; + + dev_info!(dev, "PRAMIN: All self-tests PASSED\n"); + Ok(()) + } +} + +#[cfg(CONFIG_NOVA_MM_SELFTESTS)] +pub(crate) use selftest::run_self_test; -- 2.34.1
