[PATCH 12/12] gpu: nova-core: mm: Add PRAMIN aperture self-tests
Eliot Courtney <[email protected]> Wed, 05 Aug 2026 14:44:59 +0900
| Newsgroups | gmane.linux.documentation,gmane.linux.kernel.rust,gmane.linux.kernel,gmane.comp.video.dri.devel |
|---|---|
| Message-ID | <[email protected]> |
From: Joel Fernandes <[email protected]> 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_CORE_SELFTESTS. When enabled, tests run after GSP boot during probe. Signed-off-by: Joel Fernandes <[email protected]> [ecourtney: convert the tests to window_at(), macros, and the new types] [ecourtney: cfg-gate the tests and expect(dead_code), not a runtime no-op] [ecourtney: run the self-tests on all architectures, drop the chipset arg] [ecourtney: test within a usable FB region, skip when none is large enough] [ecourtney: report failures without failing probe, start banner at dev_dbg] [ecourtney: removed the mm-specific Kconfig option] Signed-off-by: Eliot Courtney <[email protected]> --- drivers/gpu/nova-core/driver.rs | 3 + drivers/gpu/nova-core/gpu.rs | 12 ++++ drivers/gpu/nova-core/mm.rs | 40 +++++++++++- drivers/gpu/nova-core/mm/pramin.rs | 129 +++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs index 5738d4ac521b..2c11eeee92d2 100644 --- a/drivers/gpu/nova-core/driver.rs +++ b/drivers/gpu/nova-core/driver.rs @@ -86,6 +86,9 @@ fn probe<'bound>( // (`try_pin_init!()` initializes fields in declaration order), lives at a pinned // stable address, and is dropped after `gpu` (struct field drop order). gpu <- Gpu::new(pdev, unsafe { &*core::ptr::from_ref(bar) }), + // Run optional GPU selftests. + #[cfg(CONFIG_NOVA_CORE_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 e29e07488e78..04e1e30eeed7 100644 --- a/drivers/gpu/nova-core/gpu.rs +++ b/drivers/gpu/nova-core/gpu.rs @@ -429,4 +429,16 @@ pub(crate) fn new( )?, }) } + + /// Runs self-tests on the constructed [`Gpu`], logging failures without failing probe. + #[cfg(CONFIG_NOVA_CORE_SELFTESTS)] + pub(crate) fn run_selftests(self: Pin<&mut Self>, pdev: &pci::Device<device::Bound>) { + let this = self.project(); + let dev = pdev.as_ref(); + let regions = &this.gsp_static_info.usable_fb_regions; + + if let Err(err) = crate::mm::selftest::run(dev, this.mm, regions) { + dev_err!(dev, "self-tests failed: {:?}\n", err); + } + } } diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs index 58dce211a337..9e4338c7c393 100644 --- a/drivers/gpu/nova-core/mm.rs +++ b/drivers/gpu/nova-core/mm.rs @@ -3,7 +3,7 @@ //! Memory management subsystems. -#![expect(dead_code)] +#![cfg_attr(not(CONFIG_NOVA_CORE_SELFTESTS), expect(dead_code))] use core::{ fmt::LowerHex, @@ -123,3 +123,41 @@ fn sub(self, rhs: Self) -> Self::Output { self.into_raw() - rhs.into_raw() } } + +#[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<device::Bound>, + mm: &mut GpuMm<'_>, + usable_fb_regions: &[Range<u64>], + ) -> 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)) + } +} diff --git a/drivers/gpu/nova-core/mm/pramin.rs b/drivers/gpu/nova-core/mm/pramin.rs index 2aa1bca22fa6..1019a6e57e26 100644 --- a/drivers/gpu/nova-core/mm/pramin.rs +++ b/drivers/gpu/nova-core/mm/pramin.rs @@ -154,3 +154,132 @@ pub(super) fn window_at<'a, T>( }) } } + +#[cfg(CONFIG_NOVA_CORE_SELFTESTS)] +pub(super) mod selftest { + use kernel::{ + device, + io::{ + io_read, + io_write, // + }, + sizes::SizeConstants, // + }; + + use super::*; + use crate::{ + selftest_assert, + selftest_assert_eq, // + }; + + /// Test read/write at byte granularity. + fn test_byte_readwrite( + dev: &device::Device<device::Bound>, + pramin: &mut Pramin<'_>, + base: VramAddress, + ) -> Result { + { + let window = pramin.window_at::<[u8; 4]>(base)?; + for (i, val) in (0xA0u8..0xA4).enumerate() { + io_write!(window.view(), [build: i], val); + } + } + + let window = pramin.window_at::<[u8; 4]>(base)?; + for (i, val) in (0xA0u8..0xA4).enumerate() { + selftest_assert_eq!(dev, io_read!(window.view(), [build: i]), val); + } + Ok(()) + } + + /// Test writing a `u32` and reading back as individual `u8`s. + fn test_u32_as_bytes( + dev: &device::Device<device::Bound>, + pramin: &mut Pramin<'_>, + base: VramAddress, + ) -> Result { + let addr = base + 0x10; + let val: u32 = 0xDEADBEEF; + pramin.window_at::<u32>(addr)?.view().write_val(val); + + let window = pramin.window_at::<[u8; 4]>(addr)?; + for (i, &expected) in val.to_le_bytes().iter().enumerate() { + selftest_assert_eq!(dev, io_read!(window.view(), [build: i]), expected); + } + Ok(()) + } + + /// Test window repositioning across 1 MiB boundaries. + fn test_window_reposition( + dev: &device::Device<device::Bound>, + pramin: &mut Pramin<'_>, + base: VramAddress, + ) -> Result { + let addr_a = base; + let addr_b = base + u64::SZ_2M; // base + 2 MiB (different 1 MiB region). + let val_a: u32 = 0x11111111; + let val_b: u32 = 0x22222222; + + pramin.window_at::<u32>(addr_a)?.view().write_val(val_a); + pramin.window_at::<u32>(addr_b)?.view().write_val(val_b); + + selftest_assert_eq!( + dev, + pramin.window_at::<u32>(addr_a)?.view().read_val(), + val_a + ); + selftest_assert_eq!( + dev, + pramin.window_at::<u32>(addr_b)?.view().read_val(), + val_b + ); + Ok(()) + } + + /// Test that offsets outside the VRAM region are rejected. + fn test_invalid_offset( + dev: &device::Device<device::Bound>, + pramin: &mut Pramin<'_>, + vram_end: VramAddress, + ) -> Result { + selftest_assert!(dev, pramin.window_at::<u32>(vram_end).is_err()); + Ok(()) + } + + /// Test that misaligned accesses are rejected. + fn test_misaligned_access( + dev: &device::Device<device::Bound>, + pramin: &mut Pramin<'_>, + base: VramAddress, + ) -> Result { + // Misaligned `window_at()` start (`Region` bases must be 4-byte aligned). + selftest_assert!(dev, pramin.window_at::<u32>(base + 2).is_err()); + + // `u64` at a 4-byte-aligned (not 8-byte-aligned) address. + selftest_assert!(dev, pramin.window_at::<u64>(base + 0x44).is_err()); + Ok(()) + } + + /// Run PRAMIN self-tests during probe. + /// + /// `base` is the start of a driver-usable VRAM span that the tests are free to + /// overwrite. + pub(crate) fn run( + dev: &device::Device<device::Bound>, + pramin: &mut Pramin<'_>, + base: VramAddress, + ) -> Result { + dev_dbg!(dev, "PRAMIN: starting self-tests\n"); + + let vram_end = pramin.vram_range.end; + + test_byte_readwrite(dev, pramin, base)?; + test_u32_as_bytes(dev, pramin, base)?; + test_window_reposition(dev, pramin, base)?; + test_invalid_offset(dev, pramin, vram_end)?; + test_misaligned_access(dev, pramin, base)?; + + dev_info!(dev, "PRAMIN: self-tests passed\n"); + Ok(()) + } +} -- 2.55.0