* [PATCH v1 01/12] rust: pci: add resource_flags accessor
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 02/12] rust: bitfield: support cast+shift accessor syntax Joel Fernandes
` (10 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread* [PATCH v1 02/12] rust: bitfield: support cast+shift accessor syntax
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 01/12] rust: pci: add resource_flags accessor Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 03/12] gpu: nova-core: gsp: Return GspStaticInfo from boot() Joel Fernandes
` (9 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread* [PATCH v1 03/12] gpu: nova-core: gsp: Return GspStaticInfo from boot()
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 01/12] rust: pci: add resource_flags accessor Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 02/12] rust: bitfield: support cast+shift accessor syntax Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 04/12] gpu: nova-core: gsp: Extract usable FB region from GSP Joel Fernandes
` (8 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread* [PATCH v1 04/12] gpu: nova-core: gsp: Extract usable FB region from GSP
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
` (2 preceding siblings ...)
2026-05-18 18:03 ` [PATCH v1 03/12] gpu: nova-core: gsp: Return GspStaticInfo from boot() Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 05/12] gpu: nova-core: gsp: Expose total physical VRAM end from FB region info Joel Fernandes
` (7 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread* [PATCH v1 05/12] gpu: nova-core: gsp: Expose total physical VRAM end from FB region info
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
` (3 preceding siblings ...)
2026-05-18 18:03 ` [PATCH v1 04/12] gpu: nova-core: gsp: Extract usable FB region from GSP Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 06/12] gpu: nova-core: mm: Add Pfn (Physical Frame Number) type Joel Fernandes
` (6 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread* [PATCH v1 06/12] gpu: nova-core: mm: Add Pfn (Physical Frame Number) type
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
` (4 preceding siblings ...)
2026-05-18 18:03 ` [PATCH v1 05/12] gpu: nova-core: gsp: Expose total physical VRAM end from FB region info Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 07/12] gpu: nova-core: mm: Add VramAddress type and conversion traits Joel Fernandes
` (5 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread* [PATCH v1 07/12] gpu: nova-core: mm: Add VramAddress type and conversion traits
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
` (5 preceding siblings ...)
2026-05-18 18:03 ` [PATCH v1 06/12] gpu: nova-core: mm: Add Pfn (Physical Frame Number) type Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 08/12] gpu: nova-core: mm: Add VramAddress arithmetic and ordering Joel Fernandes
` (4 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread* [PATCH v1 08/12] gpu: nova-core: mm: Add VramAddress arithmetic and ordering
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
` (6 preceding siblings ...)
2026-05-18 18:03 ` [PATCH v1 07/12] gpu: nova-core: mm: Add VramAddress type and conversion traits Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 09/12] gpu: nova-core: mm: Add support to use PRAMIN windows to write to VRAM Joel Fernandes
` (3 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread* [PATCH v1 09/12] gpu: nova-core: mm: Add support to use PRAMIN windows to write to VRAM
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
` (7 preceding siblings ...)
2026-05-18 18:03 ` [PATCH v1 08/12] gpu: nova-core: mm: Add VramAddress arithmetic and ordering Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 10/12] docs: gpu: nova-core: Document the PRAMIN aperture mechanism Joel Fernandes
` (2 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread* [PATCH v1 10/12] docs: gpu: nova-core: Document the PRAMIN aperture mechanism
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
` (8 preceding siblings ...)
2026-05-18 18:03 ` [PATCH v1 09/12] gpu: nova-core: mm: Add support to use PRAMIN windows to write to VRAM Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 11/12] gpu: nova-core: mm: Add GpuMm centralized memory manager Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 12/12] gpu: nova-core: mm: Add PRAMIN aperture self-tests Joel Fernandes
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread* [PATCH v1 11/12] gpu: nova-core: mm: Add GpuMm centralized memory manager
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
` (9 preceding siblings ...)
2026-05-18 18:03 ` [PATCH v1 10/12] docs: gpu: nova-core: Document the PRAMIN aperture mechanism Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
2026-05-18 18:03 ` [PATCH v1 12/12] gpu: nova-core: mm: Add PRAMIN aperture self-tests Joel Fernandes
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread* [PATCH v1 12/12] gpu: nova-core: mm: Add PRAMIN aperture self-tests
2026-05-18 18:03 [PATCH v1 00/12] Introduce nova-core mm prerequisites Joel Fernandes
` (10 preceding siblings ...)
2026-05-18 18:03 ` [PATCH v1 11/12] gpu: nova-core: mm: Add GpuMm centralized memory manager Joel Fernandes
@ 2026-05-18 18:03 ` Joel Fernandes
11 siblings, 0 replies; 13+ messages in thread
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
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 [flat|nested] 13+ messages in thread