Linux Documentation
 help / color / mirror / Atom feed
* [PATCH v1 12/16] gpu: nova-core: mm: Add virtual address range tracking to VMM
From: Joel Fernandes @ 2026-05-18 18:11 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518181126.2493572-1-joelagnelf@nvidia.com>

Add virtual address range tracking to the VMM using a maple tree
allocator. This enables contiguous virtual address range allocation
for mappings.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/mm/vmm.rs | 83 +++++++++++++++++++++++++++++----
 1 file changed, 74 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/nova-core/mm/vmm.rs b/drivers/gpu/nova-core/mm/vmm.rs
index 3e18adc23b68..05ff77c5f888 100644
--- a/drivers/gpu/nova-core/mm/vmm.rs
+++ b/drivers/gpu/nova-core/mm/vmm.rs
@@ -9,18 +9,27 @@
 use kernel::{
     device,
     gpu::buddy::AllocatedBlocks,
+    maple_tree::MapleTreeAlloc,
     prelude::*, //
 };
 
-use crate::mm::{
-    pagetable::{
-        walk::{PtWalk, WalkResult},
-        MmuVersion, //
+use core::ops::Range;
+
+use crate::{
+    mm::{
+        pagetable::{
+            walk::{PtWalk, WalkResult},
+            MmuVersion, //
+        },
+        GpuMm,
+        Pfn,
+        Vfn,
+        VramAddress,
+        PAGE_SIZE, //
+    },
+    num::{
+        IntoSafeCast, //
     },
-    GpuMm,
-    Pfn,
-    Vfn,
-    VramAddress, //
 };
 
 /// Virtual Memory Manager for a GPU address space.
@@ -35,18 +44,74 @@ pub(crate) struct Vmm {
     mmu_version: MmuVersion,
     /// Page table allocations required for mappings.
     page_table_allocs: KVec<Pin<KBox<AllocatedBlocks>>>,
+    /// Maple tree allocator for virtual address range tracking.
+    virt_alloc: Pin<KBox<MapleTreeAlloc<()>>>,
+    /// Total number of pages in the virtual address space.
+    va_pages: usize,
 }
 
 impl Vmm {
     /// Create a new [`Vmm`] for the given Page Directory Base address.
-    pub(crate) fn new(pdb_addr: VramAddress, mmu_version: MmuVersion) -> Result<Self> {
+    ///
+    /// The [`Vmm`] will manage a virtual address space of `va_size` bytes.
+    pub(crate) fn new(
+        pdb_addr: VramAddress,
+        mmu_version: MmuVersion,
+        va_size: u64,
+    ) -> Result<Self> {
+        let page_size: u64 = PAGE_SIZE.into_safe_cast();
+        let va_pages: usize = (va_size / page_size).into_safe_cast();
+        let virt_alloc = KBox::pin_init(MapleTreeAlloc::<()>::new(), GFP_KERNEL)?;
+
         Ok(Self {
             pdb_addr,
             mmu_version,
             page_table_allocs: KVec::new(),
+            virt_alloc,
+            va_pages,
         })
     }
 
+    /// Allocate a contiguous virtual frame number range.
+    ///
+    /// # Arguments
+    ///
+    /// - `num_pages`: Number of pages to allocate.
+    /// - `va_range`: `None` = allocate anywhere, `Some(range)` = constrain allocation to the given
+    ///   range.
+    fn alloc_vfn_range(&self, num_pages: usize, va_range: Option<Range<u64>>) -> Result<Vfn> {
+        let page_size: u64 = PAGE_SIZE.into_safe_cast();
+
+        let start_vfn = match va_range {
+            Some(r) => {
+                let num_pages_u64: u64 = num_pages.into_safe_cast();
+                let size = num_pages_u64.checked_mul(page_size).ok_or(EOVERFLOW)?;
+                let range_size = r.end.checked_sub(r.start).ok_or(EOVERFLOW)?;
+                if range_size != size {
+                    return Err(EINVAL);
+                }
+                let start_vfn: usize = (r.start / page_size).into_safe_cast();
+                let end_vfn: usize = (r.end / page_size).into_safe_cast();
+                self.virt_alloc
+                    .insert_range(start_vfn..end_vfn, (), GFP_KERNEL)?;
+                start_vfn
+            }
+            None => self
+                .virt_alloc
+                .alloc_range(num_pages, (), ..self.va_pages, GFP_KERNEL)?,
+        };
+
+        Ok(Vfn::new(start_vfn.into_safe_cast()))
+    }
+
+    /// Free a virtual frame number range back to the maple tree.
+    fn free_vfn(&self, vfn: Vfn) {
+        let vfn_index: usize = vfn.raw().into_safe_cast();
+        if self.virt_alloc.erase(vfn_index).is_none() {
+            kernel::pr_warn!("free_vfn: VFN {} not found in maple tree\n", vfn_index);
+        }
+    }
+
     /// Read the [`Pfn`] for a mapped [`Vfn`] if one is mapped.
     pub(super) fn read_mapping(
         &self,
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 11/16] gpu: nova-core: mm: Add Virtual Memory Manager
From: Joel Fernandes @ 2026-05-18 18:11 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518181126.2493572-1-joelagnelf@nvidia.com>

Add the Virtual Memory Manager (VMM) infrastructure for GPU address
space management. Each Vmm instance manages a single address space
identified by its Page Directory Base (PDB) address, used for Channel,
BAR1 and BAR2 mappings.

Mapping APIs and virtual address range tracking are added in later
commits.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/mm.rs     |  1 +
 drivers/gpu/nova-core/mm/vmm.rs | 64 +++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+)
 create mode 100644 drivers/gpu/nova-core/mm/vmm.rs

diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs
index 66cc33389159..502c7fdceba2 100644
--- a/drivers/gpu/nova-core/mm.rs
+++ b/drivers/gpu/nova-core/mm.rs
@@ -34,6 +34,7 @@ macro_rules! impl_pfn_bounded {
 pub(super) mod pagetable;
 pub(crate) mod pramin;
 pub(super) mod tlb;
+pub(super) mod vmm;
 
 use core::ops::Range;
 
diff --git a/drivers/gpu/nova-core/mm/vmm.rs b/drivers/gpu/nova-core/mm/vmm.rs
new file mode 100644
index 000000000000..3e18adc23b68
--- /dev/null
+++ b/drivers/gpu/nova-core/mm/vmm.rs
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Virtual Memory Manager for NVIDIA GPU page table management.
+//!
+//! The [`Vmm`] provides high-level page mapping and unmapping operations for GPU
+//! virtual address spaces (Channels, BAR1, BAR2). It wraps the page table walker
+//! and handles TLB flushing after modifications.
+
+use kernel::{
+    device,
+    gpu::buddy::AllocatedBlocks,
+    prelude::*, //
+};
+
+use crate::mm::{
+    pagetable::{
+        walk::{PtWalk, WalkResult},
+        MmuVersion, //
+    },
+    GpuMm,
+    Pfn,
+    Vfn,
+    VramAddress, //
+};
+
+/// Virtual Memory Manager for a GPU address space.
+///
+/// Each [`Vmm`] instance manages a single address space identified by its Page
+/// Directory Base (`PDB`) address. The [`Vmm`] is used for Channel, BAR1 and
+/// BAR2 mappings.
+pub(crate) struct Vmm {
+    /// Page Directory Base address for this address space.
+    pdb_addr: VramAddress,
+    /// MMU version used for page table layout.
+    mmu_version: MmuVersion,
+    /// Page table allocations required for mappings.
+    page_table_allocs: KVec<Pin<KBox<AllocatedBlocks>>>,
+}
+
+impl Vmm {
+    /// Create a new [`Vmm`] for the given Page Directory Base address.
+    pub(crate) fn new(pdb_addr: VramAddress, mmu_version: MmuVersion) -> Result<Self> {
+        Ok(Self {
+            pdb_addr,
+            mmu_version,
+            page_table_allocs: KVec::new(),
+        })
+    }
+
+    /// Read the [`Pfn`] for a mapped [`Vfn`] if one is mapped.
+    pub(super) fn read_mapping(
+        &self,
+        dev: &device::Device<device::Bound>,
+        mm: &GpuMm,
+        vfn: Vfn,
+    ) -> Result<Option<Pfn>> {
+        let walker = PtWalk::new(self.pdb_addr, self.mmu_version);
+
+        match walker.walk_to_pte(dev, mm, vfn)? {
+            WalkResult::Mapped { pfn, .. } => Ok(Some(pfn)),
+            WalkResult::Unmapped { .. } | WalkResult::PageTableMissing => Ok(None),
+        }
+    }
+}
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 06/16] gpu: nova-core: mm: pagetable: Add DualPdeOps trait
From: Joel Fernandes @ 2026-05-18 18:11 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518181126.2493572-1-joelagnelf@nvidia.com>

Introduce a trait for 128-bit Dual Page Directory Entries. The
`read()`/`write()` helpers issue two 64-bit accesses through a
`PraminWindow` to load/store the 128-bit value.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/mm/pagetable.rs | 34 +++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/drivers/gpu/nova-core/mm/pagetable.rs b/drivers/gpu/nova-core/mm/pagetable.rs
index 1c94b3afa8b2..7ea090024d91 100644
--- a/drivers/gpu/nova-core/mm/pagetable.rs
+++ b/drivers/gpu/nova-core/mm/pagetable.rs
@@ -159,6 +159,40 @@ fn is_valid_vram(&self) -> bool {
     }
 }
 
+/// Operations on Dual Page Directory Entries (128-bit `DualPde`s).
+pub(super) trait DualPdeOps: Copy + core::fmt::Debug {
+    /// Create a `DualPde` from raw 128-bit value (two `u64`s).
+    fn from_raw(big: u64, small: u64) -> Self;
+
+    /// Create a `DualPde` with only the small page table pointer set.
+    fn new_small(table_pfn: Pfn) -> Self;
+
+    /// Check if the small page table pointer is valid.
+    fn has_small(&self) -> bool;
+
+    /// Get the small page table VRAM address.
+    fn small_vram_address(&self) -> VramAddress;
+
+    /// Get the raw `u64` value of the big PDE.
+    fn big_raw_u64(&self) -> u64;
+
+    /// Get the raw `u64` value of the small PDE.
+    fn small_raw_u64(&self) -> u64;
+
+    /// Read a dual PDE (128-bit) from VRAM.
+    fn read(window: &mut pramin::PraminWindow<'_>, addr: VramAddress) -> Result<Self> {
+        let lo = window.try_read64(addr)?;
+        let hi = window.try_read64(addr + 8)?;
+        Ok(Self::from_raw(lo, hi))
+    }
+
+    /// Write this dual PDE (128-bit) to VRAM.
+    fn write(&self, window: &mut pramin::PraminWindow<'_>, addr: VramAddress) -> Result {
+        window.try_write64(addr, self.big_raw_u64())?;
+        window.try_write64(addr + 8, self.small_raw_u64())
+    }
+}
+
 /// Memory aperture for Page Table Entries (`PTE`s).
 ///
 /// Determines which memory region the `PTE` points to.
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 03/16] gpu: nova-core: mm: Add common types for all page table formats
From: Joel Fernandes @ 2026-05-18 18:11 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518181126.2493572-1-joelagnelf@nvidia.com>

Add common page table types shared between MMU v2 and v3. These types
are hardware-agnostic and used by both MMU versions.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/mm.rs           |   1 +
 drivers/gpu/nova-core/mm/pagetable.rs | 158 ++++++++++++++++++++++++++
 2 files changed, 159 insertions(+)
 create mode 100644 drivers/gpu/nova-core/mm/pagetable.rs

diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs
index ea415a88b221..66cc33389159 100644
--- a/drivers/gpu/nova-core/mm.rs
+++ b/drivers/gpu/nova-core/mm.rs
@@ -31,6 +31,7 @@ macro_rules! impl_pfn_bounded {
     };
 }
 
+pub(super) mod pagetable;
 pub(crate) mod pramin;
 pub(super) mod tlb;
 
diff --git a/drivers/gpu/nova-core/mm/pagetable.rs b/drivers/gpu/nova-core/mm/pagetable.rs
new file mode 100644
index 000000000000..ed0f3d731c63
--- /dev/null
+++ b/drivers/gpu/nova-core/mm/pagetable.rs
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Common page table types shared between MMU v2 and v3.
+//!
+//! This module provides foundational types used by both MMU versions:
+//! - Page table level hierarchy
+//! - Memory aperture types for PDEs and PTEs
+
+#![expect(dead_code)]
+
+use kernel::num::Bounded;
+
+use crate::gpu::Architecture;
+
+/// Extracts the page table index at a given level from a virtual address.
+pub(super) trait VaLevelIndex {
+    /// Return the page table index at `level` for this virtual address.
+    fn level_index(&self, level: u64) -> u64;
+}
+
+/// MMU version enumeration.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum MmuVersion {
+    /// MMU v2 for Turing/Ampere/Ada.
+    V2,
+    /// MMU v3 for Hopper and later.
+    V3,
+}
+
+impl From<Architecture> for MmuVersion {
+    fn from(arch: Architecture) -> Self {
+        match arch {
+            Architecture::Turing | Architecture::Ampere | Architecture::Ada => Self::V2,
+            Architecture::Hopper | Architecture::BlackwellGB10x | Architecture::BlackwellGB20x => {
+                Self::V3
+            }
+        }
+    }
+}
+
+/// Page Table Level hierarchy for MMU v2/v3.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(super) enum PageTableLevel {
+    /// Level 0 - Page Directory Base (root).
+    Pdb,
+    /// Level 1 - Intermediate page directory.
+    L1,
+    /// Level 2 - Intermediate page directory.
+    L2,
+    /// Level 3 - Intermediate page directory or dual PDE (version-dependent).
+    L3,
+    /// Level 4 - PTE level for v2, intermediate page directory for v3.
+    L4,
+    /// Level 5 - PTE level used for MMU v3 only.
+    L5,
+}
+
+impl PageTableLevel {
+    /// Number of entries per page table (512 for 4KB pages).
+    pub(super) const ENTRIES_PER_TABLE: usize = 512;
+
+    /// Get the next level in the hierarchy.
+    pub(super) const fn next(&self) -> Option<PageTableLevel> {
+        match self {
+            Self::Pdb => Some(Self::L1),
+            Self::L1 => Some(Self::L2),
+            Self::L2 => Some(Self::L3),
+            Self::L3 => Some(Self::L4),
+            Self::L4 => Some(Self::L5),
+            Self::L5 => None,
+        }
+    }
+
+    /// Convert level to index.
+    pub(super) const fn as_index(&self) -> u64 {
+        match self {
+            Self::Pdb => 0,
+            Self::L1 => 1,
+            Self::L2 => 2,
+            Self::L3 => 3,
+            Self::L4 => 4,
+            Self::L5 => 5,
+        }
+    }
+}
+
+/// Memory aperture for Page Table Entries (`PTE`s).
+///
+/// Determines which memory region the `PTE` points to.
+#[repr(u8)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub(super) enum AperturePte {
+    /// Local video memory (VRAM).
+    #[default]
+    VideoMemory = 0,
+    /// Peer GPU's video memory.
+    PeerMemory = 1,
+    /// System memory with cache coherence.
+    SystemCoherent = 2,
+    /// System memory without cache coherence.
+    SystemNonCoherent = 3,
+}
+
+// TODO[FPRI]: Replace with `#[derive(FromPrimitive)]` when available.
+impl From<Bounded<u64, 2>> for AperturePte {
+    fn from(val: Bounded<u64, 2>) -> Self {
+        match *val {
+            0 => Self::VideoMemory,
+            1 => Self::PeerMemory,
+            2 => Self::SystemCoherent,
+            3 => Self::SystemNonCoherent,
+            _ => Self::VideoMemory,
+        }
+    }
+}
+
+// TODO[FPRI]: Replace with `#[derive(ToPrimitive)]` when available.
+impl From<AperturePte> for Bounded<u64, 2> {
+    fn from(val: AperturePte) -> Self {
+        Bounded::from_expr(val as u64 & 0x3)
+    }
+}
+
+/// Memory aperture for Page Directory Entries (`PDE`s).
+///
+/// Note: For `PDE`s, `Invalid` (0) means the entry is not valid.
+#[repr(u8)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub(super) enum AperturePde {
+    /// Invalid/unused entry.
+    #[default]
+    Invalid = 0,
+    /// Page table is in video memory.
+    VideoMemory = 1,
+    /// Page table is in system memory with coherence.
+    SystemCoherent = 2,
+    /// Page table is in system memory without coherence.
+    SystemNonCoherent = 3,
+}
+
+// TODO[FPRI]: Replace with `#[derive(FromPrimitive)]` when available.
+impl From<Bounded<u64, 2>> for AperturePde {
+    fn from(val: Bounded<u64, 2>) -> Self {
+        match *val {
+            1 => Self::VideoMemory,
+            2 => Self::SystemCoherent,
+            3 => Self::SystemNonCoherent,
+            _ => Self::Invalid,
+        }
+    }
+}
+
+// TODO[FPRI]: Replace with `#[derive(ToPrimitive)]` when available.
+impl From<AperturePde> for Bounded<u64, 2> {
+    fn from(val: AperturePde) -> Self {
+        Bounded::from_expr(val as u64 & 0x3)
+    }
+}
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 02/16] gpu: nova-core: mm: Add buddy allocator and TLB to GpuMm
From: Joel Fernandes @ 2026-05-18 18:11 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518181126.2493572-1-joelagnelf@nvidia.com>

Extend GpuMm with the remaining two memory-management components:

- Buddy allocator for VRAM allocation.
- TLB manager for translation buffer operations.

PRAMIN was added in an earlier commit; this completes the centralized
ownership model with accessor methods for each component.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/Kconfig         |   1 +
 drivers/gpu/nova-core/gpu.rs          |  22 ++++-
 drivers/gpu/nova-core/gsp/commands.rs |   1 -
 drivers/gpu/nova-core/mm.rs           |  27 ++++++
 drivers/gpu/nova-core/mm/tlb.rs       | 130 ++++++++++++++++++++++++++
 drivers/gpu/nova-core/regs.rs         |  65 +++++++++++++
 6 files changed, 244 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/nova-core/mm/tlb.rs

diff --git a/drivers/gpu/nova-core/Kconfig b/drivers/gpu/nova-core/Kconfig
index abf10e82647b..8eebb430856a 100644
--- a/drivers/gpu/nova-core/Kconfig
+++ b/drivers/gpu/nova-core/Kconfig
@@ -5,6 +5,7 @@ config NOVA_CORE
 	depends on RUST
 	depends on !CPU_BIG_ENDIAN
 	select AUXILIARY_BUS
+	select GPU_BUDDY
 	select RUST_FW_LOADER_ABSTRACTIONS
 	default n
 	help
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index aa047fe91054..f789d956cc49 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -4,11 +4,16 @@
     device,
     devres::Devres,
     fmt,
+    gpu::buddy::GpuBuddyParams,
     io::Io,
     num::Bounded,
     pci,
     prelude::*,
-    sizes::SizeConstants,
+    ptr::Alignment,
+    sizes::{
+        SizeConstants,
+        SZ_4K, //
+    },
     sync::Arc, //
 };
 
