// SPDX-License-Identifier: GPL-2.0 // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. //! Utilities for accessing VRAM through the PRAMIN window. use core::ops::Range; use kernel::{ io::{ io_project, register, register::OffsetLoc, Io, Mmio, // }, prelude::*, ptr::{ Alignable, Alignment, // }, sizes::{ SZ_1M, SZ_64K, // }, }; use crate::{ driver::{ Bar0, NovaRegisters, // }, gpu::Chipset, mm::{ hal::{ self, MmHal, // }, VramAddress, // }, num::IntoSafeCast, // }; /// Size of the PRAMIN window (1 MiB). const WINDOW_SIZE: usize = SZ_1M; /// The PRAMIN window, which is a 1 MiB window into VRAM at a fixed BAR0 offset. #[derive(FromBytes, IntoBytes)] struct PraminWindow([u8; WINDOW_SIZE]); register! { base: NovaRegisters; /// Location of the window inside BAR0. PRAMIN: PraminWindow @ 0x700000; } /// Owner of the PRAMIN window state. /// /// [`Pramin::window_at()`] repositions the window as needed and returns a typed MMIO view into /// it, holding the manager borrowed for the lifetime of the view. pub(super) struct Pramin<'gpu> { bar: Bar0<'gpu>, hal: &'static dyn MmHal, /// MMIO view of the PRAMIN window in BAR0. window: Mmio<'gpu, PraminWindow>, /// VRAM range to keep the PRAMIN window inside. vram_range: Range, /// Cached window position. window_range: Range, } /// Typed view of VRAM through the PRAMIN window. /// /// Inserts an ordering point after previous writes through the window on drop. Views returned /// by [`PraminAccess::view()`] cannot outlive this access, so the ordering point covers every /// write made through them. pub(super) struct PraminAccess<'a, T> where T: FromBytes + IntoBytes, { view: Mmio<'a, T>, } impl PraminAccess<'_, T> where T: FromBytes + IntoBytes, { /// Returns the MMIO view of the accessed location. pub(super) fn view(&self) -> Mmio<'_, T> { self.view } } impl Drop for PraminAccess<'_, T> where T: FromBytes + IntoBytes, { fn drop(&mut self) { // Insert an ordering point after previous writes through this window. self.view.cast::().read_val(); } } impl<'gpu> Pramin<'gpu> { /// Alignment required by the PRAMIN window. const BASE_ALIGN: Alignment = Alignment::new::(); /// Creates the window manager for the given VRAM region. pub(super) fn new( bar: Bar0<'gpu>, chipset: Chipset, vram_range: Range, ) -> Result { let hal = hal::mm_hal(chipset); let window = io_project!(bar, build: PRAMIN); let base = vram_range.start.align_down(Self::BASE_ALIGN); let window_range = Self::window_range(base)?; hal.write_pramin_window_base(bar, base)?; Ok(Self { bar, hal, window, vram_range, window_range, }) } /// Returns the VRAM range a window based at `base` exposes. fn window_range(base: VramAddress) -> Result> { let end = base .checked_add(WINDOW_SIZE.into_safe_cast()) .ok_or(EINVAL)?; Ok(base..end) } /// Check the window covers `len` bytes at `addr`, moving it if needed. /// /// Returns the window offset at which to perform the access. fn window_offset(&mut self, addr: VramAddress, len: usize) -> Result { let end = addr.checked_add(len.into_safe_cast()).ok_or(EINVAL)?; let inside = |r: &Range| r.contains(&addr) && end <= r.end; if !inside(&self.vram_range) { return Err(EINVAL); } // Reposition the window if the access falls outside it. if !inside(&self.window_range) { let base = addr.align_down(Self::BASE_ALIGN); let window_range = Self::window_range(base)?; if !inside(&window_range) { return Err(EINVAL); } self.hal.write_pramin_window_base(self.bar, base)?; self.window_range = window_range; } Ok((addr - self.window_range.start).into_safe_cast()) } /// Return a typed MMIO view of a `T` at `vram_addr`. /// /// Returns an error if `vram_addr` is not aligned to `T`'s alignment, or if /// a `T` at `vram_addr` does not fit within the VRAM region. pub(super) fn window_at<'a, T>( &'a mut self, vram_addr: VramAddress, ) -> Result> where T: FromBytes + IntoBytes, { let offset = self.window_offset(vram_addr, size_of::())?; let view = io_project!(self.window, try: OffsetLoc::new(offset)); Ok(PraminAccess { view }) } } #[cfg(CONFIG_NOVA_CORE_SELFTESTS)] pub(super) mod selftest { use kernel::{ device, io::io_read, sizes::SizeConstants, // }; use super::*; use crate::{ selftest_assert, selftest_assert_eq, // }; /// Test read/write at byte granularity, at unaligned addresses. fn test_byte_readwrite( dev: &device::Device, pramin: &mut Pramin<'_>, base: VramAddress, ) -> Result { for i in 0u8..4 { let addr = base + 1 + u64::from(i); pramin.window_at::(addr)?.view().write_val(0xA0 + i); } for i in 0u8..4 { let addr = base + 1 + u64::from(i); selftest_assert_eq!( dev, pramin.window_at::(addr)?.view().read_val(), 0xA0 + i ); } Ok(()) } /// Test writing a `u32` and reading back as individual `u8`s. fn test_u32_as_bytes( dev: &device::Device, pramin: &mut Pramin<'_>, base: VramAddress, ) -> Result { let addr = base + 0x10; let val: u32 = 0xDEADBEEF; pramin.window_at::(addr)?.view().write_val(val); let window = pramin.window_at::<[u8; 4]>(addr)?; for (i, &expected) in val.to_le_bytes().iter().enumerate() { selftest_assert_eq!(dev, io_read!(window.view(), [build: i]), expected); } Ok(()) } /// Test window repositioning across 1 MiB boundaries. fn test_window_reposition( dev: &device::Device, pramin: &mut Pramin<'_>, base: VramAddress, ) -> Result { let addr_a = base; let addr_b = base + u64::SZ_2M; // base + 2 MiB (different 1 MiB region). let val_a: u32 = 0x11111111; let val_b: u32 = 0x22222222; pramin.window_at::(addr_a)?.view().write_val(val_a); pramin.window_at::(addr_b)?.view().write_val(val_b); selftest_assert_eq!( dev, pramin.window_at::(addr_a)?.view().read_val(), val_a ); selftest_assert_eq!( dev, pramin.window_at::(addr_b)?.view().read_val(), val_b ); Ok(()) } /// Test that offsets outside the VRAM region are rejected. fn test_invalid_offset( dev: &device::Device, pramin: &mut Pramin<'_>, vram_end: VramAddress, ) -> Result { selftest_assert!(dev, pramin.window_at::(vram_end).is_err()); Ok(()) } /// Test that misaligned accesses are rejected. fn test_misaligned_access( dev: &device::Device, pramin: &mut Pramin<'_>, base: VramAddress, ) -> Result { // `u16` at odd offset (not 2-byte aligned). selftest_assert!(dev, pramin.window_at::(base + 0x21).is_err()); // `u32` at 2-byte-aligned (not 4-byte-aligned) offset. selftest_assert!(dev, pramin.window_at::(base + 2).is_err()); // `u64` at a 4-byte-aligned (not 8-byte-aligned) address. selftest_assert!(dev, pramin.window_at::(base + 0x44).is_err()); // A `u16` view at an even address is allowed. pramin.window_at::(base + 0x22)?; Ok(()) } /// Run PRAMIN self-tests during probe. /// /// `base` is the start of a driver-usable VRAM span that the tests are free to /// overwrite. pub(crate) fn run( dev: &device::Device, pramin: &mut Pramin<'_>, base: VramAddress, ) -> Result { dev_dbg!(dev, "PRAMIN: starting self-tests\n"); let vram_end = pramin.vram_range.end; test_byte_readwrite(dev, pramin, base)?; test_u32_as_bytes(dev, pramin, base)?; test_window_reposition(dev, pramin, base)?; test_invalid_offset(dev, pramin, vram_end)?; test_misaligned_access(dev, pramin, base)?; dev_info!(dev, "PRAMIN: self-tests passed\n"); Ok(()) } }