[PATCH 11/13] gpu: nova-core: correct FRTS vidmem offset calculation

From: Eliot Courtney

Date: Mon Jun 15 2026 - 10:45:36 EST


Currently, the frts vidmem offset is calculated based on the non-wpr
heap size and pmu reservation size, but this is not right. The layout
actually looks like this:

| non-wpr heap | WPR2 .. FRTS | PMU reserved | ... | VGA workspace |

It's just by coincidence + generous alignment that the values happened
to match. Instead, define a per-architecture reserved size at the end of
the framebuffer and use this plus the PMU reserved size to calculate the
frts vidmem offset.

Fixes: d317e4585fa3 ("gpu: nova-core: Hopper/Blackwell: add FSP Chain of Trust boot")
Signed-off-by: Eliot Courtney <ecourtney@xxxxxxxxxx>
---
drivers/gpu/nova-core/fb.rs | 4 ++++
drivers/gpu/nova-core/fb/hal.rs | 3 +++
drivers/gpu/nova-core/fb/hal/ga100.rs | 4 ++++
drivers/gpu/nova-core/fb/hal/ga102.rs | 4 ++++
drivers/gpu/nova-core/fb/hal/gb100.rs | 5 +++++
drivers/gpu/nova-core/fb/hal/gb202.rs | 5 +++++
drivers/gpu/nova-core/fb/hal/gh100.rs | 4 ++++
drivers/gpu/nova-core/fb/hal/tu102.rs | 8 ++++++++
drivers/gpu/nova-core/fsp.rs | 25 ++++++++++++++++++-------
9 files changed, 55 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/nova-core/fb.rs b/drivers/gpu/nova-core/fb.rs
index facecb8b411f..2089fa1d7a22 100644
--- a/drivers/gpu/nova-core/fb.rs
+++ b/drivers/gpu/nova-core/fb.rs
@@ -304,6 +304,9 @@ pub(crate) struct FbSizes {
pub(crate) heap_size: u64,
/// PMU reserved memory size, in bytes.
pub(crate) pmu_reserved_size: u32,
+ /// Size reserved at the end of the framebuffer. This is architecture dependent and used to
+ /// compute the FRTS offset for the FSP CoT message.
+ pub(crate) fb_end_reserved_size: u32,
/// Number of VF partitions.
pub(crate) vf_partition_count: u8,
}
@@ -321,6 +324,7 @@ fn new(chipset: Chipset, bar: Bar0<'_>) -> Result<Self> {
.wpr_heap_size(chipset, fb_size)?,
heap_size: u64::from(hal.non_wpr_heap_size()),
pmu_reserved_size: hal.pmu_reserved_size(),
+ fb_end_reserved_size: hal.fb_end_reserved_size(),
vf_partition_count: 0,
})
}
diff --git a/drivers/gpu/nova-core/fb/hal.rs b/drivers/gpu/nova-core/fb/hal.rs
index 714f0b51cd8f..aa50534550eb 100644
--- a/drivers/gpu/nova-core/fb/hal.rs
+++ b/drivers/gpu/nova-core/fb/hal.rs
@@ -41,6 +41,9 @@ pub(crate) trait FbHal {

/// Returns the FRTS size, in bytes.
fn frts_size(&self) -> u64;
+
+ /// Returns the size reserved at the end of the framebuffer, in bytes.
+ fn fb_end_reserved_size(&self) -> u32;
}

/// Returns the HAL corresponding to `chipset`.
diff --git a/drivers/gpu/nova-core/fb/hal/ga100.rs b/drivers/gpu/nova-core/fb/hal/ga100.rs
index 3cc1caf361c7..ce544cbafa2d 100644
--- a/drivers/gpu/nova-core/fb/hal/ga100.rs
+++ b/drivers/gpu/nova-core/fb/hal/ga100.rs
@@ -81,6 +81,10 @@ fn non_wpr_heap_size(&self) -> u32 {
fn frts_size(&self) -> u64 {
0
}
+
+ fn fb_end_reserved_size(&self) -> u32 {
+ super::tu102::fb_end_reserved_size_tu102()
+ }
}

const GA100: Ga100 = Ga100;
diff --git a/drivers/gpu/nova-core/fb/hal/ga102.rs b/drivers/gpu/nova-core/fb/hal/ga102.rs
index 44a2cf8a00f1..82b4c6034c4a 100644
--- a/drivers/gpu/nova-core/fb/hal/ga102.rs
+++ b/drivers/gpu/nova-core/fb/hal/ga102.rs
@@ -48,6 +48,10 @@ fn non_wpr_heap_size(&self) -> u32 {
fn frts_size(&self) -> u64 {
super::tu102::frts_size_tu102()
}
+
+ fn fb_end_reserved_size(&self) -> u32 {
+ super::tu102::fb_end_reserved_size_tu102()
+ }
}

const GA102: Ga102 = Ga102;
diff --git a/drivers/gpu/nova-core/fb/hal/gb100.rs b/drivers/gpu/nova-core/fb/hal/gb100.rs
index 6e0eba101ca1..a53932eaf483 100644
--- a/drivers/gpu/nova-core/fb/hal/gb100.rs
+++ b/drivers/gpu/nova-core/fb/hal/gb100.rs
@@ -78,6 +78,7 @@ fn write_sysmem_flush_page_gb100(bar: Bar0<'_>, addr: Bounded<u64, 52>) {
);
}