@@ -305,6 +310,13 @@ pub(crate) fn new<'a>(
             gsp_static_info: gsp
                 .boot(pdev, bar, spec.chipset, gsp_falcon, sec2_falcon)
                 .inspect(|info| {
+                    dev_info!(
+                        pdev.as_ref(),
+                        "Using FB region: {:#x}..{:#x}\n",
+                        info.usable_fb_region.start,
+                        info.usable_fb_region.end
+                    );
+
                     dev_info!(
                         pdev.as_ref(),
                         "Total physical VRAM: {} MiB\n",
@@ -314,14 +326,22 @@ pub(crate) fn new<'a>(
 
             // Create GPU memory manager owning memory management resources.
             mm: {
+                let usable_vram = &gsp_static_info.usable_fb_region;
+
                 // PRAMIN covers all physical VRAM (including GSP-reserved areas
                 // above the usable region, e.g. the BAR1 page directory).
                 let pramin_vram_region = (0..gsp_static_info.total_fb_end).into_vram_range();
+                let buddy_params = GpuBuddyParams {
+                    base_offset: usable_vram.start,
+                    size: usable_vram.end - usable_vram.start,
+                    chunk_size: Alignment::new::<SZ_4K>(),
+                };
                 Arc::pin_init(
                     GpuMm::new(
                         devres_bar.clone(),
                         pdev.as_ref(),
                         spec.chipset,
+                        buddy_params,
                         pramin_vram_region,
                     )?,
                     GFP_KERNEL,
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 172411d7b475..5abd7950320b 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -194,7 +194,6 @@ fn init(&self) -> impl Init<Self::Command, Self::InitError> {
 pub(crate) struct GetGspStaticInfoReply {
     gpu_name: [u8; 64],
     /// Usable FB (VRAM) region for driver memory allocation.
-    #[expect(dead_code)]
     pub(crate) usable_fb_region: Range<u64>,
     /// End of VRAM.
     pub(crate) total_fb_end: u64,
diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs
index b23667a55ecd..ea415a88b221 100644
--- a/drivers/gpu/nova-core/mm.rs
+++ b/drivers/gpu/nova-core/mm.rs
@@ -32,6 +32,7 @@ macro_rules! impl_pfn_bounded {
 }
 
 pub(crate) mod pramin;
+pub(super) mod tlb;
 
 use core::ops::Range;
 
@@ -39,6 +40,10 @@ macro_rules! impl_pfn_bounded {
     bitfield,
     device,
     devres::Devres,
+    gpu::buddy::{
+        GpuBuddy,
+        GpuBuddyParams, //
+    },
     num::Bounded,
     pci,
     prelude::*,
@@ -51,14 +56,21 @@ macro_rules! impl_pfn_bounded {
     gpu::Chipset, //
 };
 
+pub(crate) use tlb::Tlb;
+
 /// GPU Memory Manager - owns all core MM components.
 ///
 /// Provides centralized ownership of memory management resources:
+/// - [`GpuBuddy`] allocator for VRAM page table allocation.
 /// - [`pramin::Pramin`] for direct VRAM access.
+/// - [`Tlb`] manager for translation buffer flush operations.
 #[pin_data]
 pub(crate) struct GpuMm {
+    buddy: GpuBuddy,
     #[pin]
     pramin: pramin::Pramin,
+    #[pin]
+    tlb: Tlb,
 }
 
 impl GpuMm {
@@ -70,19 +82,34 @@ pub(crate) fn new(
         bar: Arc<Devres<Bar0>>,
         dev: &device::Device<device::Bound>,
         chipset: Chipset,
+        buddy_params: GpuBuddyParams,
         pramin_vram_region: Range<VramAddress>,
     ) -> Result<impl PinInit<Self>> {
+        let buddy = GpuBuddy::new(buddy_params)?;
+        let tlb_init = Tlb::new(bar.clone());
         let pramin_init = pramin::Pramin::new(bar, dev, chipset, pramin_vram_region)?;
 
         Ok(pin_init!(Self {
+            buddy,
             pramin <- pramin_init,
+            tlb <- tlb_init,
         }))
     }
 
+    /// Access the [`GpuBuddy`] allocator.
+    pub(crate) fn buddy(&self) -> &GpuBuddy {
+        &self.buddy
+    }
+
     /// Access the [`pramin::Pramin`].
     pub(crate) fn pramin(&self) -> &pramin::Pramin {
         &self.pramin
     }
+
+    /// Access the [`Tlb`] manager.
+    pub(crate) fn tlb(&self) -> &Tlb {
+        &self.tlb
+    }
 }
 
 /// Run MM subsystem self-tests during probe.
diff --git a/drivers/gpu/nova-core/mm/tlb.rs b/drivers/gpu/nova-core/mm/tlb.rs
new file mode 100644
index 000000000000..1c4f8944a01b
--- /dev/null
+++ b/drivers/gpu/nova-core/mm/tlb.rs
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! TLB (Translation Lookaside Buffer) flush support for GPU MMU.
+//!
+//! After modifying page table entries, the GPU's TLB must be flushed to
+//! ensure the new mappings take effect. This module provides TLB flush
+//! functionality for virtual memory managers.
+//!
+//! # Examples
+//!
+//! ```ignore
+//! use crate::mm::tlb::Tlb;
+//!
+//! fn page_table_update(
+//!     dev: &device::Device<device::Bound>,
+//!     tlb: &Tlb,
+//!     pdb_addr: VramAddress,
+//! ) -> Result<()> {
+//!     // ... modify page tables ...
+//!
+//!     // Flush TLB to make changes visible (polls for completion).
+//!     tlb.flush(dev, pdb_addr)?;
+//!
+//!     Ok(())
+//! }
+//! ```
+
+use kernel::{
+    device,
+    devres::Devres,
+    io::poll::read_poll_timeout,
+    io::Io,
+    new_mutex,
+    prelude::*,
+    sync::{
+        Arc,
+        Mutex, //
+    },
+    time::Delta, //
+};
+
+use crate::{
+    bounded_enum,
+    driver::Bar0,
+    mm::VramAddress,
+    regs, //
+};
+
+bounded_enum! {
+    /// TLB invalidation acknowledgment scope.
+    ///
+    /// Controls how far the hardware waits for the invalidation to propagate
+    /// before clearing the `trigger` bit of `NV_TLB_FLUSH_CTRL`.
+    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
+    pub(crate) enum TlbAckMode with TryFrom<Bounded<u32, 2>> {
+        /// Fire-and-forget: no acknowledgment required.
+        None = 0,
+        /// Wait for acknowledgment from all consumers, including remote GPUs
+        /// reachable over NVLink.
+        ///
+        /// Globally is strictly required only during unmap or permission
+        /// tightening, because the backing memory may be reassigned after the
+        /// flush returns and a stale TLB entry could let the GPU access freed
+        /// memory. For new mapping or relaxing permissions, a stale entry would
+        /// merely cause a redundant fault and retry, so [`TlbAckMode::None`]
+        /// would suffice.
+        Globally = 1,
+        /// Wait for acknowledgment from consumers within the local NVLink
+        /// fabric node only; skip cross-node ack.
+        Intranode = 2,
+    }
+}
+
+/// TLB manager for GPU translation buffer operations.
+#[pin_data]
+pub(crate) struct Tlb {
+    bar: Arc<Devres<Bar0>>,
+    /// TLB flush serialization lock: This lock is designed to be acquired during
+    /// the DMA fence signalling critical path. It should NEVER be held across any
+    /// reclaimable CPU memory allocations because the memory reclaim path can
+    /// call `dma_fence_wait()` (when implemented), which would deadlock if lock held.
+    #[pin]
+    lock: Mutex<()>,
+}
+
+impl Tlb {
+    /// Create a new TLB manager.
+    pub(super) fn new(bar: Arc<Devres<Bar0>>) -> impl PinInit<Self> {
+        pin_init!(Self {
+            bar,
+            lock <- new_mutex!((), "tlb_flush"),
+        })
+    }
+
+    /// Flush the GPU TLB for a specific page directory base.
+    ///
+    /// This invalidates all TLB entries associated with the given PDB address.
+    /// Must be called after modifying page table entries to ensure the GPU sees
+    /// the updated mappings.
+    pub(super) fn flush(
+        &self,
+        dev: &device::Device<device::Bound>,
+        pdb_addr: VramAddress,
+    ) -> Result {
+        let _guard = self.lock.lock();
+        let bar = self.bar.access(dev)?;
+
+        // Write PDB address.
+        bar.write_reg(regs::NV_TLB_FLUSH_PDB_LO::from_pdb_addr(pdb_addr.raw()));
+        bar.write_reg(regs::NV_TLB_FLUSH_PDB_HI::from_pdb_addr(pdb_addr.raw()));
+
+        // Trigger flush.
+        bar.write_reg(
+            regs::NV_TLB_FLUSH_CTRL::zeroed()
+                .with_all_va(true)
+                .with_ack(TlbAckMode::None)
+                .with_trigger(true),
+        );
+
+        // Poll for completion.
+        read_poll_timeout(
+            || Ok(bar.read(regs::NV_TLB_FLUSH_CTRL)),
+            |ctrl: &regs::NV_TLB_FLUSH_CTRL| !ctrl.trigger(),
+            Delta::ZERO,
+            Delta::from_secs(2),
+        )?;
+
+        Ok(())
+    }
+}
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index fb42d96a59b2..277eb1a064f7 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -37,6 +37,7 @@
     },
     mm::{
         pramin::Bar0WindowTarget,
+        tlb::TlbAckMode,
         VramAddress, //
     },
 };
@@ -659,3 +660,67 @@ pub(crate) fn pramin_window_write_base(
         }
     }
 }
+
+// MMU TLB
+
+register! {
+    /// TLB flush register: PDB address lower bits.
+    pub(crate) NV_TLB_FLUSH_PDB_LO(u32) @ 0x00b830a0 {
+        /// PDB address bits [39:8].
+        31:0    pdb_lo => u32;
+    }
+
+    /// TLB flush register: PDB address higher bits.
+    pub(crate) NV_TLB_FLUSH_PDB_HI(u32) @ 0x00b830a4 {
+        /// PDB address bits [47:40].
+        7:0     pdb_hi => u8;
+    }
+
+    /// TLB flush control register.
+    pub(crate) NV_TLB_FLUSH_CTRL(u32) @ 0x00b830b0 {
+        /// Invalidate every VA in the PDB selected by `NV_TLB_FLUSH_PDB_LO/HI`.
+        0:0     all_va => bool;
+        /// Invalidate TLBs for all PDBs (ignores `NV_TLB_FLUSH_PDB_LO/HI`).
+        1:1     all_pdb => bool;
+        /// Restrict the flush to the HUB MMU's TLBs; skip broadcasting to the
+        /// per-GPC L2 TLBs.
+        ///
+        /// The GPU MMU has a two-level TLB hierarchy:
+        /// 1. The *HUB MMU* sits at the top and serves memory requests from
+        ///    "host-side" engines: the host/channel interface, copy engines,
+        ///    display, and BAR1/BAR2 accesses.
+        /// 2. Each GPC (Graphics Processing Cluster — the block that houses
+        ///    shader cores / SMs) has its own L2 TLB that serves requests from
+        ///    the compute and graphics engines inside the cluster.
+        ///
+        /// When set, only the HUB TLBs are invalidated. This is a performance
+        /// optimization for flushes that only affect HUB-side mappings (e.g.
+        /// BAR1/BAR2 windows), where fanning the invalidation out to every
+        /// GPC's L2 TLB would be wasted work. Must be false when flushing
+        /// mappings that may be cached by compute/graphics engines.
+        2:2     hubtlb_only => bool;
+        /// Invalidation acknowledgment scope. See [`TlbAckMode`] for details.
+        8:7     ack ?=> TlbAckMode;
+        /// Write 1 to kick off the flush. Hardware clears this bit when the
+        /// flush completes; reads as 1 while the flush is in progress.
+        31:31   trigger => bool;
+    }
+}
+
+impl NV_TLB_FLUSH_PDB_LO {
+    /// Create a register value from a PDB address.
+    ///
+    /// Extracts bits [39:8] of the address and shifts it right by 8 bits.
+    pub(crate) fn from_pdb_addr(addr: u64) -> Self {
+        Self::zeroed().with_pdb_lo(((addr >> 8) & 0xFFFF_FFFF) as u32)
+    }
+}
+
+impl NV_TLB_FLUSH_PDB_HI {
+    /// Create a register value from a PDB address.
+    ///
+    /// Extracts bits [47:40] of the address and shifts it right by 40 bits.
+    pub(crate) fn from_pdb_addr(addr: u64) -> Self {
+        Self::zeroed().with_pdb_hi(((addr >> 40) & 0xFF) as u8)
+    }
+}
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 00/16] Introduce page table types, vmm and bar1 mapping support
From: Joel Fernandes @ 2026-05-18 18:11 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes

This series introduces page-table types and a Virtual Memory Manager (VMM)
for the nova-core driver, plus a BAR1 user (with selftests) layered on top.
The page-table layer uses trait-based generics (PteOps, PdeOps, DualPdeOps,
MmuConfig) to dispatch between MMU v2 and v3 formats. The Vmm tracks
virtual-address ranges with a maple tree, and the BAR1 user exposes mapped
CPU access to VRAM through a small allocation API exercised by the selftests.

This series is based on drm-rust-next.

It depends on the prerequisite series "Introduce nova-core mm prerequisites"
posted alongside this one:
https://lore.kernel.org/all/20260518180342.2387845-1-joelagnelf@nvidia.com/
Please apply the prerequisite series first, or pull the git tag below which
contains everything.

Dependencies (not yet merged):

- Alexandre Courbot's bitfield series. Tested on v2:
  https://lore.kernel.org/all/20260409-bitfield-v2-0-23ac400071cb@nvidia.com/
  A newer v3 of bitfield is available and should also work (haven't tested):
  https://lore.kernel.org/all/20260501-bitfield-v3-0-aa1076c3337d@nvidia.com/

- rust: maple_tree: implement Send and Sync for MapleTree (v3):
  https://lore.kernel.org/all/20260511143604.3848176-1-joelagnelf@nvidia.com/

The git tree (containing the dependencies above, the prerequisite series
"Introduce nova-core mm prerequisites", and this series) can be found at:
git://git.kernel.org/pub/scm/linux/kernel/git/jfern/linux.git (tag: nova-mm-v1-20260518)

Change log:

Changes from v12 to v1 (split-out):

- Part 2 of 2; depends on the prereq series posted just before:
  https://lore.kernel.org/all/20260518180342.2387845-1-joelagnelf@nvidia.com/
- Kept the virtual-memory portion of v12's "Add common memory management types" patch here as "Add common types for virtual memory management".
- Folded v12's "Add TLB flush support" into a new "Add buddy allocator and TLB to GpuMm" patch.
- Split v12's "Add page table entry operation traits" into PteOps, PdeOps, and DualPdeOps trait patches.
- Extracted "Add MmuConfig trait" into its own patch.
- Moved "rust: maple_tree: Send and Sync" out as a standalone dependency; VMM's maple-tree-backed range tracking remains.
- Smaller code touch-ups across most carried-over patches.

Link to v12: https://lore.kernel.org/all/20260425211454.174696-1-joelagnelf@nvidia.com/

Joel Fernandes (16):
  gpu: nova-core: mm: Add common types for virtual memory management
  gpu: nova-core: mm: Add buddy allocator and TLB to GpuMm
  gpu: nova-core: mm: Add common types for all page table formats
  gpu: nova-core: mm: pagetable: Add PteOps trait
  gpu: nova-core: mm: pagetable: Add PdeOps trait
  gpu: nova-core: mm: pagetable: Add DualPdeOps trait
  gpu: nova-core: mm: Add MMU v2 page table types
  gpu: nova-core: mm: Add MMU v3 page table types
  gpu: nova-core: mm: pagetable: Add MmuConfig trait
  gpu: nova-core: mm: Add page table walker for MMU v2/v3
  gpu: nova-core: mm: Add Virtual Memory Manager
  gpu: nova-core: mm: Add virtual address range tracking to VMM
  gpu: nova-core: mm: Add multi-page mapping API to VMM
  gpu: nova-core: Add BAR1 aperture type and size constant
  gpu: nova-core: mm: Add BAR1 user interface
  gpu: nova-core: mm: Add BAR1 memory management self-tests

 drivers/gpu/nova-core/Kconfig              |   1 +
 drivers/gpu/nova-core/driver.rs            |  22 +
 drivers/gpu/nova-core/gpu.rs               |  71 +++-
 drivers/gpu/nova-core/gsp/commands.rs      |   4 +-
 drivers/gpu/nova-core/gsp/fw/commands.rs   |   8 +
 drivers/gpu/nova-core/mm.rs                | 108 ++++-
 drivers/gpu/nova-core/mm/bar_user.rs       | 447 +++++++++++++++++++++
 drivers/gpu/nova-core/mm/pagetable.rs      | 414 +++++++++++++++++++
 drivers/gpu/nova-core/mm/pagetable/map.rs  | 367 +++++++++++++++++
 drivers/gpu/nova-core/mm/pagetable/ver2.rs | 271 +++++++++++++
 drivers/gpu/nova-core/mm/pagetable/ver3.rs | 421 +++++++++++++++++++
 drivers/gpu/nova-core/mm/pagetable/walk.rs | 258 ++++++++++++
 drivers/gpu/nova-core/mm/tlb.rs            | 130 ++++++
 drivers/gpu/nova-core/mm/vmm.rs            | 361 +++++++++++++++++
 drivers/gpu/nova-core/regs.rs              |  65 +++
 15 files changed, 2941 insertions(+), 7 deletions(-)
 create mode 100644 drivers/gpu/nova-core/mm/bar_user.rs
 create mode 100644 drivers/gpu/nova-core/mm/pagetable.rs
 create mode 100644 drivers/gpu/nova-core/mm/pagetable/map.rs
 create mode 100644 drivers/gpu/nova-core/mm/pagetable/ver2.rs
 create mode 100644 drivers/gpu/nova-core/mm/pagetable/ver3.rs
 create mode 100644 drivers/gpu/nova-core/mm/pagetable/walk.rs
 create mode 100644 drivers/gpu/nova-core/mm/tlb.rs
 create mode 100644 drivers/gpu/nova-core/mm/vmm.rs


base-commit: 03f35250485a1f78f5055c034ce7073b4c887636
-- 
2.34.1


^ permalink raw reply

* [PATCH v1 05/16] gpu: nova-core: mm: pagetable: Add PdeOps trait
From: Joel Fernandes @ 2026-05-18 18:11 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518181126.2493572-1-joelagnelf@nvidia.com>

Introduce a trait for GPU Page Directory Entries
(PDEs). Default `read()`/`write()` helpers via a `PraminWindow` are
provided.

The forthcoming MMU v2, v3 PDE structs will each implement `PdeOps`,
allowing the later page-table walker and mapper to call PDE operations.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/mm/pagetable.rs | 37 +++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/drivers/gpu/nova-core/mm/pagetable.rs b/drivers/gpu/nova-core/mm/pagetable.rs
index a92add82eb10..1c94b3afa8b2 100644
--- a/drivers/gpu/nova-core/mm/pagetable.rs
+++ b/drivers/gpu/nova-core/mm/pagetable.rs
@@ -122,6 +122,43 @@ fn write(&self, window: &mut pramin::PraminWindow<'_>, addr: VramAddress) -> Res
     }
 }
 
+/// Operations on Page Directory Entries (`PDE`s).
+pub(super) trait PdeOps: Copy + core::fmt::Debug + Into<u64> {
+    /// Create a `PDE` from a raw `u64` value.
+    fn from_raw(val: u64) -> Self;
+
+    /// Create a valid `PDE` pointing to a page table in the given aperture.
+    fn new(aperture: AperturePde, table_pfn: Pfn) -> Self;
+
+    /// Create an invalid `PDE`.
+    fn invalid() -> Self;
+
+    /// Check if this `PDE` is valid.
+    fn is_valid(&self) -> bool;
+
+    /// Get the memory aperture of this `PDE`.
+    fn aperture(&self) -> AperturePde;
+
+    /// Get the VRAM address of the page table.
+    fn table_vram_address(&self) -> VramAddress;
+
+    /// Read a `PDE` from VRAM.
+    fn read(window: &mut pramin::PraminWindow<'_>, addr: VramAddress) -> Result<Self> {
+        let val = window.try_read64(addr)?;
+        Ok(Self::from_raw(val))
+    }
+
+    /// Write this `PDE` to VRAM.
+    fn write(&self, window: &mut pramin::PraminWindow<'_>, addr: VramAddress) -> Result {
+        window.try_write64(addr, (*self).into())
+    }
+
+    /// Check if this `PDE` is valid and points to video memory.
+    fn is_valid_vram(&self) -> bool {
+        self.is_valid() && self.aperture() == AperturePde::VideoMemory
+    }
+}
+
 /// Memory aperture for Page Table Entries (`PTE`s).
 ///
 /// Determines which memory region the `PTE` points to.
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 01/16] gpu: nova-core: mm: Add common types for virtual memory management
From: Joel Fernandes @ 2026-05-18 18:11 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518181126.2493572-1-joelagnelf@nvidia.com>

Add common virtual memory memory management types: `PAGE_SIZE` constant,
`VirtualAddress` bitfield type, `Vfn` (Virtual Frame Number) type.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/mm.rs | 66 +++++++++++++++++++++++++++++++++++++
 1 file changed, 66 insertions(+)

diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs
index 08d74710f790..b23667a55ecd 100644
--- a/drivers/gpu/nova-core/mm.rs
+++ b/drivers/gpu/nova-core/mm.rs
@@ -42,6 +42,7 @@ macro_rules! impl_pfn_bounded {
     num::Bounded,
     pci,
     prelude::*,
+    sizes::SZ_4K,
     sync::Arc, //
 };
 
@@ -99,6 +100,9 @@ pub(crate) fn run_mm_selftests(
     Ok(())
 }
 
+/// Page size in bytes (4 KiB).
+pub(crate) const PAGE_SIZE: usize = SZ_4K;
+
 bitfield! {
     /// Physical VRAM address in GPU video memory.
     pub(crate) struct VramAddress(u64) {
@@ -207,6 +211,29 @@ fn into_vram_range(self) -> Range<VramAddress> {
     }
 }
 
+bitfield! {
+    /// Virtual address in GPU address space.
+    pub(crate) struct VirtualAddress(u64) {
+        /// Offset within 4KB page.
+        11:0    offset;
+        /// Virtual frame number.
+        63:12   frame_number => Vfn;
+    }
+}
+
+impl VirtualAddress {
+    /// Create a new virtual address from a raw value.
+    pub(crate) const fn new(addr: u64) -> Self {
+        Self::from_raw(addr)
+    }
+}
+
+impl From<Vfn> for VirtualAddress {
+    fn from(vfn: Vfn) -> Self {
+        Self::zeroed().with_frame_number(vfn)
+    }
+}
+
 /// Physical Frame Number.
 ///
 /// Represents a physical page in VRAM.
@@ -245,3 +272,42 @@ fn from(pfn: Pfn) -> Self {
 }
 
 impl_pfn_bounded!(52);
+
+/// Virtual Frame Number.
+///
+/// Represents a virtual page in GPU address space.
+#[repr(transparent)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub(crate) struct Vfn(u64);
+
+impl Vfn {
+    /// Create a new VFN from a frame number.
+    pub(crate) const fn new(frame_number: u64) -> Self {
+        Self(frame_number)
+    }
+
+    /// Get the raw frame number.
+    pub(crate) const fn raw(self) -> u64 {
+        self.0
+    }
+}
+
+impl From<VirtualAddress> for Vfn {
+    fn from(addr: VirtualAddress) -> Self {
+        addr.frame_number()
+    }
+}
+
+impl From<u64> for Vfn {
+    fn from(val: u64) -> Self {
+        Self(val)
+    }
+}
+
+impl From<Vfn> for u64 {
+    fn from(vfn: Vfn) -> Self {
+        vfn.0
+    }
+}
+
+impl_frame_number_bounded!(Vfn, 52);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 04/16] gpu: nova-core: mm: pagetable: Add PteOps trait
From: Joel Fernandes @ 2026-05-18 18:11 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518181126.2493572-1-joelagnelf@nvidia.com>

Introduce a trait for GPU Page Table Entries (PTEs).  New
`read()`/`write()` helpers are provided that go through a
`PraminWindow`).

The forthcoming MMU v2, v3 PTE structs will each implement `PteOps`,
allowing the later page-table walker and mapper to call PTE operations.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/mm/pagetable.rs | 38 +++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/drivers/gpu/nova-core/mm/pagetable.rs b/drivers/gpu/nova-core/mm/pagetable.rs
index ed0f3d731c63..a92add82eb10 100644
--- a/drivers/gpu/nova-core/mm/pagetable.rs
+++ b/drivers/gpu/nova-core/mm/pagetable.rs
@@ -8,9 +8,16 @@
 
 #![expect(dead_code)]
 
+use kernel::prelude::*;
+
 use kernel::num::Bounded;
 
 use crate::gpu::Architecture;
+use crate::mm::{
+    pramin,
+    Pfn,
+    VramAddress, //
+};
 
 /// Extracts the page table index at a given level from a virtual address.
 pub(super) trait VaLevelIndex {
@@ -84,6 +91,37 @@ pub(super) const fn as_index(&self) -> u64 {
     }
 }
 
+// Trait abstractions for page table operations.
+
+/// Operations on Page Table Entries (`PTE`s).
+pub(super) trait PteOps: Copy + core::fmt::Debug + Into<u64> {
+    /// Create a `PTE` from a raw `u64` value.
+    fn from_raw(val: u64) -> Self;
+
+    /// Create an invalid `PTE`.
+    fn invalid() -> Self;
+
+    /// Create a valid `PTE` for the given memory aperture.
+    fn new(aperture: AperturePte, pfn: Pfn, writable: bool) -> Self;
+
+    /// Check if this `PTE` is valid.
+    fn is_valid(&self) -> bool;
+
+    /// Get the physical frame number.
+    fn frame_number(&self) -> Pfn;
+
+    /// Read a `PTE` from VRAM.
+    fn read(window: &mut pramin::PraminWindow<'_>, addr: VramAddress) -> Result<Self> {
+        let val = window.try_read64(addr)?;
+        Ok(Self::from_raw(val))
+    }
+
+    /// Write this `PTE` to VRAM.
+    fn write(&self, window: &mut pramin::PraminWindow<'_>, addr: VramAddress) -> Result {
+        window.try_write64(addr, (*self).into())
+    }
+}
+
 /// Memory aperture for Page Table Entries (`PTE`s).
 ///
 /// Determines which memory region the `PTE` points to.
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH v3 2/2] cpufreq: CPPC: add autonomous mode boot parameter support
From: Mario Limonciello @ 2026-05-18 18:08 UTC (permalink / raw)
  To: Sumit Gupta, rafael, viresh.kumar, pierre.gondois,
	ionela.voinescu, zhenglifeng1, zhanjie9, corbet, skhan, rdunlap,
	linux-pm, linux-doc, linux-kernel
  Cc: linux-tegra, treding, jonathanh, vsethi, ksitaraman, sanjayc,
	mochs, bbasu
In-Reply-To: <985f976f-1144-445b-96c2-df5bd57ecf05@nvidia.com>



