[PATCH 10/13] gpu: nova-core: split FbLayout into FSP and non-FSP versions

From: Eliot Courtney

Date: Mon Jun 15 2026 - 10:44:19 EST


`FbLayout` is currently used for both pre and post FSP architectures. It
contains ranges for each region of framebuffer, but on post FSP
architectures, only the size is actually used by GSP. The offsets are
not decided by the driver. So, for post FSP architectures `FbLayout`
contains essentially guesses for the offsets. Instead, make separate
types so that we only store the information that's actually needed,
rather than keeping around offsets that may not be correct.

Signed-off-by: Eliot Courtney <ecourtney@xxxxxxxxxx>
---
drivers/gpu/nova-core/fb.rs | 69 +++++++++++++++++++++---
drivers/gpu/nova-core/fsp.rs | 15 +++---
drivers/gpu/nova-core/gsp/boot.rs | 10 ++--
drivers/gpu/nova-core/gsp/fw.rs | 95 ++++++++++++++++++++++++++--------
drivers/gpu/nova-core/gsp/hal.rs | 4 +-
drivers/gpu/nova-core/gsp/hal/gh100.rs | 10 ++--
drivers/gpu/nova-core/gsp/hal/tu102.rs | 24 +++++----
7 files changed, 170 insertions(+), 57 deletions(-)

diff --git a/drivers/gpu/nova-core/fb.rs b/drivers/gpu/nova-core/fb.rs
index 725e428154cf..facecb8b411f 100644
--- a/drivers/gpu/nova-core/fb.rs
+++ b/drivers/gpu/nova-core/fb.rs
@@ -144,11 +144,29 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
}
}

