[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <9da480a7-f8ff-4b58-b474-378485762c7c@nvidia.com>
Date: Tue, 14 Oct 2025 19:08:51 -0700
From: John Hubbard <jhubbard@...dia.com>
To: Joel Fernandes <joelagnelf@...dia.com>, linux-kernel@...r.kernel.org,
rust-for-linux@...r.kernel.org, dri-devel@...ts.freedesktop.org,
dakr@...nel.org, acourbot@...dia.com
Cc: Alistair Popple <apopple@...dia.com>, Miguel Ojeda <ojeda@...nel.org>,
Alex Gaynor <alex.gaynor@...il.com>, Boqun Feng <boqun.feng@...il.com>,
Gary Guo <gary@...yguo.net>, bjorn3_gh@...tonmail.com,
Benno Lossin <lossin@...nel.org>, Andreas Hindborg <a.hindborg@...nel.org>,
Alice Ryhl <aliceryhl@...gle.com>, Trevor Gross <tmgross@...ch.edu>,
David Airlie <airlied@...il.com>, Simona Vetter <simona@...ll.ch>,
Maarten Lankhorst <maarten.lankhorst@...ux.intel.com>,
Maxime Ripard <mripard@...nel.org>, Thomas Zimmermann <tzimmermann@...e.de>,
Timur Tabi <ttabi@...dia.com>, joel@...lfernandes.org,
Elle Rhumsaa <elle@...thered-steel.dev>,
Daniel Almeida <daniel.almeida@...labora.com>,
Andrea Righi <arighi@...dia.com>, nouveau@...ts.freedesktop.org
Subject: Re: [PATCH RFC] nova-core: mm: Add initial structures for page table
management
On 10/6/25 2:09 PM, Joel Fernandes wrote:
> Add initial structures and helpers for page table management. Will
> expand and build on this in later patches. Uses bitstruct for clean
> code.
>
> Signed-off-by: Joel Fernandes <joelagnelf@...dia.com>
> ---
> Rebased on rust-drm-next + bitfield patches [1].
>
> [1] https://lore.kernel.org/all/20251003154748.1687160-1-joelagnelf@nvidia.com/
>
> drivers/gpu/nova-core/falcon/gsp.rs | 2 +-
> drivers/gpu/nova-core/mm/mod.rs | 3 +
> drivers/gpu/nova-core/mm/types.rs | 382 ++++++++++++++++++++++++++++
> drivers/gpu/nova-core/nova_core.rs | 1 +
> 4 files changed, 387 insertions(+), 1 deletion(-)
> create mode 100644 drivers/gpu/nova-core/mm/mod.rs
> create mode 100644 drivers/gpu/nova-core/mm/types.rs
>
Hi Joel,
This will be easier to provide a meaningful review for, once there are
some callers to look at. Structures alone are very difficult to review,
in the absence of behaviors (callers).
thanks,
John Hubbard
> diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs
> index cd4960e997c8..839c803001b5 100644
> --- a/drivers/gpu/nova-core/falcon/gsp.rs
> +++ b/drivers/gpu/nova-core/falcon/gsp.rs
> @@ -5,7 +5,7 @@
> use crate::{
> driver::Bar0,
> falcon::{Falcon, FalconEngine, PFalcon2Base, PFalconBase},
> - regs::self,
> + regs,
> };
>
> /// Type specifying the `Gsp` falcon engine. Cannot be instantiated.
> diff --git a/drivers/gpu/nova-core/mm/mod.rs b/drivers/gpu/nova-core/mm/mod.rs
> new file mode 100644
> index 000000000000..4cebfa3cf184
> --- /dev/null
> +++ b/drivers/gpu/nova-core/mm/mod.rs
> @@ -0,0 +1,3 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +pub(crate) mod types;
> diff --git a/drivers/gpu/nova-core/mm/types.rs b/drivers/gpu/nova-core/mm/types.rs
> new file mode 100644
> index 000000000000..6eca8bcb24a6
> --- /dev/null
> +++ b/drivers/gpu/nova-core/mm/types.rs
> @@ -0,0 +1,382 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//! Page table data management for NVIDIA GPUs
> +
> +#![expect(dead_code)]
> +
> +use kernel::bitfield;
> +
> +/// Memory size constants
> +pub(crate) const KB: usize = 1024;
> +pub(crate) const MB: usize = KB * 1024;
> +
> +/// Page size: 4 KiB
> +pub(crate) const PAGE_SIZE: usize = 4 * KB;
> +
> +/// Page Table Level hierarchy
> +#[derive(Debug, Clone, Copy, PartialEq)]
> +pub(crate) enum PageTableLevel {
> + Pdb, // Level 0 - Page Directory Base
> + L1, // Level 1
> + L2, // Level 2
> + L3, // Level 3 - Dual PDE (128-bit entries)
> + L4, // Level 4 - PTEs
> +}
> +
> +impl PageTableLevel {
> + /// Get the entry size for this level
> + pub(crate) fn entry_size(&self) -> usize {
> + match self {
> + Self::L3 => 16, // 128-bit dual PDE
> + _ => 8, // 64-bit PDE/PTE
> + }
> + }
> +
> + /// PDE levels constant array for iteration
> + const PDE_LEVELS: [PageTableLevel; 4] = [
> + PageTableLevel::Pdb,
> + PageTableLevel::L1,
> + PageTableLevel::L2,
> + PageTableLevel::L3,
> + ];
> +
> + /// Get iterator over PDE levels
> + pub(crate) fn pde_levels() -> impl Iterator<Item = PageTableLevel> {
> + Self::PDE_LEVELS.into_iter()
> + }
> +}
> +
> +/// Memory aperture for Page Directory Entries (PDEs)
> +#[repr(u8)]
> +#[derive(Debug, Clone, Copy, PartialEq, Default)]
> +pub(crate) enum AperturePde {
> + #[default]
> + Invalid = 0,
> + VideoMemory = 1,
> + SystemCoherent = 2,
> + SystemNonCoherent = 3,
> +}
> +
> +impl From<u8> for AperturePde {
> + fn from(val: u8) -> Self {
> + match val {
> + 1 => Self::VideoMemory,
> + 2 => Self::SystemCoherent,
> + 3 => Self::SystemNonCoherent,
> + _ => Self::Invalid,
> + }
> + }
> +}
> +
> +impl From<AperturePde> for u8 {
> + fn from(val: AperturePde) -> Self {
> + val as u8
> + }
> +}
> +
> +impl From<AperturePde> for u64 {
> + fn from(val: AperturePde) -> Self {
> + val as u64
> + }
> +}
> +
> +/// Memory aperture for Page Table Entries (PTEs)
> +#[repr(u8)]
> +#[derive(Debug, Clone, Copy, PartialEq, Default)]
> +pub(crate) enum AperturePte {
> + #[default]
> + VideoMemory = 0,
> + PeerVideoMemory = 1,
> + SystemCoherent = 2,
> + SystemNonCoherent = 3,
> +}
> +
> +impl From<u8> for AperturePte {
> + fn from(val: u8) -> Self {
> + match val {
> + 0 => Self::VideoMemory,
> + 1 => Self::PeerVideoMemory,
> + 2 => Self::SystemCoherent,
> + 3 => Self::SystemNonCoherent,
> + _ => Self::VideoMemory,
> + }
> + }
> +}
> +
> +impl From<AperturePte> for u8 {
> + fn from(val: AperturePte) -> Self {
> + val as u8
> + }
> +}
> +
> +impl From<AperturePte> for u64 {
> + fn from(val: AperturePte) -> Self {
> + val as u64
> + }
> +}
> +
> +/// Common trait for address types
> +pub(crate) trait Address {
> + /// Get raw u64 value
> + fn raw(&self) -> u64;
> +
> + /// Get the frame number (upper 52 bits)
> + fn frame_number(&self) -> u64 {
> + self.raw() >> 12
> + }
> +
> + /// Get the offset within the frame (lower 12 bits)
> + fn frame_offset(&self) -> u16 {
> + (self.raw() & 0xFFF) as u16
> + }
> +
> + /// Convert to usize
> + fn to_usize(&self) -> usize {
> + self.raw() as usize
> + }
> +}
> +
> +// VRAM address representation - used with Pfn for GPU video memory allocation
> +// and page table entries.
> +bitfield! {
> + pub(crate) struct VramAddress(u64) {
> + 11:0 offset as u16; // Offset within 4KB page
> + 63:12 frame_number as u64; // Frame number (52 bits)
> + }
> +}
> +
> +impl Address for VramAddress {
> + fn raw(&self) -> u64 {
> + self.0
> + }
> +}
> +
> +impl From<u64> for VramAddress {
> + fn from(val: u64) -> Self {
> + VramAddress(val)
> + }
> +}
> +
> +impl From<usize> for VramAddress {
> + fn from(val: usize) -> Self {
> + VramAddress(val as u64)
> + }
> +}
> +
> +impl From<Pfn> for VramAddress {
> + fn from(pfn: Pfn) -> VramAddress {
> + VramAddress::default().set_frame_number(pfn.raw())
> + }
> +}
> +
> +// Virtual address
> +bitfield! {
> + pub(crate) struct VirtualAddress(u64) {
> + 11:0 offset as u16; // Offset within 4KB page
> + 20:12 l4_index as u16; // Level 4 index (PTE)
> + 29:21 l3_index as u16; // Level 3 index
> + 38:30 l2_index as u16; // Level 2 index
> + 47:39 l1_index as u16; // Level 1 index
> + 56:48 l0_index as u16; // Level 0 index (PDB)
> + }
> +}
> +
> +impl VirtualAddress {
> + /// Get index for a specific page table level
> + pub(crate) fn level_index(&self, level: PageTableLevel) -> u16 {
> + match level {
> + PageTableLevel::Pdb => self.l0_index(),
> + PageTableLevel::L1 => self.l1_index(),
> + PageTableLevel::L2 => self.l2_index(),
> + PageTableLevel::L3 => self.l3_index(),
> + PageTableLevel::L4 => self.l4_index(),
> + }
> + }
> +}
> +
> +impl Address for VirtualAddress {
> + fn raw(&self) -> u64 {
> + self.0
> + }
> +}
> +
> +impl From<u64> for VirtualAddress {
> + fn from(addr: u64) -> Self {
> + Self(addr)
> + }
> +}
> +
> +impl From<Vfn> for VirtualAddress {
> + fn from(vfn: Vfn) -> VirtualAddress {
> + VirtualAddress::from(vfn.raw() << 12)
> + }
> +}
> +
> +// Page Table Entry (PTE) - 64-bit entry at level 4
> +// Note: PTE has V=1 for valid, and includes aperture bits
> +bitfield! {
> + pub(crate) struct Pte(u64) {
> + 0:0 valid as bool; // (1 = valid for PTEs)
> + 1:1 privilege as bool; // P - Privileged/kernel-only access
> + 2:2 read_only as bool; // RO - Write protection
> + 3:3 atomic_disable as bool; // AD - Disable atomic ops
> + 4:4 encrypted as bool; // E - Encryption enabled
> + 39:8 frame_number as u64; // PA[39:8] - Physical frame number (32 bits)
> + 41:40 aperture as u8 => AperturePte; // Memory aperture type.
> + 42:42 volatile as bool; // VOL - Volatile flag
> + 50:43 kind as u8; // K[7:0] - Compression/tiling kind
> + 63:51 comptag_line as u16; // CTL[12:0] - Compression tag line (partial, shared with PA)
> + }
> +}
> +
> +impl Pte {
> + /// Set the address for this PTE
> + pub(crate) fn set_address(&mut self, addr: VramAddress) {
> + self.set_frame_number(addr.frame_number());
> + }
> +
> + /// Get the address from this PTE
> + pub(crate) fn address(&self) -> VramAddress {
> + VramAddress::default().set_frame_number(self.frame_number())
> + }
> +
> + /// Get raw u64 value
> + pub(crate) fn raw(&self) -> u64 {
> + self.0
> + }
> +}
> +
> +// Page Directory Entry (PDE) - 64-bit entry at levels 0-2
> +// Note: V=0 means valid for PDEs (inverted), aperture=0 means invalid
> +bitfield! {
> + pub(crate) struct Pde(u64) {
> + 0:0 valid_inverted as bool; // V - Valid bit (0=valid for PDEs)
> + 2:1 aperture as u8 => AperturePde; // Memory aperture type
> + 3:3 volatile as bool; // VOL - Volatile flag
> + 39:8 table_frame_number as u64; // PA[39:8] - Table frame number (32 bits)
> + }
> +}
> +
> +impl Pde {
> + /// Check if PDE is valid
> + pub(crate) fn is_valid(&self) -> bool {
> + !self.valid_inverted() && self.aperture() != AperturePde::Invalid
> + }
> +
> + /// The valid bit is inverted so add an accessor to flip it
> + pub(crate) fn set_valid(&self, value: bool) -> Pde {
> + self.set_valid_inverted(!value)
> + }
> +
> + /// Set the table address for this PDE
> + pub(crate) fn set_table_address(&mut self, addr: VramAddress) {
> + self.set_table_frame_number(addr.frame_number());
> + }
> +
> + /// Get the table address from this PDE
> + pub(crate) fn table_address(&self) -> VramAddress {
> + VramAddress::default().set_frame_number(self.table_frame_number())
> + }
> +
> + /// Get raw u64 value
> + pub(crate) fn raw(&self) -> u64 {
> + self.0
> + }
> +}
> +
> +/// Dual PDE at Level 3 - 128-bit entry containing both LPT and SPT pointers
> +/// Lower 64 bits = big/large page, upper 64 bits = small page
> +#[repr(C)]
> +#[derive(Debug, Clone, Copy)]
> +pub(crate) struct DualPde {
> + pub lpt: Pde, // Large/Big Page Table pointer (2MB pages) - bits 63:0 (lower)
> + pub spt: Pde, // Small Page Table pointer (4KB pages) - bits 127:64 (upper)
> +}
> +
> +impl DualPde {
> + /// Create a new empty dual PDE
> + pub(crate) fn new() -> Self {
> + Self {
> + spt: Pde::default(),
> + lpt: Pde::default(),
> + }
> + }
> +
> + /// Set the Small Page Table address with aperture
> + pub(crate) fn set_small_pt_address(&mut self, addr: VramAddress, aperture: AperturePde) {
> + self.spt = Pde::default()
> + .set_valid(true)
> + .set_table_frame_number(addr.frame_number())
> + .set_aperture(aperture);
> + }
> +
> + /// Set the Large Page Table address with aperture
> + pub(crate) fn set_large_pt_address(&mut self, addr: VramAddress, aperture: AperturePde) {
> + self.lpt = Pde::default()
> + .set_valid(true)
> + .set_table_frame_number(addr.frame_number())
> + .set_aperture(aperture);
> + }
> +
> + /// Check if has valid Small Page Table
> + pub(crate) fn has_small_pt_address(&self) -> bool {
> + self.spt.is_valid()
> + }
> +
> + /// Check if has valid Large Page Table
> + pub(crate) fn has_large_pt_address(&self) -> bool {
> + self.lpt.is_valid()
> + }
> +
> + /// Set SPT (Small Page Table) using Pfn
> + pub(crate) fn set_spt(&mut self, pfn: Pfn, aperture: AperturePde) {
> + self.spt = Pde::default()
> + .set_valid(true)
> + .set_aperture(aperture)
> + .set_table_frame_number(pfn.raw());
> + }
> +}
> +
> +/// Virtual Frame Number - virtual address divided by 4KB
> +#[derive(Debug, Clone, Copy, PartialEq, Eq)]
> +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 raw frame number
> + pub(crate) const fn raw(&self) -> u64 {
> + self.0
> + }
> +}
> +
> +impl From<VirtualAddress> for Vfn {
> + fn from(vaddr: VirtualAddress) -> Self {
> + Self(vaddr.frame_number())
> + }
> +}
> +
> +/// Physical Frame Number - physical address divided by 4KB
> +#[derive(Debug, Clone, Copy, PartialEq, Eq)]
> +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 raw frame number
> + pub(crate) const fn raw(&self) -> u64 {
> + self.0
> + }
> +}
> +
> +impl From<VramAddress> for Pfn {
> + fn from(addr: VramAddress) -> Self {
> + let frame = addr.frame_number();
> + Self(frame)
> + }
> +}
> diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
> index fffcaee2249f..ff994f1ec9b9 100644
> --- a/drivers/gpu/nova-core/nova_core.rs
> +++ b/drivers/gpu/nova-core/nova_core.rs
> @@ -10,6 +10,7 @@
> mod gfw;
> mod gpu;
> mod gsp;
> +mod mm;
> mod regs;
> mod util;
> mod vbios;
Powered by blists - more mailing lists