On 5/18/26 12:22, Sumit Gupta wrote:
> 
> On 18/05/26 19:51, Mario Limonciello wrote:
>> External email: Use caution opening links or attachments
>>
>>
>> On 5/18/26 09:15, Sumit Gupta wrote:
>>>
>>> On 18/05/26 19:20, Mario Limonciello wrote:
>>>> External email: Use caution opening links or attachments
>>>>
>>>>
>>>> On 5/18/26 08:44, Sumit Gupta wrote:
>>>>> Hi Mario,
>>>>>
>>>>>
>>>>> On 16/05/26 02:43, Mario Limonciello wrote:
>>>>>> External email: Use caution opening links or attachments
>>>>>>
>>>>>>
>>>>>> On 5/15/26 07:26, Sumit Gupta wrote:
>>>>>>> Add a kernel boot parameter 'cppc_cpufreq.auto_sel_mode' to enable
>>>>>>> CPPC autonomous performance selection on all CPUs at system startup.
>>>>>>> When autonomous mode is enabled, the hardware automatically adjusts
>>>>>>> CPU performance based on workload demands using Energy Performance
>>>>>>> Preference (EPP) hints.
>>>>>>>
>>>>>>> When the parameter is set:
>>>>>>> - Configure all CPUs for autonomous operation on first init
>>>>>>> - Use HW min/max_perf when available; otherwise initialize from caps
>>>>>>> - Initialize desired_perf to max_perf as a starting hint
>>>>>>> - Hardware controls frequency instead of the OS governor
>>>>>>> - EPP behavior depends on parameter value:
>>>>>>>    - performance (or 1): override EPP to performance preference 
>>>>>>> (0x0)
>>>>>>>    - default_epp (or 2): preserve EPP value programmed by BIOS/
>>>>>>> firmware
>>>>>>>
>>>>>>> The boot parameter is applied only during first policy 
>>>>>>> initialization.
>>>>>>> Skip applying it on CPU hotplug to preserve runtime sysfs
>>>>>>> configuration.
>>>>>>>
>>>>>>> This patch depends on patch series [1] ("cpufreq: Set policy->min 
>>>>>>> and
>>>>>>> max as real QoS constraints") so that the policy->min/max set in
>>>>>>> cppc_cpufreq_cpu_init() are not overridden by cpufreq_set_policy()
>>>>>>> during init.
>>>>>>>
>>>>>>> Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
>>>>>>> ---
>>>>>>> [1] https://lore.kernel.org/lkml/20260511135538.522653-1-
>>>>>>> pierre.gondois@arm.com/
>>>>>>> ---
>>>>>>>   .../admin-guide/kernel-parameters.txt         |  16 +++
>>>>>>>   drivers/cpufreq/cppc_cpufreq.c                | 122 +++++++++++++
>>>>>>> ++++-
>>>>>>>   2 files changed, 133 insertions(+), 5 deletions(-)
>>>>>>>
>>>>>>> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/
>>>>>>> Documentation/admin-guide/kernel-parameters.txt
>>>>>>> index 0eb64aab3685..7e4b3a8fd76f 100644
>>>>>>> --- a/Documentation/admin-guide/kernel-parameters.txt
>>>>>>> +++ b/Documentation/admin-guide/kernel-parameters.txt
>>>>>>> @@ -1048,6 +1048,22 @@ Kernel parameters
>>>>>>>                       policy to use. This governor must be 
>>>>>>> registered
>>>>>>> in the
>>>>>>>                       kernel before the cpufreq driver probes.
>>>>>>>
>>>>>>> +     cppc_cpufreq.auto_sel_mode=
>>>>>>> +                     [CPU_FREQ] Enable ACPI CPPC autonomous
>>>>>>> performance
>>>>>>> +                     selection. When enabled, hardware 
>>>>>>> automatically
>>>>>>> adjusts
>>>>>>> +                     CPU frequency on all CPUs based on workload
>>>>>>> demands.
>>>>>>> +                     In Autonomous mode, Energy Performance
>>>>>>> Preference (EPP)
>>>>>>> +                     hints guide hardware toward performance (0x0)
>>>>>>> or energy
>>>>>>> +                     efficiency (0xff).
>>>>>>> +                     Requires ACPI CPPC autonomous selection 
>>>>>>> register
>>>>>>> +                     support.
>>>>>>> +                     Accepts:
>>>>>>> +                       performance, 1: enable auto_sel + set EPP to
>>>>>>> +                                       performance (0x0)
>>>>>>> +                       default_epp, 2: enable auto_sel, preserve 
>>>>>>> EPP
>>>>>>> value
>>>>>>> +                                       programmed by BIOS/firmware
>>>>>>> +                     Unset: cpufreq governors are used (auto_sel
>>>>>>> disabled).
>>>>>>
>>>>>> Rather than unset doing nothing, have you considered having it take a
>>>>>> midpoint like 128?  That's what we do in amd-pstate (default to
>>>>>> balance_performance).  I think it turns into a reasonable balance.
>>>>>
>>>>> Thanks for the suggestion.
>>>>> I can add balance_performance that enables auto_sel with EPP=128 in 
>>>>> v4.
>>>>>
>>>>> On changing the driver default (no param behavior) to auto enable
>>>>> balance_performance, it would be good to keep the current behavior for
>>>>> now since cppc_cpufreq is generic across ARM64/RISC-V platforms where
>>>>> EPP and Autonomous Selection registers are optional.
>>>>> A default change would affect existing users relying on governors.
>>>>>
>>>>> Thank you,
>>>>> Sumit Gupta
>>>>
>>>> But couldn't you make the "no module parameter set" follow the behavior
>>>> to only set the registers if they're available?
>>>>
>>>> So the systems that support it start using it, the ones that don't it's
>>>> a NOP.
>>>>
>>>
>>> Would it work to add balance_performance as a new mode in v4,
>>> and discuss changing the default separately as a follow-up?
>>>
>>
>> Sure.
>>
>>> Runtime detection helps for unsupported platforms. But platforms which
>>> support the registers use OS governors today, and silently switching
>>> them to autonomous mode on a kernel update is a behavior change for
>>> existing users. They would also have no way to boot into sw governor.
>>>
>>
>> But hopefully it should be better battery life/responsiveness for those
>> scenarios too, right?
>>
> 
> Yes in many cases, but if some workloads rely on specific OS governor
> configurations, then that would get impacted.
> I will send a separate change later to seek broader consensus on
> enabling auto_sel as default without any param.
> 

I suppose another option is to have a Kconfig to decide at compile time 
whether to turn on autonomous mode by default, so systems can avoid 
moving to this if they don't want to.


^ permalink raw reply

* [PATCH v1 12/12] gpu: nova-core: mm: Add PRAMIN aperture self-tests
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

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 <joelagnelf@nvidia.com>
---
 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


^ permalink raw reply related

* [PATCH v1 11/12] gpu: nova-core: mm: Add GpuMm centralized memory manager
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

Introduce GpuMm as the centralized GPU memory manager. At this point in
the series, GpuMm only owns the PRAMIN window for direct VRAM access;
the buddy allocator and TLB manager are added later when those backing
types become available.

This provides a clean ownership model where GpuMm provides accessor
methods for its components that can be used for memory management
operations, and lets follow-on patches (such as the PRAMIN aperture
self-tests) reference `self.mm.pramin()` cleanly.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/gpu.rs | 22 +++++++++++++++++
 drivers/gpu/nova-core/mm.rs  | 46 ++++++++++++++++++++++++++++++++++--
 2 files changed, 66 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index d9d1a7417a2e..38544c38d660 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -25,6 +25,10 @@
         commands::GetGspStaticInfoReply,
         Gsp, //
     },
+    mm::{
+        GpuMm,
+        IntoVramRange, //
+    },
     regs,
 };
 
@@ -261,6 +265,8 @@ pub(crate) struct Gpu {
     gsp_falcon: Falcon<GspFalcon>,
     /// SEC2 falcon instance, used for GSP boot up and cleanup.
     sec2_falcon: Falcon<Sec2Falcon>,
+    /// GPU memory manager owning memory management resources.
+    mm: Arc<GpuMm>,
     /// GSP runtime data. Temporarily an empty placeholder.
     #[pin]
     gsp: Gsp,
@@ -306,6 +312,22 @@ pub(crate) fn new<'a>(
                     );
                 })?,
 
+            // Create GPU memory manager owning memory management resources.
+            mm: {
+                // PRAMIN covers all physical VRAM (including GSP-reserved areas
+                // above the usable region, e.g. the BAR1 page directory).
+                let pramin_vram_region = (0..gsp_static_info.total_fb_end).into_vram_range();
+                Arc::pin_init(
+                    GpuMm::new(
+                        devres_bar.clone(),
+                        pdev.as_ref(),
+                        spec.chipset,
+                        pramin_vram_region,
+                    )?,
+                    GFP_KERNEL,
+                )?
+            },
+
             bar: devres_bar,
         })
     }
diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs
index f425467281d3..5c1941d20d1b 100644
--- a/drivers/gpu/nova-core/mm.rs
+++ b/drivers/gpu/nova-core/mm.rs
@@ -2,7 +2,7 @@
 
 //! Memory management subsystems for nova-core.
 
-#![expect(dead_code)]
+#![allow(dead_code)]
 
 /// Implements `From` conversions between a frame-number type and `Bounded<u64, N>`.
 ///
@@ -37,10 +37,52 @@ macro_rules! impl_pfn_bounded {
 
 use kernel::{
     bitfield,
+    device,
+    devres::Devres,
     num::Bounded,
-    prelude::*, //
+    prelude::*,
+    sync::Arc, //
 };
 
+use crate::{
+    driver::Bar0,
+    gpu::Chipset, //
+};
+
+/// GPU Memory Manager - owns all core MM components.
+///
+/// Provides centralized ownership of memory management resources:
+/// - [`pramin::Pramin`] for direct VRAM access.
+#[pin_data]
+pub(crate) struct GpuMm {
+    #[pin]
+    pramin: pramin::Pramin,
+}
+
+impl GpuMm {
+    /// Create a pin-initializer for `GpuMm`.
+    ///
+    /// `pramin_vram_region` is the full physical VRAM range (including GSP-reserved
+    /// areas). PRAMIN window accesses are validated against this range.
+    pub(crate) fn new(
+        bar: Arc<Devres<Bar0>>,
+        dev: &device::Device<device::Bound>,
+        chipset: Chipset,
+        pramin_vram_region: Range<VramAddress>,
+    ) -> Result<impl PinInit<Self>> {
+        let pramin_init = pramin::Pramin::new(bar, dev, chipset, pramin_vram_region)?;
+
+        Ok(pin_init!(Self {
+            pramin <- pramin_init,
+        }))
+    }
+
+    /// Access the [`pramin::Pramin`].
+    pub(crate) fn pramin(&self) -> &pramin::Pramin {
+        &self.pramin
+    }
+}
+
 bitfield! {
     /// Physical VRAM address in GPU video memory.
     pub(crate) struct VramAddress(u64) {
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 10/12] docs: gpu: nova-core: Document the PRAMIN aperture mechanism
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

Add documentation for the PRAMIN aperture mechanism used by nova-core
for direct VRAM access.

Nova only uses TARGET=VRAM for VRAM access. The SYS_MEM target values
are documented for completeness but not used by the driver.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 Documentation/gpu/nova/core/pramin.rst | 123 +++++++++++++++++++++++++
 Documentation/gpu/nova/index.rst       |   1 +
 2 files changed, 124 insertions(+)
 create mode 100644 Documentation/gpu/nova/core/pramin.rst

diff --git a/Documentation/gpu/nova/core/pramin.rst b/Documentation/gpu/nova/core/pramin.rst
new file mode 100644
index 000000000000..f6cbb0811163
--- /dev/null
+++ b/Documentation/gpu/nova/core/pramin.rst
@@ -0,0 +1,123 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=========================
+PRAMIN aperture mechanism
+=========================
+
+.. note::
+   The following description is approximate and current as of the Ampere family.
+   It may change for future generations and is intended to assist in understanding
+   the driver code.
+
+Introduction
+============
+
+PRAMIN is a hardware aperture mechanism that provides CPU access to GPU Video RAM (VRAM) before
+the GPU's Memory Management Unit (MMU) and page tables are initialized. This 1MB sliding window,
+located at a fixed offset within BAR0, is essential for setting up page tables and other critical
+GPU data structures without relying on the GPU's MMU.
+
+Architecture Overview
+=====================
+
+The PRAMIN aperture mechanism is logically implemented by the GPU's PBUS (PCIe Bus Controller Unit)
+and provides a CPU-accessible window into VRAM through the PCIe interface::
+
+    +-----------------+    PCIe     +------------------------------+
+    |      CPU        |<----------->|           GPU                |
+    +-----------------+             |                              |
+                                    |  +----------------------+    |
+                                    |  |       PBUS           |    |
+                                    |  |  (Bus Controller)    |    |
+                                    |  |                      |    |
+                                    |  |  +--------------+<------------ (window starts at
+                                    |  |  |   PRAMIN     |    |    |     BAR0 + 0x700000)
+                                    |  |  |   Window     |    |    |
+                                    |  |  |   (1MB)      |    |    |
+                                    |  |  +--------------+    |    |
+                                    |  |         |            |    |
+                                    |  +---------|------------+    |
+                                    |            |                 |
+                                    |            v                 |
+                                    |  +----------------------+<------------ (Program PRAMIN to any
+                                    |  |       VRAM           |    |    64KB-aligned VRAM boundary)
+                                    |  |    (Several GBs)     |    |
+                                    |  |                      |    |
+                                    |  |   FB[0x0000000000]   |    |
+                                    |  |          ...         |    |
+                                    |  |   FB[0xFFFFFFFFFF]   |    |
+                                    |  +----------------------+    |
+                                    +------------------------------+
+
+PBUS (PCIe Bus Controller) is responsible for, among other things, handling MMIO
+accesses to the BAR registers.
+
+PRAMIN Window Operation
+=======================
+
+The PRAMIN window provides a 1MB sliding aperture that can be repositioned over
+the entire VRAM address space using the ``NV_PBUS_BAR0_WINDOW`` register.
+
+Window Control Mechanism
+-------------------------
+
+::
+
+    NV_PBUS_BAR0_WINDOW Register (0x1700):
+    +-------+--------+--------------------------------------+
+    | 31:26 | 25:24  |               23:0                   |
+    | RSVD  | TARGET |            BASE_ADDR                 |
+    |       |        |        (bits 39:16 of VRAM address)  |
+    +-------+--------+--------------------------------------+
+
+    The 24-bit BASE_ADDR field encodes bits [39:16] of the target VRAM address,
+    providing 40-bit (1TB) address space coverage with 64KB alignment.
+
+    TARGET field (bits 25:24):
+    - 0x0: VRAM (Video Memory)
+    - 0x1: SYS_MEM_COH (Coherent System Memory)
+    - 0x2: SYS_MEM_NONCOH (Non-coherent System Memory)
+    - 0x3: Reserved
+
+.. note::
+   Nova only uses TARGET=VRAM (0x0) for video memory access. The SYS_MEM
+   target values are documented here for hardware completeness but are
+   not used by the driver.
+
+64KB Alignment Requirement
+---------------------------
+
+The PRAMIN window must be aligned to 64KB boundaries in VRAM. This is enforced
+by the ``BASE_ADDR`` field representing bits [39:16] of the target address::
+
+    VRAM Address Calculation:
+    actual_vram_addr = (BASE_ADDR << 16) + pramin_offset
+    Where:
+    - BASE_ADDR: 24-bit value from NV_PBUS_BAR0_WINDOW[23:0]
+    - pramin_offset: 20-bit offset within the PRAMIN window [0x00000-0xFFFFF]
+
+    Example Window Positioning:
+    +---------------------------------------------------------+
+    |                    VRAM Space                           |
+    |                                                         |
+    |  0x0000000000 +-----------------+ <-- 64KB aligned      |
+    |               | PRAMIN Window   |                       |
+    |               |    (1MB)        |                       |
+    |  0x00000FFFFF +-----------------+                       |
+    |                                                         |
+    |       |              ^                                  |
+    |       |              | Window can slide                 |
+    |       v              | to any 64KB-aligned boundary     |
+    |                                                         |
+    |  0x0123400000 +-----------------+ <-- 64KB aligned      |
+    |               | PRAMIN Window   |                       |
+    |               |    (1MB)        |                       |
+    |  0x01234FFFFF +-----------------+                       |
+    |                                                         |
+    |                       ...                               |
+    |                                                         |
+    |  0xFFFFF00000 +-----------------+ <-- 64KB aligned      |
+    |               | PRAMIN Window   |                       |
+    |               |    (1MB)        |                       |
+    |  0xFFFFFFFFFF +-----------------+                       |
+    +---------------------------------------------------------+
diff --git a/Documentation/gpu/nova/index.rst b/Documentation/gpu/nova/index.rst
index e39cb3163581..b8254b1ffe2a 100644
--- a/Documentation/gpu/nova/index.rst
+++ b/Documentation/gpu/nova/index.rst
@@ -32,3 +32,4 @@ vGPU manager VFIO driver and the nova-drm driver.
    core/devinit
    core/fwsec
    core/falcon
+   core/pramin
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 08/12] gpu: nova-core: mm: Add VramAddress arithmetic and ordering
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

Add arithmetic helpers, comparison, and operator overloads for
`VramAddress` which are required in later patches for address
arithmetic.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/mm.rs | 60 +++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs
index f8a70f93bc03..3bc9befab397 100644
--- a/drivers/gpu/nova-core/mm.rs
+++ b/drivers/gpu/nova-core/mm.rs
@@ -59,6 +59,38 @@ pub(crate) const fn new(addr: u64) -> Self {
     pub(crate) const fn raw(&self) -> u64 {
         self.into_raw()
     }
+
+    /// Align the address down to the given power-of-two `alignment`.
+    pub(crate) const fn align_down(self, alignment: u64) -> Self {
+        Self::new(self.raw() & !(alignment - 1))
+    }
+
+    /// Add `rhs` to this address, returning `None` on overflow.
+    pub(crate) fn checked_add<O: IntoVramOffset>(self, rhs: O) -> Option<Self> {
+        self.raw()
+            .checked_add(rhs.into_vram_offset())
+            .map(Self::new)
+    }
+}
+
+/// Lossless conversion into a `u64` byte offset, for use as a [`VramAddress`] `checked_add()`
+/// operand which can be either a `u64` or a `usize`.
+pub(crate) trait IntoVramOffset {
+    /// Convert `self` into a `u64` byte offset.
+    fn into_vram_offset(self) -> u64;
+}
+
+impl IntoVramOffset for u64 {
+    fn into_vram_offset(self) -> u64 {
+        self
+    }
+}
+
+impl IntoVramOffset for usize {
+    fn into_vram_offset(self) -> u64 {
+        use crate::num::IntoSafeCast;
+        self.into_safe_cast()
+    }
 }
 
 // Allow VRAM addresses to be printed with the `{:#x}` format specifier.
@@ -68,12 +100,40 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
     }
 }
 
