* [PATCH 1/7] gpu: nova-core: separate the generic falcon bootloader from FWSEC
2026-11-03 10:22 [PATCH 0/7] gpu: nova-core: generic bootloader and falcon DMA changes for r000 Alexandre Courbot
@ 2026-11-03 10:22 ` Alexandre Courbot
2026-11-03 10:22 ` [PATCH 2/7] gpu: nova-core: place the generic bootloader at the top of IMEM Alexandre Courbot
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Alexandre Courbot @ 2026-11-03 10:22 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Timur Tabi, Eliot Courtney,
Zhi Wang, nova-gpu, rust-for-linux, linux-kernel,
Alexandre Courbot
From: John Hubbard <jhubbard@nvidia.com>
The generic falcon bootloader is a small program that loads a larger
image into a falcon. The r000 boot protocol needs it to run an image
that GSP-RM names in a load-and-execute event during boot.
Nova-core used the bootloader only to load FWSEC, so the bootloader code
was part of the FWSEC code, and there was no way to run it on another
image.
Move the bootloader into its own type, which FWSEC then uses.
No functional changes.
Assisted-by: LLM
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Reviewed-by: Timur Tabi <ttabi@nvidia.com>
Reviewed-by: Zhi Wang <zhiw@nvidia.com>
[acourbot: document boot_addr(); split the change of IMEM placement into
its own patch]
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/firmware.rs | 1 +
drivers/gpu/nova-core/firmware/fwsec/bootloader.rs | 59 ++-----------
drivers/gpu/nova-core/firmware/gen_bootloader.rs | 99 ++++++++++++++++++++++
3 files changed, 106 insertions(+), 53 deletions(-)
diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs
index d8f6509a35d1..358c9b8db0b8 100644
--- a/drivers/gpu/nova-core/firmware.rs
+++ b/drivers/gpu/nova-core/firmware.rs
@@ -24,6 +24,7 @@
pub(crate) mod booter;
pub(crate) mod fwsec;
+pub(crate) mod gen_bootloader;
pub(crate) mod gsp;
pub(crate) mod gsp_fmc;
pub(crate) mod radix3;
diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
index 68858b48390e..59f57714fcfb 100644
--- a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
@@ -17,14 +17,11 @@
Io, //
},
prelude::*,
- ptr::Alignable,
- sizes,
transmute::AsBytes,
};
use crate::{
falcon::{
- self,
gsp::Gsp,
Falcon,
FalconBromParams,
@@ -38,10 +35,7 @@
},
firmware::{
fwsec::FwsecFirmware,
- tlv::{
- request_tlv, //
- Tlv,
- },
+ gen_bootloader::GenericBootloader, //
},
gpu::Chipset,
num::FromSafeCast, //
@@ -100,16 +94,11 @@ unsafe impl AsBytes for BootloaderDmemDescV2 {}
pub(crate) struct FwsecFirmwareWithBl<'a> {
/// DMA object the bootloader will copy the firmware from.
_firmware_dma: Coherent<'a, [u8]>,
- /// Code of the bootloader to be loaded into non-secure IMEM.
- ucode: KVec<u8>,
+ bootloader: GenericBootloader,
/// Descriptor to be loaded into DMEM for the bootloader to read.
dmem_desc: BootloaderDmemDescV2,
- /// Range-validated start offset of the firmware code in IMEM.
- imem_dst_start: u16,
/// BROM parameters of the loaded firmware.
brom_params: FalconBromParams,
- /// Range-validated `desc.start_tag`.
- start_tag: u16,
}
impl<'a> FwsecFirmwareWithBl<'a> {
@@ -120,28 +109,7 @@ pub(crate) fn new(
dev: &'a Device<device::Bound>,
chipset: Chipset,
) -> Result<Self> {
- let fw = request_tlv(dev, chipset, "gen_bootloader")?;
- let tlv = Tlv::new(fw.data())?;
- dev_dbg!(
- dev,
- "loaded generic bootloader firmware v{}\n",
- tlv.get_string(b"VERS")?
- );
-
- let ucode = {
- let blob = tlv.get_bytes(b"BLOB")?;
- let code_size = usize::from_safe_cast(tlv.get_u32(b"CDSZ")?);
- let code = blob.get(..code_size).ok_or(EINVAL)?;
- let aligned_code_size = code_size
- .align_up(cv!(falcon::MEM_BLOCK_ALIGNMENT))
- .ok_or(EINVAL)?;
-
- let mut ucode = KVec::with_capacity(aligned_code_size, GFP_KERNEL)?;
- ucode.extend_from_slice(code, GFP_KERNEL)?;
- ucode.resize(aligned_code_size, 0, GFP_KERNEL)?;
-
- ucode
- };
+ let bootloader = GenericBootloader::new(dev, chipset)?;
// `BootloaderDmemDescV2` expects the source to be a mirror image of the destination and
// uses the same offset parameter for both.
@@ -211,21 +179,11 @@ pub(crate) fn new(
}
};
- // 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)?;
-
- let start_tag = u16::try_from(tlv.get_u32(b"STRT")?)?;
-
Ok(Self {
_firmware_dma: firmware_dma,
- ucode,
+ bootloader,
dmem_desc,
brom_params: firmware.brom_params(),
- imem_dst_start: u16::try_from(imem_dst_start)?,
- start_tag,
})
}
@@ -274,7 +232,7 @@ fn brom_params(&self) -> FalconBromParams {
fn boot_addr(&self) -> u32 {
// On V2 platforms, the boot address is extracted from the generic bootloader, because the
// gbl is what actually copies FWSEC into memory, so that is what needs to be booted.
- u32::from(self.start_tag) << 8
+ self.bootloader.boot_addr()
}
}
@@ -284,12 +242,7 @@ fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
}
fn imem_ns_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
- Some(FalconPioImemLoadTarget {
- data: self.ucode.as_ref(),
- dst_start: self.imem_dst_start,
- secure: false,
- start_tag: self.start_tag,
- })
+ Some(self.bootloader.imem_load_params())
}
fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> {
diff --git a/drivers/gpu/nova-core/firmware/gen_bootloader.rs b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
new file mode 100644
index 000000000000..b1ed6c2ccf7d
--- /dev/null
+++ b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! The generic falcon bootloader: a small program that the driver loads into a falcon's IMEM by
+//! PIO. It reads a descriptor that the driver writes at DMEM offset 0, and loads the image that
+//! the descriptor names into IMEM and DMEM by DMA.
+
+use kernel::{
+ device,
+ prelude::*,
+ ptr::Alignable,
+ sizes, //
+};
+
+use crate::{
+ falcon::{
+ self,
+ FalconPioImemLoadTarget, //
+ },
+ firmware::tlv::{
+ request_tlv, //
+ Tlv,
+ },
+ gpu::Chipset,
+ num::FromSafeCast, //
+};
+
+/// The generic falcon bootloader image and its IMEM load parameters.
+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: u16,
+ /// 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.
+ ///
+ /// # 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<device::Bound>, chipset: Chipset) -> Result<Self> {
+ let fw = request_tlv(dev, chipset, "gen_bootloader")?;
+ let tlv = Tlv::new(fw.data())?;
+ dev_dbg!(
+ dev,
+ "loaded generic bootloader firmware v{}\n",
+ tlv.get_string(b"VERS")?
+ );
+
+ let ucode = {
+ let blob = tlv.get_bytes(b"BLOB")?;
+ let code_size = usize::from_safe_cast(tlv.get_u32(b"CDSZ")?);
+ let code = blob.get(..code_size).ok_or(EINVAL)?;
+ let aligned_code_size = code_size
+ .align_up(cv!(falcon::MEM_BLOCK_ALIGNMENT))
+ .ok_or(EINVAL)?;
+
+ let mut ucode = KVec::with_capacity(aligned_code_size, GFP_KERNEL)?;
+ ucode.extend_from_slice(code, GFP_KERNEL)?;
+ ucode.resize(aligned_code_size, 0, GFP_KERNEL)?;
+
+ 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: u16::try_from(imem_dst_start)?,
+ start_tag: u16::try_from(tlv.get_u32(b"STRT")?)?,
+ })
+ }
+
+ /// Returns the address at which the falcon starts executing the bootloader: the IMEM virtual
+ /// address of its first code block.
+ pub(crate) fn boot_addr(&self) -> u32 {
+ u32::from(self.start_tag) << 8
+ }
+
+ /// Returns the PIO parameters that place this bootloader in non-secure IMEM.
+ pub(crate) fn imem_load_params(&self) -> FalconPioImemLoadTarget<'_> {
+ FalconPioImemLoadTarget {
+ data: self.ucode.as_ref(),
+ dst_start: self.imem_dst_start,
+ secure: false,
+ start_tag: self.start_tag,
+ }
+ }
+}
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 2/7] gpu: nova-core: place the generic bootloader at the top of IMEM
2026-11-03 10:22 [PATCH 0/7] gpu: nova-core: generic bootloader and falcon DMA changes for r000 Alexandre Courbot
2026-11-03 10:22 ` [PATCH 1/7] gpu: nova-core: separate the generic falcon bootloader from FWSEC Alexandre Courbot
@ 2026-11-03 10:22 ` Alexandre Courbot
2026-11-03 10:22 ` [PATCH 3/7] gpu: nova-core: move the bootloader DMEM descriptor out of FWSEC Alexandre Courbot
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Alexandre Courbot @ 2026-11-03 10:22 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Timur Tabi, Eliot Courtney,
Zhi Wang, nova-gpu, rust-for-linux, linux-kernel,
Alexandre Courbot
From: John Hubbard <jhubbard@nvidia.com>
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.
Assisted-by: LLM
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Reviewed-by: Timur Tabi <ttabi@nvidia.com>
Reviewed-by: Zhi Wang <zhiw@nvidia.com>
[acourbot: split out of the patch separating the bootloader from FWSEC;
convert the IMEM size with usize::from()]
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 7 +++++++
drivers/gpu/nova-core/firmware/fwsec/bootloader.rs | 3 ++-
drivers/gpu/nova-core/firmware/gen_bootloader.rs | 23 +++++++++++++---------
drivers/gpu/nova-core/gsp/hal/tu102.rs | 6 ++++--
drivers/gpu/nova-core/regs.rs | 5 +++++
5 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 0b377bd6524a..1bceb38c33c6 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -382,6 +382,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/fwsec/bootloader.rs b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
index 59f57714fcfb..3943948d74bc 100644
--- a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
@@ -108,8 +108,9 @@ pub(crate) fn new(
firmware: FwsecFirmware,
dev: &'a Device<device::Bound>,
chipset: Chipset,
+ falcon: &Falcon<'_, Gsp>,
) -> Result<Self> {
- let bootloader = GenericBootloader::new(dev, chipset)?;
+ let bootloader = GenericBootloader::new(dev, chipset, falcon)?;
// `BootloaderDmemDescV2` expects the source to be a mirror image of the destination and
// uses the same offset parameter for both.
diff --git a/drivers/gpu/nova-core/firmware/gen_bootloader.rs b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
index b1ed6c2ccf7d..d51948c6a2b8 100644
--- a/drivers/gpu/nova-core/firmware/gen_bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
@@ -8,13 +8,14 @@
use kernel::{
device,
prelude::*,
- ptr::Alignable,
- sizes, //
+ ptr::Alignable, //
};
use crate::{
falcon::{
self,
+ gsp::Gsp,
+ Falcon,
FalconPioImemLoadTarget, //
},
firmware::tlv::{
@@ -36,15 +37,19 @@ pub(crate) struct GenericBootloader {
}
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`, placed in the last blocks of an IMEM of
+ /// `imem_size` bytes.
///
/// # Errors
///
/// - `EINVAL` if a required TLV field is absent.
- /// - `EOVERFLOW` if the image does not fit below 64 KiB.
+ /// - `EOVERFLOW` if the image does not fit in IMEM.
/// - `ENOMEM` if the padded copy of the code cannot be allocated.
- pub(crate) fn new(dev: &device::Device<device::Bound>, chipset: Chipset) -> Result<Self> {
+ pub(crate) fn new(
+ dev: &device::Device<device::Bound>,
+ chipset: Chipset,
+ falcon: &Falcon<'_, Gsp>,
+ ) -> Result<Self> {
let fw = request_tlv(dev, chipset, "gen_bootloader")?;
let tlv = Tlv::new(fw.data())?;
dev_dbg!(
@@ -68,9 +73,9 @@ pub(crate) fn new(dev: &device::Device<device::Bound>, chipset: Chipset) -> Resu
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
+ // The top of IMEM, above the blocks that the bootloader loads the image into.
+ let imem_dst_start = falcon
+ .imem_size()
.checked_sub(ucode.len())
.ok_or(EOVERFLOW)?;
diff --git a/drivers/gpu/nova-core/gsp/hal/tu102.rs b/drivers/gpu/nova-core/gsp/hal/tu102.rs
index e90db1a23032..1b17ece53bd6 100644
--- a/drivers/gpu/nova-core/gsp/hal/tu102.rs
+++ b/drivers/gpu/nova-core/gsp/hal/tu102.rs
@@ -165,7 +165,7 @@ fn run_fwsec_frts(
)?;
if self.needs_fwsec_bootloader {
- let fwsec_frts_bl = FwsecFirmwareWithBl::new(fwsec_frts, dev, chipset)?;
+ let fwsec_frts_bl = FwsecFirmwareWithBl::new(fwsec_frts, dev, chipset, falcon)?;
// Load and run the bootloader, which will load FWSEC-FRTS and run it.
fwsec_frts_bl.run(dev, falcon)?;
} else {
@@ -223,7 +223,9 @@ fn build_unload_bundle<'gpu>(
// Load the FWSEC SB firmware, as well as its bootloader if required.
let fwsec_sb = FwsecFirmware::new(dev, gsp_falcon, bios, FwsecCommand::Sb)?;
let fwsec_sb = if self.needs_fwsec_bootloader {
- FwsecUnloadFirmware::WithBl(FwsecFirmwareWithBl::new(fwsec_sb, dev, chipset)?)
+ FwsecUnloadFirmware::WithBl(FwsecFirmwareWithBl::new(
+ fwsec_sb, dev, chipset, gsp_falcon,
+ )?)
} else {
FwsecUnloadFirmware::WithoutBl(fwsec_sb)
};
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 88662da634dc..b7070282d692 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
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 3/7] gpu: nova-core: move the bootloader DMEM descriptor out of FWSEC
2026-11-03 10:22 [PATCH 0/7] gpu: nova-core: generic bootloader and falcon DMA changes for r000 Alexandre Courbot
2026-11-03 10:22 ` [PATCH 1/7] gpu: nova-core: separate the generic falcon bootloader from FWSEC Alexandre Courbot
2026-11-03 10:22 ` [PATCH 2/7] gpu: nova-core: place the generic bootloader at the top of IMEM Alexandre Courbot
@ 2026-11-03 10:22 ` Alexandre Courbot
2026-11-03 10:22 ` [PATCH 4/7] gpu: nova-core: run the generic bootloader from its own type Alexandre Courbot
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Alexandre Courbot @ 2026-11-03 10:22 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Timur Tabi, Eliot Courtney,
Zhi Wang, nova-gpu, rust-for-linux, linux-kernel,
Alexandre Courbot
From: John Hubbard <jhubbard@nvidia.com>
The generic falcon bootloader reads its load parameters from a
descriptor that the driver places in DMEM. The r000 load-and-execute
bootloader event carries such a descriptor, so its handler will need the
descriptor type.
The descriptor was defined in the FWSEC code, which was its only user.
Move the definition, unchanged, into the generic bootloader module.
No functional changes.
Assisted-by: LLM
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
[acourbot: keep the fields private, add a constructor and the ctx_dma()
accessor instead of making every field pub(crate)]
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/firmware/fwsec/bootloader.rs | 95 +++++-----------------
drivers/gpu/nova-core/firmware/gen_bootloader.rs | 94 ++++++++++++++++++++-
2 files changed, 112 insertions(+), 77 deletions(-)
diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
index 3943948d74bc..ad798ec5645a 100644
--- a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
@@ -35,60 +35,16 @@
},
firmware::{
fwsec::FwsecFirmware,
- gen_bootloader::GenericBootloader, //
+ gen_bootloader::{
+ BootloaderDmemDescV2,
+ GenericBootloader, //
+ },
},
gpu::Chipset,
num::FromSafeCast, //
regs,
};
-/// Structure used by the boot-loader to load the rest of the code.
-///
-/// This has to be filled by the GPU driver and copied into DMEM at offset
-/// [`BootloaderDesc.dmem_load_off`].
-#[repr(C, packed)]
-#[derive(Debug, Clone)]
-struct BootloaderDmemDescV2 {
- /// Reserved, should always be first element.
- reserved: [u32; 4],
- /// 16B signature for secure code, 0s if no secure code.
- signature: [u32; 4],
- /// DMA context used by the bootloader while loading code/data.
- ctx_dma: u32,
- /// 256B-aligned physical FB address where code is located.
- code_dma_base: u64,
- /// Offset from `code_dma_base` where the non-secure code is located.
- ///
- /// Also used as destination IMEM offset of non-secure code as the DMA firmware object is
- /// expected to be a mirror image of its loaded state.
- ///
- /// Must be multiple of 256.
- non_sec_code_off: u32,
- /// Size of the non-secure code part.
- non_sec_code_size: u32,
- /// Offset from `code_dma_base` where the secure code is located (must be multiple of 256).
- ///
- /// Also used as destination IMEM offset of secure code as the DMA firmware object is expected
- /// to be a mirror image of its loaded state.
- ///
- /// Must be multiple of 256.
- sec_code_off: u32,
- /// Size of the secure code part.
- sec_code_size: u32,
- /// Code entry point invoked by the bootloader after code is loaded.
- code_entry_point: u32,
- /// 256B-aligned physical FB address where data is located.
- data_dma_base: u64,
- /// Size of data block (should be multiple of 256B).
- data_size: u32,
- /// Number of arguments to be passed to the target firmware being loaded.
- argc: u32,
- /// Arguments to be passed to the target firmware being loaded.
- argv: u32,
-}
-// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
-unsafe impl AsBytes for BootloaderDmemDescV2 {}
-
/// Wrapper for [`FwsecFirmware`] that includes the bootloader performing the actual load
/// operation.
pub(crate) struct FwsecFirmwareWithBl<'a> {
@@ -140,9 +96,6 @@ pub(crate) fn new(
};
let dmem_desc = {
- // Bootloader payload is in non-coherent system memory.
- const FALCON_DMAIDX_PHYS_SYS_NCOH: u32 = 4;
-
let imem_sec = firmware.imem_sec_load_params();
let imem_ns = firmware.imem_ns_load_params().ok_or(EINVAL)?;
let dmem = firmware.dmem_load_params();
@@ -154,30 +107,20 @@ pub(crate) fn new(
return Err(EINVAL);
}
- BootloaderDmemDescV2 {
- reserved: [0; 4],
- signature: [0; 4],
- ctx_dma: FALCON_DMAIDX_PHYS_SYS_NCOH,
- code_dma_base: firmware_dma.dma_address(),
- // `dst_start` is also valid as the source offset since the firmware DMA object is
- // a mirror image of the target IMEM layout.
- non_sec_code_off: imem_ns.dst_start,
- non_sec_code_size: imem_ns.len,
- // `dst_start` is also valid as the source offset since the firmware DMA object is
- // a mirror image of the target IMEM layout.
- sec_code_off: imem_sec.dst_start,
- sec_code_size: imem_sec.len,
- code_entry_point: 0,
- // Start of data section is the added padding + the DMEM `src_start` field.
- data_dma_base: firmware_dma
- .dma_address()
- .checked_add(u64::from_safe_cast(align_padding))
- .and_then(|offset| offset.checked_add(dmem.src_start.into()))
- .ok_or(EOVERFLOW)?,
- data_size: dmem.len,
- argc: 0,
- argv: 0,
- }
+ // Start of data section is the added padding + the DMEM `src_start` field.
+ let data_dma_base = firmware_dma
+ .dma_address()
+ .checked_add(u64::from_safe_cast(align_padding))
+ .and_then(|offset| offset.checked_add(dmem.src_start.into()))
+ .ok_or(EOVERFLOW)?;
+
+ BootloaderDmemDescV2::new(
+ &imem_ns,
+ &imem_sec,
+ &dmem,
+ firmware_dma.dma_address(),
+ data_dma_base,
+ )
};
Ok(Self {
@@ -203,7 +146,7 @@ pub(crate) fn run(&self, dev: &Device<device::Bound>, falcon: &Falcon<'_, Gsp>)
// 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(self.dmem_desc.ctx_dma))
+ regs::NV_PFALCON_FBIF_TRANSCFG::try_at(usize::from_safe_cast(self.dmem_desc.ctx_dma()))
.ok_or(EINVAL)?,
|v| {
v.with_target(FalconFbifTarget::CoherentSysmem)
diff --git a/drivers/gpu/nova-core/firmware/gen_bootloader.rs b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
index d51948c6a2b8..a3de545731f5 100644
--- a/drivers/gpu/nova-core/firmware/gen_bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
@@ -8,7 +8,8 @@
use kernel::{
device,
prelude::*,
- ptr::Alignable, //
+ ptr::Alignable,
+ transmute::AsBytes, //
};
use crate::{
@@ -16,6 +17,7 @@
self,
gsp::Gsp,
Falcon,
+ FalconDmaLoadTarget,
FalconPioImemLoadTarget, //
},
firmware::tlv::{
@@ -26,6 +28,96 @@
num::FromSafeCast, //
};
+/// Structure used by the boot-loader to load the rest of the code.
+///
+/// This has to be filled by the GPU driver and copied into DMEM at offset
+/// [`BootloaderDesc.dmem_load_off`].
+#[repr(C, packed)]
+#[derive(Debug, Clone)]
+pub(crate) struct BootloaderDmemDescV2 {
+ /// Reserved, should always be first element.
+ reserved: [u32; 4],
+ /// 16B signature for secure code, 0s if no secure code.
+ signature: [u32; 4],
+ /// DMA context used by the bootloader while loading code/data.
+ ctx_dma: u32,
+ /// 256B-aligned physical FB address where code is located.
+ code_dma_base: u64,
+ /// Offset from `code_dma_base` where the non-secure code is located.
+ ///
+ /// Also used as destination IMEM offset of non-secure code as the DMA firmware object is
+ /// expected to be a mirror image of its loaded state.
+ ///
+ /// Must be multiple of 256.
+ non_sec_code_off: u32,
+ /// Size of the non-secure code part.
+ non_sec_code_size: u32,
+ /// Offset from `code_dma_base` where the secure code is located (must be multiple of 256).
+ ///
+ /// Also used as destination IMEM offset of secure code as the DMA firmware object is expected
+ /// to be a mirror image of its loaded state.
+ ///
+ /// Must be multiple of 256.
+ sec_code_off: u32,
+ /// Size of the secure code part.
+ sec_code_size: u32,
+ /// Code entry point invoked by the bootloader after code is loaded.
+ code_entry_point: u32,
+ /// 256B-aligned physical FB address where data is located.
+ data_dma_base: u64,
+ /// Size of data block (should be multiple of 256B).
+ data_size: u32,
+ /// Number of arguments to be passed to the target firmware being loaded.
+ argc: u32,
+ /// Arguments to be passed to the target firmware being loaded.
+ argv: u32,
+}
+
+impl BootloaderDmemDescV2 {
+ /// Context DMA slot through which the bootloader reads the image: physical, non-coherent
+ /// system memory.
+ const FALCON_DMAIDX_PHYS_SYS_NCOH: u32 = 4;
+
+ /// Creates the descriptor of an image whose code is at `code_dma_base` and whose data is at
+ /// `data_dma_base`.
+ ///
+ /// `imem_ns` and `imem_sec` locate the non-secure and secure code in the image, and `dmem`
+ /// gives the size of its data. The code DMA object must be a mirror image of the target IMEM
+ /// layout, because the bootloader uses the destination offset of each code segment as its
+ /// source offset too.
+ pub(crate) fn new(
+ imem_ns: &FalconDmaLoadTarget,
+ imem_sec: &FalconDmaLoadTarget,
+ dmem: &FalconDmaLoadTarget,
+ code_dma_base: u64,
+ data_dma_base: u64,
+ ) -> Self {
+ Self {
+ reserved: [0; 4],
+ signature: [0; 4],
+ ctx_dma: Self::FALCON_DMAIDX_PHYS_SYS_NCOH,
+ code_dma_base,
+ non_sec_code_off: imem_ns.dst_start,
+ non_sec_code_size: imem_ns.len,
+ sec_code_off: imem_sec.dst_start,
+ sec_code_size: imem_sec.len,
+ code_entry_point: 0,
+ data_dma_base,
+ data_size: dmem.len,
+ argc: 0,
+ argv: 0,
+ }
+ }
+
+ /// Returns the context DMA slot through which the bootloader reads the image.
+ pub(crate) fn ctx_dma(&self) -> u32 {
+ self.ctx_dma
+ }
+}
+
+// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
+unsafe impl AsBytes for BootloaderDmemDescV2 {}
+
/// The generic falcon bootloader image and its IMEM load parameters.
pub(crate) struct GenericBootloader {
/// Bootloader code, zero-padded to a whole number of falcon memory blocks.
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 4/7] gpu: nova-core: run the generic bootloader from its own type
2026-11-03 10:22 [PATCH 0/7] gpu: nova-core: generic bootloader and falcon DMA changes for r000 Alexandre Courbot
` (2 preceding siblings ...)
2026-11-03 10:22 ` [PATCH 3/7] gpu: nova-core: move the bootloader DMEM descriptor out of FWSEC Alexandre Courbot
@ 2026-11-03 10:22 ` Alexandre Courbot
2026-11-03 10:22 ` [PATCH 5/7] gpu: nova-core: falcon: allow DMEM DMA transfers to request tagging Alexandre Courbot
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Alexandre Courbot @ 2026-11-03 10:22 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Timur Tabi, Eliot Courtney,
Zhi Wang, nova-gpu, rust-for-linux, linux-kernel,
Alexandre Courbot
From: John Hubbard <jhubbard@nvidia.com>
The generic falcon bootloader needs a descriptor in DMEM to know what to
load, so loading it into a falcon means loading the two together by PIO
and starting the falcon. The FWSEC wrapper did that through its own
implementation of the PIO loading traits. The r000 load-and-execute
bootloader event will need to do the same thing.
Give the generic bootloader a run method, which pairs it with the
descriptor that it receives, loads the pair by PIO, starts the falcon
with the given MAILBOX0 value and returns the mailboxes once the falcon
has halted. This will allow the FWSEC and r000 load-and-execute
bootloader event to share the same code.
No functional changes.
Assisted-by: LLM
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Co-developed-by: Alexandre Courbot <acourbot@nvidia.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/firmware/fwsec/bootloader.rs | 55 ++-------------
drivers/gpu/nova-core/firmware/gen_bootloader.rs | 80 +++++++++++++++++++++-
2 files changed, 84 insertions(+), 51 deletions(-)
diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
index ad798ec5645a..c7bb877fcf20 100644
--- a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
@@ -16,22 +16,16 @@
register::Array,
Io, //
},
- prelude::*,
- transmute::AsBytes,
+ prelude::*, //
};
use crate::{
falcon::{
gsp::Gsp,
Falcon,
- FalconBromParams,
FalconDmaLoadable,
FalconFbifMemType,
- FalconFbifTarget,
- FalconFirmware,
- FalconPioDmemLoadTarget,
- FalconPioImemLoadTarget,
- FalconPioLoadable, //
+ FalconFbifTarget, //
},
firmware::{
fwsec::FwsecFirmware,
@@ -50,11 +44,10 @@
pub(crate) struct FwsecFirmwareWithBl<'a> {
/// DMA object the bootloader will copy the firmware from.
_firmware_dma: Coherent<'a, [u8]>,
+ /// The bootloader that copies the firmware.
bootloader: GenericBootloader,
/// Descriptor to be loaded into DMEM for the bootloader to read.
dmem_desc: BootloaderDmemDescV2,
- /// BROM parameters of the loaded firmware.
- brom_params: FalconBromParams,
}
impl<'a> FwsecFirmwareWithBl<'a> {
@@ -127,7 +120,6 @@ pub(crate) fn new(
_firmware_dma: firmware_dma,
bootloader,
dmem_desc,
- brom_params: firmware.brom_params(),
})
}
@@ -136,13 +128,9 @@ pub(crate) fn new(
/// The bootloader will load the FWSEC firmware and then execute it. This function returns
/// after FWSEC has reached completion.
pub(crate) fn run(&self, dev: &Device<device::Bound>, falcon: &Falcon<'_, Gsp>) -> Result<()> {
- // Reset falcon, load the firmware, and run it.
falcon
.reset()
.inspect_err(|e| dev_err!(dev, "Failed to reset GSP falcon: {:?}\n", e))?;
- falcon
- .pio_load(self)
- .inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?;
// Configure DMA index for the bootloader to fetch the FWSEC firmware from system memory.
falcon.pfalcon.update(
@@ -154,8 +142,10 @@ pub(crate) fn run(&self, dev: &Device<device::Bound>, falcon: &Falcon<'_, Gsp>)
},
);
- let (mbox0, _) = falcon
- .boot(Some(0), None)
+ // Run the bootloader, which loads the FWSEC firmware and runs it.
+ let (mbox0, _) = self
+ .bootloader
+ .run(falcon, &self.dmem_desc, 0)
.inspect_err(|e| dev_err!(dev, "Failed to boot FWSEC firmware: {:?}\n", e))?;
if mbox0 != 0 {
dev_err!(dev, "FWSEC firmware returned error {}\n", mbox0);
@@ -165,34 +155,3 @@ pub(crate) fn run(&self, dev: &Device<device::Bound>, falcon: &Falcon<'_, Gsp>)
}
}
}
-
-impl FalconFirmware for FwsecFirmwareWithBl<'_> {
- type Target = Gsp;
-
- fn brom_params(&self) -> FalconBromParams {
- self.brom_params.clone()
- }
-
- fn boot_addr(&self) -> u32 {
- // On V2 platforms, the boot address is extracted from the generic bootloader, because the
- // gbl is what actually copies FWSEC into memory, so that is what needs to be booted.
- self.bootloader.boot_addr()
- }
-}
-
-impl FalconPioLoadable for FwsecFirmwareWithBl<'_> {
- fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
- None
- }
-
- fn imem_ns_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
- Some(self.bootloader.imem_load_params())
- }
-
- fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> {
- FalconPioDmemLoadTarget {
- data: self.dmem_desc.as_bytes(),
- dst_start: 0,
- }
- }
-}
diff --git a/drivers/gpu/nova-core/firmware/gen_bootloader.rs b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
index a3de545731f5..fe820ac6b49a 100644
--- a/drivers/gpu/nova-core/firmware/gen_bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
@@ -17,8 +17,12 @@
self,
gsp::Gsp,
Falcon,
+ FalconBromParams,
FalconDmaLoadTarget,
- FalconPioImemLoadTarget, //
+ FalconFirmware,
+ FalconPioDmemLoadTarget,
+ FalconPioImemLoadTarget,
+ FalconPioLoadable, //
},
firmware::tlv::{
request_tlv, //
@@ -180,12 +184,12 @@ pub(crate) fn new(
/// Returns the address at which the falcon starts executing the bootloader: the IMEM virtual
/// address of its first code block.
- pub(crate) fn boot_addr(&self) -> u32 {
+ fn boot_addr(&self) -> u32 {
u32::from(self.start_tag) << 8
}
/// Returns the PIO parameters that place this bootloader in non-secure IMEM.
- pub(crate) fn imem_load_params(&self) -> FalconPioImemLoadTarget<'_> {
+ fn imem_load_params(&self) -> FalconPioImemLoadTarget<'_> {
FalconPioImemLoadTarget {
data: self.ucode.as_ref(),
dst_start: self.imem_dst_start,
@@ -193,4 +197,74 @@ pub(crate) fn imem_load_params(&self) -> FalconPioImemLoadTarget<'_> {
start_tag: self.start_tag,
}
}
+
+ /// 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 caller resets `falcon` beforehand, and programs the FBIF aperture of the context DMA
+ /// slot that the descriptor names with the location of the image.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if the falcon has not halted within two seconds of starting.
+ ///
+ /// Errors from the PIO load are propagated as-is.
+ pub(crate) fn run(
+ &self,
+ falcon: &Falcon<'_, Gsp>,
+ dmem_desc: &BootloaderDmemDescV2,
+ mbox0: u32,
+ ) -> Result<(u32, u32)> {
+ falcon.pio_load(&GenericBootloaderLoad {
+ bootloader: self,
+ dmem_desc,
+ })?;
+
+ falcon.boot(Some(mbox0), None)
+ }
+}
+
+/// The generic bootloader together with the descriptor that it reads from DMEM offset 0, as one
+/// firmware that [`Falcon::pio_load`] accepts.
+struct GenericBootloaderLoad<'a> {
+ bootloader: &'a GenericBootloader,
+ dmem_desc: &'a BootloaderDmemDescV2,
+}
+
+impl FalconFirmware for GenericBootloaderLoad<'_> {
+ type Target = Gsp;
+
+ fn brom_params(&self) -> FalconBromParams {
+ // The bootloader is unsigned, and the falcon HALs of the chipsets that load it this way
+ // write no BROM registers, so these values never reach hardware.
+ FalconBromParams {
+ pkc_data_offset: 0,
+ engine_id_mask: 0,
+ ucode_id: 0,
+ }
+ }
+
+ fn boot_addr(&self) -> u32 {
+ self.bootloader.boot_addr()
+ }
+}
+
+impl FalconPioLoadable for GenericBootloaderLoad<'_> {
+ fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
+ None
+ }
+
+ fn imem_ns_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
+ Some(self.bootloader.imem_load_params())
+ }
+
+ fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> {
+ FalconPioDmemLoadTarget {
+ data: self.dmem_desc.as_bytes(),
+ dst_start: 0,
+ }
+ }
}
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 5/7] gpu: nova-core: falcon: allow DMEM DMA transfers to request tagging
2026-11-03 10:22 [PATCH 0/7] gpu: nova-core: generic bootloader and falcon DMA changes for r000 Alexandre Courbot
` (3 preceding siblings ...)
2026-11-03 10:22 ` [PATCH 4/7] gpu: nova-core: run the generic bootloader from its own type Alexandre Courbot
@ 2026-11-03 10:22 ` Alexandre Courbot
2026-11-03 10:22 ` [PATCH 6/7] gpu: nova-core: falcon: allow DMA transfers to read from a framebuffer address Alexandre Courbot
2026-11-03 10:22 ` [PATCH 7/7] gpu: nova-core: falcon: check the per-block DMA offsets for overflow Alexandre Courbot
6 siblings, 0 replies; 8+ messages in thread
From: Alexandre Courbot @ 2026-11-03 10:22 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Timur Tabi, Eliot Courtney,
Zhi Wang, nova-gpu, rust-for-linux, linux-kernel,
Alexandre Courbot
Add a tag flag to FalconMem::Dmem and set DMATRFCMD's DMTAG bit from it.
This is needed for the r000 load-and-execute event which specifies
whether DMEM should be tagged or not.
No functional change intended.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 20 +++++++++++++-------
drivers/gpu/nova-core/regs.rs | 2 +-
2 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 1bceb38c33c6..f48f2eabab0f 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -131,8 +131,9 @@ pub(crate) enum FalconMem {
/// Non-Secure Instruction Memory.
#[expect(unused)]
ImemNonSecure,
- /// Data Memory.
- Dmem,
+ /// Data Memory. If `tag` is set, a transfer into DMEM also tags each block with its source
+ /// offset, which is then the DMEM virtual address of the image.
+ Dmem { tag: bool },
}
bounded_enum! {
@@ -529,9 +530,9 @@ fn dma_wr(
// code addresses in the firmware (and the boot vector) are virtual.
//
// For DMEM, the start offset is folded into the DMA address.
- let (src_start, dma_start) = match target_mem {
- FalconMem::ImemSecure | FalconMem::ImemNonSecure => (load_offsets.src_start, 0),
- FalconMem::Dmem => (0, usize::from_safe_cast(load_offsets.src_start)),
+ let (src_start, dma_start, tag_dmem) = match target_mem {
+ FalconMem::ImemSecure | FalconMem::ImemNonSecure => (load_offsets.src_start, 0, false),
+ FalconMem::Dmem { tag } => (0, usize::from_safe_cast(load_offsets.src_start), tag),
};
let dma_address = {
@@ -568,7 +569,8 @@ fn dma_wr(
let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::zeroed()
.with_size(DmaTrfCmdSize::Size256B)
- .with_falcon_mem(target_mem);
+ .with_falcon_mem(target_mem)
+ .with_set_dmtag(tag_dmem);
for pos in (0..num_transfers).map(|i| i * DMA_LEN) {
// Perform a transfer of size `DMA_LEN`.
@@ -625,7 +627,11 @@ fn dma_load<F: FalconFirmware<Target = E> + FalconDmaLoadable>(&self, fw: &F) ->
});
self.dma_wr(&dma_obj, FalconMem::ImemSecure, fw.imem_sec_load_params())?;
- self.dma_wr(&dma_obj, FalconMem::Dmem, fw.dmem_load_params())?;
+ self.dma_wr(
+ &dma_obj,
+ FalconMem::Dmem { tag: false },
+ fw.dmem_load_params(),
+ )?;
self.hal.program_brom(self, &fw.brom_params());
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index b7070282d692..cba080ccaab2 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -323,7 +323,7 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
impl NV_PFALCON_FALCON_DMATRFCMD {
/// Programs the `imem` and `sec` fields for the given FalconMem
pub(crate) fn with_falcon_mem(self, mem: FalconMem) -> Self {
- let this = self.with_imem(mem != FalconMem::Dmem);
+ let this = self.with_imem(!matches!(mem, FalconMem::Dmem { .. }));
match mem {
FalconMem::ImemSecure => this.with_const_sec::<1>(),
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 6/7] gpu: nova-core: falcon: allow DMA transfers to read from a framebuffer address
2026-11-03 10:22 [PATCH 0/7] gpu: nova-core: generic bootloader and falcon DMA changes for r000 Alexandre Courbot
` (4 preceding siblings ...)
2026-11-03 10:22 ` [PATCH 5/7] gpu: nova-core: falcon: allow DMEM DMA transfers to request tagging Alexandre Courbot
@ 2026-11-03 10:22 ` Alexandre Courbot
2026-11-03 10:22 ` [PATCH 7/7] gpu: nova-core: falcon: check the per-block DMA offsets for overflow Alexandre Courbot
6 siblings, 0 replies; 8+ messages in thread
From: Alexandre Courbot @ 2026-11-03 10:22 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Timur Tabi, Eliot Courtney,
Zhi Wang, nova-gpu, rust-for-linux, linux-kernel,
Alexandre Courbot
The r000 boot protocol has a load-and-execute event in which GSP-RM asks
the driver to load a Heavy-Secured binary from framebuffer memory into
the GSP falcon's memory by DMA in order to run it.
Nova-core's falcon DMA loading method currently only supports loading
from a DMA object that the driver had allocated, so there is no way to
copy an image from framebuffer memory that an event supplied.
Address this by making the source of a falcon DMA transfer an enum,
which can be either a DMA object, or a GPU framebuffer address.
The framebuffer address source has no user yet, but will be used by the
r000 boot protocol.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 65 +++++++++++++++++++++++++++++------------
1 file changed, 46 insertions(+), 19 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index f48f2eabab0f..fb43d0ed2821 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -136,6 +136,15 @@ pub(crate) enum FalconMem {
Dmem { tag: bool },
}
+/// Where a falcon DMA transfer reads from.
+pub(crate) enum FalconDmaSource<'a> {
+ /// A DMA object in system memory.
+ Sysmem(&'a Coherent<'a, [u8]>),
+ /// GPU framebuffer memory at physical address `base`.
+ #[expect(dead_code)]
+ Framebuffer { base: u64 },
+}
+
bounded_enum! {
/// Defines the Framebuffer Interface (FBIF) aperture type.
/// This determines the memory type for external memory access during a DMA transfer, which is
@@ -510,13 +519,11 @@ pub(crate) fn pio_load<F: FalconFirmware<Target = E> + FalconPioLoadable>(
Ok(())
}
- /// Perform a DMA write according to `load_offsets` from `dma_obj` into the falcon's
+ /// Perform a DMA write according to `load_offsets` from `src` into the falcon's
/// `target_mem`.
- ///
- /// `sec` is set if the loaded firmware is expected to run in secure mode.
fn dma_wr(
&self,
- dma_obj: &Coherent<'_, [u8]>,
+ src: FalconDmaSource<'_>,
target_mem: FalconMem,
load_offsets: FalconDmaLoadTarget,
) -> Result {
@@ -529,21 +536,37 @@ fn dma_wr(
// For IMEM, we want to use the start offset as a virtual address tag for each page, since
// code addresses in the firmware (and the boot vector) are virtual.
//
- // For DMEM, the start offset is folded into the DMA address.
- let (src_start, dma_start, tag_dmem) = match target_mem {
- FalconMem::ImemSecure | FalconMem::ImemNonSecure => (load_offsets.src_start, 0, false),
- FalconMem::Dmem { tag } => (0, usize::from_safe_cast(load_offsets.src_start), tag),
- };
+ // For DMEM in a DMA object, the start offset is folded into the DMA address. For DMEM at a
+ // framebuffer address, the DMA engine adds it to the address.
+ let (src_start, dma_address, tag_dmem) = match src {
+ FalconDmaSource::Sysmem(dma_obj) => {
+ let (src_start, dma_start, tag_dmem) = match target_mem {
+ FalconMem::ImemSecure | FalconMem::ImemNonSecure => {
+ (load_offsets.src_start, 0, false)
+ }
+ FalconMem::Dmem { tag } => {
+ (0, usize::from_safe_cast(load_offsets.src_start), tag)
+ }
+ };
- let dma_address = {
- // Upper limit of transfer is `(num_transfers * DMA_LEN) + load_offsets.src_start`.
- let dma_end = num_transfers
- .checked_mul(DMA_LEN)
- .and_then(|size| size.checked_add(load_offsets.src_start))
- .map(usize::from_safe_cast)
- .ok_or(EOVERFLOW)?;
+ // Upper limit of transfer is `(num_transfers * DMA_LEN) + load_offsets.src_start`.
+ let dma_end = num_transfers
+ .checked_mul(DMA_LEN)
+ .and_then(|size| size.checked_add(load_offsets.src_start))
+ .map(usize::from_safe_cast)
+ .ok_or(EOVERFLOW)?;
- io_project!(dma_obj, [try: dma_start..dma_end]).dma_address()
+ (
+ src_start,
+ io_project!(dma_obj, [try: dma_start..dma_end]).dma_address(),
+ tag_dmem,
+ )
+ }
+ FalconDmaSource::Framebuffer { base } => (
+ load_offsets.src_start,
+ base,
+ matches!(target_mem, FalconMem::Dmem { tag: true }),
+ ),
};
if dma_address % DmaAddress::from(DMA_LEN) > 0 {
@@ -626,9 +649,13 @@ fn dma_load<F: FalconFirmware<Target = E> + FalconDmaLoadable>(&self, fw: &F) ->
.with_mem_type(FalconFbifMemType::Physical)
});
- self.dma_wr(&dma_obj, FalconMem::ImemSecure, fw.imem_sec_load_params())?;
self.dma_wr(
- &dma_obj,
+ FalconDmaSource::Sysmem(&dma_obj),
+ FalconMem::ImemSecure,
+ fw.imem_sec_load_params(),
+ )?;
+ self.dma_wr(
+ FalconDmaSource::Sysmem(&dma_obj),
FalconMem::Dmem { tag: false },
fw.dmem_load_params(),
)?;
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 7/7] gpu: nova-core: falcon: check the per-block DMA offsets for overflow
2026-11-03 10:22 [PATCH 0/7] gpu: nova-core: generic bootloader and falcon DMA changes for r000 Alexandre Courbot
` (5 preceding siblings ...)
2026-11-03 10:22 ` [PATCH 6/7] gpu: nova-core: falcon: allow DMA transfers to read from a framebuffer address Alexandre Courbot
@ 2026-11-03 10:22 ` Alexandre Courbot
6 siblings, 0 replies; 8+ messages in thread
From: Alexandre Courbot @ 2026-11-03 10:22 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Timur Tabi, Eliot Courtney,
Zhi Wang, nova-gpu, rust-for-linux, linux-kernel,
Alexandre Courbot
From: John Hubbard <jhubbard@nvidia.com>
Each block of a falcon DMA transfer is addressed by the start offset of
the transfer plus the block's position. The two additions were plain, so
a source or destination offset near the top of the 32-bit range would
have wrapped or panicked rather than failing the transfer.
Add the two with checked arithmetic and fail the transfer with EOVERFLOW
when a sum does not fit.
Assisted-by: LLM
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
[acourbot: split out of the DMA helper patch of v3.]
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index fb43d0ed2821..c54875e55da5 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -599,10 +599,11 @@ fn dma_wr(
// Perform a transfer of size `DMA_LEN`.
self.pfalcon.write_reg(
regs::NV_PFALCON_FALCON_DMATRFMOFFS::zeroed()
- .try_with_offs(load_offsets.dst_start + pos)?,
+ .try_with_offs(load_offsets.dst_start.checked_add(pos).ok_or(EOVERFLOW)?)?,
);
self.pfalcon.write_reg(
- regs::NV_PFALCON_FALCON_DMATRFFBOFFS::zeroed().with_offs(src_start + pos),
+ regs::NV_PFALCON_FALCON_DMATRFFBOFFS::zeroed()
+ .with_offs(src_start.checked_add(pos).ok_or(EOVERFLOW)?),
);
self.pfalcon.write_reg(cmd);
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread