All of lore.kernel.org
 help / color / mirror / Atom feed
From: Eliot Courtney <ecourtney@nvidia.com>
To: Danilo Krummrich <dakr@kernel.org>,
	 Alexandre Courbot <acourbot@nvidia.com>
Cc: Alice Ryhl <aliceryhl@google.com>,
	John Hubbard <jhubbard@nvidia.com>,
	 Alistair Popple <apopple@nvidia.com>,
	Timur Tabi <ttabi@nvidia.com>,
	 nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org,
	 linux-kernel@vger.kernel.org,
	Eliot Courtney <ecourtney@nvidia.com>,
	 Joel Fernandes <joelagnelf@nvidia.com>
Subject: [PATCH 16/16] gpu: nova-core: mm: Add BAR1 memory management self-tests
Date: Wed, 09 Sep 2026 12:59:54 +0900	[thread overview]
Message-ID: <20260909-mmrebase-v1-16-8dd5d4225d2e@nvidia.com> (raw)
In-Reply-To: <20260909-mmrebase-v1-0-8dd5d4225d2e@nvidia.com>

From: Joel Fernandes <joelagnelf@nvidia.com>

Add self-tests for BAR1 access during driver probe when
CONFIG_NOVA_CORE_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.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
[ecourtney: use existing self-test runner and CONFIG_NOVA_CORE_SELFTESTS]
[ecourtney: update for mutable GpuMm, PRAMIN views and VramAddress raw API]
[ecourtney: restore the conditional dead_code expect]
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
 drivers/gpu/nova-core/gpu.rs          |   9 +-
 drivers/gpu/nova-core/mm.rs           |  12 +-
 drivers/gpu/nova-core/mm/bar_user.rs  | 251 ++++++++++++++++++++++++++++++++++
 drivers/gpu/nova-core/mm/pagetable.rs |  24 ++++
 drivers/gpu/nova-core/mm/vmm.rs       |   1 +
 5 files changed, 293 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index f72a92e045e5..f5bdfdea803a 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -482,7 +482,14 @@ pub(crate) fn run_selftests(self: Pin<&mut Self>, pdev: &pci::Device<device::Bou
         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) {
+        if let Err(err) = crate::mm::selftest::run(
+            dev,
+            this.mm,
+            regions,
+            this.bar_user,
+            this.gsp_static_info.bar1_pde_base,
+            this.spec.chipset,
+        ) {
             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 e04497256519..a5bc4042577b 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))]
 
 /// Implements `From` conversions between a frame-number type and `Bounded<u64, N>`.
 ///
@@ -202,6 +202,7 @@ pub(crate) struct VirtualAddress(u64) {
 
 impl VirtualAddress {
     /// Create a new virtual address from a raw value.
+    #[expect(dead_code)]
     pub(crate) const fn new(addr: u64) -> Self {
         Self::from_raw(addr)
     }
@@ -297,7 +298,8 @@ pub(crate) mod selftest {
 
     use kernel::{
         device,
-        sizes::SizeConstants, //
+        sizes::SizeConstants,
+        sync::Arc, //
     };
 
     use super::*;
@@ -307,6 +309,9 @@ pub(crate) fn run(
         dev: &device::Device<device::Bound>,
         mm: &mut GpuMm<'_>,
         usable_fb_regions: &[Range<u64>],
+        bar_user: &Arc<bar_user::BarUser<'_>>,
+        bar1_pdb: u64,
+        chipset: Chipset,
     ) -> Result {
         // VRAM span the self-tests are free to overwrite, from the chosen test base.
         const SELFTEST_SPAN: u64 = u64::SZ_64M;
@@ -325,6 +330,7 @@ pub(crate) fn run(
             return Ok(());
         };
 
-        pramin::selftest::run(dev, mm.pramin_mut(), VramAddress::from_raw(base))
+        pramin::selftest::run(dev, mm.pramin_mut(), VramAddress::from_raw(base))?;
+        bar_user::run_self_test(dev, mm, bar_user, bar1_pdb, chipset)
     }
 }
diff --git a/drivers/gpu/nova-core/mm/bar_user.rs b/drivers/gpu/nova-core/mm/bar_user.rs
index ef1d8e6f8c9c..1bef01d147ad 100644
--- a/drivers/gpu/nova-core/mm/bar_user.rs
+++ b/drivers/gpu/nova-core/mm/bar_user.rs
@@ -31,6 +31,9 @@
     num::IntoSafeCast,
 };
 
+#[cfg(CONFIG_NOVA_CORE_SELFTESTS)]
+use kernel::device;
+
 /// BAR1 user interface for virtual memory mappings.
 ///
 /// Owns the [`Vmm`] for the BAR1 address space.
@@ -84,6 +87,7 @@ pub(crate) struct BarUserAccess<'gpu> {
     mapped: Option<MappedRange>,
 }
 