+impl PartialOrd for VramAddress {
+    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for VramAddress {
+    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+        self.into_raw().cmp(&other.into_raw())
+    }
+}
+
 impl From<Pfn> for VramAddress {
     fn from(pfn: Pfn) -> Self {
         Self::zeroed().with_frame_number(pfn)
     }
 }
 
+impl core::ops::Add<u64> for VramAddress {
+    type Output = Self;
+
+    fn add(self, rhs: u64) -> Self {
+        Self::new(self.raw() + rhs)
+    }
+}
+
+impl core::ops::Sub<VramAddress> for VramAddress {
+    type Output = u64;
+
+    fn sub(self, rhs: VramAddress) -> u64 {
+        self.raw() - rhs.raw()
+    }
+}
+
 /// Extension trait to convert a `Range<u64>` of byte addresses into a
 /// `Range<VramAddress>`.
 pub(crate) trait IntoVramRange {
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 09/12] gpu: nova-core: mm: Add support to use PRAMIN windows to write to VRAM
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

PRAMIN apertures are a crucial mechanism for direct CPU read/write to
VRAM. Add support for PRAMIN windows on all supported GPU architectures:
Turing, Ampere, Ada (via `NV_PBUS_BAR0_WINDOW`), Hopper (via
`gh100::NV_XAL_EP_BAR0_WINDOW`), and Blackwell (via
`gb100::NV_XAL_EP_BAR0_WINDOW`). Architecture-dispatched
`pramin_window_{read,write}_base()` helpers in `regs.rs` encapsulate the
per-arch register selection.

Hopper/Blackwell window-base dispatch is based on Eliot Courtney's
offlist reference patch.

Cc: Eliot Courtney <ecourtney@nvidia.com>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/mm.rs        |   2 +
 drivers/gpu/nova-core/mm/pramin.rs | 298 +++++++++++++++++++++++++++++
 drivers/gpu/nova-core/nova_core.rs |   1 +
 drivers/gpu/nova-core/regs.rs      | 122 ++++++++++++
 4 files changed, 423 insertions(+)
 create mode 100644 drivers/gpu/nova-core/mm/pramin.rs

diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs
index 3bc9befab397..f425467281d3 100644
--- a/drivers/gpu/nova-core/mm.rs
+++ b/drivers/gpu/nova-core/mm.rs
@@ -31,6 +31,8 @@ macro_rules! impl_pfn_bounded {
     };
 }
 
+pub(crate) mod pramin;
+
 use core::ops::Range;
 
 use kernel::{
diff --git a/drivers/gpu/nova-core/mm/pramin.rs b/drivers/gpu/nova-core/mm/pramin.rs
new file mode 100644
index 000000000000..38758ca971be
--- /dev/null
+++ b/drivers/gpu/nova-core/mm/pramin.rs
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Direct VRAM access through the PRAMIN aperture.
+//!
+//! PRAMIN provides a 1MB sliding window into VRAM through BAR0, allowing the CPU to access
+//! video memory directly. Access is managed through a two-level API:
+//!
+//! - [`Pramin`]: The parent object that owns the BAR0 reference and synchronization lock.
+//! - [`PraminWindow`]: A guard object that holds exclusive PRAMIN access for its lifetime.
+//!
+//! The PRAMIN aperture is a 1MB region at a fixed offset from BAR0. The window base is
+//! controlled by an architecture-specific register and is 64KB aligned.
+//!
+//! # Examples
+//!
+//! ## Basic read/write
+//!
+//! ```no_run
+//! use crate::driver::Bar0;
+//! use crate::gpu::Chipset;
+//! use crate::mm::{pramin, VramAddress};
+//! use kernel::device;
+//! use kernel::devres::Devres;
+//! use kernel::prelude::*;
+//! use kernel::sync::Arc;
+//!
+//! fn example(
+//!     devres_bar: Arc<Devres<Bar0>>,
+//!     dev: &device::Device<device::Bound>,
+//!     chipset: Chipset,
+//!     vram_region: core::ops::Range<VramAddress>,
+//! ) -> Result<()> {
+//!     let pramin = Arc::pin_init(
+//!         pramin::Pramin::new(devres_bar, dev, chipset, vram_region)?,
+//!         GFP_KERNEL,
+//!     )?;
+//!     let mut window = pramin.get_window(dev)?;
+//!
+//!     // Write and read back.
+//!     window.try_write32(0x100u64, 0xDEADBEEF)?;
+//!     let val = window.try_read32(0x100u64)?;
+//!     assert_eq!(val, 0xDEADBEEF);
+//!
+//!     Ok(())
+//! }
+//! ```
+//!
+//! ## Auto-repositioning across VRAM regions
+//!
+//! ```no_run
+//! use crate::driver::Bar0;
+//! use crate::gpu::Chipset;
+//! use crate::mm::{pramin, VramAddress};
+//! use kernel::device;
+//! use kernel::devres::Devres;
+//! use kernel::prelude::*;
+//! use kernel::sync::Arc;
+//!
+//! fn example(
+//!     devres_bar: Arc<Devres<Bar0>>,
+//!     dev: &device::Device<device::Bound>,
+//!     chipset: Chipset,
+//!     vram_region: core::ops::Range<VramAddress>,
+//! ) -> Result<()> {
+//!     let pramin = Arc::pin_init(
+//!         pramin::Pramin::new(devres_bar, dev, chipset, vram_region)?,
+//!         GFP_KERNEL,
+//!     )?;
+//!     let mut window = pramin.get_window(dev)?;
+//!
+//!     // Access first 1MB region.
+//!     window.try_write32(0x100u64, 0x11111111)?;
+//!
+//!     // Access at 2MB - window auto-repositions.
+//!     window.try_write32(0x200000u64, 0x22222222)?;
+//!
+//!     // Back to first region - window repositions again.
+//!     let val = window.try_read32(0x100u64)?;
+//!     assert_eq!(val, 0x11111111);
+//!
+//!     Ok(())
+//! }
+//! ```
+
+#![expect(unused)]
+
+use core::ops::Range;
+
+use crate::{
+    bounded_enum,
+    driver::Bar0,
+    gpu::Chipset,
+    mm::VramAddress,
+    num::IntoSafeCast,
+    regs, //
+};
+
+use kernel::{
+    device,
+    devres::Devres,
+    io::Io,
+    new_mutex,
+    prelude::*,
+    sizes::{
+        SZ_1M,
+        SZ_64K, //
+    },
+    sync::{
+        lock::mutex::MutexGuard,
+        Arc,
+        Mutex, //
+    },
+};
+
+bounded_enum! {
+    /// Target memory type for the BAR0 window register.
+    ///
+    /// Only VRAM is supported; Hopper+ GPUs do not support other targets.
+    #[derive(Debug)]
+    pub(crate) enum Bar0WindowTarget with TryFrom<Bounded<u32, 2>> {
+        /// Video RAM (GPU framebuffer memory).
+        Vram = 0,
+    }
+}
+
+/// PRAMIN aperture base offset in BAR0.
+const PRAMIN_BASE: usize = 0x700000;
+
+/// PRAMIN aperture size (1MB).
+const PRAMIN_SIZE: usize = SZ_1M;
+
+/// Generate a PRAMIN read accessor that takes an absolute VRAM address.
+///
+/// `$name` matches the underlying [`Bar0`] method (e.g. `try_read32`).
+macro_rules! define_pramin_read {
+    ($name:ident, $ty:ty) => {
+        #[doc = concat!("Read a `", stringify!($ty), "` from VRAM at the given address.")]
+        pub(crate) fn $name(&mut self, vram_addr: impl Into<VramAddress>) -> Result<$ty> {
+            let (bar_offset, new_base) =
+                self.compute_window(vram_addr.into(), ::core::mem::size_of::<$ty>())?;
+
+            if let Some(base) = new_base {
+                regs::pramin_window_write_base(self.chipset.arch(), self.bar, base)?;
+                *self.state = base;
+            }
+            self.bar.$name(bar_offset)
+        }
+    };
+}
+
+/// Generate a PRAMIN write accessor that takes an absolute VRAM address.
+///
+/// `$name` matches the underlying [`Bar0`] method (e.g. `try_write32`).
+macro_rules! define_pramin_write {
+    ($name:ident, $ty:ty) => {
+        #[doc = concat!("Write a `", stringify!($ty), "` to VRAM at the given address.")]
+        pub(crate) fn $name(&mut self, vram_addr: impl Into<VramAddress>, value: $ty) -> Result {
+            let (bar_offset, new_base) =
+                self.compute_window(vram_addr.into(), ::core::mem::size_of::<$ty>())?;
+
+            if let Some(base) = new_base {
+                regs::pramin_window_write_base(self.chipset.arch(), self.bar, base)?;
+                *self.state = base;
+            }
+            self.bar.$name(value, bar_offset)
+        }
+    };
+}
+
+/// PRAMIN aperture manager.
+///
+/// Call [`Pramin::get_window()`] to acquire exclusive PRAMIN access.
+#[pin_data]
+pub(crate) struct Pramin {
+    bar: Arc<Devres<Bar0>>,
+    chipset: Chipset,
+    /// Valid VRAM region. Accesses outside this range are rejected.
+    vram_region: Range<VramAddress>,
+    /// PRAMIN aperture state, protected by a mutex.
+    ///
+    /// # Invariants
+    ///
+    /// This lock is acquired during the DMA fence signaling critical path.
+    /// It must NEVER be held across any reclaimable CPU memory / allocations
+    /// (`GFP_KERNEL`), because the memory reclaim path can call
+    /// `dma_fence_wait()`, which would deadlock with this lock held.
+    #[pin]
+    state: Mutex<VramAddress>,
+}
+
+impl Pramin {
+    /// Create a pin-initializer for PRAMIN.
+    ///
+    /// `vram_region` specifies the valid VRAM address range.
+    pub(crate) fn new(
+        bar: Arc<Devres<Bar0>>,
+        dev: &device::Device<device::Bound>,
+        chipset: Chipset,
+        vram_region: Range<VramAddress>,
+    ) -> Result<impl PinInit<Self>> {
+        let bar_access = bar.access(dev)?;
+        let current_base = regs::pramin_window_read_base(chipset.arch(), bar_access);
+
+        Ok(pin_init!(Self {
+            bar,
+            chipset,
+            vram_region,
+            state <- new_mutex!(current_base, "pramin_state"),
+        }))
+    }
+
+    /// Returns the valid VRAM region for this PRAMIN instance.
+    fn vram_region(&self) -> &Range<VramAddress> {
+        &self.vram_region
+    }
+
+    /// Acquire exclusive PRAMIN access.
+    ///
+    /// Returns a [`PraminWindow`] guard that provides VRAM read/write accessors.
+    /// The [`PraminWindow`] is exclusive and only one can exist at a time.
+    pub(crate) fn get_window<'a>(
+        &'a self,
+        dev: &'a device::Device<device::Bound>,
+    ) -> Result<PraminWindow<'a>> {
+        let bar = self.bar.access(dev)?;
+        let state = self.state.lock();
+        Ok(PraminWindow {
+            bar,
+            chipset: self.chipset,
+            vram_region: self.vram_region.clone(),
+            state,
+        })
+    }
+}
+
+/// PRAMIN window guard for direct VRAM access.
+///
+/// This guard holds exclusive access to the PRAMIN aperture. The window auto-repositions
+/// when accessing VRAM offsets outside the current 1MB range.
+///
+/// Only one [`PraminWindow`] can exist at a time per [`Pramin`] instance (enforced by the
+/// internal `MutexGuard`).
+pub(crate) struct PraminWindow<'a> {
+    bar: &'a Bar0,
+    chipset: Chipset,
+    vram_region: Range<VramAddress>,
+    state: MutexGuard<'a, VramAddress>,
+}
+
+impl PraminWindow<'_> {
+    /// Compute window parameters for a VRAM access.
+    ///
+    /// Returns (`bar_offset`, `new_base`) where:
+    /// - `bar_offset`: The BAR0 offset to use for the access.
+    /// - `new_base`: `Some(base)` if window needs repositioning, `None` otherwise.
+    fn compute_window(
+        &self,
+        vram_addr: VramAddress,
+        access_size: usize,
+    ) -> Result<(usize, Option<VramAddress>)> {
+        // Validate VRAM address is within the valid VRAM region.
+        let end_addr = vram_addr.checked_add(access_size).ok_or(EINVAL)?;
+        if vram_addr < self.vram_region.start || end_addr > self.vram_region.end {
+            return Err(EINVAL);
+        }
+
+        // Check if access fits within the current 1MB window.
+        let current_base = *self.state;
+        if vram_addr >= current_base {
+            let offset_within: usize = (vram_addr - current_base).into_safe_cast();
+            if offset_within + access_size <= PRAMIN_SIZE {
+                return Ok((PRAMIN_BASE + offset_within, None));
+            }
+        }
+
+        // Access doesn't fit in current window - reposition.
+        // Hardware requires 64KB alignment for the window base register.
+        let needed_base = vram_addr.align_down(SZ_64K as u64);
+        let offset_within: usize = (vram_addr - needed_base).into_safe_cast();
+
+        // Verify access fits in the 1MB window from the new base.
+        if offset_within + access_size > PRAMIN_SIZE {
+            return Err(EINVAL);
+        }
+
+        Ok((PRAMIN_BASE + offset_within, Some(needed_base)))
+    }
+
+    define_pramin_read!(try_read8, u8);
+    define_pramin_read!(try_read16, u16);
+    define_pramin_read!(try_read32, u32);
+    define_pramin_read!(try_read64, u64);
+
+    define_pramin_write!(try_write8, u8);
+    define_pramin_write!(try_write16, u16);
+    define_pramin_write!(try_write32, u32);
+    define_pramin_write!(try_write64, u64);
+}
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index 38b8aeb750ba..8bff10dbf327 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -16,6 +16,7 @@
 mod firmware;
 mod gpu;
 mod gsp;
