blob: 63a7e1855caa300a5f61ef3b3576c34fd6067ec4 [file] [edit]
// 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)]
pub(super) mod map;
pub(super) mod ver2;
pub(super) mod ver3;
pub(super) mod walk;
use kernel::{
io::Io,
num::Bounded,
prelude::*, //
};
use crate::{
gpu::Architecture,
mm::{
pramin,
Pfn,
VirtualAddress,
VramAddress, //
},
};
/// 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,
}
}
}
// 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(pramin: &mut pramin::Pramin<'_>, addr: VramAddress) -> Result<Self> {
let val = pramin.window_at::<u64>(addr)?.view().read_val();
Ok(Self::from_raw(val))
}
/// Write this `PTE` to VRAM.
fn write(&self, pramin: &mut pramin::Pramin<'_>, addr: VramAddress) -> Result {
pramin
.window_at::<u64>(addr)?
.view()
.write_val((*self).into());
Ok(())
}
}
/// 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(pramin: &mut pramin::Pramin<'_>, addr: VramAddress) -> Result<Self> {
let val = pramin.window_at::<u64>(addr)?.view().read_val();
Ok(Self::from_raw(val))
}
/// Write this `PDE` to VRAM.
fn write(&self, pramin: &mut pramin::Pramin<'_>, addr: VramAddress) -> Result {
pramin
.window_at::<u64>(addr)?
.view()
.write_val((*self).into());
Ok(())
}
/// Check if this `PDE` is valid and points to video memory.
fn is_valid_vram(&self) -> bool {
self.is_valid() && self.aperture() == AperturePde::VideoMemory
}
}
/// 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(pramin: &mut pramin::Pramin<'_>, addr: VramAddress) -> Result<Self> {
let lo = pramin.window_at::<u64>(addr)?.view().read_val();
let hi = pramin.window_at::<u64>(addr + 8)?.view().read_val();
Ok(Self::from_raw(lo, hi))
}
/// Write this dual PDE (128-bit) to VRAM.
fn write(&self, pramin: &mut pramin::Pramin<'_>, addr: VramAddress) -> Result {
pramin
.window_at::<u64>(addr)?
.view()
.write_val(self.big_raw_u64());
pramin
.window_at::<u64>(addr + 8)?
.view()
.write_val(self.small_raw_u64());
Ok(())
}
}
/// MMU configuration trait -- encodes version-specific constants and types.
pub(super) trait MmuConfig: 'static {
/// Page Table Entry type.
type Pte: PteOps;
/// Page Directory Entry type.
type Pde: PdeOps;
/// Dual Page Directory Entry type (128-bit).
type DualPde: DualPdeOps;
/// PDE levels (excluding PTE level) for page table walking.
const PDE_LEVELS: &'static [PageTableLevel];
/// PTE level for this MMU version.
const PTE_LEVEL: PageTableLevel;
/// Dual PDE level (128-bit entries) for this MMU version.
const DUAL_PDE_LEVEL: PageTableLevel;
/// Get the number of entries per page table page for a given level.
fn entries_per_page(level: PageTableLevel) -> usize;
/// Extract the page table index at `level` from `va`.
fn level_index(va: VirtualAddress, level: u64) -> u64;
/// Get the entry size in bytes for a given level.
fn entry_size(level: PageTableLevel) -> usize {
if level == Self::DUAL_PDE_LEVEL {
16 // 128-bit dual PDE
} else {
8 // 64-bit PDE/PTE
}
}
/// Compute upper bound on page table pages needed for `num_virt_pages`.
///
/// Walks from PTE level up through PDE levels, accumulating the tree.
fn pt_pages_upper_bound(num_virt_pages: usize) -> usize {
let mut total = 0;
// PTE pages at the leaf level.
let pte_epp = Self::entries_per_page(Self::PTE_LEVEL);
let mut pages_at_level = num_virt_pages.div_ceil(pte_epp);
total += pages_at_level;
// Walk PDE levels bottom-up (reverse of PDE_LEVELS).
for &level in Self::PDE_LEVELS.iter().rev() {
let epp = Self::entries_per_page(level);
// How many pages at this level do we need to point to
// the previous pages_at_level?
pages_at_level = pages_at_level.div_ceil(epp);
total += pages_at_level;
}
total
}
}
/// Marker struct for MMU v2 (Turing/Ampere/Ada).
pub(super) struct MmuV2;
impl MmuConfig for MmuV2 {
type Pte = ver2::Pte;
type Pde = ver2::Pde;
type DualPde = ver2::DualPde;
const PDE_LEVELS: &'static [PageTableLevel] = ver2::PDE_LEVELS;
const PTE_LEVEL: PageTableLevel = ver2::PTE_LEVEL;
const DUAL_PDE_LEVEL: PageTableLevel = ver2::DUAL_PDE_LEVEL;
fn entries_per_page(level: PageTableLevel) -> usize {
// TODO: Calculate these values from the bitfield dynamically
// instead of hardcoding them.
match level {
PageTableLevel::Pdb => 4, // PD3 root: bits [48:47] = 2 bits
PageTableLevel::L3 => 256, // PD0 dual: bits [28:21] = 8 bits
_ => 512, // PD2, PD1, PT: 9 bits each
}
}
fn level_index(va: VirtualAddress, level: u64) -> u64 {
ver2::VirtualAddressV2::new(va).level_index(level)
}
}
/// Marker struct for MMU v3 (Hopper and later).
pub(super) struct MmuV3;
impl MmuConfig for MmuV3 {
type Pte = ver3::Pte;
type Pde = ver3::Pde;
type DualPde = ver3::DualPde;
const PDE_LEVELS: &'static [PageTableLevel] = ver3::PDE_LEVELS;
const PTE_LEVEL: PageTableLevel = ver3::PTE_LEVEL;
const DUAL_PDE_LEVEL: PageTableLevel = ver3::DUAL_PDE_LEVEL;
fn entries_per_page(level: PageTableLevel) -> usize {
match level {
PageTableLevel::Pdb => 2, // PDE4 root: bit [56] = 1 bit, 2 entries
PageTableLevel::L4 => 256, // PDE0 dual: bits [28:21] = 8 bits
_ => 512, // PDE3, PDE2, PDE1, PT: 9 bits each
}
}
fn level_index(va: VirtualAddress, level: u64) -> u64 {
ver3::VirtualAddressV3::new(va).level_index(level)
}
}
/// 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)
}
}
/// Check if the PDB has valid, VRAM-backed page tables.
#[cfg(CONFIG_NOVA_CORE_SELFTESTS)]
fn check_pdb_inner<M: MmuConfig>(pramin: &mut pramin::Pramin<'_>, pdb_addr: VramAddress) -> Result {
let raw = pramin.window_at::<u64>(pdb_addr)?.view().read_val();
if !M::Pde::from_raw(raw).is_valid_vram() {
return Err(ENOENT);
}
Ok(())
}
/// Check if the PDB has valid, VRAM-backed page tables, dispatching by MMU version.
#[cfg(CONFIG_NOVA_CORE_SELFTESTS)]
pub(super) fn check_pdb_valid(
pramin: &mut pramin::Pramin<'_>,
pdb_addr: VramAddress,
chipset: crate::gpu::Chipset,
) -> Result {
match MmuVersion::from(chipset.arch()) {
MmuVersion::V2 => check_pdb_inner::<MmuV2>(pramin, pdb_addr),
MmuVersion::V3 => check_pdb_inner::<MmuV3>(pramin, pdb_addr),
}
}