[PATCH v3 05/13] gpu: nova-core: place the generic bootloader at the top of IMEM
From: Alexandre Courbot
Date: Tue Nov 10 2026 - 05:25:17 EST
From: John Hubbard <jhubbard@xxxxxxxxxx>
The generic bootloader was loaded right below the first 64 KiB of IMEM,
on the assumption that every falcon it runs on has a 64 KiB IMEM. The
r000 load-and-execute bootloader event can name any image for it to
load, and the bootloader must then sit above the blocks that the image
occupies, that is, in the last blocks of IMEM, regardless of its size.
Place the bootloader at the top of the falcon's IMEM, using the IMEM
size that the HWCFG register reports, instead of assuming a 64 KiB IMEM.
The computation of `imem_dst_start` is moved to the `run` method as it
is the one that receives the Falcon as parameter.
Signed-off-by: John Hubbard <jhubbard@xxxxxxxxxx>
Reviewed-by: Timur Tabi <ttabi@xxxxxxxxxx>
Reviewed-by: Zhi Wang <zhiw@xxxxxxxxxx>
Reviewed-by: Eliot Courtney <ecourtney@xxxxxxxxxx>
[acourbot: split out of the patch separating the bootloader from FWSEC;
convert the IMEM size with usize::from()]
Signed-off-by: Alexandre Courbot <acourbot@xxxxxxxxxx>
---
drivers/gpu/nova-core/falcon.rs | 7 ++++
drivers/gpu/nova-core/firmware/gen_bootloader.rs | 42 +++++++++++++-----------
drivers/gpu/nova-core/regs.rs | 5 +++
3 files changed, 34 insertions(+), 20 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index a34748abc74a..b17a997860ef 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -386,6 +386,13 @@ pub(crate) fn new(
})
}
+ /// Returns the size of this falcon's IMEM in bytes.
+ pub(crate) fn imem_size(&self) -> usize {
+ let blocks = usize::from(self.pfalcon.read(regs::NV_PFALCON_FALCON_HWCFG).imem_size());
+
+ blocks * MEM_BLOCK_ALIGNMENT
+ }
+
/// Resets DMA-related registers.
pub(crate) fn dma_reset(&self) {
self.pfalcon.update(regs::NV_PFALCON_FBIF_CTL, |v| {
diff --git a/drivers/gpu/nova-core/firmware/gen_bootloader.rs b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
index 2f42d3ad1ba5..54d9796b4897 100644
--- a/drivers/gpu/nova-core/firmware/gen_bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
@@ -17,7 +17,6 @@
},
prelude::*,
ptr::Alignable,
- sizes,
transmute::AsBytes, //
};
@@ -133,20 +132,16 @@ unsafe impl AsBytes for BootloaderDmemDescV2 {}
pub(crate) struct GenericBootloader {
/// Bootloader code, zero-padded to a whole number of falcon memory blocks.
ucode: KVec<u8>,
- /// Byte offset in IMEM at which the code is loaded.
- imem_dst_start: Bounded<u32, 24>,
/// Tag under which the first code block is loaded.
start_tag: u16,
}
impl GenericBootloader {
- /// Loads the generic bootloader image for `chipset`, placed right below the first 64 KiB of
- /// IMEM.
+ /// Loads the generic bootloader image for `chipset`.
///
/// # Errors
///
/// - `EINVAL` if a required TLV field is absent.
- /// - `EOVERFLOW` if the image does not fit below 64 KiB.
/// - `ENOMEM` if the padded copy of the code cannot be allocated.
pub(crate) fn new(dev: &device::Device, chipset: Chipset) -> Result<Self> {
let fw = request_tlv(dev, chipset, "gen_bootloader")?;
@@ -172,15 +167,8 @@ pub(crate) fn new(dev: &device::Device, chipset: Chipset) -> Result<Self> {
ucode
};
- // The bootloader's code must be loaded in the area right below the first 64K of IMEM.
- const BOOTLOADER_LOAD_CEILING: usize = sizes::SZ_64K;
- let imem_dst_start = BOOTLOADER_LOAD_CEILING
- .checked_sub(ucode.len())
- .ok_or(EOVERFLOW)?;
-
Ok(Self {
ucode,
- imem_dst_start: imem_dst_start.try_into_bounded().ok_or(EINVAL)?,
start_tag: u16::try_from(tlv.get_u32(b"STRT")?)?,
})
}
@@ -191,11 +179,12 @@ fn boot_addr(&self) -> u32 {
u32::from(self.start_tag) << 8
}
- /// Returns the PIO parameters that place this bootloader in non-secure IMEM.
- fn imem_load_params(&self) -> FalconPioImemLoadTarget<'_> {
+ /// Returns the PIO parameters that place this bootloader in non-secure IMEM, at byte offset
+ /// `dst_start`.
+ fn imem_load_params(&self, dst_start: Bounded<u32, 24>) -> FalconPioImemLoadTarget<'_> {
FalconPioImemLoadTarget {
data: self.ucode.as_ref(),
- dst_start: self.imem_dst_start,
+ dst_start,
secure: false,
start_tag: self.start_tag,
}
@@ -203,9 +192,10 @@ fn imem_load_params(&self) -> FalconPioImemLoadTarget<'_> {
/// Runs the bootloader on `falcon` to load and start the image that `dmem_desc` names.
///
- /// The bootloader and the descriptor are loaded by PIO, the bootloader is started with `mbox0`
- /// in `MAILBOX0`, and this returns the mailbox values once the falcon has halted, that is,
- /// once the loaded image has run to completion.
+ /// The bootloader and the descriptor are loaded by PIO, the bootloader in the last blocks of
+ /// `falcon`'s IMEM. The bootloader is started with `mbox0` in `MAILBOX0`, and this returns the
+ /// mailbox values once the falcon has halted, that is, once the loaded image has run to
+ /// completion.
///
/// The caller is expected to reset `falcon` beforehand.
///
@@ -213,6 +203,7 @@ fn imem_load_params(&self) -> FalconPioImemLoadTarget<'_> {
///
/// - `ETIMEDOUT` if the falcon has not halted within two seconds of starting.
/// - `EINVAL` if `dmem_desc`'s DMA context isn't valid.
+ /// - `EOVERFLOW` if the bootloader does not fit in `falcon`'s IMEM.
///
/// Errors from the PIO load are propagated as-is.
pub(crate) fn run(
@@ -222,6 +213,14 @@ pub(crate) fn run(
target: FalconFbifTarget,
mbox0: u32,
) -> Result<(u32, u32)> {
+ // The top of IMEM, above the blocks that the bootloader loads the image into.
+ let imem_dst_start = falcon
+ .imem_size()
+ .checked_sub(self.ucode.len())
+ .ok_or(EOVERFLOW)?
+ .try_into_bounded()
+ .ok_or(EINVAL)?;
+
// Configure DMA index for the bootloader to fetch the FWSEC firmware from system memory.
falcon.pfalcon.update(
regs::NV_PFALCON_FBIF_TRANSCFG::try_at(usize::from_safe_cast(dmem_desc.ctx_dma))
@@ -234,6 +233,7 @@ pub(crate) fn run(
falcon.pio_load(&GenericBootloaderLoad {
bootloader: self,
+ imem_dst_start,
dmem_desc,
})?;
@@ -245,6 +245,8 @@ pub(crate) fn run(
/// firmware that [`Falcon::pio_load`] accepts.
struct GenericBootloaderLoad<'a> {
bootloader: &'a GenericBootloader,
+ /// Byte offset in IMEM at which the bootloader's code is loaded.
+ imem_dst_start: Bounded<u32, 24>,
dmem_desc: &'a BootloaderDmemDescV2,
}
@@ -272,7 +274,7 @@ fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
}
fn imem_ns_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
- Some(self.bootloader.imem_load_params())
+ Some(self.bootloader.imem_load_params(self.imem_dst_start))
}
fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> {
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index bf3a43f1c09e..af0bbb92e992 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -181,6 +181,11 @@ pub(crate) fn usable_fb_size(self) -> u64 {
31:0 value => u32;
}
+ pub(crate) NV_PFALCON_FALCON_HWCFG(u32) @ 0x00000108 {
+ /// Number of 256-byte blocks in this falcon's IMEM.
+ 8:0 imem_size;
+ }
+
pub(crate) NV_PFALCON_FALCON_DMACTL(u32) @ 0x0000010c {
7:7 secure_stat => bool;
6:3 dmaq_num;
--
2.56.0