+mod mm;
 #[macro_use]
 mod num;
 mod regs;
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 6faeed73901d..fb42d96a59b2 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -6,6 +6,10 @@
         register::WithBase,
         Io, //
     },
+    num::{
+        Bounded,
+        TryIntoBounded, //
+    },
     prelude::*,
     sizes::SizeConstants,
     time, //
@@ -31,6 +35,10 @@
         Architecture,
         Chipset, //
     },
+    mm::{
+        pramin::Bar0WindowTarget,
+        VramAddress, //
+    },
 };
 
 // PMC
@@ -115,6 +123,15 @@ fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
     }
 }
 
+register! {
+    /// BAR0 window control for PRAMIN access.
+    pub(crate) NV_PBUS_BAR0_WINDOW(u32) @ 0x00001700 {
+        25:24   target ?=> Bar0WindowTarget;
+        /// PRAMIN window base byte address (40-bit FB addr; bits 39:16 stored in 23:0).
+        23:0    window_base as Bounded<u64, 40> shl 16;
+    }
+}
+
 // PFB
 
 register! {
@@ -537,3 +554,108 @@ pub(crate) mod ga100 {
         }
     }
 }
+
+pub(crate) mod gh100 {
+    use kernel::io::register;
+
+    register! {
+        /// Hopper register for PRAMIN window.
+        pub(crate) NV_XAL_EP_BAR0_WINDOW(u32) @ 0x0010_fd40 {
+            /// PRAMIN window base byte address (38-bit FB addr; bits 37:16 stored in 21:0).
+            21:0    window_base as Bounded<u64, 38> shl 16;
+        }
+    }
+}
+
+pub(crate) mod gb100 {
+    use kernel::io::register;
+
+    register! {
+        /// Blackwell+ register for PRAMIN window.
+        pub(crate) NV_XAL_EP_BAR0_WINDOW(u32) @ 0x0010_fd40 {
+            /// PRAMIN window base byte address (39-bit FB addr; bits 38:16 stored in 22:0).
+            22:0    window_base as Bounded<u64, 39> shl 16;
+        }
+    }
+}
+
+/// Common interface for all PRAMIN window registers across GPU architectures.
+pub(crate) trait PraminWindow {
+    /// Reads the current PRAMIN window base address from this register.
+    fn read_base(bar: &Bar0) -> VramAddress;
+
+    /// Writes a new PRAMIN window base address into this register.
+    fn write_base(bar: &Bar0, base: VramAddress) -> Result;
+}
+
+impl PraminWindow for NV_PBUS_BAR0_WINDOW {
+    fn read_base(bar: &Bar0) -> VramAddress {
+        VramAddress::new(bar.read(NV_PBUS_BAR0_WINDOW).window_base().into())
+    }
+
+    fn write_base(bar: &Bar0, base: VramAddress) -> Result {
+        let bounded: Bounded<u64, 40> = base.raw().try_into_bounded().ok_or(EINVAL)?;
+        bar.write_reg(
+            NV_PBUS_BAR0_WINDOW::zeroed()
+                .with_target(Bar0WindowTarget::Vram)
+                .with_window_base(bounded),
+        );
+        Ok(())
+    }
+}
+
+impl PraminWindow for gh100::NV_XAL_EP_BAR0_WINDOW {
+    fn read_base(bar: &Bar0) -> VramAddress {
+        VramAddress::new(bar.read(gh100::NV_XAL_EP_BAR0_WINDOW).window_base().into())
+    }
+
+    fn write_base(bar: &Bar0, base: VramAddress) -> Result {
+        let bounded: Bounded<u64, 38> = base.raw().try_into_bounded().ok_or(EINVAL)?;
+        bar.write_reg(gh100::NV_XAL_EP_BAR0_WINDOW::zeroed().with_window_base(bounded));
+        Ok(())
+    }
+}
+
+impl PraminWindow for gb100::NV_XAL_EP_BAR0_WINDOW {
+    fn read_base(bar: &Bar0) -> VramAddress {
+        VramAddress::new(bar.read(gb100::NV_XAL_EP_BAR0_WINDOW).window_base().into())
+    }
+
+    fn write_base(bar: &Bar0, base: VramAddress) -> Result {
+        let bounded: Bounded<u64, 39> = base.raw().try_into_bounded().ok_or(EINVAL)?;
+        bar.write_reg(gb100::NV_XAL_EP_BAR0_WINDOW::zeroed().with_window_base(bounded));
+        Ok(())
+    }
+}
+
+/// Reads the current BAR0 PRAMIN window base address, dispatching to the
+/// register variant appropriate for `arch`.
+pub(crate) fn pramin_window_read_base(arch: Architecture, bar: &Bar0) -> VramAddress {
+    match arch {
+        Architecture::Turing | Architecture::Ampere | Architecture::Ada => {
+            NV_PBUS_BAR0_WINDOW::read_base(bar)
+        }
+        Architecture::Hopper => gh100::NV_XAL_EP_BAR0_WINDOW::read_base(bar),
+        Architecture::BlackwellGB10x | Architecture::BlackwellGB20x => {
+            gb100::NV_XAL_EP_BAR0_WINDOW::read_base(bar)
+        }
+    }
+}
+
+/// Writes a new BAR0 PRAMIN window base address, dispatching to the register
+/// variant appropriate for `arch`.
+pub(crate) fn pramin_window_write_base(
+    arch: Architecture,
+    bar: &Bar0,
+    base: VramAddress,
+) -> Result {
+    match arch {
+        Architecture::Turing | Architecture::Ampere | Architecture::Ada => {
+            NV_PBUS_BAR0_WINDOW::write_base(bar, base)
+        }
+        Architecture::Hopper => gh100::NV_XAL_EP_BAR0_WINDOW::write_base(bar, base),
+        Architecture::BlackwellGB10x | Architecture::BlackwellGB20x => {
+            gb100::NV_XAL_EP_BAR0_WINDOW::write_base(bar, base)
+        }
+    }
+}
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 07/12] gpu: nova-core: mm: Add VramAddress type and conversion traits
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

Add the `VramAddress` bitfield-backed type representing a physical address
in VRAM. The bitfield layout splits the address into a 12-bit intra-page
offset and a 52-bit physical frame number, matching the GPU MMU
addressing scheme. also add a few conversion traits required in later
patches.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/mm.rs | 57 +++++++++++++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)

diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs
index 3b131aedf2f9..f8a70f93bc03 100644
--- a/drivers/gpu/nova-core/mm.rs
+++ b/drivers/gpu/nova-core/mm.rs
@@ -31,11 +31,62 @@ macro_rules! impl_pfn_bounded {
     };
 }
 
+use core::ops::Range;
+
 use kernel::{
+    bitfield,
     num::Bounded,
     prelude::*, //
 };
 
+bitfield! {
+    /// Physical VRAM address in GPU video memory.
+    pub(crate) struct VramAddress(u64) {
+        /// Offset within 4KB page.
+        11:0    offset;
+        /// Physical frame number.
+        63:12   frame_number => Pfn;
+    }
+}
+
+impl VramAddress {
+    /// Create a new VRAM address from a raw value.
+    pub(crate) const fn new(addr: u64) -> Self {
+        Self::from_raw(addr)
+    }
+
+    /// Get the raw address value as `u64`.
+    pub(crate) const fn raw(&self) -> u64 {
+        self.into_raw()
+    }
+}
+
+// Allow VRAM addresses to be printed with the `{:#x}` format specifier.
+impl core::fmt::LowerHex for VramAddress {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        core::fmt::LowerHex::fmt(&self.raw(), f)
+    }
+}
+
+impl From<Pfn> for VramAddress {
+    fn from(pfn: Pfn) -> Self {
+        Self::zeroed().with_frame_number(pfn)
+    }
+}
+
+/// Extension trait to convert a `Range<u64>` of byte addresses into a
+/// `Range<VramAddress>`.
+pub(crate) trait IntoVramRange {
+    /// Convert this range of byte addresses into a `Range<VramAddress>`.
+    fn into_vram_range(self) -> Range<VramAddress>;
+}
+
+impl IntoVramRange for Range<u64> {
+    fn into_vram_range(self) -> Range<VramAddress> {
+        VramAddress::new(self.start)..VramAddress::new(self.end)
+    }
+}
+
 /// Physical Frame Number.
 ///
 /// Represents a physical page in VRAM.
@@ -55,6 +106,12 @@ pub(crate) const fn raw(self) -> u64 {
     }
 }
 
+impl From<VramAddress> for Pfn {
+    fn from(addr: VramAddress) -> Self {
+        addr.frame_number()
+    }
+}
+
 impl From<u64> for Pfn {
     fn from(val: u64) -> Self {
         Self(val)
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 06/12] gpu: nova-core: mm: Add Pfn (Physical Frame Number) type
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

Add the `Pfn` (Physical Frame Number) type representing a physical page
in VRAM, along with the macros used by frame-number types to interop with
the `Bounded<u64, N>` representation used by bitfield-derived PTE/PDE
fields.

In later patches in the series, we will use `Pfn` in the page table
structures.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/mm.rs | 70 +++++++++++++++++++++++++++++++++++++
 1 file changed, 70 insertions(+)
 create mode 100644 drivers/gpu/nova-core/mm.rs

diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs
new file mode 100644
index 000000000000..3b131aedf2f9
--- /dev/null
+++ b/drivers/gpu/nova-core/mm.rs
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Memory management subsystems for nova-core.
+
+#![expect(dead_code)]
+
+/// Implements `From` conversions between a frame-number type and `Bounded<u64, N>`.
+///
+/// Each MMU version module should invoke this for the specific bit widths used by that version's
+/// PTE/PDE bitfield definitions.
+macro_rules! impl_frame_number_bounded {
+    ($type:ty, $bits:literal) => {
+        impl From<Bounded<u64, $bits>> for $type {
+            fn from(val: Bounded<u64, $bits>) -> Self {
+                Self::new(val.get())
+            }
+        }
+
+        impl From<$type> for Bounded<u64, $bits> {
+            fn from(v: $type) -> Self {
+                Bounded::from_expr(v.raw() & ::kernel::bits::genmask_u64(0..=($bits - 1)))
+            }
+        }
+    };
+}
+
+/// Implements `From` conversions between [`Pfn`] and `Bounded<u64, N>` for bitfield interop.
+macro_rules! impl_pfn_bounded {
+    ($bits:literal) => {
+        impl_frame_number_bounded!(Pfn, $bits);
+    };
+}
+
+use kernel::{
+    num::Bounded,
+    prelude::*, //
+};
+
+/// Physical Frame Number.
+///
+/// Represents a physical page in VRAM.
+#[repr(transparent)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub(crate) struct Pfn(u64);
+
+impl Pfn {
+    /// Create a new PFN from a frame number.
+    pub(crate) const fn new(frame_number: u64) -> Self {
+        Self(frame_number)
+    }
+
+    /// Get the raw frame number.
+    pub(crate) const fn raw(self) -> u64 {
+        self.0
+    }
+}
+
+impl From<u64> for Pfn {
+    fn from(val: u64) -> Self {
+        Self(val)
+    }
+}
+
+impl From<Pfn> for u64 {
+    fn from(pfn: Pfn) -> Self {
+        pfn.0
+    }
+}
+
+impl_pfn_bounded!(52);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 03/12] gpu: nova-core: gsp: Return GspStaticInfo from boot()
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

Refactor the GSP boot function to return GetGspStaticInfoReply.

This enables access required for memory management initialization to:
- bar1_pde_base: BAR1 page directory base.
- bar2_pde_base: BAR2 page directory base.
- usable memory regions in video memory.

Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Reviewed-by: John Hubbard <jhubbard@nvidia.com>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/gpu.rs      |  8 ++++++--
 drivers/gpu/nova-core/gsp/boot.rs | 12 ++++++++----
 2 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 659f6a24ee13..775cdb653830 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -20,7 +20,10 @@
         Falcon, //
     },
     fb::SysmemFlush,
-    gsp::Gsp,
+    gsp::{
+        commands::GetGspStaticInfoReply,
+        Gsp, //
+    },
     regs,
 };
 
