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

Reply via email to