-/// Layout of the GPU framebuffer memory.
-///
-/// Contains ranges of GPU memory reserved for a given purpose during the GSP boot process.
+/// Framebuffer information required for GSP boot.
#[derive(Debug)]
-pub(crate) struct FbLayout {
+pub(crate) enum GspFbInfo {
+ /// Concrete framebuffer ranges for host computed framebuffer layout.
+ Ranges(FbRanges),
+ /// Sizes of framebuffer ranges for GSP-FMC computed ranges.
+ Sizes(FbSizes),
+}
+
+impl GspFbInfo {
+ /// Computes the framebuffer region information required for boot.
+ pub(crate) fn new(chipset: Chipset, bar: Bar0<'_>, gsp_fw: &GspFirmware) -> Result<Self> {
+ if chipset.uses_fsp() {
+ FbSizes::new(chipset, bar).map(Self::Sizes)
+ } else {
+ FbRanges::new(chipset, bar, gsp_fw).map(Self::Ranges)
+ }
+ }
+}
+
+/// Framebuffer ranges needed for GSP boot process.
+#[derive(Debug)]
+pub(crate) struct FbRanges {
/// Range of the framebuffer. Starts at `0`.
pub(crate) fb: FbRange,
/// VGA workspace, small area of reserved memory at the end of the framebuffer.
@@ -163,15 +181,17 @@ pub(crate) struct FbLayout {
pub(crate) wpr2_heap: FbRange,
/// WPR2 region range, starting with an instance of `GspFwWprMeta`.
pub(crate) wpr2: FbRange,
+ /// Non-WPR heap, located just below WPR2.
pub(crate) heap: FbRange,
+ /// Number of VF partitions.
pub(crate) vf_partition_count: u8,
/// PMU reserved memory size, in bytes.
pub(crate) pmu_reserved_size: u32,
}

-impl FbLayout {
- /// Computes the FB layout for `chipset` required to run the `gsp_fw` GSP firmware.
- pub(crate) fn new(chipset: Chipset, bar: Bar0<'_>, gsp_fw: &GspFirmware) -> Result<Self> {
+impl FbRanges {
+ /// Computes concrete framebuffer ranges required on non-FSP booting architectures.
+ fn new(chipset: Chipset, bar: Bar0<'_>, gsp_fw: &GspFirmware) -> Result<Self> {
let hal = hal::fb_hal(chipset);

let fb = {
@@ -270,3 +290,38 @@ pub(crate) fn new(chipset: Chipset, bar: Bar0<'_>, gsp_fw: &GspFirmware) -> Resu
})
}
}
+
+/// Framebuffer region sizes needed for GSP-FMC boot.
+#[derive(Debug)]
+pub(crate) struct FbSizes {
+ /// VGA workspace size, in bytes.
+ pub(crate) vga_workspace_size: u64,
+ /// FRTS size, in bytes.
+ pub(crate) frts_size: u64,
+ /// WPR2 heap size, in bytes.
+ pub(crate) wpr2_heap_size: u64,
+ /// Non-WPR heap size, in bytes.
+ pub(crate) heap_size: u64,
+ /// PMU reserved memory size, in bytes.
+ pub(crate) pmu_reserved_size: u32,
+ /// Number of VF partitions.
+ pub(crate) vf_partition_count: u8,
+}
+
+impl FbSizes {
+ /// Computes the framebuffer region sizes for GSP-FMC boot.
+ fn new(chipset: Chipset, bar: Bar0<'_>) -> Result<Self> {
+ let hal = hal::fb_hal(chipset);
+ let fb_size = hal.vidmem_size(bar);
+
+ Ok(Self {
+ vga_workspace_size: u64::SZ_128K,
+ frts_size: hal.frts_size(),
+ wpr2_heap_size: gsp::LibosParams::from_chipset(chipset)
+ .wpr_heap_size(chipset, fb_size)?,
+ heap_size: u64::from(hal.non_wpr_heap_size()),
+ pmu_reserved_size: hal.pmu_reserved_size(),
+ vf_partition_count: 0,
+ })
+ }
+}
diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index bf0baa5ac4ae..6778e5546cee 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -30,7 +30,7 @@
fsp::Fsp as FspEngine,
Falcon, //
},
- fb::FbLayout,
+ fb::FbSizes,
firmware::{
fsp::{
FmcSignatures,
@@ -134,14 +134,14 @@ struct FspCotMessage {
impl FspCotMessage {
/// Returns an in-place initializer for [`FspCotMessage`].
fn new<'a>(
- fb_layout: &FbLayout,
+ 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_layout.heap.len() + u64::from(fb_layout.pmu_reserved_size);
+ let frts_reserved_size = fb_info.heap_size + u64::from(fb_info.pmu_reserved_size);

frts_reserved_size
.align_up(Alignment::new::<SZ_2M>())
@@ -151,7 +151,7 @@ fn new<'a>(
};

let frts_size: u32 = if !args.resume {
- fb_layout.frts.len().try_into()?
+ fb_info.frts_size.try_into()?
} else {
0
};
@@ -333,15 +333,12 @@ pub(crate) fn boot_fmc(
&mut self,
dev: &device::Device<device::Bound>,
bar: Bar0<'_>,
- fb_layout: &FbLayout,
+ fb_info: &FbSizes,
args: &FmcBootArgs<'_>,
) -> Result {
dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset);

- let msg = KBox::init(
- FspCotMessage::new(fb_layout, &self.fsp_fw, args)?,
- GFP_KERNEL,
- )?;
+ let msg = KBox::init(FspCotMessage::new(fb_info, &self.fsp_fw, args)?, GFP_KERNEL)?;

let _response_buf = self.send_sync_fsp(dev, bar, &*msg)?;

diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 8afb62d689cb..670a94545b61 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -19,7 +19,7 @@
sec2::Sec2,
Falcon, //
},
- fb::FbLayout,
+ fb::GspFbInfo,
firmware::{
gsp::GspFirmware,
FIRMWARE_VERSION, //
@@ -114,10 +114,10 @@ pub(crate) fn boot(

let gsp_fw = KBox::pin_init(GspFirmware::new(dev, chipset, FIRMWARE_VERSION), GFP_KERNEL)?;

- let fb_layout = FbLayout::new(chipset, bar, &gsp_fw)?;
- dev_dbg!(dev, "{:#x?}\n", fb_layout);
+ let fb_info = GspFbInfo::new(chipset, bar, &gsp_fw)?;
+ dev_dbg!(dev, "{:#x?}\n", fb_info);

- let wpr_meta = Coherent::init(dev, GFP_KERNEL, GspFwWprMeta::new(&gsp_fw, &fb_layout))?;
+ let wpr_meta = Coherent::init(dev, GFP_KERNEL, GspFwWprMeta::new(&gsp_fw, &fb_info))?;

// Perform the chipset-specific boot sequence, and retrieve the unload bundle.
let unload_guard = hal.boot(
@@ -125,7 +125,7 @@ pub(crate) fn boot(
dev,
bar,
chipset,
- &fb_layout,
+ &fb_info,
&wpr_meta,
gsp_falcon,
sec2_falcon,
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 4db0cfa4dc4d..042b0122e98d 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -28,7 +28,7 @@
};

use crate::{
- fb::FbLayout,
+ fb::GspFbInfo,
firmware::gsp::GspFirmware,
gpu::{
Architecture,
@@ -214,11 +214,65 @@ unsafe impl FromBytes for GspFwWprMeta {}

impl GspFwWprMeta {
/// Returns an initializer for a `GspFwWprMeta` suitable for booting `gsp_firmware` using the
- /// `fb_layout` layout.
+ /// framebuffer information.
pub(crate) fn new<'a>(
gsp_firmware: &'a GspFirmware,
- fb_layout: &'a FbLayout,
+ fb_info: &'a GspFbInfo,
) -> impl Init<Self> + 'a {
+ #[derive(Default)]
+ struct WprMetaFields {
+ gsp_fw_rsvd_start: u64,
+ non_wpr_heap_offset: u64,
+ non_wpr_heap_size: u64,
+ gsp_fw_wpr_start: u64,
+ gsp_fw_heap_offset: u64,
+ gsp_fw_heap_size: u64,
+ gsp_fw_offset: u64,
+ boot_bin_offset: u64,
+ frts_offset: u64,
+ frts_size: u64,
+ gsp_fw_wpr_end: u64,
+ gsp_fw_heap_vf_partition_count: u8,
+ fb_size: u64,
+ vga_workspace_offset: u64,
+ vga_workspace_size: u64,
+ pmu_reserved_size: u32,
+ }
+
+ let fields = match fb_info {
+ GspFbInfo::Ranges(ranges) => WprMetaFields {
+ gsp_fw_rsvd_start: ranges.heap.start,
+ non_wpr_heap_offset: ranges.heap.start,
+ non_wpr_heap_size: ranges.heap.len(),
+ gsp_fw_wpr_start: ranges.wpr2.start,
+ gsp_fw_heap_offset: ranges.wpr2_heap.start,
+ gsp_fw_heap_size: ranges.wpr2_heap.len(),
+ gsp_fw_offset: ranges.elf.start,
+ boot_bin_offset: ranges.boot.start,
+ frts_offset: ranges.frts.start,
+ frts_size: ranges.frts.len(),
+ gsp_fw_wpr_end: ranges
+ .vga_workspace
+ .start
+ .align_down(Alignment::new::<SZ_128K>()),
+ gsp_fw_heap_vf_partition_count: ranges.vf_partition_count,
+ fb_size: ranges.fb.len(),
+ vga_workspace_offset: ranges.vga_workspace.start,
+ vga_workspace_size: ranges.vga_workspace.len(),
+ pmu_reserved_size: ranges.pmu_reserved_size,
+ },
+ GspFbInfo::Sizes(sizes) => WprMetaFields {
+ non_wpr_heap_size: sizes.heap_size,
+ gsp_fw_heap_size: sizes.wpr2_heap_size,
+ frts_size: sizes.frts_size,
+ gsp_fw_heap_vf_partition_count: sizes.vf_partition_count,
+ vga_workspace_size: sizes.vga_workspace_size,
+ pmu_reserved_size: sizes.pmu_reserved_size,
+ // When only sizes are supplied, offsets and several other parameters are not used.
+ ..Default::default()
+ },
+ };
+
#[allow(non_snake_case)]
let init_inner = init!(bindings::GspFwWprMeta {
// CAST: we want to store the bits of `GSP_FW_WPR_META_MAGIC` unmodified.
@@ -237,25 +291,22 @@ pub(crate) fn new<'a>(
sizeOfSignature: u64::from_safe_cast(gsp_firmware.signatures.size()),
},
},
- gspFwRsvdStart: fb_layout.heap.start,
- nonWprHeapOffset: fb_layout.heap.start,
- nonWprHeapSize: fb_layout.heap.end - fb_layout.heap.start,
- gspFwWprStart: fb_layout.wpr2.start,
- gspFwHeapOffset: fb_layout.wpr2_heap.start,
- gspFwHeapSize: fb_layout.wpr2_heap.end - fb_layout.wpr2_heap.start,
- gspFwOffset: fb_layout.elf.start,
- bootBinOffset: fb_layout.boot.start,
- frtsOffset: fb_layout.frts.start,
- frtsSize: fb_layout.frts.end - fb_layout.frts.start,
- gspFwWprEnd: fb_layout
- .vga_workspace
- .start
- .align_down(Alignment::new::<SZ_128K>()),
- gspFwHeapVfPartitionCount: fb_layout.vf_partition_count,
- fbSize: fb_layout.fb.end - fb_layout.fb.start,
- vgaWorkspaceOffset: fb_layout.vga_workspace.start,
- vgaWorkspaceSize: fb_layout.vga_workspace.end - fb_layout.vga_workspace.start,
- pmuReservedSize: fb_layout.pmu_reserved_size,
+ gspFwRsvdStart: fields.gsp_fw_rsvd_start,
+ nonWprHeapOffset: fields.non_wpr_heap_offset,
+ nonWprHeapSize: fields.non_wpr_heap_size,
+ gspFwWprStart: fields.gsp_fw_wpr_start,
+ gspFwHeapOffset: fields.gsp_fw_heap_offset,
+ gspFwHeapSize: fields.gsp_fw_heap_size,
+ gspFwOffset: fields.gsp_fw_offset,
+ bootBinOffset: fields.boot_bin_offset,
+ frtsOffset: fields.frts_offset,
+ frtsSize: fields.frts_size,
+ gspFwWprEnd: fields.gsp_fw_wpr_end,
+ gspFwHeapVfPartitionCount: fields.gsp_fw_heap_vf_partition_count,
+ fbSize: fields.fb_size,
+ vgaWorkspaceOffset: fields.vga_workspace_offset,
+ vgaWorkspaceSize: fields.vga_workspace_size,
+ pmuReservedSize: fields.pmu_reserved_size,
..Zeroable::init_zeroed()
});

diff --git a/drivers/gpu/nova-core/gsp/hal.rs b/drivers/gpu/nova-core/gsp/hal.rs
index 04f004856c60..aa41da9902b5 100644
--- a/drivers/gpu/nova-core/gsp/hal.rs
+++ b/drivers/gpu/nova-core/gsp/hal.rs
@@ -18,7 +18,7 @@
sec2::Sec2,
Falcon, //
},
- fb::FbLayout,
+ fb::GspFbInfo,
firmware::gsp::GspFirmware,
gpu::{
Architecture,
@@ -60,7 +60,7 @@ fn boot<'a>(
dev: &'a device::Device<device::Bound>,
bar: Bar0<'a>,
chipset: Chipset,
- fb_layout: &FbLayout,
+ fb_info: &GspFbInfo,
wpr_meta: &Coherent<GspFwWprMeta>,
gsp_falcon: &'a Falcon<GspEngine>,
sec2_falcon: &'a Falcon<Sec2>,
diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
index 35554d92fda9..ddf3f67e6338 100644
--- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
@@ -17,7 +17,7 @@
sec2::Sec2,
Falcon, //
},
- fb::FbLayout,
+ fb::GspFbInfo,
fsp::{
FmcBootArgs,
Fsp, //
@@ -152,11 +152,15 @@ fn boot<'a>(
dev: &'a device::Device<device::Bound>,
bar: Bar0<'a>,
chipset: Chipset,
- fb_layout: &FbLayout,
+ fb_info: &GspFbInfo,
wpr_meta: &Coherent<GspFwWprMeta>,
gsp_falcon: &'a Falcon<GspEngine>,
sec2_falcon: &'a Falcon<Sec2>,
) -> Result<BootUnloadGuard<'a>> {
+ let GspFbInfo::Sizes(fb_sizes) = fb_info else {
+ return Err(EINVAL);
+ };
+
let args = FmcBootArgs::new(dev, chipset, wpr_meta, &gsp.libos, false)?;

let mut fsp = Fsp::new(dev, chipset)?;
@@ -169,7 +173,7 @@ fn boot<'a>(
let unload_guard =
BootUnloadGuard::new(gsp, dev, bar, gsp_falcon, sec2_falcon, Some(unload_bundle));

- fsp.boot_fmc(dev, bar, fb_layout, &args)?;
+ fsp.boot_fmc(dev, bar, fb_sizes, &args)?;

wait_for_gsp_lockdown_release(dev, bar, gsp_falcon, args.boot_params())?;

diff --git a/drivers/gpu/nova-core/gsp/hal/tu102.rs b/drivers/gpu/nova-core/gsp/hal/tu102.rs
index 1e08c482fd39..e809deb72055 100644
--- a/drivers/gpu/nova-core/gsp/hal/tu102.rs
+++ b/drivers/gpu/nova-core/gsp/hal/tu102.rs
@@ -16,7 +16,10 @@
sec2::Sec2,
Falcon, //
},
- fb::FbLayout,
+ fb::{
+ FbRanges,
+ GspFbInfo, //
+ },
firmware::{
booter::{
BooterFirmware,
@@ -185,7 +188,7 @@ fn run_fwsec_frts(
falcon: &Falcon<GspEngine>,
bar: Bar0<'_>,
bios: &Vbios,
- fb_layout: &FbLayout,
+ fb_ranges: &FbRanges,
) -> Result {
// Check that the WPR2 region does not already exist - if it does, we cannot run
// FWSEC-FRTS until the GPU is reset.
@@ -204,8 +207,8 @@ fn run_fwsec_frts(
bar,
bios,
FwsecCommand::Frts {
- frts_addr: fb_layout.frts.start,
- frts_size: fb_layout.frts.len(),
+ frts_addr: fb_ranges.frts.start,
+ frts_size: fb_ranges.frts.len(),
},
)?;

@@ -244,12 +247,12 @@ fn run_fwsec_frts(

Err(EIO)
}
- (wpr2_lo, _) if wpr2_lo != fb_layout.frts.start => {
+ (wpr2_lo, _) if wpr2_lo != fb_ranges.frts.start => {
dev_err!(
dev,
"WPR2 region created at unexpected address {:#x}; expected {:#x}\n",
wpr2_lo,
- fb_layout.frts.start,
+ fb_ranges.frts.start,
);

Err(EIO)
@@ -272,11 +275,14 @@ fn boot<'a>(
dev: &'a device::Device<device::Bound>,
bar: Bar0<'a>,
chipset: Chipset,
- fb_layout: &FbLayout,
+ fb_info: &GspFbInfo,
wpr_meta: &Coherent<GspFwWprMeta>,
gsp_falcon: &'a Falcon<GspEngine>,
sec2_falcon: &'a Falcon<Sec2>,
) -> Result<BootUnloadGuard<'a>> {
+ let GspFbInfo::Ranges(fb_ranges) = fb_info else {
+ return Err(EINVAL);
+ };
let bios = Vbios::new(dev, bar)?;

// Try and prepare the unload bundle.
@@ -301,8 +307,8 @@ fn boot<'a>(
BootUnloadGuard::new(gsp, dev, bar, gsp_falcon, sec2_falcon, unload_bundle);

// FWSEC-FRTS is not executed on chips where the FRTS region size is 0 (e.g. GA100).
- if !fb_layout.frts.is_empty() {
- run_fwsec_frts(dev, chipset, gsp_falcon, bar, &bios, fb_layout)?;
+ if !fb_ranges.frts.is_empty() {
+ run_fwsec_frts(dev, chipset, gsp_falcon, bar, &bios, fb_ranges)?;
}

gsp_falcon.reset(bar)?;

--
2.54.0