From: Alexandre Courbot <acourbot@nvidia.com>
To: "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>,
"Danilo Krummrich" <dakr@kernel.org>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>
Cc: John Hubbard <jhubbard@nvidia.com>,
Alistair Popple <apopple@nvidia.com>,
Joel Fernandes <joelagnelf@nvidia.com>,
Timur Tabi <ttabi@nvidia.com>,
rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
nouveau@lists.freedesktop.org, dri-devel@lists.freedesktop.org,
Alexandre Courbot <acourbot@nvidia.com>
Subject: [PATCH v2 5/8] gpu: nova-core: firmware: process and prepare the GSP firmware
Date: Tue, 26 Aug 2025 13:07:41 +0900 [thread overview]
Message-ID: <20250826-nova_firmware-v2-5-93566252fe3a@nvidia.com> (raw)
In-Reply-To: <20250826-nova_firmware-v2-0-93566252fe3a@nvidia.com>
The GSP firmware is a binary blob that is verified, loaded, and run by
the GSP bootloader. Its presentation is a bit peculiar as the GSP
bootloader expects to be given a DMA address to a 3-levels page table
mapping the GSP firmware at address 0 of its own address space.
Prepare such a structure containing the DMA-mapped firmware as well as
the DMA-mapped page tables, and a way to obtain the DMA handle of the
level 0 page table.
As we are performing the required ELF section parsing and radix3 page
table building, remove these items from the TODO file.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
Documentation/gpu/nova/core/todo.rst | 17 -----
drivers/gpu/nova-core/firmware.rs | 110 +++++++++++++++++++++++++++++++-
drivers/gpu/nova-core/firmware/gsp.rs | 117 ++++++++++++++++++++++++++++++++++
drivers/gpu/nova-core/gsp.rs | 4 ++
drivers/gpu/nova-core/nova_core.rs | 1 +
5 files changed, 229 insertions(+), 20 deletions(-)
diff --git a/Documentation/gpu/nova/core/todo.rst b/Documentation/gpu/nova/core/todo.rst
index 89431fec9041b1f35cc55799c91f48dc6bc918eb..0972cb905f7ae64dfbaef4808276757319009e9c 100644
--- a/Documentation/gpu/nova/core/todo.rst
+++ b/Documentation/gpu/nova/core/todo.rst
@@ -229,23 +229,6 @@ Rust abstraction for debugfs APIs.
GPU (general)
=============
-Parse firmware headers
-----------------------
-
-Parse ELF headers from the firmware files loaded from the filesystem.
-
-| Reference: ELF utils
-| Complexity: Beginner
-| Contact: Abdiel Janulgue
-
-Build radix3 page table
------------------------
-
-Build the radix3 page table to map the firmware.
-
-| Complexity: Intermediate
-| Contact: Abdiel Janulgue
-
Initial Devinit support
-----------------------
diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs
index 9bee0e0a0ab99d10be7e56d366970fdf4c813fc4..fb751287e938e6a323db185ff8c4ba2781d25285 100644
--- a/drivers/gpu/nova-core/firmware.rs
+++ b/drivers/gpu/nova-core/firmware.rs
@@ -7,6 +7,7 @@
use core::mem::size_of;
use booter::BooterFirmware;
+use gsp::GspFirmware;
use kernel::device;
use kernel::firmware;
use kernel::prelude::*;
@@ -19,14 +20,100 @@
use crate::falcon::FalconFirmware;
use crate::falcon::{sec2::Sec2, Falcon};
use crate::gpu;
-use crate::gpu::Chipset;
+use crate::gpu::{Architecture, Chipset};
pub(crate) mod booter;
pub(crate) mod fwsec;
+pub(crate) mod gsp;
pub(crate) mod riscv;
pub(crate) const FIRMWARE_VERSION: &str = "535.113.01";
+/// Ad-hoc and temporary module to extract sections from ELF images.
+///
+/// Some firmware images are currently packaged as ELF files, where sections names are used as keys
+/// to specific and related bits of data. Future firmware versions are scheduled to move away from
+/// that scheme before nova-core becomes stable, which means this module will eventually be
+/// removed.
+mod elf {
+ use kernel::bindings;
+ use kernel::str::CStr;
+ use kernel::transmute::FromBytes;
+
+ /// Newtype to provide a [`FromBytes`] implementation.
+ #[repr(transparent)]
+ struct Elf64Hdr(bindings::elf64_hdr);
+
+ // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
+ unsafe impl FromBytes for Elf64Hdr {}
+
+ /// Tries to extract section with name `name` from the ELF64 image `elf`, and returns it.
+ pub(super) fn elf64_section<'a, 'b>(elf: &'a [u8], name: &'b str) -> Option<&'a [u8]> {
+ let hdr = &elf
+ .get(0..size_of::<bindings::elf64_hdr>())
+ .and_then(Elf64Hdr::from_bytes)?
+ .0;
+
+ // Get all the section headers.
+ let shdr = {
+ let shdr_num = usize::from(hdr.e_shnum);
+ let shdr_start = usize::try_from(hdr.e_shoff).ok()?;
+ let shdr_end = shdr_num
+ .checked_mul(size_of::<bindings::elf64_shdr>())
+ .and_then(|v| v.checked_add(shdr_start))?;
+
+ elf.get(shdr_start..shdr_end)
+ .map(|slice| slice.as_ptr())
+ .filter(|ptr| ptr.align_offset(align_of::<bindings::elf64_shdr>()) == 0)
+ // `FromBytes::from_bytes` does not support slices yet, so build it manually.
+ //
+ // SAFETY:
+ // * `get` guarantees that the slice is within the bounds of `elf` and of size
+ // `elf64_shdr * shdr_num`.
+ // * We checked that `ptr` had the correct alignment for `elf64_shdr`.
+ .map(|ptr| unsafe {
+ core::slice::from_raw_parts(ptr.cast::<bindings::elf64_shdr>(), shdr_num)
+ })?
+ };
+
+ // Get the strings table.
+ let strhdr = shdr.get(usize::from(hdr.e_shstrndx))?;
+
+ // Find the section which name matches `name` and return it.
+ shdr.iter()
+ .find(|sh| {
+ let Some(name_idx) = strhdr
+ .sh_offset
+ .checked_add(u64::from(sh.sh_name))
+ .and_then(|idx| usize::try_from(idx).ok())
+ else {
+ return false;
+ };
+
+ // Get the start of the name.
+ elf.get(name_idx..)
+ // Stop at the first `0`.
+ .and_then(|nstr| nstr.get(0..=nstr.iter().position(|b| *b == 0)?))
+ // Convert into CStr. This should never fail because of the line above.
+ .and_then(|nstr| CStr::from_bytes_with_nul(nstr).ok())
+ // Convert into str.
+ .and_then(|c_str| c_str.to_str().ok())
+ // Check that the name matches.
+ .map(|str| str == name)
+ .unwrap_or(false)
+ })
+ // Return the slice containing the section.
+ .and_then(|sh| {
+ let start = usize::try_from(sh.sh_offset).ok()?;
+ let end = usize::try_from(sh.sh_size)
+ .ok()
+ .and_then(|sh_size| start.checked_add(sh_size))?;
+
+ elf.get(start..end)
+ })
+ }
+}
+
/// Structure encapsulating the firmware blobs required for the GPU to operate.
#[expect(dead_code)]
pub(crate) struct Firmware {
@@ -36,7 +123,10 @@ pub(crate) struct Firmware {
booter_unloader: BooterFirmware,
/// GSP bootloader, verifies the GSP firmware before loading and running it.
gsp_bootloader: RiscvFirmware,
- gsp: firmware::Firmware,
+ /// GSP firmware.
+ gsp: Pin<KBox<GspFirmware>>,
+ /// GSP signatures, to be passed as parameter to the bootloader for validation.
+ gsp_sigs: DmaObject,
}
impl Firmware {
@@ -56,13 +146,27 @@ pub(crate) fn new(
.and_then(|path| firmware::Firmware::request(&path, dev))
};
+ let gsp_fw = request("gsp")?;
+ let gsp = elf::elf64_section(gsp_fw.data(), ".fwimage")
+ .ok_or(EINVAL)
+ .map(|data| GspFirmware::new(dev, data))?;
+
+ let gsp_sigs_section = match chipset.arch() {
+ Architecture::Ampere => ".fwsignature_ga10x",
+ _ => return Err(ENOTSUPP),
+ };
+ let gsp_sigs = elf::elf64_section(gsp_fw.data(), gsp_sigs_section)
+ .ok_or(EINVAL)
+ .and_then(|data| DmaObject::from_data(dev, data))?;
+
Ok(Firmware {
booter_loader: request("booter_load")
.and_then(|fw| BooterFirmware::new(dev, &fw, sec2, bar))?,
booter_unloader: request("booter_unload")
.and_then(|fw| BooterFirmware::new(dev, &fw, sec2, bar))?,
gsp_bootloader: request("bootloader").and_then(|fw| RiscvFirmware::new(dev, &fw))?,
- gsp: request("gsp")?,
+ gsp: KBox::pin_init(gsp, GFP_KERNEL)?,
+ gsp_sigs,
})
}
}
diff --git a/drivers/gpu/nova-core/firmware/gsp.rs b/drivers/gpu/nova-core/firmware/gsp.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f37bd619bfb71629ed86ee8b7828971bbe4c5916
--- /dev/null
+++ b/drivers/gpu/nova-core/firmware/gsp.rs
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use kernel::device;
+use kernel::dma::DataDirection;
+use kernel::dma::DmaAddress;
+use kernel::prelude::*;
+use kernel::scatterlist::Owned;
+use kernel::scatterlist::SGTable;
+
+use crate::dma::DmaObject;
+use crate::gsp::GSP_PAGE_SIZE;
+
+/// A device-mapped firmware with a set of (also device-mapped) pages tables mapping the firmware
+/// to the start of their own address space, also known as a `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>>>,
+ /// The level 2 page table, mapping [`Self::fw`] at its beginning.
+ #[pin]
+ lvl2: SGTable<Owned<VVec<u8>>>,
+ /// The level 1 page table, mapping [`Self::lvl2`] at its beginning.
+ #[pin]
+ lvl1: SGTable<Owned<VVec<u8>>>,
+ /// The level 0 page table, mapping [`Self::lvl1`] at its beginning.
+ lvl0: DmaObject,
+ /// Size in bytes of the firmware contained in [`Self::fw`].
+ pub size: usize,
+}
+
+impl GspFirmware {
+ /// Maps the GSP firmware image `fw` into `dev`'s address-space, and creates the page tables
+ /// expected by the GSP bootloader to load it.
+ pub(crate) fn new<'a>(
+ dev: &'a device::Device<device::Bound>,
+ fw: &'a [u8],
+ ) -> impl PinInit<Self, Error> + 'a {
+ try_pin_init!(&this in Self {
+ fw <- {
+ // Move the firmware into a vmalloc'd vector and map it into the device address
+ // space.
+ VVec::with_capacity(fw.len(), GFP_KERNEL)
+ .and_then(|mut v| {
+ v.extend_from_slice(fw, GFP_KERNEL)?;
+ Ok(v)
+ })
+ .map_err(|_| ENOMEM)
+ .map(|v| SGTable::new(dev, v, DataDirection::ToDevice, GFP_KERNEL))?
+ },
+ lvl2 <- {
+ // Allocate the level 2 page table, map the firmware onto it, and map it into the
+ // device address space.
+ // SAFETY: `this` is a valid pointer, and `fw` has been initialized.
+ let fw_sg_table = unsafe { &(*this.as_ptr()).fw };
+ VVec::<u8>::with_capacity(
+ fw_sg_table.iter().count() * core::mem::size_of::<u64>(),
+ GFP_KERNEL,
+ )
+ .map_err(|_| ENOMEM)
+ .and_then(|lvl2| map_into_lvl(fw_sg_table, lvl2))
+ .map(|lvl2| SGTable::new(dev, lvl2, DataDirection::ToDevice, GFP_KERNEL))?
+ },
+ lvl1 <- {
+ // Allocate the level 1 page table, map the level 2 page table onto it, and map it
+ // into the device address space.
+ // SAFETY: `this` is a valid pointer, and `lvl2` has been initialized.
+ let lvl2_sg_table = unsafe { &(*this.as_ptr()).lvl2 };
+ VVec::<u8>::with_capacity(
+ lvl2_sg_table.iter().count() * core::mem::size_of::<u64>(),
+ GFP_KERNEL,
+ )
+ .map_err(|_| ENOMEM)
+ .and_then(|lvl1| map_into_lvl(lvl2_sg_table, lvl1))
+ .map(|lvl1| SGTable::new(dev, lvl1, DataDirection::ToDevice, GFP_KERNEL))?
+ },
+ lvl0: {
+ // Allocate the level 0 page table as a device-visible DMA object, and map the
+ // level 1 page table onto it.
+ // SAFETY: `this` is a valid pointer, and `lvl1` has been initialized.
+ let lvl1_sg_table = unsafe { &(*this.as_ptr()).lvl1 };
+ let mut lvl0 = DmaObject::new(dev, GSP_PAGE_SIZE)?;
+ // SAFETY: we are the only owner of this newly-created object, making races
+ // impossible.
+ let lvl0_slice = unsafe { lvl0.as_slice_mut(0, GSP_PAGE_SIZE) }?;
+ lvl0_slice[0..core::mem::size_of::<u64>()].copy_from_slice(
+ #[allow(clippy::useless_conversion)]
+ &(u64::from(lvl1_sg_table.iter().next().unwrap().dma_address())).to_le_bytes(),
+ );
+
+ lvl0
+ },
+ size: fw.len(),
+ })
+ }
+
+ #[expect(unused)]
+ /// Returns the DMA handle of the level 0 page table.
+ pub(crate) fn lvl0_dma_handle(&self) -> DmaAddress {
+ self.lvl0.dma_handle()
+ }
+}
+
+/// Create a linear mapping the device mapping of the buffer described by `sg_table` into `dst`.
+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 = (sg_entry.dma_len() as usize).div_ceil(GSP_PAGE_SIZE);
+
+ for i in 0..num_pages {
+ let entry = sg_entry.dma_address() + (i as u64 * GSP_PAGE_SIZE as u64);
+ dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?;
+ }
+ }
+
+ Ok(dst)
+}
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a0e7ec5f6c9c959d57540b3ebf4b782f2e002b08
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
+
+pub(crate) const GSP_PAGE_SHIFT: usize = 12;
+pub(crate) const GSP_PAGE_SIZE: usize = 1 << GSP_PAGE_SHIFT;
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index cb2bbb30cba142265b354c9acf70349a6e40759e..fffcaee2249fe6cd7f55a7291c1e44be42e791d9 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -9,6 +9,7 @@
mod firmware;
mod gfw;
mod gpu;
+mod gsp;
mod regs;
mod util;
mod vbios;
--
2.50.1
next prev parent reply other threads:[~2025-08-26 4:08 UTC|newest]
Thread overview: 43+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-26 4:07 [PATCH v2 0/8] gpu: nova-core: process and prepare more firmwares to boot GSP Alexandre Courbot
2025-08-26 4:07 ` [PATCH v2 1/8] rust: transmute: add `from_bytes_copy` method to `FromBytes` trait Alexandre Courbot
2025-08-26 6:50 ` Benno Lossin
2025-08-27 0:51 ` John Hubbard
2025-08-28 7:05 ` Alexandre Courbot
2025-08-28 11:26 ` Alexandre Courbot
2025-08-28 11:45 ` Miguel Ojeda
2025-08-29 1:51 ` Alexandre Courbot
2025-08-26 4:07 ` [PATCH v2 2/8] gpu: nova-core: firmware: add support for common firmware header Alexandre Courbot
2025-08-27 1:34 ` John Hubbard
2025-08-27 8:47 ` Alexandre Courbot
2025-08-27 21:50 ` John Hubbard
2025-08-28 7:08 ` Alexandre Courbot
2025-08-29 0:21 ` John Hubbard
2025-08-28 11:26 ` Miguel Ojeda
2025-08-26 4:07 ` [PATCH v2 3/8] gpu: nova-core: firmware: process Booter and patch its signature Alexandre Courbot
2025-08-27 2:29 ` John Hubbard
2025-08-28 7:19 ` Alexandre Courbot
2025-08-29 0:26 ` John Hubbard
2025-08-28 20:58 ` Timur Tabi
2025-08-26 4:07 ` [PATCH v2 4/8] gpu: nova-core: firmware: process the GSP bootloader Alexandre Courbot
2025-08-28 3:09 ` John Hubbard
2025-08-26 4:07 ` Alexandre Courbot [this message]
2025-08-28 4:01 ` [PATCH v2 5/8] gpu: nova-core: firmware: process and prepare the GSP firmware John Hubbard
2025-08-28 11:13 ` Alexandre Courbot
2025-08-29 0:27 ` John Hubbard
2025-08-28 11:27 ` Danilo Krummrich
2025-08-29 11:16 ` Alexandre Courbot
2025-08-30 12:56 ` Danilo Krummrich
2025-09-01 7:11 ` Alexandre Courbot
2025-08-26 4:07 ` [PATCH v2 6/8] gpu: nova-core: firmware: use 570.144 firmware Alexandre Courbot
2025-08-28 4:07 ` John Hubbard
2025-08-26 4:07 ` [PATCH v2 7/8] gpu: nova-core: Add base files for r570.144 firmware bindings Alexandre Courbot
2025-08-28 4:08 ` John Hubbard
2025-08-26 4:07 ` [PATCH v2 8/8] gpu: nova-core: compute layout of more framebuffer regions required for GSP Alexandre Courbot
2025-08-29 23:30 ` John Hubbard
2025-08-30 0:59 ` Alexandre Courbot
2025-08-30 5:46 ` John Hubbard
2025-08-27 0:29 ` [PATCH v2 0/8] gpu: nova-core: process and prepare more firmwares to boot GSP John Hubbard
2025-08-27 8:39 ` Alexandre Courbot
2025-08-27 21:56 ` John Hubbard
2025-08-28 20:44 ` Konstantin Ryabitsev
2025-08-29 0:33 ` John Hubbard
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=20250826-nova_firmware-v2-5-93566252fe3a@nvidia.com \
--to=acourbot@nvidia.com \
--cc=a.hindborg@kernel.org \
--cc=airlied@gmail.com \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=apopple@nvidia.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=dakr@kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=gary@garyguo.net \
--cc=jhubbard@nvidia.com \
--cc=joelagnelf@nvidia.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@kernel.org \
--cc=nouveau@lists.freedesktop.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=simona@ffwll.ch \
--cc=tmgross@umich.edu \
--cc=ttabi@nvidia.com \
--cc=tzimmermann@suse.de \
/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;
as well as URLs for NNTP newsgroup(s).