@@ -260,6 +263,7 @@ pub(crate) struct Gpu {
     /// GSP runtime data. Temporarily an empty placeholder.
     #[pin]
     gsp: Gsp,
+    gsp_static_info: GetGspStaticInfoReply,
 }
 
 impl Gpu {
@@ -291,7 +295,7 @@ pub(crate) fn new<'a>(
 
             gsp <- Gsp::new(pdev),
 
-            _: { gsp.boot(pdev, bar, spec.chipset, gsp_falcon, sec2_falcon)? },
+            gsp_static_info: { gsp.boot(pdev, bar, spec.chipset, gsp_falcon, sec2_falcon)? },
 
             bar: devres_bar,
         })
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index df105ef4b371..842aef403f07 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -36,7 +36,10 @@
         Chipset, //
     },
     gsp::{
-        commands,
+        commands::{
+            self,
+            GetGspStaticInfoReply, //
+        },
         sequencer::{
             GspSequencer,
             GspSequencerParams, //
@@ -148,7 +151,7 @@ pub(crate) fn boot(
         chipset: Chipset,
         gsp_falcon: &Falcon<Gsp>,
         sec2_falcon: &Falcon<Sec2>,
-    ) -> Result {
+    ) -> Result<GetGspStaticInfoReply> {
         // The FSP boot process of Hopper+ is not supported for now.
         if matches!(
             chipset.arch(),
@@ -243,12 +246,13 @@ pub(crate) fn boot(
         commands::wait_gsp_init_done(&self.cmdq)?;
 
         // Obtain and display basic GPU information.
-        let info = commands::get_gsp_info(&self.cmdq, bar)?;
+        let info = commands::get_gsp_info(&self.cmdq, bar)
+            .inspect_err(|e| dev_err!(pdev, "Failed to obtain GSP static info ({:?})\n", e))?;
         match info.gpu_name() {
             Ok(name) => dev_info!(pdev, "GPU name: {}\n", name),
             Err(e) => dev_warn!(pdev, "GPU name unavailable: {:?}\n", e),
         }
 
-        Ok(())
+        Ok(info)
     }
 }
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 05/12] gpu: nova-core: gsp: Expose total physical VRAM end from FB region info
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

Add `total_fb_end()` to `GspStaticConfigInfo` that computes the
exclusive end address of the highest valid FB region covering both
usable and GSP-reserved areas.

This allows callers to know the full physical VRAM extent, not just
the allocatable portion.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/gpu.rs             | 11 ++++++++++-
 drivers/gpu/nova-core/gsp/commands.rs    |  5 +++++
 drivers/gpu/nova-core/gsp/fw/commands.rs |  7 +++++++
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 775cdb653830..d9d1a7417a2e 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -8,6 +8,7 @@
     num::Bounded,
     pci,
     prelude::*,
+    sizes::SizeConstants,
     sync::Arc, //
 };
 
@@ -295,7 +296,15 @@ pub(crate) fn new<'a>(
 
             gsp <- Gsp::new(pdev),
 
-            gsp_static_info: { gsp.boot(pdev, bar, spec.chipset, gsp_falcon, sec2_falcon)? },
+            gsp_static_info: gsp
+                .boot(pdev, bar, spec.chipset, gsp_falcon, sec2_falcon)
+                .inspect(|info| {
+                    dev_info!(
+                        pdev.as_ref(),
+                        "Total physical VRAM: {} MiB\n",
+                        info.total_fb_end / u64::SZ_1M
+                    );
+                })?,
 
             bar: devres_bar,
         })
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 049fff337611..172411d7b475 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -196,6 +196,8 @@ pub(crate) struct GetGspStaticInfoReply {
     /// Usable FB (VRAM) region for driver memory allocation.
     #[expect(dead_code)]
     pub(crate) usable_fb_region: Range<u64>,
+    /// End of VRAM.
+    pub(crate) total_fb_end: u64,
 }
 
 impl MessageFromGsp for GetGspStaticInfoReply {
@@ -207,9 +209,12 @@ fn read(
         msg: &Self::Message,
         _sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
     ) -> Result<Self, Self::InitError> {
+        let total_fb_end = msg.total_fb_end().ok_or(ENODEV)?;
+
         Ok(GetGspStaticInfoReply {
             gpu_name: msg.gpu_name_str(),
             usable_fb_region: msg.usable_fb_regions_iter().next().ok_or(ENODEV)?,
+            total_fb_end,
         })
     }
 }
diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-core/gsp/fw/commands.rs
index 50b9c205566f..ea663079d95c 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -161,6 +161,13 @@ pub(crate) fn usable_fb_regions_iter(&self) -> impl Iterator<Item = Range<u64>>
             }
         })
     }
+
+    /// Compute the end of physical VRAM from all FB regions.
+    pub(crate) fn total_fb_end(&self) -> Option<u64> {
+        self.fb_regions()
+            .filter_map(|reg| reg.limit.checked_add(1))
+            .max()
+    }
 }
 
 // SAFETY: Padding is explicit and will not contain uninitialized data.
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 02/12] rust: bitfield: support cast+shift accessor syntax
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

The `bitfield!` macro previously generated accessors that returned the
field's value as a `Bounded<$storage, N>` for its raw N-bit width. For
fields whose logical interpretation is a wider value built by widening
the storage type and shifting left (e.g., a 24-bit register field that
stores bits 16..40 of a 40-bit address), callers had to chain
`cast::<TargetType>()` and `shl::<SHIFT, RES>()` (or worse, raw shift
operators) at every read site, and the inverse for writes.

Add a new field declaration shape:

  $hi:$lo $field as Bounded<$target, $res> shl $shift;

The macro generates:

  - A getter `$field(self) -> Bounded<$target, $res>` that extracts the
    raw N-bit field, widens it to $target, and shifts left by $shift.
  - A setter `with_$field(self, value: Bounded<$target, $res>) -> Self`
    that shifts right by $shift, narrows to the storage type, and writes.

Add a KUnit test mirroring nova-core driver's PRAMIN window register
pattern as well which is the usecase for it.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 rust/kernel/bitfield.rs | 67 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/rust/kernel/bitfield.rs b/rust/kernel/bitfield.rs
index 9ab8dafff36c..1c1fc86441f2 100644
--- a/rust/kernel/bitfield.rs
+++ b/rust/kernel/bitfield.rs
@@ -57,6 +57,8 @@
 //!         hi:lo field_2 => ConvertedType;
 //!         // `field_3` documentation.
 //!         hi:lo field_3 ?=> ConvertedType;
+//!         // `field_4` documentation.
+//!         hi:lo field_4 as Bounded<TargetType, RES> shl SHIFT;
 //!         ...
 //!     }
 //! }
@@ -66,6 +68,8 @@
 //! - `hi:lo`: Bit range (inclusive), where `hi >= lo`.
 //! - `=> Type`: Optional infallible conversion (see [below](#infallible-conversion-)).
 //! - `?=> Type`: Optional fallible conversion (see [below](#fallible-conversion-)).
+//! - `as Bounded<T, RES> shl SHIFT`: Optional cast-and-shift accessor (see
+//!   [below](#cast-and-shift-accessors-as-bounded-t-res-shl-shift)).
 //! - Documentation strings and attributes are optional.
 //!
 //! # Generated code
@@ -299,6 +303,7 @@ fn from(val: $storage) -> $name {
         $($(#[doc = $doc:expr])* $hi:literal:$lo:literal $field:ident
             $(?=> $try_into_type:ty)?
             $(=> $into_type:ty)?
+            $(as Bounded<$target:ty, $res:literal> shl $shift:literal)?
         ;
         )*
     }
@@ -311,6 +316,7 @@ impl $name {
             @public_field_accessors $(#[doc = $doc])* $vis $name $storage : $hi:$lo $field
             $(?=> $try_into_type)?
             $(=> $into_type)?
+            $(as Bounded<$target, $res> shl $shift)?
         );
         )*
         }
@@ -475,6 +481,43 @@ const fn [<__with_ $field>](
         );
     };
 
+    // Public accessors for fields cast to a wider type and left-shifted, exposing them as
+    // `Bounded<$target, $res>` where `$res == ($hi + 1 - $lo) + $shift`.
+    (
+        @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty :
+            $hi:literal:$lo:literal $field:ident
+            as Bounded<$target:ty, $res:literal> shl $shift:literal
+    ) => {
+        ::kernel::macros::paste!(
+
+        $(#[doc = $doc])*
+        #[doc = "Returns the value of this field, cast to the target type and shifted left."]
+        #[inline(always)]
+        $vis fn $field(self) -> ::kernel::num::Bounded<$target, $res> {
+            $crate::const_assert!($res == ($hi + 1 - $lo) + $shift);
+
+            self.[<__ $field>]()
+                .cast::<$target>()
+                .shl::<$shift, $res>()
+        }
+
+        $(#[doc = $doc])*
+        #[doc = "Sets this field from a target-typed, pre-shifted `Bounded` value."]
+        #[inline(always)]
+        $vis fn [<with_ $field>](
+            self,
+            value: ::kernel::num::Bounded<$target, $res>,
+        ) -> Self {
+            $crate::const_assert!($res == ($hi + 1 - $lo) + $shift);
+
+            self.[<__with_ $field>](
+                value.shr::<$shift, { $hi + 1 - $lo }>().cast::<$storage>()
+            )
+        }
+
+        );
+    };
+
     // `Debug` implementation.
     (@debug $name:ident { $($field:ident;)* }) => {
         impl ::kernel::fmt::Debug for $name {
@@ -582,6 +625,15 @@ struct TestStatusRegister(u8) {
         }
     }
 
+    // Mirrors the PRAMIN window register pattern: a 24-bit field in a `u32` storage that
+    // represents bits 16..40 of a 40-bit address. The accessor exposes it as the full
+    // 40-bit `Bounded<u64, 40>`.
+    bitfield! {
+        struct TestWindowReg(u32) {
+            23:0      window_base as Bounded<u64, 40> shl 16;
+        }
+    }
+
     #[test]
     fn test_single_bits() {
         let mut pte = TestPageTableEntry::zeroed();
@@ -806,4 +858,19 @@ fn test_u8_bitfield() {
         assert_eq!(status4.reserved(), 0xF);
         assert_eq!(status4.full_byte(), 0xFF);
     }
+
+    #[test]
+    fn test_cast_shift_accessor() {
+        // Set a value via the pre-shifted setter and read it back via the getter.
+        let addr = Bounded::<u64, 40>::new::<0x12_3456_0000>();
+        let reg = TestWindowReg::zeroed().with_window_base(addr);
+        assert_eq!(reg.window_base().get(), 0x12_3456_0000u64);
+        assert_eq!(u32::from(reg), 0x0012_3456u32);
+
+        // Setting and reading the largest 40-bit aligned value.
+        let max_addr = Bounded::<u64, 40>::new::<0xFF_FFFF_0000>();
+        let reg = TestWindowReg::zeroed().with_window_base(max_addr);
+        assert_eq!(reg.window_base().get(), 0xFF_FFFF_0000u64);
+        assert_eq!(u32::from(reg), 0x00FF_FFFFu32);
+    }
 }
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 04/12] gpu: nova-core: gsp: Extract usable FB region from GSP
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

Add first_usable_fb_region() to GspStaticConfigInfo to extract the first
usable FB region from GSP's fbRegionInfoParams. Usable regions are those
that are not reserved or protected.

The extracted region is stored in GetGspStaticInfoReply and exposed as
usable_fb_region field for use by the memory subsystem.

Reviewed-by: John Hubbard <jhubbard@nvidia.com>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 drivers/gpu/nova-core/gsp/commands.rs    | 11 +++++--
 drivers/gpu/nova-core/gsp/fw/commands.rs | 42 +++++++++++++++++++++++-
 2 files changed, 49 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index c89c7b57a751..049fff337611 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -4,6 +4,7 @@
     array,
     convert::Infallible,
     ffi::FromBytesUntilNulError,
+    ops::Range,
     str::Utf8Error, //
 };
 
@@ -189,15 +190,18 @@ fn init(&self) -> impl Init<Self::Command, Self::InitError> {
     }
 }
 
-/// The reply from the GSP to the [`GetGspInfo`] command.
+/// The reply from the GSP to the [`GetGspStaticInfo`] command.
 pub(crate) struct GetGspStaticInfoReply {
     gpu_name: [u8; 64],
+    /// Usable FB (VRAM) region for driver memory allocation.
+    #[expect(dead_code)]
+    pub(crate) usable_fb_region: Range<u64>,
 }
 
 impl MessageFromGsp for GetGspStaticInfoReply {
     const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
     type Message = GspStaticConfigInfo;
-    type InitError = Infallible;
+    type InitError = Error;
 
     fn read(
         msg: &Self::Message,
@@ -205,6 +209,7 @@ fn read(
     ) -> Result<Self, Self::InitError> {
         Ok(GetGspStaticInfoReply {
             gpu_name: msg.gpu_name_str(),
+            usable_fb_region: msg.usable_fb_regions_iter().next().ok_or(ENODEV)?,
         })
     }
 }
@@ -233,7 +238,7 @@ pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
     }
 }
 
-/// Send the [`GetGspInfo`] command and awaits for its reply.
+/// Send the [`GetGspStaticInfo`] command and awaits for its reply.
 pub(crate) fn get_gsp_info(cmdq: &Cmdq, bar: &Bar0) -> Result<GetGspStaticInfoReply> {
     cmdq.send_command(bar, GetGspStaticInfo)
 }
diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-core/gsp/fw/commands.rs
index db46276430be..50b9c205566f 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -1,5 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
+use core::ops::Range;
+
 use kernel::{
     device,
     pci,
@@ -10,7 +12,10 @@
     }, //
 };
 
-use crate::gsp::GSP_PAGE_SIZE;
+use crate::{
+    gsp::GSP_PAGE_SIZE,
+    num::IntoSafeCast, //
+};
 
 use super::bindings;
 
@@ -121,6 +126,41 @@ impl GspStaticConfigInfo {
     pub(crate) fn gpu_name_str(&self) -> [u8; 64] {
         self.0.gpuNameString
     }
+
+    /// Returns an iterator over valid FB regions from GSP firmware data.
+    fn fb_regions(
+        &self,
+    ) -> impl Iterator<Item = &bindings::NV2080_CTRL_CMD_FB_GET_FB_REGION_FB_REGION_INFO> {
+        let fb_info = &self.0.fbRegionInfoParams;
+        fb_info
+            .fbRegion
+            .iter()
+            .take(fb_info.numFBRegions.into_safe_cast())
+            .filter(|reg| reg.limit >= reg.base)
+    }
+
+    /// Iterates over usable FB regions from GSP firmware data.
+    ///
+    /// Each yielded region is a [`Range<u64>`] suitable for driver memory allocation.
+    /// Usable regions are those that satisfy all the following properties:
+    /// - Are not reserved for firmware internal use.
+    /// - Are not protected (hardware-enforced access restrictions).
+    /// - Support compression (can use GPU memory compression for bandwidth).
+    /// - Support ISO (isochronous memory for display requiring guaranteed bandwidth).
+    pub(crate) fn usable_fb_regions_iter(&self) -> impl Iterator<Item = Range<u64>> + '_ {
+        self.fb_regions().filter_map(|reg| {
+            // Filter: not reserved, not protected, supports compression and ISO.
+            if reg.reserved == 0
+                && reg.bProtected == 0
+                && reg.supportCompressed != 0
+                && reg.supportISO != 0
+            {
+                reg.limit.checked_add(1).map(|end| reg.base..end)
+            } else {
+                None
+            }
+        })
+    }
 }
 
 // SAFETY: Padding is explicit and will not contain uninitialized data.
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 01/12] rust: pci: add resource_flags accessor
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes
In-Reply-To: <20260518180342.2387845-1-joelagnelf@nvidia.com>

Add a `Device::resource_flags()` method to the PCI Rust abstraction,
wrapping the C-side static inline `pci_resource_flags()`.

The flags returned correspond to the `IORESOURCE` bitmask carried by a
PCI BAR's `struct resource`.

The immediate motivation is BAR layout discovery on NVIDIA GPUs: a
64-bit BAR consumes two consecutive Linux PCI resource slots (the lower
32 bits at index N and the upper 32 bits at index N+1, with the latter
having no flags or size of its own).

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
 rust/helpers/pci.c         |  6 ++++++
 rust/kernel/io/resource.rs |  8 ++++++++
 rust/kernel/pci.rs         | 14 ++++++++++++++
 3 files changed, 28 insertions(+)

diff --git a/rust/helpers/pci.c b/rust/helpers/pci.c
index e44905317d75..51148987618a 100644
--- a/rust/helpers/pci.c
+++ b/rust/helpers/pci.c
@@ -19,6 +19,12 @@ __rust_helper resource_size_t rust_helper_pci_resource_len(struct pci_dev *pdev,
 	return pci_resource_len(pdev, bar);
 }
 
+__rust_helper unsigned long rust_helper_pci_resource_flags(const struct pci_dev *pdev,
+							   int bar)
+{
+	return pci_resource_flags(pdev, bar);
+}
+
 __rust_helper bool rust_helper_dev_is_pci(const struct device *dev)
 {
 	return dev_is_pci(dev);
diff --git a/rust/kernel/io/resource.rs b/rust/kernel/io/resource.rs
index b7ac9faf141d..78f353d1605b 100644
--- a/rust/kernel/io/resource.rs
+++ b/rust/kernel/io/resource.rs
@@ -226,10 +226,18 @@ impl Flags {
     /// Resource represents a memory region that must be ioremaped using `ioremap_np`.
     pub const IORESOURCE_MEM_NONPOSTED: Flags = Flags::new(bindings::IORESOURCE_MEM_NONPOSTED);
 
+    /// Memory region uses a 64-bit address (consumes two consecutive PCI resource slots).
+    pub const IORESOURCE_MEM_64: Flags = Flags::new(bindings::IORESOURCE_MEM_64);
+
     // Always inline to optimize out error path of `build_assert`.
     #[inline(always)]
     const fn new(value: u32) -> Self {
         crate::build_assert!(value as u64 <= c_ulong::MAX as u64);
         Flags(value as c_ulong)
     }
+
+    /// Wrap a raw `c_ulong` value returned by a C API into [`Flags`].
+    pub(crate) const fn from_raw(value: c_ulong) -> Self {
+        Flags(value)
+    }
 }
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index af74ddff6114..d76a1377195e 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -17,6 +17,7 @@
         from_result,
         to_result, //
     },
+    io::resource,
     prelude::*,
     str::CStr,
     types::Opaque,
@@ -437,6 +438,19 @@ pub fn resource_len(&self, bar: u32) -> Result<bindings::resource_size_t> {
         Ok(unsafe { bindings::pci_resource_len(self.as_raw(), bar.try_into()?) })
     }
 
+    /// Returns the resource flags (`IORESOURCE_*`) of the given PCI BAR.
+    pub fn resource_flags(&self, bar: u32) -> Result<resource::Flags> {
+        if !Bar::index_is_valid(bar) {
+            return Err(EINVAL);
+        }
+
+        // SAFETY:
+        // - `bar` is a valid bar number, as guaranteed by the above call to `Bar::index_is_valid`,
+        // - by its type invariant `self.as_raw` is always a valid pointer to a `struct pci_dev`.
+        let raw = unsafe { bindings::pci_resource_flags(self.as_raw(), bar.try_into()?) };
+        Ok(resource::Flags::from_raw(raw))
+    }
+
     /// Returns the PCI class as a `Class` struct.
     #[inline]
     pub fn pci_class(&self) -> Class {
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 00/12] Introduce nova-core mm prerequisites
From: Joel Fernandes @ 2026-05-18 18:03 UTC (permalink / raw)
  To: linux-kernel
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
	Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
	Dave Airlie, Daniel Almeida, dri-devel, rust-for-linux, nova-gpu,
	Nikola Djukic, David Airlie, Boqun Feng, John Hubbard,
	Alistair Popple, Timur Tabi, Edwin Peer, Alexandre Courbot,
	Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
	Philipp Stanner, alexeyi, Eliot Courtney, joel, linux-doc,
	Joel Fernandes

This series introduces the prerequisite memory-management infrastructure for
the nova-core driver: a centralized GpuMm manager, types for addressing VRAM
(Pfn, VramAddress), the PRAMIN aperture for indirect VRAM access from the CPU,
and the GSP plumbing that surfaces the usable FB region and total VRAM extent
at boot. It also picks up two small Rust enablers (pci::Bar::resource_flags()
and a cast+shift accessor form of bitfield!) that the rest of the nova-core
mm code relies on.

This series is based on drm-rust-next.

Dependencies (not yet merged):

- Alex Courbot's bitfield series. Tested on v2:
  https://lore.kernel.org/all/20260409-bitfield-v2-0-23ac400071cb@nvidia.com/
  A newer v3 of bitfield is available and should also work (haven't tested):
  https://lore.kernel.org/all/20260501-bitfield-v3-0-aa1076c3337d@nvidia.com/

- rust: maple_tree: implement Send and Sync for MapleTree (v3):
  https://lore.kernel.org/all/20260511143604.3848176-1-joelagnelf@nvidia.com/

The git tree (containing the dependencies above, this series, and the
follow-on page-table/VMM/BAR1 series) can be found at:
git://git.kernel.org/pub/scm/linux/kernel/git/jfern/linux.git (tag: nova-mm-v1-20260518)

Change log:

Changes from v12 to v1 (split-out):

- Part 1 of 2; the v12 series was split for easier review. Page-table/VMM/BAR1 patches in companion series.
- Broke v12's "Add common memory management types" into atomic patches: Pfn, VramAddress, VramAddress arithmetic.
- New prereq: "rust: pci: add resource_flags accessor".
- New prereq: "rust: bitfield: support cast+shift accessor syntax".
- "Add GpuMm centralized memory manager" scoped to scaffolding only; buddy/TLB wiring deferred to companion series.
- Squashed v12's "pramin: drop useless as_ref()" cleanup into "Add PRAMIN aperture self-tests".
- Moved "rust: maple_tree: Send and Sync" out as a standalone dependency.
- Smaller code touch-ups across most carried-over patches.

Link to v12: https://lore.kernel.org/all/20260425211454.174696-1-joelagnelf@nvidia.com/

Joel Fernandes (12):
  rust: pci: add resource_flags accessor
  rust: bitfield: support cast+shift accessor syntax
  gpu: nova-core: gsp: Return GspStaticInfo from boot()
  gpu: nova-core: gsp: Extract usable FB region from GSP
  gpu: nova-core: gsp: Expose total physical VRAM end from FB region
    info
  gpu: nova-core: mm: Add Pfn (Physical Frame Number) type
  gpu: nova-core: mm: Add VramAddress type and conversion traits
  gpu: nova-core: mm: Add VramAddress arithmetic and ordering
  gpu: nova-core: mm: Add support to use PRAMIN windows to write to VRAM
  docs: gpu: nova-core: Document the PRAMIN aperture mechanism
  gpu: nova-core: mm: Add GpuMm centralized memory manager
  gpu: nova-core: mm: Add PRAMIN aperture self-tests

 Documentation/gpu/nova/core/pramin.rst   | 123 ++++++
 Documentation/gpu/nova/index.rst         |   1 +
 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/boot.rs        |  12 +-
 drivers/gpu/nova-core/gsp/commands.rs    |  16 +-
 drivers/gpu/nova-core/gsp/fw/commands.rs |  49 ++-
 drivers/gpu/nova-core/mm.rs              | 247 +++++++++++
 drivers/gpu/nova-core/mm/pramin.rs       | 512 +++++++++++++++++++++++
 drivers/gpu/nova-core/nova_core.rs       |   1 +
 drivers/gpu/nova-core/regs.rs            | 122 ++++++
 rust/helpers/pci.c                       |   6 +
 rust/kernel/bitfield.rs                  |  67 +++
 rust/kernel/io/resource.rs               |   8 +
 rust/kernel/pci.rs                       |  14 +
 16 files changed, 1228 insertions(+), 10 deletions(-)
 create mode 100644 Documentation/gpu/nova/core/pramin.rst
 create mode 100644 drivers/gpu/nova-core/mm.rs
 create mode 100644 drivers/gpu/nova-core/mm/pramin.rs


base-commit: 9bd99adf7cee4b8ed4adecd53269010250a0d2ec
-- 
2.34.1


^ permalink raw reply

* Re: [PATCH v2 0/3] mm/hmm: Add mmap lock-drop support for userfaultfd-backed mappings
From: Andrew Morton @ 2026-05-18 17:48 UTC (permalink / raw)
  To: Stanislav Kinsburskii
  Cc: kys, Liam.Howlett, david, jgg, corbet, leon, ljs, mhocko, rppt,
	shuah, skhan, surenb, vbabka, skinsburskii, linux-doc,
	linux-kernel, linux-kselftest, linux-mm
In-Reply-To: <177863991557.82528.15288076059759579141.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net>

On Wed, 13 May 2026 02:40:11 +0000 Stanislav Kinsburskii <skinsburskii@linux.microsoft.com> wrote:

> This series extends the HMM framework to support userfaultfd-backed memory
> by allowing the mmap read lock to be dropped during hmm_range_fault().
> 
> Some page fault handlers — most notably userfaultfd — require the mmap lock
> to be released so that userspace can resolve the fault. The current HMM
> interface never sets FAULT_FLAG_ALLOW_RETRY, making it impossible to fault
> in pages from userfaultfd-registered regions.
> 
> This series follows the established int *locked pattern from
> get_user_pages_remote() in mm/gup.c. A new entry point,
> hmm_range_fault_unlockable(), accepts an int *locked parameter. When the
> mmap lock is dropped during fault resolution (VM_FAULT_RETRY or
> VM_FAULT_COMPLETED), the function returns 0 with *locked = 0, signalling
> the caller to restart its walk. The existing hmm_range_fault() is
> refactored into a thin wrapper that passes NULL, preserving current
> behavior for all existing callers.
> 
> Faulting hugetlb pages on the unlockable path is not supported because
> walk_hugetlb_range() unconditionally holds and releases
> hugetlb_vma_lock_read across the callback; if the mmap lock is dropped
> inside the callback, the VMA may be freed before the walk framework's
> unlock. Hugetlb pages already present in page tables are handled normally.
> Possible approaches to lift this limitation are documented in
> Documentation/mm/hmm.rst.

Thanks.  AI review asked some questions:
	https://sashiko.dev/#/patchset/177863991557.82528.15288076059759579141.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net

I'd ignore the fist one: don't write buggy fault handlers!



^ permalink raw reply

* Re: [PATCH v3 2/2] cpufreq: CPPC: add autonomous mode boot parameter support
From: Sumit Gupta @ 2026-05-18 17:22 UTC (permalink / raw)
  To: Mario Limonciello, rafael, viresh.kumar, pierre.gondois,
	ionela.voinescu, zhenglifeng1, zhanjie9, corbet, skhan, rdunlap,
	linux-pm, linux-doc, linux-kernel
  Cc: linux-tegra, treding, jonathanh, vsethi, ksitaraman, sanjayc,
	mochs, bbasu
In-Reply-To: <7d7a6ab6-b1ea-484c-a275-19acca50c483@amd.com>


On 18/05/26 19:51, Mario Limonciello wrote:
> External email: Use caution opening links or attachments
>
>
> On 5/18/26 09:15, Sumit Gupta wrote:
>>
>> On 18/05/26 19:20, Mario Limonciello wrote:
>>> External email: Use caution opening links or attachments
>>>
>>>
>>> On 5/18/26 08:44, Sumit Gupta wrote:
>>>> Hi Mario,
>>>>
>>>>
>>>> On 16/05/26 02:43, Mario Limonciello wrote:
>>>>> External email: Use caution opening links or attachments
>>>>>
>>>>>
>>>>> On 5/15/26 07:26, Sumit Gupta wrote:
>>>>>> Add a kernel boot parameter 'cppc_cpufreq.auto_sel_mode' to enable
>>>>>> CPPC autonomous performance selection on all CPUs at system startup.
>>>>>> When autonomous mode is enabled, the hardware automatically adjusts
>>>>>> CPU performance based on workload demands using Energy Performance
>>>>>> Preference (EPP) hints.
>>>>>>
>>>>>> When the parameter is set:
>>>>>> - Configure all CPUs for autonomous operation on first init
>>>>>> - Use HW min/max_perf when available; otherwise initialize from caps
>>>>>> - Initialize desired_perf to max_perf as a starting hint
>>>>>> - Hardware controls frequency instead of the OS governor
>>>>>> - EPP behavior depends on parameter value:
>>>>>>    - performance (or 1): override EPP to performance preference 
>>>>>> (0x0)
>>>>>>    - default_epp (or 2): preserve EPP value programmed by BIOS/
>>>>>> firmware
>>>>>>
>>>>>> The boot parameter is applied only during first policy 
>>>>>> initialization.
>>>>>> Skip applying it on CPU hotplug to preserve runtime sysfs
>>>>>> configuration.
>>>>>>
>>>>>> This patch depends on patch series [1] ("cpufreq: Set policy->min 
>>>>>> and
>>>>>> max as real QoS constraints") so that the policy->min/max set in
>>>>>> cppc_cpufreq_cpu_init() are not overridden by cpufreq_set_policy()
>>>>>> during init.
>>>>>>
>>>>>> Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
>>>>>> ---
>>>>>> [1] https://lore.kernel.org/lkml/20260511135538.522653-1-
>>>>>> pierre.gondois@arm.com/
>>>>>> ---
>>>>>>   .../admin-guide/kernel-parameters.txt         |  16 +++
>>>>>>   drivers/cpufreq/cppc_cpufreq.c                | 122 +++++++++++++
>>>>>> ++++-
>>>>>>   2 files changed, 133 insertions(+), 5 deletions(-)
>>>>>>
>>>>>> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/
>>>>>> Documentation/admin-guide/kernel-parameters.txt
>>>>>> index 0eb64aab3685..7e4b3a8fd76f 100644
>>>>>> --- a/Documentation/admin-guide/kernel-parameters.txt
>>>>>> +++ b/Documentation/admin-guide/kernel-parameters.txt
>>>>>> @@ -1048,6 +1048,22 @@ Kernel parameters
>>>>>>                       policy to use. This governor must be 
>>>>>> registered
>>>>>> in the
>>>>>>                       kernel before the cpufreq driver probes.
>>>>>>
>>>>>> +     cppc_cpufreq.auto_sel_mode=
>>>>>> +                     [CPU_FREQ] Enable ACPI CPPC autonomous
>>>>>> performance
>>>>>> +                     selection. When enabled, hardware 
>>>>>> automatically
>>>>>> adjusts
>>>>>> +                     CPU frequency on all CPUs based on workload
>>>>>> demands.
>>>>>> +                     In Autonomous mode, Energy Performance
>>>>>> Preference (EPP)
>>>>>> +                     hints guide hardware toward performance (0x0)
>>>>>> or energy
>>>>>> +                     efficiency (0xff).
>>>>>> +                     Requires ACPI CPPC autonomous selection 
>>>>>> register
>>>>>> +                     support.
>>>>>> +                     Accepts:
>>>>>> +                       performance, 1: enable auto_sel + set EPP to
>>>>>> +                                       performance (0x0)
>>>>>> +                       default_epp, 2: enable auto_sel, preserve 
>>>>>> EPP
>>>>>> value
>>>>>> +                                       programmed by BIOS/firmware
>>>>>> +                     Unset: cpufreq governors are used (auto_sel
>>>>>> disabled).
>>>>>
>>>>> Rather than unset doing nothing, have you considered having it take a
>>>>> midpoint like 128?  That's what we do in amd-pstate (default to
>>>>> balance_performance).  I think it turns into a reasonable balance.
>>>>
>>>> Thanks for the suggestion.
>>>> I can add balance_performance that enables auto_sel with EPP=128 in 
>>>> v4.
>>>>
>>>> On changing the driver default (no param behavior) to auto enable
>>>> balance_performance, it would be good to keep the current behavior for
>>>> now since cppc_cpufreq is generic across ARM64/RISC-V platforms where
>>>> EPP and Autonomous Selection registers are optional.
>>>> A default change would affect existing users relying on governors.
>>>>
>>>> Thank you,
>>>> Sumit Gupta
>>>
>>> But couldn't you make the "no module parameter set" follow the behavior
>>> to only set the registers if they're available?
>>>
>>> So the systems that support it start using it, the ones that don't it's
>>> a NOP.
>>>
>>
>> Would it work to add balance_performance as a new mode in v4,
>> and discuss changing the default separately as a follow-up?
>>
>
> Sure.
>
>> Runtime detection helps for unsupported platforms. But platforms which
>> support the registers use OS governors today, and silently switching
>> them to autonomous mode on a kernel update is a behavior change for
>> existing users. They would also have no way to boot into sw governor.
>>
>
> But hopefully it should be better battery life/responsiveness for those
> scenarios too, right?
>

Yes in many cases, but if some workloads rely on specific OS governor
configurations, then that would get impacted.
I will send a separate change later to seek broader consensus on
enabling auto_sel as default without any param.

Thank you,
Sumit Gupta

....



^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox