[PATCH v3 24/33] gpu: nova-core: add the ucodes firmware loader
From: John Hubbard
Date: Thu Sep 17 2026 - 21:15:21 EST
The r000 GSP firmware includes a ucodes image, microcode that GSP-RM
loads at run time from a buffer that the driver maps for it. The image
is described by a TLV whose FILE tag names a separate file and whose
SIZE tag gives that file's length, which is the convention that the GSP
firmware image already uses.
The GSP firmware wrapper open-coded the reading of the FILE and SIZE
tags, so the ucodes loader could not share it.
Move the reading of the two tags into the TLV parser, and add the ucodes
loader on top of the parser. The loader maps the image through a radix3
page table, as the GSP firmware image is mapped, and the image is
required on every chipset that nova-core supports. The loader has no
caller until the switch to r000.
Assisted-by: LLM
Reviewed-by: Timur Tabi <ttabi@xxxxxxxxxx>
Signed-off-by: John Hubbard <jhubbard@xxxxxxxxxx>
---
drivers/gpu/nova-core/firmware.rs | 7 ++-
drivers/gpu/nova-core/firmware/bindata.rs | 59 +++++++++++++++++++++++
drivers/gpu/nova-core/firmware/gsp.rs | 15 ++----
drivers/gpu/nova-core/firmware/tlv.rs | 40 +++++++++++++--
4 files changed, 105 insertions(+), 16 deletions(-)
create mode 100644 drivers/gpu/nova-core/firmware/bindata.rs
diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs
index 358c9b8db0b8..d7ba03184ad5 100644
--- a/drivers/gpu/nova-core/firmware.rs
+++ b/drivers/gpu/nova-core/firmware.rs
@@ -22,6 +22,7 @@
num::IntoSafeCast, //
};
+pub(crate) mod bindata;
pub(crate) mod booter;
pub(crate) mod fwsec;
pub(crate) mod gen_bootloader;
@@ -349,7 +350,11 @@ const fn make_entry_chipset(self, chipset: gpu::Chipset) -> Self {
let mut this = self
.make_entry_file(name, "gsp_bootloader.tlv")
.make_entry_file(name, "gsp.tlv")
- .make_entry_file(name, "gsp.bin");
+ .make_entry_file(name, "gsp.bin")
+ .make_entry_file(name, "ucodes.tlv")
+ // The metadata's FILE tag gives the image's real file name at run time. This static
+ // entry names the usual one.
+ .make_entry_file(name, "ucodes.bin");
// Add the firmware files specific to the GSP boot method of `chipset`.
let boot_files = boot_firmware_files(chipset);
diff --git a/drivers/gpu/nova-core/firmware/bindata.rs b/drivers/gpu/nova-core/firmware/bindata.rs
new file mode 100644
index 000000000000..410cb741273c
--- /dev/null
+++ b/drivers/gpu/nova-core/firmware/bindata.rs
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! Loading of the ucodes image, the bindata blob of microcode that GSP-RM loads at run time.
+
+use kernel::{
+ device,
+ dma::DmaAddress,
+ prelude::*, //
+};
+
+use crate::{
+ firmware::{
+ radix3::Radix3,
+ tlv::{
+ request_tlv,
+ Tlv, //
+ },
+ },
+ gpu::Chipset,
+};
+
+/// The ucodes image, mapped for GSP-RM through a radix3 page table.
+pub(crate) struct UcodesImage<'a> {
+ /// The image and the page table that maps it.
+ radix3: Pin<KBox<Radix3<'a>>>,
+}
+
+#[expect(dead_code)]
+impl<'a> UcodesImage<'a> {
+ /// Loads the ucodes image that the `ucodes` metadata file names, and maps it for `dev`.
+ ///
+ /// # Errors
+ ///
+ /// - `ENOENT` if the metadata file is not installed.
+ /// - `EINVAL` if the metadata is malformed.
+ /// - `ENOMEM` if the page table cannot be allocated.
+ ///
+ /// Errors from [`Tlv::load_file`] are propagated as-is.
+ pub(crate) fn new(dev: &'a device::Device<device::Bound>, chipset: Chipset) -> Result<Self> {
+ let firmware = request_tlv(dev, chipset, "ucodes")?;
+ let tlv = Tlv::new(firmware.data())?;
+ let image = tlv.load_file(dev, chipset)?;
+
+ Ok(Self {
+ radix3: KBox::pin_init(Radix3::new(dev, image), GFP_KERNEL)?,
+ })
+ }
+
+ /// Returns the DMA address of the level 0 page of the page table that maps the image.
+ pub(crate) fn radix3_dma_address(&self) -> DmaAddress {
+ self.radix3.dma_address()
+ }
+
+ /// Returns the size of the image in bytes.
+ pub(crate) fn size(&self) -> usize {
+ self.radix3.size()
+ }
+}
diff --git a/drivers/gpu/nova-core/firmware/gsp.rs b/drivers/gpu/nova-core/firmware/gsp.rs
index 341a8b19aa38..a2db7b6ba131 100644
--- a/drivers/gpu/nova-core/firmware/gsp.rs
+++ b/drivers/gpu/nova-core/firmware/gsp.rs
@@ -6,9 +6,7 @@
Coherent,
DmaAddress, //
},
- firmware,
- prelude::*,
- str::CString,
+ prelude::*, //
};
use crate::{
@@ -20,8 +18,7 @@
Tlv,
},
},
- gpu::Chipset,
- num::FromSafeCast,
+ gpu::Chipset, //
};
/// The GSP firmware image, its signatures, and the GSP bootloader.
@@ -48,13 +45,7 @@ pub(crate) fn new(
let tlv = Tlv::new(firmware.data())?;
dev_dbg!(dev, "loaded gsp firmware v{}\n", tlv.get_string(b"VERS")?);
- let size = usize::from_safe_cast(tlv.get_u32(b"SIZE")?);
- let mut fw_vvec = VVec::zeroed(size, GFP_KERNEL).map_err(|_| ENOMEM)?;
-
- let chip_name = chipset.name();
- let file = tlv.get_string(b"FILE")?;
- let filename = CString::try_from_fmt(fmt!("nvidia/{chip_name}/gsp/{file}"))?;
- firmware::request_into_buf(&filename, dev, fw_vvec.as_mut_slice())?;
+ let fw_vvec = tlv.load_file(dev, chipset)?;
let signatures = Coherent::from_slice(dev, tlv.get_bytes(b"SIGN")?, GFP_KERNEL)?;
diff --git a/drivers/gpu/nova-core/firmware/tlv.rs b/drivers/gpu/nova-core/firmware/tlv.rs
index 7b879f13a61e..7f278903dc8b 100644
--- a/drivers/gpu/nova-core/firmware/tlv.rs
+++ b/drivers/gpu/nova-core/firmware/tlv.rs
@@ -4,6 +4,7 @@
use kernel::{
device,
firmware,
+ fmt,
prelude::*,
str::CString, //
};
@@ -13,15 +14,18 @@
num::*, //
};
+/// Returns the path of `file` in `chipset`'s GSP firmware directory.
+fn gsp_firmware_path(chipset: gpu::Chipset, file: fmt::Arguments<'_>) -> Result<CString> {
+ CString::try_from_fmt(fmt!("nvidia/{}/gsp/{}", chipset.name(), file))
+}
+
/// Requests the GPU firmware TLV `name` suitable for `chipset`.
pub(crate) fn request_tlv(
dev: &device::Device,
chipset: gpu::Chipset,
name: &str,
) -> Result<firmware::Firmware> {
- let chip_name = chipset.name();
-
- let filename = CString::try_from_fmt(fmt!("nvidia/{chip_name}/gsp/{name}.tlv"))?;
+ let filename = gsp_firmware_path(chipset, fmt!("{name}.tlv"))?;
dev_dbg!(dev, "loading firmware image {:?}\n", &filename);
@@ -198,6 +202,36 @@ fn iter(&self) -> TlvIter<'_, 'a> {
self.iter().find(|b| b.tag == *tag).ok_or(EINVAL)
}
+ /// Loads the file that the `FILE` tag names from `chipset`'s GSP firmware directory.
+ ///
+ /// The `SIZE` tag gives the file's length, and the returned buffer is that long.
+ ///
+ /// # Errors
+ ///
+ /// - `EINVAL` if `FILE` or `SIZE` is absent, or `FILE` does not hold a valid string.
+ /// - `ENODATA` if `SIZE` is zero.
+ /// - `ENOMEM` if the buffer cannot be allocated.
+ ///
+ /// Errors from the firmware request, `ENOENT` in particular, are propagated as-is.
+ pub(crate) fn load_file(
+ &self,
+ dev: &device::Device,
+ chipset: gpu::Chipset,
+ ) -> Result<VVec<u8>> {
+ let file = self.get_string(b"FILE")?;
+ let path = gsp_firmware_path(chipset, fmt!("{file}"))?;
+
+ let size = usize::from_safe_cast(self.get_u32(b"SIZE")?);
+ if size == 0 {
+ return Err(ENODATA);
+ }
+
+ let mut data = VVec::zeroed(size, GFP_KERNEL).map_err(|_| ENOMEM)?;
+ firmware::request_into_buf(&path, dev, data.as_mut_slice())?;
+
+ Ok(data)
+ }
+
/// Return a slice of bytes.
///
/// Returns `EINVAL` if the value is empty.
--
2.55.0