+// This PMU reservation size is r570-specific.
pub(super) const fn pmu_reserved_size_gb100() -> u32 {
usize_into_u32::<{ const_align_up(SZ_8M + SZ_16M + SZ_4K, Alignment::new::<SZ_128K>()).unwrap() }>(
)
@@ -116,6 +117,10 @@ fn non_wpr_heap_size(&self) -> u32 {
fn frts_size(&self) -> u64 {
super::tu102::frts_size_tu102()
}
+
+ fn fb_end_reserved_size(&self) -> u32 {
+ u32::SZ_2M + u32::SZ_128K
+ }
}

const GB100: Gb100 = Gb100;
diff --git a/drivers/gpu/nova-core/fb/hal/gb202.rs b/drivers/gpu/nova-core/fb/hal/gb202.rs
index 038d1278c634..1233df4303f5 100644
--- a/drivers/gpu/nova-core/fb/hal/gb202.rs
+++ b/drivers/gpu/nova-core/fb/hal/gb202.rs
@@ -83,12 +83,17 @@ fn pmu_reserved_size(&self) -> u32 {

fn non_wpr_heap_size(&self) -> u32 {
// Non-WPR heap for GB20x (see Open RM: kgspGetNonWprHeapSize, GB202+).
+ // This size is r570-specific.
u32::SZ_2M + u32::SZ_128K
}

fn frts_size(&self) -> u64 {
super::tu102::frts_size_tu102()
}
+
+ fn fb_end_reserved_size(&self) -> u32 {
+ u32::SZ_2M + u32::SZ_128K
+ }
}

const GB202: Gb202 = Gb202;
diff --git a/drivers/gpu/nova-core/fb/hal/gh100.rs b/drivers/gpu/nova-core/fb/hal/gh100.rs
index 5450c7254dad..892c75ef26c6 100644
--- a/drivers/gpu/nova-core/fb/hal/gh100.rs
+++ b/drivers/gpu/nova-core/fb/hal/gh100.rs
@@ -44,6 +44,10 @@ fn non_wpr_heap_size(&self) -> u32 {
fn frts_size(&self) -> u64 {
super::tu102::frts_size_tu102()
}
+
+ fn fb_end_reserved_size(&self) -> u32 {
+ super::tu102::fb_end_reserved_size_tu102()
+ }
}

const GH100: Gh100 = Gh100;
diff --git a/drivers/gpu/nova-core/fb/hal/tu102.rs b/drivers/gpu/nova-core/fb/hal/tu102.rs
index f629e8e9d5d5..8bafbeec9807 100644
--- a/drivers/gpu/nova-core/fb/hal/tu102.rs
+++ b/drivers/gpu/nova-core/fb/hal/tu102.rs
@@ -52,6 +52,10 @@ pub(super) const fn frts_size_tu102() -> u64 {
u64::SZ_1M
}

+pub(super) const fn fb_end_reserved_size_tu102() -> u32 {
+ u32::SZ_2M
+}
+
struct Tu102;

impl FbHal for Tu102 {
@@ -82,6 +86,10 @@ fn non_wpr_heap_size(&self) -> u32 {
fn frts_size(&self) -> u64 {
frts_size_tu102()
}
+
+ fn fb_end_reserved_size(&self) -> u32 {
+ fb_end_reserved_size_tu102()
+ }
}

const TU102: Tu102 = Tu102;
diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index 6778e5546cee..fed9d1e4eeda 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -132,20 +132,31 @@ struct FspCotMessage {
}

impl FspCotMessage {
+ /// Computes the FRTS vidmem offset for the Chain-of-Trust message. It is measured from the end
+ /// of the framebuffer.
+ fn frts_vidmem_offset(fb_info: &FbSizes) -> Result<u64> {
+ let mut offset = u64::from(fb_info.fb_end_reserved_size);
+
+ if fb_info.pmu_reserved_size != 0 {
+ offset = offset
+ .checked_add(u64::from(fb_info.pmu_reserved_size))
+ .ok_or(EINVAL)?
+ // The 2 MiB alignment is r570-specific.
+ .align_up(Alignment::new::<SZ_2M>())
+ .ok_or(EINVAL)?;
+ }
+
+ Ok(offset)
+ }
+
/// Returns an in-place initializer for [`FspCotMessage`].
fn new<'a>(
fb_info: &FbSizes,
fsp_fw: &'a FspFirmware,
args: &'a FmcBootArgs<'_>,
) -> Result<impl Init<Self> + 'a> {
- // frts_vidmem_offset is measured from the end of FB, so FRTS sits at
- // (end of FB) - frts_vidmem_offset.
let frts_vidmem_offset = if !args.resume {
- let frts_reserved_size = fb_info.heap_size + u64::from(fb_info.pmu_reserved_size);
-
- frts_reserved_size
- .align_up(Alignment::new::<SZ_2M>())
- .ok_or(EINVAL)?
+ Self::frts_vidmem_offset(fb_info)?
} else {
0
};

--
2.54.0