blob: 7f89c093d5915566edeebad393337ee571799ad4 [file] [edit]
// 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<VramAddress>,
/// Cached window position.
window_range: Range<VramAddress>,
}
/// 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<T> PraminAccess<'_, T>
where
T: FromBytes + IntoBytes,
{
/// Returns the MMIO view of the accessed location.
pub(super) fn view(&self) -> Mmio<'_, T> {
self.view
}
}
impl<T> 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::<u8>().read_val();
}
}
impl<'gpu> Pramin<'gpu> {
/// Alignment required by the PRAMIN window.
const BASE_ALIGN: Alignment = Alignment::new::<SZ_64K>();
/// Creates the window manager for the given VRAM region.
pub(super) fn new(
bar: Bar0<'gpu>,
chipset: Chipset,
vram_range: Range<VramAddress>,
) -> Result<Self> {
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<Range<VramAddress>> {
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<usize> {
let end = addr.checked_add(len.into_safe_cast()).ok_or(EINVAL)?;
let inside = |r: &Range<VramAddress>| 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<PraminAccess<'a, T>>
where
T: FromBytes + IntoBytes,
{
let offset = self.window_offset(vram_addr, size_of::<T>())?;
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<device::Bound>,
pramin: &mut Pramin<'_>,
base: VramAddress,
) -> Result {
for i in 0u8..4 {
let addr = base + 1 + u64::from(i);
pramin.window_at::<u8>(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::<u8>(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<device::Bound>,
pramin: &mut Pramin<'_>,
base: VramAddress,
) -> Result {
let addr = base + 0x10;
let val: u32 = 0xDEADBEEF;
pramin.window_at::<u32>(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<device::Bound>,
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::<u32>(addr_a)?.view().write_val(val_a);
pramin.window_at::<u32>(addr_b)?.view().write_val(val_b);
selftest_assert_eq!(
dev,
pramin.window_at::<u32>(addr_a)?.view().read_val(),
val_a
);
selftest_assert_eq!(
dev,
pramin.window_at::<u32>(addr_b)?.view().read_val(),
val_b
);
Ok(())
}
/// Test that offsets outside the VRAM region are rejected.
fn test_invalid_offset(
dev: &device::Device<device::Bound>,
pramin: &mut Pramin<'_>,
vram_end: VramAddress,
) -> Result {
selftest_assert!(dev, pramin.window_at::<u32>(vram_end).is_err());
Ok(())
}
/// Test that misaligned accesses are rejected.
fn test_misaligned_access(
dev: &device::Device<device::Bound>,
pramin: &mut Pramin<'_>,
base: VramAddress,
) -> Result {
// `u16` at odd offset (not 2-byte aligned).
selftest_assert!(dev, pramin.window_at::<u16>(base + 0x21).is_err());
// `u32` at 2-byte-aligned (not 4-byte-aligned) offset.
selftest_assert!(dev, pramin.window_at::<u32>(base + 2).is_err());
// `u64` at a 4-byte-aligned (not 8-byte-aligned) address.
selftest_assert!(dev, pramin.window_at::<u64>(base + 0x44).is_err());
// A `u16` view at an even address is allowed.
pramin.window_at::<u16>(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<device::Bound>,
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(())
}
}