+#[expect(dead_code)]
 impl BarUserAccess<'_> {
     /// Tear down the BAR1 mapping.
     pub(crate) fn release(mut self, mm: &mut GpuMm<'_>) -> Result {
@@ -169,3 +173,250 @@ fn drop(&mut self) {
         // identifying the leaked VA range.
     }
 }
+
+/// 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_CORE_SELFTESTS)]
+pub(crate) fn run_self_test(
+    dev: &device::Device<device::Bound>,
+    mm: &mut GpuMm<'_>,
+    bar_user: &Arc<BarUser<'_>>,
+    bar1_pdb: u64,
+    chipset: Chipset,
+) -> Result {
+    use kernel::gpu::buddy::{
+        GpuBuddyAllocFlags,
+        GpuBuddyAllocMode, //
+    };
+    use kernel::ptr::Alignment;
+    use kernel::sizes::{
+        SZ_16K,
+        SZ_32K,
+        SZ_4K,
+        SZ_64K, //
+    };
+
+    // Test patterns.
+    const PATTERN_PRAMIN: u32 = 0xDEAD_BEEF;
+    const PATTERN_BAR1: u32 = 0xCAFE_BABE;
+
+    let bar1 = bar_user.bar1;
+    dev_info!(dev, "MM: Starting self-test...\n");
+
+    let pdb_addr = VramAddress::from_raw(bar1_pdb);
+
+    // Check if initial page tables are in VRAM.
+    if crate::mm::pagetable::check_pdb_valid(mm.pramin_mut(), pdb_addr, chipset).is_err() {
+        dev_info!(dev, "MM: Self-test SKIPPED - no valid VRAM page tables\n");
+        return Ok(());
+    }
+
+    // Set up a test page from the buddy allocator.
+    let test_page_blocks = KBox::pin_init(
+        mm.buddy().alloc_blocks(
+            GpuBuddyAllocMode::Simple,
+            SZ_4K.into_safe_cast(),
+            Alignment::new::<SZ_4K>(),
+            GpuBuddyAllocFlags::default(),
+        ),
+        GFP_KERNEL,
+    )?;
+    let test_vram_offset = test_page_blocks.iter().next().ok_or(ENOMEM)?.offset();
+    let test_vram = VramAddress::from_raw(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, chipset.mmu_version(), SZ_64K.into_safe_cast())?;
+
+    // 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 the PRAMIN to BAR1 read test.
+    let vfn_offset: usize = test_vfn.raw().into_safe_cast();
+    let bar1_base_offset = vfn_offset.checked_mul(PAGE_SIZE).ok_or(EOVERFLOW)?;
+    let bar1_read_offset: usize = bar1_base_offset + 0x100;
+    let vram_read_addr = test_vram + 0x100;
+
+    // Test 1: Write via PRAMIN, read via BAR1.
+    mm.pramin_mut()
+        .window_at::<u32>(vram_read_addr)?
+        .view()
+        .write_val(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
+    };
+
+    // Cleanup - invalidate PTE.
+    vmm.unmap_pages(mm, mapped)?;
+
+    // Test 2: 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 test2_passed = if readback == Some(test_pfn) {
+        true
+    } else {
+        dev_err!(dev, "MM: Test 2 FAILED - Two-phase map readback mismatch\n");
+        false
+    };
+    vmm.unmap_pages(mm, mapped2)?;
+
+    // Test 3: Range-constrained allocation with a hole — exercises block.size()-driven
+    // BAR1 mapping. A 4K hole is punched at base+16K, then a single 32K allocation
+    // is requested within [base, base+36K). The buddy allocator must split around the
+    // hole, returning multiple blocks (expected: {16K, 4K, 8K, 4K} = 32K total).
+    // Each block is mapped into BAR1 and verified via PRAMIN read-back.
+    //
+    // Address layout (base = 0x10000):
+    //   [    16K    ] [HOLE 4K] [4K] [ 8K ] [4K]
+    //   0x10000       0x14000  0x15000 0x16000 0x18000 0x19000
+    let range_base: u64 = SZ_64K.into_safe_cast();
+    let sz_4k: u64 = SZ_4K.into_safe_cast();
+    let sz_16k: u64 = SZ_16K.into_safe_cast();
+    let sz_32k_4k: u64 = (SZ_32K + SZ_4K).into_safe_cast();
+
+    // Punch a 4K hole at base+16K so the subsequent 32K allocation must split.
+    let _hole = KBox::pin_init(
+        mm.buddy().alloc_blocks(
+            GpuBuddyAllocMode::Range(range_base + sz_16k..range_base + sz_16k + sz_4k),
+            SZ_4K.into_safe_cast(),
+            Alignment::new::<SZ_4K>(),
+            GpuBuddyAllocFlags::default(),
+        ),
+        GFP_KERNEL,
+    )?;
+
+    // Allocate 32K within [base, base+36K). The hole forces the allocator to return
+    // split blocks whose sizes are determined by buddy alignment.
+    let blocks = KBox::pin_init(
+        mm.buddy().alloc_blocks(
+            GpuBuddyAllocMode::Range(range_base..range_base + sz_32k_4k),
+            SZ_32K.into_safe_cast(),
+            Alignment::new::<SZ_4K>(),
+            GpuBuddyAllocFlags::default(),
+        ),
+        GFP_KERNEL,
+    )?;
+
+    let mut test3_passed = true;
+    let mut total_size = 0usize;
+
+    for block in blocks.iter() {
+        total_size += IntoSafeCast::<usize>::into_safe_cast(block.size());
+
+        // Map all pages of this block.
+        let page_size: u64 = PAGE_SIZE.into_safe_cast();
+        let num_pages: usize = (block.size() / page_size).into_safe_cast();
+
+        let mut pfns = KVec::new();
+        for j in 0..num_pages {
+            let j_u64: u64 = j.into_safe_cast();
+            pfns.push(
+                Pfn::from(VramAddress::from_raw(
+                    block.offset() + j_u64.checked_mul(page_size).ok_or(EOVERFLOW)?,
+                )),
+                GFP_KERNEL,
+            )?;
+        }
+
+        let mapped = vmm.map_pages(mm, &pfns, None, true)?;
+        let bar1_base_vfn: usize = mapped.vfn_start.raw().into_safe_cast();
+        let bar1_base = bar1_base_vfn.checked_mul(PAGE_SIZE).ok_or(EOVERFLOW)?;
+
+        for j in 0..num_pages {
+            let page_bar1_off = bar1_base + j * PAGE_SIZE;
+            let j_u64: u64 = j.into_safe_cast();
+            let page_phys = block.offset()
+                + j_u64
+                    .checked_mul(PAGE_SIZE.into_safe_cast())
+                    .ok_or(EOVERFLOW)?;
+
+            bar1.try_write32(PATTERN_BAR1, page_bar1_off)?;
+
+            let pramin_val = mm
+                .pramin_mut()
+                .window_at::<u32>(VramAddress::from_raw(page_phys))?
+                .view()
+                .read_val();
+
+            if pramin_val != PATTERN_BAR1 {
+                dev_err!(
+                    dev,
+                    "MM: Test 3 FAILED block offset {:#x} page {} (val={:#x})\n",
+                    block.offset(),
+                    j,
+                    pramin_val
+                );
+                test3_passed = false;
+            }
+        }
+
+        vmm.unmap_pages(mm, mapped)?;
+    }
+
+    // Verify aggregate: all returned block sizes must sum to allocation size.
+    if total_size != SZ_32K {
+        dev_err!(
+            dev,
+            "MM: Test 3 FAILED - total size {} != expected {}\n",
+            total_size,
+            SZ_32K
+        );
+        test3_passed = false;
+    }
+
+    // Release Tests 1-3's Vmm before Test 4 constructs a fresh BarUser on
+    // the same PDB.
+    drop(vmm);
+
+    // Test 4: Exercise `BarUser::map()` end-to-end.
+    let bar_user = Arc::pin_init(
+        BarUser::new(pdb_addr, chipset, SZ_64K.into_safe_cast(), bar1)?,
+        GFP_KERNEL,
+    )?;
+    let access = bar_user.map(mm, &[test_pfn], true)?;
+
+    // Write pattern via PRAMIN, read via BarUserAccess.
+    mm.pramin_mut()
+        .window_at::<u32>(test_vram)?
+        .view()
+        .write_val(PATTERN_BAR1);
+
+    let readback = access.try_read32(0)?;
+    let test4_passed = if readback == PATTERN_BAR1 {
+        true
+    } else {
+        dev_err!(
+            dev,
+            "MM: Test 4 FAILED - Expected {:#010x}, got {:#010x}\n",
+            PATTERN_BAR1,
+            readback
+        );
+        false
+    };
+    access.release(mm)?;
+
+    if test1_passed && test2_passed && test3_passed && test4_passed {
+        dev_info!(dev, "MM: All self-tests PASSED\n");
+        Ok(())
+    } else {
+        dev_err!(dev, "MM: Self-tests FAILED\n");
+        Err(EIO)
+    }
+}
diff --git a/drivers/gpu/nova-core/mm/pagetable.rs b/drivers/gpu/nova-core/mm/pagetable.rs
index ffc69fbdb067..de2f8ea5aeef 100644
--- a/drivers/gpu/nova-core/mm/pagetable.rs
+++ b/drivers/gpu/nova-core/mm/pagetable.rs
@@ -396,3 +396,27 @@ fn from(val: AperturePde) -> Self {
         Bounded::from_expr(val as u64 & 0x3)
     }
 }
+
+/// Check if the PDB has valid, VRAM-backed page tables.
+#[cfg(CONFIG_NOVA_CORE_SELFTESTS)]
+fn check_pdb_inner<M: MmuConfig>(pramin: &mut pramin::Pramin<'_>, pdb_addr: VramAddress) -> Result {
+    let raw = pramin.window_at::<u64>(pdb_addr)?.view().read_val();
+
+    if !M::Pde::from_raw(raw).is_valid_vram() {
+        return Err(ENOENT);
+    }
+    Ok(())
+}
+
+/// Check if the PDB has valid, VRAM-backed page tables, dispatching by MMU version.
+#[cfg(CONFIG_NOVA_CORE_SELFTESTS)]
+pub(super) fn check_pdb_valid(
+    pramin: &mut pramin::Pramin<'_>,
+    pdb_addr: VramAddress,
+    chipset: crate::gpu::Chipset,
+) -> Result {
+    match MmuVersion::from(chipset.arch()) {
+        MmuVersion::V2 => check_pdb_inner::<MmuV2>(pramin, pdb_addr),
+        MmuVersion::V3 => check_pdb_inner::<MmuV3>(pramin, pdb_addr),
+    }
+}
diff --git a/drivers/gpu/nova-core/mm/vmm.rs b/drivers/gpu/nova-core/mm/vmm.rs
index 411710d03f7a..51b500a27233 100644
--- a/drivers/gpu/nova-core/mm/vmm.rs
+++ b/drivers/gpu/nova-core/mm/vmm.rs
@@ -126,6 +126,7 @@ fn drop(&mut self) {
 /// Directory Base (`PDB`) address. Used for Channel, BAR1 and BAR2 mappings.
 pub(crate) struct Vmm {
     /// Page Directory Base address for this address space.
+    #[expect(dead_code)]
     pdb_addr: VramAddress,
     /// Page table walker for reading existing mappings.
     pt_walk: PtWalk,

-- 
2.55.0


  parent reply	other threads:[~2026-09-09  4:01 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-09  3:59 [PATCH 00/16] gpu: nova-core: GPU page table, vmm, and bar1 mapping Eliot Courtney
2026-09-09  3:59 ` [PATCH 01/16] gpu: nova-core: mm: Add common types for virtual memory management Eliot Courtney
2026-09-09  3:59 ` [PATCH 02/16] gpu: nova-core: mm: Add buddy allocator and TLB to GpuMm Eliot Courtney
2026-09-09  4:09   ` sashiko-bot
2026-09-09  3:59 ` [PATCH 03/16] gpu: nova-core: mm: Add common types for all page table formats Eliot Courtney
2026-09-09  3:59 ` [PATCH 04/16] gpu: nova-core: mm: pagetable: Add PteOps trait Eliot Courtney
2026-09-09  4:07   ` sashiko-bot
2026-09-09  3:59 ` [PATCH 05/16] gpu: nova-core: mm: pagetable: Add PdeOps trait Eliot Courtney
2026-09-09  4:08   ` sashiko-bot
2026-09-09  3:59 ` [PATCH 06/16] gpu: nova-core: mm: pagetable: Add DualPdeOps trait Eliot Courtney
2026-09-09  3:59 ` [PATCH 07/16] gpu: nova-core: mm: Add MMU v2 page table types Eliot Courtney
2026-09-09  4:12   ` sashiko-bot
2026-09-09 18:43   ` Danilo Krummrich
2026-09-09  3:59 ` [PATCH 08/16] gpu: nova-core: mm: Add MMU v3 " Eliot Courtney
2026-09-09  3:59 ` [PATCH 09/16] gpu: nova-core: mm: pagetable: Add MmuConfig trait Eliot Courtney
2026-09-09  4:12   ` sashiko-bot
2026-09-09  3:59 ` [PATCH 10/16] gpu: nova-core: mm: Add page table walker for MMU v2/v3 Eliot Courtney
2026-09-09  4:07   ` sashiko-bot
2026-09-09  3:59 ` [PATCH 11/16] gpu: nova-core: mm: Add Virtual Memory Manager Eliot Courtney
2026-09-09  3:59 ` [PATCH 12/16] gpu: nova-core: mm: Add virtual address range tracking to VMM Eliot Courtney
2026-09-09  4:18   ` sashiko-bot
2026-09-09 19:32   ` Danilo Krummrich
2026-09-09  3:59 ` [PATCH 13/16] gpu: nova-core: mm: Add multi-page mapping API " Eliot Courtney
2026-09-09  4:17   ` sashiko-bot
2026-09-09 19:58   ` Danilo Krummrich
2026-09-10  0:47   ` Alistair Popple
2026-09-09  3:59 ` [PATCH 14/16] gpu: nova-core: Add BAR1 aperture type and size constant Eliot Courtney
2026-09-09  4:14   ` sashiko-bot
2026-09-09  3:59 ` [PATCH 15/16] gpu: nova-core: mm: Add BAR1 user interface Eliot Courtney
2026-09-09  4:16   ` sashiko-bot
2026-09-09 20:13   ` Danilo Krummrich
2026-09-09  3:59 ` Eliot Courtney [this message]
2026-09-09  4:18   ` [PATCH 16/16] gpu: nova-core: mm: Add BAR1 memory management self-tests sashiko-bot
2026-09-09 21:11 ` [PATCH 00/16] gpu: nova-core: GPU page table, vmm, and bar1 mapping Danilo Krummrich

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260909-mmrebase-v1-16-8dd5d4225d2e@nvidia.com \
    --to=ecourtney@nvidia.com \
    --cc=acourbot@nvidia.com \
    --cc=aliceryhl@google.com \
    --cc=apopple@nvidia.com \
    --cc=dakr@kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=jhubbard@nvidia.com \
    --cc=joelagnelf@nvidia.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nova-gpu@lists.linux.dev \
    --cc=ttabi@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.