[PATCH v3 1/5] gpu: nova-core: correct FRTS vidmem offset calculation

From: Eliot Courtney

Date: Fri Jul 24 2026 - 02:57:09 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/hal/gb100.rs | 1 +
drivers/gpu/nova-core/fb/hal/gb202.rs | 1 +
drivers/gpu/nova-core/fsp.rs | 27 +++++++++++++++++++--------
drivers/gpu/nova-core/fsp/hal.rs | 4 ++++
drivers/gpu/nova-core/fsp/hal/gb100.rs | 6 ++++++
drivers/gpu/nova-core/fsp/hal/gb202.rs | 9 ++++++++-
drivers/gpu/nova-core/fsp/hal/gh100.rs | 9 ++++++++-
7 files changed, 47 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/nova-core/fb/hal/gb100.rs b/drivers/gpu/nova-core/fb/hal/gb100.rs
index 6e0eba101ca1..51bd0c0bede6 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() }>(
)
diff --git a/drivers/gpu/nova-core/fb/hal/gb202.rs b/drivers/gpu/nova-core/fb/hal/gb202.rs
index b78e0970f66d..8ec30afcf701 100644
--- a/drivers/gpu/nova-core/fb/hal/gb202.rs
+++ b/drivers/gpu/nova-core/fb/hal/gb202.rs
@@ -71,6 +71,7 @@ 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
}

diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index 1475485bded3..30e53df81aa2 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -134,20 +134,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(hal: &dyn hal::FspHal, fb_layout: &FbLayout) -> Result<u64> {
+ let mut offset = u64::from(hal.fb_end_reserved_size());
+
+ if fb_layout.pmu_reserved_size != 0 {
+ offset = (offset + u64::from(fb_layout.pmu_reserved_size))
+ // 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_layout: &FbLayout,
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_layout.heap.len() + u64::from(fb_layout.pmu_reserved_size);
+ let hal = hal::fsp_hal(args.chipset).ok_or(ENOTSUPP)?;

- frts_reserved_size
- .align_up(Alignment::new::<SZ_2M>())
- .ok_or(EINVAL)?
+ let frts_vidmem_offset = if !args.resume {
+ Self::frts_vidmem_offset(hal, fb_layout)?
} else {
0
};
@@ -158,7 +169,7 @@ fn new<'a>(
0
};

- let version = hal::fsp_hal(args.chipset).ok_or(ENOTSUPP)?.cot_version();
+ let version = hal.cot_version();
let size = num::usize_into_u16::<{ core::mem::size_of::<NvdmPayloadCot>() }>();

Ok(init!(Self {
diff --git a/drivers/gpu/nova-core/fsp/hal.rs b/drivers/gpu/nova-core/fsp/hal.rs
index b6f2624bb13d..aa2f8bda59d2 100644
--- a/drivers/gpu/nova-core/fsp/hal.rs
+++ b/drivers/gpu/nova-core/fsp/hal.rs
@@ -19,6 +19,10 @@ pub(super) trait FspHal {

/// Returns the FSP Chain of Trust protocol version this chipset advertises.
fn cot_version(&self) -> u16;
+
+ // TODO: consider moving this into the TLV firmware metadata when ready
+ /// Returns the size reserved at the end of the framebuffer, in bytes.
+ fn fb_end_reserved_size(&self) -> u32;
}

/// Returns the FSP HAL, or `None` if the architecture doesn't support FSP.
diff --git a/drivers/gpu/nova-core/fsp/hal/gb100.rs b/drivers/gpu/nova-core/fsp/hal/gb100.rs
index 42f5ecfc6400..f601b5d7bf61 100644
--- a/drivers/gpu/nova-core/fsp/hal/gb100.rs
+++ b/drivers/gpu/nova-core/fsp/hal/gb100.rs
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

+use kernel::sizes::SizeConstants;
+
use crate::{
driver::Bar0,
fsp::hal::FspHal, //
@@ -17,6 +19,10 @@ fn fsp_boot_status(&self, bar: Bar0<'_>) -> u32 {
fn cot_version(&self) -> u16 {
2
}
+
+ fn fb_end_reserved_size(&self) -> u32 {
+ u32::SZ_2M + u32::SZ_128K
+ }
}

const GB100: Gb100 = Gb100;
diff --git a/drivers/gpu/nova-core/fsp/hal/gb202.rs b/drivers/gpu/nova-core/fsp/hal/gb202.rs
index 1091b169a645..b022d298195a 100644
--- a/drivers/gpu/nova-core/fsp/hal/gb202.rs
+++ b/drivers/gpu/nova-core/fsp/hal/gb202.rs
@@ -1,7 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

-use kernel::io::Io;
+use kernel::{
+ io::Io,
+ sizes::SizeConstants, //
+};

use crate::{
driver::Bar0,
@@ -21,6 +24,10 @@ fn fsp_boot_status(&self, bar: Bar0<'_>) -> u32 {
fn cot_version(&self) -> u16 {
2
}
+
+ fn fb_end_reserved_size(&self) -> u32 {
+ u32::SZ_2M + u32::SZ_128K
+ }
}

const GB202: Gb202 = Gb202;
diff --git a/drivers/gpu/nova-core/fsp/hal/gh100.rs b/drivers/gpu/nova-core/fsp/hal/gh100.rs
index 291acaf2845a..002f7ccdca3e 100644
--- a/drivers/gpu/nova-core/fsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/fsp/hal/gh100.rs
@@ -1,7 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

-use kernel::io::Io;
+use kernel::{
+ io::Io,
+ sizes::SizeConstants, //
+};

use crate::{
driver::Bar0,
@@ -26,6 +29,10 @@ fn fsp_boot_status(&self, bar: Bar0<'_>) -> u32 {
fn cot_version(&self) -> u16 {
1
}
+
+ fn fb_end_reserved_size(&self) -> u32 {
+ u32::SZ_2M
+ }
}

const GH100: Gh100 = Gh100;

--
2.55.0