From: John Hubbard <jhubbard@nvidia.com>
To: Danilo Krummrich <dakr@kernel.org>,
Alexandre Courbot <acourbot@nvidia.com>
Cc: "Timur Tabi" <ttabi@nvidia.com>,
"Alistair Popple" <apopple@nvidia.com>,
"Eliot Courtney" <ecourtney@nvidia.com>,
"Zhi Wang" <zhiw@nvidia.com>, "David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Bjorn Helgaas" <bhelgaas@google.com>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Boqun Feng" <boqun.feng@gmail.com>,
"Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
nova-gpu@lists.linux.dev, LKML <linux-kernel@vger.kernel.org>,
"John Hubbard" <jhubbard@nvidia.com>
Subject: [PATCH 02/27] gpu: nova-core: extract radix3 page table into its own module
Date: Tue, 18 Aug 2026 20:51:55 -0700 [thread overview]
Message-ID: <20260819035221.336390-3-jhubbard@nvidia.com> (raw)
In-Reply-To: <20260819035221.336390-1-jhubbard@nvidia.com>
Move the 3-level radix page table construction out of GspFirmware
into a standalone Radix3 type in firmware/radix3.rs. The ucodes
(bindata) firmware that GSP-RM loads at runtime also needs a radix3
page table, so the logic needs to be reusable.
Refactor GspFirmware to use the new Radix3 type and expose firmware
size through a size() method instead of a public field.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/fb.rs | 2 +-
drivers/gpu/nova-core/firmware.rs | 1 +
drivers/gpu/nova-core/firmware/gsp.rs | 116 ++++----------------
drivers/gpu/nova-core/firmware/radix3.rs | 133 +++++++++++++++++++++++
drivers/gpu/nova-core/gsp/fw.rs | 4 +-
5 files changed, 159 insertions(+), 97 deletions(-)
create mode 100644 drivers/gpu/nova-core/firmware/radix3.rs
diff --git a/drivers/gpu/nova-core/fb.rs b/drivers/gpu/nova-core/fb.rs
index 1576399389b1..f208d6deb5e3 100644
--- a/drivers/gpu/nova-core/fb.rs
+++ b/drivers/gpu/nova-core/fb.rs
@@ -236,7 +236,7 @@ pub(crate) fn new(
let elf = {
const ELF_DOWN_ALIGN: Alignment = Alignment::new::<SZ_64K>();
- let elf_size = u64::from_safe_cast(gsp_fw.size);
+ let elf_size = u64::from_safe_cast(gsp_fw.size());
let elf_addr = (boot.start - elf_size).align_down(ELF_DOWN_ALIGN);
FbRange(elf_addr..elf_addr + elf_size)
diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs
index b49613a90bf0..34657004568c 100644
--- a/drivers/gpu/nova-core/firmware.rs
+++ b/drivers/gpu/nova-core/firmware.rs
@@ -26,6 +26,7 @@
pub(crate) mod fsp;
pub(crate) mod fwsec;
pub(crate) mod gsp;
+pub(crate) mod radix3;
pub(crate) mod riscv;
pub(crate) mod tlv;
diff --git a/drivers/gpu/nova-core/firmware/gsp.rs b/drivers/gpu/nova-core/firmware/gsp.rs
index e8f9491e84cc..48c73a676ecb 100644
--- a/drivers/gpu/nova-core/firmware/gsp.rs
+++ b/drivers/gpu/nova-core/firmware/gsp.rs
@@ -4,21 +4,16 @@
device,
dma::{
Coherent,
- CoherentBox,
- DataDirection,
DmaAddress, //
},
firmware,
prelude::*,
- scatterlist::{
- Owned,
- SGTable, //
- },
str::CString,
};
use crate::{
firmware::{
+ radix3::Radix3,
riscv::RiscvFirmware, //
tlv::{
request_tlv, //
@@ -26,38 +21,21 @@
},
},
gpu::Chipset,
- gsp::GSP_PAGE_SIZE,
num::FromSafeCast,
};
/// GSP firmware with 3-level radix page tables for the GSP bootloader.
///
-/// The bootloader expects firmware to be mapped starting at address 0 in GSP's virtual address
-/// space:
-///
-/// ```text
-/// Level 0: 1 page, 1 entry -> points to first level 1 page
-/// Level 1: Multiple pages/entries -> each entry points to a level 2 page
-/// Level 2: Multiple pages/entries -> each entry points to a firmware page
-/// ```
-///
-/// Each page is 4KB, each entry is 8 bytes (64-bit DMA address).
/// Also known as "Radix3" firmware.
#[pin_data]
pub(crate) struct GspFirmware {
- /// The GSP firmware inside a [`VVec`], device-mapped via a SG table.
- #[pin]
- fw: SGTable<Owned<VVec<u8>>>,
- /// Level 2 page table whose entries contain DMA addresses of firmware pages.
- #[pin]
- level2: SGTable<Owned<VVec<u8>>>,
- /// Level 1 page table whose entries contain DMA addresses of level 2 pages.
+ /// The GSP firmware image mapped via a 3-level radix page table.
#[pin]
- level1: SGTable<Owned<VVec<u8>>>,
- /// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page.
- level0: Coherent<[u64]>,
- /// Size in bytes of the firmware contained in [`Self::fw`].
- pub(crate) size: usize,
+ radix3: Radix3,
+ /// Firmware file path requested from userspace.
+ pub(crate) fw_path: CString,
+ /// Firmware version from the TLV metadata.
+ pub(crate) fw_version: CString,
/// Device-mapped GSP signatures matching the GPU's [`Chipset`].
pub(crate) signatures: Coherent<[u8]>,
/// GSP bootloader, verifies the GSP firmware before loading and running it.
@@ -74,7 +52,12 @@ pub(crate) fn new<'a>(
pin_init::pin_init_scope(move || {
let firmware = request_tlv(dev, chipset, "gsp")?;
let tlv = Tlv::new(firmware.data())?;
- dev_dbg!(dev, "loaded gsp firmware v{}\n", tlv.get_string(b"VERS")?);
+ let fw_version = CString::try_from_fmt(fmt!("{}", tlv.get_string(b"VERS")?))?;
+ dev_dbg!(
+ dev,
+ "loaded gsp firmware v{}\n",
+ fw_version.to_str().unwrap_or("unknown")
+ );
let size = usize::from_safe_cast(tlv.get_u32(b"SIZE")?);
let mut fw_vvec = VVec::zeroed(size, GFP_KERNEL).map_err(|_| ENOMEM)?;
@@ -87,49 +70,9 @@ pub(crate) fn new<'a>(
let signatures = Coherent::from_slice(dev, tlv.get_bytes(b"SIGN")?, GFP_KERNEL)?;
Ok(try_pin_init!(Self {
- fw <- SGTable::new(dev, fw_vvec, DataDirection::ToDevice, GFP_KERNEL),
- level2 <- {
- // Allocate the level 2 page table, map the firmware onto it, and map it into
- // the device address space.
- VVec::<u8>::with_capacity(
- fw.iter().count() * core::mem::size_of::<u64>(),
- GFP_KERNEL,
- )
- .map_err(|_| ENOMEM)
- .and_then(|level2| map_into_lvl(&fw, level2))
- .map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))?
- },
- level1 <- {
- // Allocate the level 1 page table, map the level 2 page table onto it, and map
- // it into the device address space.
- VVec::<u8>::with_capacity(
- level2.iter().count() * core::mem::size_of::<u64>(),
- GFP_KERNEL,
- )
- .map_err(|_| ENOMEM)
- .and_then(|level1| map_into_lvl(&level2, level1))
- .map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))?
- },
- level0: {
- // Allocate the level 0 page table as a device-visible DMA object, and map the
- // level 1 page table onto it.
-
- // Fill level 1 page entry.
- let level1_entry = level1.iter().next().ok_or(EINVAL)?;
- let level1_entry_addr = level1_entry.dma_address();
-
- // Create level 0 page table data and fill its first entry with the level 1
- // table.
- let mut level0 = CoherentBox::<[u64]>::zeroed_slice(
- dev,
- GSP_PAGE_SIZE / size_of::<u64>(),
- GFP_KERNEL
- )?;
- level0[0] = level1_entry_addr.to_le();
-
- level0.into()
- },
- size,
+ radix3 <- Radix3::new(dev, fw_vvec),
+ fw_path: filename,
+ fw_version,
signatures,
bootloader: {
let bl = request_tlv(dev, chipset, "gsp_bootloader")?;
@@ -140,28 +83,13 @@ pub(crate) fn new<'a>(
})
}
- /// Returns the DMA address of the radix3 level 0 page table.
- pub(crate) fn radix3_dma_address(&self) -> DmaAddress {
- self.level0.dma_address()
+ /// Returns the size of the GSP firmware image, in bytes.
+ pub(crate) fn size(&self) -> usize {
+ self.radix3.size()
}
-}
-/// Build a page table from a scatter-gather list.
-///
-/// Takes each DMA-mapped region from `sg_table` and writes page table entries
-/// for all 4KB pages within that region. For example, a 16KB SG entry becomes
-/// 4 consecutive page table entries.
-fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Result<VVec<u8>> {
- for sg_entry in sg_table.iter() {
- // Number of pages we need to map.
- let num_pages = usize::from_safe_cast(sg_entry.dma_len()).div_ceil(GSP_PAGE_SIZE);
-
- for i in 0..num_pages {
- let entry = sg_entry.dma_address()
- + (u64::from_safe_cast(i) * u64::from_safe_cast(GSP_PAGE_SIZE));
- dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?;
- }
+ /// Returns the DMA address of the radix3 level 0 page table.
+ pub(crate) fn radix3_dma_address(&self) -> DmaAddress {
+ self.radix3.dma_address()
}
-
- Ok(dst)
}
diff --git a/drivers/gpu/nova-core/firmware/radix3.rs b/drivers/gpu/nova-core/firmware/radix3.rs
new file mode 100644
index 000000000000..b60611c7bea0
--- /dev/null
+++ b/drivers/gpu/nova-core/firmware/radix3.rs
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! 3-level radix page table for GSP firmware data.
+//!
+//! The GSP bootloader expects data to be mapped via a 3-level page table:
+//!
+//! ```text
+//! Level 0: 1 page, 1 entry -> points to first level 1 page
+//! Level 1: Multiple pages/entries -> each entry points to a level 2 page
+//! Level 2: Multiple pages/entries -> each entry points to a data page
+//! ```
+//!
+//! Each page is 4KB, each entry is 8 bytes (64-bit DMA address).
+
+use core::mem::size_of;
+
+use kernel::{
+ device,
+ dma::{
+ Coherent,
+ CoherentBox,
+ DataDirection,
+ DmaAddress, //
+ },
+ prelude::*,
+ scatterlist::{
+ Owned,
+ SGTable, //
+ },
+};
+
+use crate::{
+ gsp::GSP_PAGE_SIZE,
+ num::FromSafeCast, //
+};
+
+/// 3-level radix page table mapping arbitrary data for the GSP.
+#[pin_data]
+pub(crate) struct Radix3 {
+ /// The data mapped via a SG table.
+ #[pin]
+ data: SGTable<Owned<VVec<u8>>>,
+ /// Level 2 page table whose entries contain DMA addresses of data pages.
+ #[pin]
+ level2: SGTable<Owned<VVec<u8>>>,
+ /// Level 1 page table whose entries contain DMA addresses of level 2 pages.
+ #[pin]
+ level1: SGTable<Owned<VVec<u8>>>,
+ /// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page.
+ level0: Coherent<[u64]>,
+ /// Size in bytes of the data contained in [`Self::data`].
+ size: usize,
+}
+
+impl Radix3 {
+ /// Builds a 3-level radix page table that maps `data` into `dev`'s DMA address space.
+ ///
+ /// Takes ownership of `data`. May sleep.
+ pub(crate) fn new<'a>(
+ dev: &'a device::Device<device::Bound>,
+ data: VVec<u8>,
+ ) -> impl PinInit<Self, Error> + 'a {
+ let size = data.len();
+
+ pin_init::pin_init_scope(move || {
+ Ok(try_pin_init!(Self {
+ data <- SGTable::new(dev, data, DataDirection::ToDevice, GFP_KERNEL),
+ level2 <- {
+ VVec::<u8>::with_capacity(
+ data.iter().count() * core::mem::size_of::<u64>(),
+ GFP_KERNEL,
+ )
+ .map_err(|_| ENOMEM)
+ .and_then(|level2| map_into_lvl(&data, level2))
+ .map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))?
+ },
+ level1 <- {
+ VVec::<u8>::with_capacity(
+ level2.iter().count() * core::mem::size_of::<u64>(),
+ GFP_KERNEL,
+ )
+ .map_err(|_| ENOMEM)
+ .and_then(|level1| map_into_lvl(&level2, level1))
+ .map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))?
+ },
+ level0: {
+ let level1_entry = level1.iter().next().ok_or(EINVAL)?;
+ let level1_entry_addr = level1_entry.dma_address();
+
+ let mut level0 = CoherentBox::<[u64]>::zeroed_slice(
+ dev,
+ GSP_PAGE_SIZE / size_of::<u64>(),
+ GFP_KERNEL,
+ )?;
+ level0[0] = level1_entry_addr.to_le();
+
+ level0.into()
+ },
+ size,
+ }))
+ })
+ }
+
+ /// Returns the DMA address of the radix3 level 0 page table.
+ pub(crate) fn dma_address(&self) -> DmaAddress {
+ self.level0.dma_address()
+ }
+
+ /// Returns the size of the mapped data, in bytes.
+ pub(crate) fn size(&self) -> usize {
+ self.size
+ }
+}
+
+/// Build a page table from a scatter-gather list.
+///
+/// Takes each DMA-mapped region from `sg_table` and writes page table entries
+/// for all 4KB pages within that region. For example, a 16KB SG entry becomes
+/// 4 consecutive page table entries.
+fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Result<VVec<u8>> {
+ for sg_entry in sg_table.iter() {
+ let num_pages = usize::from_safe_cast(sg_entry.dma_len()).div_ceil(GSP_PAGE_SIZE);
+
+ for i in 0..num_pages {
+ let entry = sg_entry.dma_address()
+ + (u64::from_safe_cast(i) * u64::from_safe_cast(GSP_PAGE_SIZE));
+ dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?;
+ }
+ }
+
+ Ok(dst)
+}
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 9b29308678a7..d3678f16750a 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -188,7 +188,7 @@ pub(crate) fn from_ranges<'a>(
magic: bindings::GSP_FW_WPR_META_MAGIC as u64,
revision: u64::from(bindings::GSP_FW_WPR_META_REVISION),
sysmemAddrOfRadix3Elf: gsp_firmware.radix3_dma_address(),
- sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size),
+ sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size()),
sysmemAddrOfBootloader: gsp_firmware.bootloader.ucode.dma_address(),
sizeOfBootloader: u64::from_safe_cast(gsp_firmware.bootloader.ucode.size()),
bootloaderCodeOffset: u64::from(gsp_firmware.bootloader.code_offset),
@@ -243,7 +243,7 @@ pub(crate) fn from_sizes<'a>(
magic: bindings::GSP_FW_WPR_META_MAGIC as u64,
revision: u64::from(bindings::GSP_FW_WPR_META_REVISION),
sysmemAddrOfRadix3Elf: gsp_firmware.radix3_dma_address(),
- sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size),
+ sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size()),
sysmemAddrOfBootloader: gsp_firmware.bootloader.ucode.dma_address(),
sizeOfBootloader: u64::from_safe_cast(gsp_firmware.bootloader.ucode.size()),
bootloaderCodeOffset: u64::from(gsp_firmware.bootloader.code_offset),
--
2.55.0
next prev parent reply other threads:[~2026-08-19 3:52 UTC|newest]
Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
2026-08-19 3:51 ` [PATCH 01/27] gpu: nova-core: firmware: add r000 bindings John Hubbard
2026-08-19 3:51 ` John Hubbard [this message]
2026-08-19 17:37 ` [PATCH 02/27] gpu: nova-core: extract radix3 page table into its own module Timur Tabi
2026-08-20 1:05 ` John Hubbard
2026-08-19 3:51 ` [PATCH 03/27] gpu: nova-core: set MCTP transport header version to 1 John Hubbard
2026-08-19 3:51 ` [PATCH 04/27] gpu: nova-core: add Falcon helpers for r000 LOAD_EXEC events John Hubbard
2026-08-19 3:51 ` [PATCH 05/27] gpu: nova-core: zero-pad radix3 page table levels to page boundary John Hubbard
2026-08-19 17:41 ` Timur Tabi
2026-08-20 2:18 ` John Hubbard
2026-08-19 3:51 ` [PATCH 06/27] gpu: nova-core: distinguish async GSP RPC traffic in debug logs John Hubbard
2026-08-19 3:52 ` [PATCH 07/27] gpu: nova-core: add optional ucodes firmware loading John Hubbard
2026-08-19 17:55 ` Timur Tabi
2026-08-20 2:22 ` John Hubbard
2026-08-19 18:41 ` Timur Tabi
2026-08-20 2:31 ` John Hubbard
2026-08-19 3:52 ` [PATCH 08/27] gpu: nova-core: add LIBOS3 log buffers and state monitor buffer John Hubbard
2026-08-19 18:12 ` Timur Tabi
2026-08-20 2:26 ` John Hubbard
2026-08-19 3:52 ` [PATCH 09/27] gpu: nova-core: add build ID headers to debugfs log buffer dumps John Hubbard
2026-08-19 18:20 ` Timur Tabi
2026-08-20 1:00 ` John Hubbard
2026-08-20 1:30 ` Timur Tabi
2026-08-20 2:03 ` John Hubbard
2026-08-19 3:52 ` [PATCH 10/27] gpu: nova-core: rename the FbRanges elf field to fw_image John Hubbard
2026-08-19 18:21 ` Timur Tabi
2026-08-20 1:01 ` John Hubbard
2026-08-19 3:52 ` [PATCH 11/27] gpu: nova-core: regs: add msgq v2 BAR0 register declarations John Hubbard
2026-08-19 3:52 ` [PATCH 12/27] gpu: nova-core: gsp: add msgq v2 internals John Hubbard
2026-08-19 3:52 ` [PATCH 13/27] gpu: nova-core: generalize allocate_command() for variable headers John Hubbard
2026-08-19 3:52 ` [PATCH 14/27] gpu: nova-core: add GMC API message types John Hubbard
2026-08-19 3:52 ` [PATCH 15/27] gpu: nova-core: add GMC send path John Hubbard
2026-08-19 3:52 ` [PATCH 16/27] gpu: nova-core: add GMC transport receive path John Hubbard
2026-08-19 3:52 ` [PATCH 17/27] gpu: nova-core: gsp: add GMC dispatch on receive John Hubbard
2026-08-19 3:52 ` [PATCH 18/27] gpu: nova-core: separate the generic falcon bootloader from FWSEC John Hubbard
2026-08-19 3:52 ` [PATCH 19/27] gpu: nova-core: handle the r000 load-and-execute HS binary event John Hubbard
2026-08-19 3:52 ` [PATCH 20/27] gpu: nova-core: handle the r000 load-and-execute bootloader event John Hubbard
2026-08-19 3:52 ` [PATCH 21/27] gpu: nova-core: gsp: add the GMC boot event dispatcher John Hubbard
2026-08-19 3:52 ` [PATCH 22/27] gpu: nova-core: gsp: add the GSP_INIT request builder John Hubbard
2026-08-19 3:52 ` [PATCH 23/27] gpu: nova-core: gsp: send GSP_INIT and decode its reply John Hubbard
2026-08-19 3:52 ` [PATCH 24/27] gpu: nova-core: gsp: pass the remaining log buffers to GSP-RM John Hubbard
2026-08-19 3:52 ` [PATCH 26/27] gpu: nova-core: gsp: remove the retired system-info and static-info RPCs John Hubbard
2026-08-19 3:52 ` [PATCH 27/27] gpu: nova-core: firmware: delete the r570 bindings John Hubbard
[not found] ` <20260819035221.336390-26-jhubbard@nvidia.com>
2026-08-19 18:58 ` [PATCH 25/27] gpu: nova-core: switch to the r000 GSP firmware Timur Tabi
2026-08-20 1:02 ` John Hubbard
2026-08-19 19:25 ` [PATCH 00/27] gpu: nova-core: boot on " Timur Tabi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260819035221.336390-3-jhubbard@nvidia.com \
--to=jhubbard@nvidia.com \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=apopple@nvidia.com \
--cc=bhelgaas@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=dakr@kernel.org \
--cc=ecourtney@nvidia.com \
--cc=gary@garyguo.net \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=nova-gpu@lists.linux.dev \
--cc=ojeda@kernel.org \
--cc=simona@ffwll.ch \
--cc=tmgross@umich.edu \
--cc=ttabi@nvidia.com \
--cc=zhiw@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox