lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250822-nova_firmware-v1-5-ff5633679460@nvidia.com>
Date: Fri, 22 Aug 2025 21:47:20 +0900
From: Alexandre Courbot <acourbot@...dia.com>
To: Miguel Ojeda <ojeda@...nel.org>, Alex Gaynor <alex.gaynor@...il.com>, 
 Boqun Feng <boqun.feng@...il.com>, Gary Guo <gary@...yguo.net>, 
 Björn Roy Baron <bjorn3_gh@...tonmail.com>, 
 Benno Lossin <lossin@...nel.org>, Andreas Hindborg <a.hindborg@...nel.org>, 
 Alice Ryhl <aliceryhl@...gle.com>, Trevor Gross <tmgross@...ch.edu>, 
 Danilo Krummrich <dakr@...nel.org>, David Airlie <airlied@...il.com>, 
 Simona Vetter <simona@...ll.ch>, 
 Maarten Lankhorst <maarten.lankhorst@...ux.intel.com>, 
 Maxime Ripard <mripard@...nel.org>, Thomas Zimmermann <tzimmermann@...e.de>
Cc: rust-for-linux@...r.kernel.org, linux-kernel@...r.kernel.org, 
 nouveau@...ts.freedesktop.org, dri-devel@...ts.freedesktop.org, 
 Alexandre Courbot <acourbot@...dia.com>
Subject: [PATCH 5/5] gpu: nova-core: firmware: process and prepare the GSP
 firmware

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@...dia.com>
---
 Documentation/gpu/nova/core/todo.rst  |  17 -----
 drivers/gpu/nova-core/firmware.rs     | 108 ++++++++++++++++++++++++++++++-
 drivers/gpu/nova-core/firmware/gsp.rs | 116 ++++++++++++++++++++++++++++++++++
 drivers/gpu/nova-core/gsp.rs          |   4 ++
 drivers/gpu/nova-core/nova_core.rs    |   1 +
 5 files changed, 226 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 7006696bb8e8ec0d7fa3a94fb931d5f0b21fb79d..b97fe53487cab12069961b132ba989a88d3ace81 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,98 @@
 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;
+
+        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))?;
+        // Get all the section headers.
+        let shdr = 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 +121,10 @@ pub(crate) struct Firmware {
     booter_unloader: BooterFirmware,
     /// GSP bootloader, verifies the GSP firmware before loading and running it.
     bootloader: RiscvFirmware,
-    gsp: firmware::Firmware,
+    /// GSP firmware.
+    gsp: GspFirmware,
+    /// GSP signatures, to be passed as parameter to the bootloader for validation.
+    gsp_sigs: DmaObject,
 }
 
 impl Firmware {
@@ -56,13 +144,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)
+            .and_then(|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))?,
             bootloader: request("bootloader").and_then(|fw| RiscvFirmware::new(dev, &fw))?,
-            gsp: request("gsp")?,
+            gsp,
+            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..34714156e40c0b41e7d6f67b7abe9d76659b5d18
--- /dev/null
+++ b/drivers/gpu/nova-core/firmware/gsp.rs
@@ -0,0 +1,116 @@
+// 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.
+pub(crate) struct GspFirmware {
+    /// The GSP firmware inside a [`VVec`], device-mapped via a SG table.
+    #[expect(unused)]
+    fw: Pin<KBox<SGTable<Owned<VVec<u8>>>>>,
+    /// The level 2 page table, mapping [`Self::fw`] at its beginning.
+    #[expect(unused)]
+    lvl2: Pin<KBox<SGTable<Owned<VVec<u8>>>>>,
+    /// The level 1 page table, mapping [`Self::lvl2`] at its beginning.
+    #[expect(unused)]
+    lvl1: Pin<KBox<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`].
+    #[expect(unused)]
+    pub size: usize,
+}
+
+impl GspFirmware {
+    pub(crate) fn new(dev: &device::Device<device::Bound>, fw: &[u8]) -> Result<Self> {
+        // Move the firmware into a vmalloc'd vector and map it into the device address space.
+        let fw_sg_table = VVec::with_capacity(fw.len(), GFP_KERNEL)
+            .and_then(|mut v| {
+                v.extend_from_slice(fw, GFP_KERNEL)?;
+                Ok(v)
+            })
+            .map_err(|_| ENOMEM)
+            .and_then(|v| {
+                KBox::pin_init(
+                    SGTable::new(dev, v, DataDirection::ToDevice, GFP_KERNEL),
+                    GFP_KERNEL,
+                )
+            })?;
+
+        // Allocate the level 2 page table, map the firmware onto it, and map it into the device
+        // address space.
+        let lvl2_sg_table = VVec::<u8>::with_capacity(
+            fw_sg_table.into_iter().count() * core::mem::size_of::<u64>(),
+            GFP_KERNEL,
+        )
+        .map_err(|_| ENOMEM)
+        .and_then(|lvl2| map_into_lvl(&fw_sg_table, lvl2))
+        .and_then(|lvl2| {
+            KBox::pin_init(
+                SGTable::new(dev, lvl2, DataDirection::ToDevice, GFP_KERNEL),
+                GFP_KERNEL,
+            )
+        })?;
+
+        // Allocate the level 1 page table, map the level 2 page table onto it, and map it into the
+        // device address space.
+        let lvl1_sg_table = VVec::<u8>::with_capacity(
+            lvl2_sg_table.into_iter().count() * core::mem::size_of::<u64>(),
+            GFP_KERNEL,
+        )
+        .map_err(|_| ENOMEM)
+        .and_then(|lvl1| map_into_lvl(&lvl2_sg_table, lvl1))
+        .and_then(|lvl1| {
+            KBox::pin_init(
+                SGTable::new(dev, lvl1, DataDirection::ToDevice, GFP_KERNEL),
+                GFP_KERNEL,
+            )
+        })?;
+
+        // Allocate the level 0 page table as a device-visible DMA object, and map the level 1 page
+        // table onto it.
+        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(
+            &(lvl1_sg_table.into_iter().next().unwrap().dma_address() as u64).to_le_bytes(),
+        );
+
+        Ok(Self {
+            fw: fw_sg_table,
+            lvl2: lvl2_sg_table,
+            lvl1: lvl1_sg_table,
+            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.into_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


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