From: John Hubbard <jhubbard@nvidia.com>
To: Danilo Krummrich <dakr@kernel.org>,
Alexandre Courbot <acourbot@nvidia.com>
Cc: "Timur Tabi" <ttabi@nvidia.com>,
"Alistair Popple" <apopple@nvidia.com>,
"Eliot Courtney" <ecourtney@nvidia.com>,
"Zhi Wang" <zhiw@nvidia.com>, "David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Bjorn Helgaas" <bhelgaas@google.com>,
"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>,
nova-gpu@lists.linux.dev, LKML <linux-kernel@vger.kernel.org>,
"John Hubbard" <jhubbard@nvidia.com>
Subject: [PATCH 20/27] gpu: nova-core: handle the r000 load-and-execute bootloader event
Date: Tue, 18 Aug 2026 20:52:13 -0700 [thread overview]
Message-ID: <20260819035221.336390-21-jhubbard@nvidia.com> (raw)
In-Reply-To: <20260819035221.336390-1-jhubbard@nvidia.com>
The r000 GSP boot protocol asks the driver to run an image on the GSP
falcon, and passes a bootloader descriptor naming where the image lives.
Open RM does not copy the image itself. It runs the generic falcon
bootloader, which reads that descriptor from DMEM and does the copy.
Nova-core has no handler for that event, and keeps its copy of the
descriptor in the FWSEC module that first needed one.
Move the descriptor next to the bootloader that reads it, and add the
handler. It points the requested FBIF aperture at the image, loads the
descriptor and the bootloader, runs it, and restores the aperture.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
.../nova-core/firmware/fwsec/bootloader.rs | 52 +-----
.../gpu/nova-core/firmware/gen_bootloader.rs | 115 ++++++++++++-
drivers/gpu/nova-core/gsp/boot.rs | 161 +++++++++++++++++-
3 files changed, 278 insertions(+), 50 deletions(-)
diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
index 6670d17b4eeb..43908eb11f7d 100644
--- a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
@@ -33,60 +33,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 {
diff --git a/drivers/gpu/nova-core/firmware/gen_bootloader.rs b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
index f949223af2d0..f0a6c841d2aa 100644
--- a/drivers/gpu/nova-core/firmware/gen_bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
@@ -13,14 +13,23 @@
Alignable,
Alignment, //
},
+ transmute::{
+ AsBytes,
+ FromBytes, //
+ },
};
use crate::{
falcon::{
self,
+ gsp::Gsp,
Falcon,
+ FalconBromParams,
FalconEngine,
- FalconPioImemLoadTarget, //
+ FalconFirmware,
+ FalconPioDmemLoadTarget,
+ FalconPioImemLoadTarget,
+ FalconPioLoadable, //
},
firmware::tlv::{
request_tlv, //
@@ -30,6 +39,57 @@
num::FromSafeCast, //
};
+/// Structure the generic bootloader reads from DMEM offset 0 to find the image it must load.
+///
+/// Mirrors Open RM's `RM_FLCN_BL_DMEM_DESC`. The driver fills one in when it loads a firmware
+/// through the bootloader, and GSP-RM sends one in a load-and-execute event.
+#[repr(C, packed)]
+#[derive(Debug, Clone)]
+pub(crate) struct BootloaderDmemDescV2 {
+ /// Reserved, should always be first element.
+ pub(crate) reserved: [u32; 4],
+ /// 16B signature for secure code, 0s if no secure code.
+ pub(crate) signature: [u32; 4],
+ /// DMA context used by the bootloader while loading code/data.
+ pub(crate) ctx_dma: u32,
+ /// 256B-aligned physical FB address where code is located.
+ pub(crate) 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.
+ pub(crate) non_sec_code_off: u32,
+ /// Size of the non-secure code part.
+ pub(crate) 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.
+ pub(crate) sec_code_off: u32,
+ /// Size of the secure code part.
+ pub(crate) sec_code_size: u32,
+ /// Code entry point invoked by the bootloader after code is loaded.
+ pub(crate) code_entry_point: u32,
+ /// 256B-aligned physical FB address where data is located.
+ pub(crate) data_dma_base: u64,
+ /// Size of data block (should be multiple of 256B).
+ pub(crate) data_size: u32,
+ /// Number of arguments to be passed to the target firmware being loaded.
+ pub(crate) argc: u32,
+ /// Arguments to be passed to the target firmware being loaded.
+ pub(crate) argv: u32,
+}
+
+// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
+unsafe impl AsBytes for BootloaderDmemDescV2 {}
+
+// SAFETY: This struct only contains integer types for which all bit patterns are valid.
+unsafe impl FromBytes for BootloaderDmemDescV2 {}
+
/// The generic falcon bootloader image and the IMEM placement it was loaded for.
pub(crate) struct GenericBootloader {
/// Bootloader code, zero-padded to a whole number of falcon memory blocks.
@@ -99,4 +159,57 @@ pub(crate) fn imem_load_params(&self) -> FalconPioImemLoadTarget<'_> {
start_tag: self.start_tag,
}
}
+
+ /// Pairs this bootloader with the descriptor of the image it is to load, giving something
+ /// [`Falcon::pio_load`] accepts.
+ pub(crate) fn with_descriptor<'a>(
+ &'a self,
+ dmem_desc: &'a BootloaderDmemDescV2,
+ ) -> GenericBootloaderLoad<'a> {
+ GenericBootloaderLoad {
+ bootloader: self,
+ dmem_desc,
+ }
+ }
+}
+
+/// The generic bootloader together with the descriptor it reads from DMEM offset 0.
+pub(crate) 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 not signed. Every chipset that loads it this way uses a falcon HAL
+ // whose BROM programming is a no-op, so these values are never written to 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,
+ }
+ }
}
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index ae2aca5d935a..d62a5c834ccf 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -27,7 +27,13 @@
FalconMem,
FalconModSelAlgo, //
},
- firmware::gsp::GspFirmware,
+ firmware::{
+ gen_bootloader::{
+ BootloaderDmemDescV2,
+ GenericBootloader, //
+ },
+ gsp::GspFirmware,
+ },
gsp::{
cmdq::Cmdq,
commands, //
@@ -156,6 +162,101 @@ fn core_resume(
Ok(())
}
+ /// Handle a `GSP_LOAD_EXEC_GENERIC_BOOTLOADER` event.
+ ///
+ /// The driver does not copy the image the GSP asks for. It writes the descriptor the event
+ /// carries to DMEM offset 0, places the generic bootloader in IMEM, points the requested FBIF
+ /// aperture at wherever the image lives, and runs the bootloader, which does the copy from
+ /// the descriptor and jumps to the image. The aperture is restored afterwards.
+ ///
+ /// # Errors
+ ///
+ /// - `EINVAL` if the payload is shorter than the parameter block, the descriptor is not the
+ /// size this driver mirrors, or the event names a context DMA slot or an aperture that does
+ /// not exist.
+ /// - `ETIMEDOUT` if the GSP does not suspend, or the image does not halt, in time.
+ #[expect(dead_code)]
+ #[allow(clippy::too_many_arguments)]
+ fn handle_load_exec_bootloader(
+ payload: &[u8],
+ bootloader: &GenericBootloader,
+ gsp_falcon: &Falcon<'_, Gsp>,
+ sec2_falcon: &Falcon<'_, Sec2>,
+ bar: Bar0<'_>,
+ dev: &device::Device,
+ bootloader_app_version: u32,
+ libos_dma_handle: u64,
+ ) -> Result {
+ let params = LoadExecGenericBootloaderParams::from_bytes_prefix(payload)
+ .ok_or(EINVAL)?
+ .0;
+
+ let desc_size =
+ u32::try_from(core::mem::size_of::<BootloaderDmemDescV2>()).map_err(|_| EOVERFLOW)?;
+ if params.dmem_desc_size != desc_size {
+ dev_err!(
+ dev,
+ "Load-exec descriptor is {} bytes, expected {}\n",
+ params.dmem_desc_size,
+ desc_size
+ );
+ return Err(EINVAL);
+ }
+
+ let ctx_dma = params.ctx_dma()?;
+ let fbif_target = params.fbif_target()?;
+ let transcfg = || {
+ regs::NV_PFALCON_FBIF_TRANSCFG::of::<Gsp>()
+ .try_at(usize::from(ctx_dma))
+ .ok_or(EINVAL)
+ };
+
+ gsp_falcon.wait_for_processor_suspend().inspect_err(|_| {
+ dev_err!(
+ dev,
+ "Timeout waiting for GSP suspend (mbox0={:#x})\n",
+ gsp_falcon.read_mailbox0()
+ );
+ })?;
+
+ gsp_falcon.reset()?;
+ gsp_falcon.dma_reset();
+
+ let saved_transcfg = bar.read(transcfg()?);
+ bar.update(transcfg()?, |v| {
+ v.with_target(fbif_target)
+ .with_mem_type(FalconFbifMemType::Physical)
+ });
+
+ let run = (|| -> Result {
+ gsp_falcon.pio_load(&bootloader.with_descriptor(¶ms.dmem_desc))?;
+
+ // Also clears the suspend bit that `wait_for_processor_suspend` polls, so the next
+ // load-and-execute event does not read this one's suspension.
+ gsp_falcon.write_mailboxes(Some(FLCN_ERR_BINARY_NOT_STARTED), None);
+
+ gsp_falcon.start()?;
+ gsp_falcon.wait_till_halted().inspect_err(|_| {
+ dev_err!(
+ dev,
+ "Timeout waiting for the loaded image to halt (mbox0={:#x})\n",
+ gsp_falcon.read_mailbox0()
+ );
+ })
+ })();
+
+ bar.update(transcfg()?, |_| saved_transcfg);
+ run?;
+
+ Self::core_resume(
+ gsp_falcon,
+ sec2_falcon,
+ dev,
+ bootloader_app_version,
+ libos_dma_handle,
+ )
+ }
+
/// Handle a `GSP_LOAD_EXEC_HS_BINARY` event.
///
/// The GSP asks the driver to run a high-security binary that it has already placed in the
@@ -347,6 +448,64 @@ pub(crate) fn unload(
/// points it at local framebuffer.
const HS_BINARY_CTX_DMA: u8 = 0;
+/// Number of FBIF context DMA slots a falcon has.
+const NUM_CTX_DMA: usize = 8;
+
+/// Parameters for loading and executing the generic bootloader.
+///
+/// Sent by GSP-RM as the payload of `GSP_LOAD_EXEC_GENERIC_BOOTLOADER`. The descriptor carries
+/// the code and data addresses, while `addr_space` and `cpu_cache_attrib` say which FBIF aperture
+/// reaches them.
+#[repr(C)]
+struct LoadExecGenericBootloaderParams {
+ dmem_desc: BootloaderDmemDescV2,
+ dmem_desc_size: u32,
+ addr_space: u32,
+ cpu_cache_attrib: u32,
+ _reserved: [u32; 4],
+}
+
+impl LoadExecGenericBootloaderParams {
+ const ADDR_SYSMEM: u32 = 1;
+ const ADDR_FBMEM: u32 = 2;
+ const NV_MEMORY_CACHED: u32 = 0;
+ const NV_MEMORY_UNCACHED: u32 = 1;
+
+ /// Returns the context DMA slot the bootloader is to fetch the image through.
+ ///
+ /// # Errors
+ ///
+ /// - `EINVAL` if the slot is outside the FBIF `TRANSCFG` array.
+ fn ctx_dma(&self) -> Result<u8> {
+ let ctx_dma = self.dmem_desc.ctx_dma;
+
+ u8::try_from(ctx_dma)
+ .ok()
+ .filter(|slot| usize::from(*slot) < NUM_CTX_DMA)
+ .ok_or(EINVAL)
+ }
+
+ /// Returns the FBIF aperture that reaches the image.
+ ///
+ /// # Errors
+ ///
+ /// - `EINVAL` if the address space and cache attribute pair is not one this driver maps.
+ fn fbif_target(&self) -> Result<FalconFbifTarget> {
+ match (self.addr_space, self.cpu_cache_attrib) {
+ (Self::ADDR_FBMEM, _) => Ok(FalconFbifTarget::LocalFb),
+ (Self::ADDR_SYSMEM, Self::NV_MEMORY_CACHED) => Ok(FalconFbifTarget::CoherentSysmem),
+ (Self::ADDR_SYSMEM, Self::NV_MEMORY_UNCACHED) => {
+ Ok(FalconFbifTarget::NoncoherentSysmem)
+ }
+ _ => Err(EINVAL),
+ }
+ }
+}
+
+// SAFETY: The nested descriptor is `FromBytes`, and every other field is an integer type for
+// which all bit patterns are valid.
+unsafe impl FromBytes for LoadExecGenericBootloaderParams {}
+
/// Parameters for loading and executing an HS (High-Security) binary.
///
/// Sent by GSP-RM as the payload of `GSP_LOAD_EXEC_HS_BINARY`. The firmware
--
2.55.0
next prev parent reply other threads:[~2026-08-19 3:52 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
2026-08-19 3:51 ` [PATCH 01/27] gpu: nova-core: firmware: add r000 bindings John Hubbard
2026-08-19 3:51 ` [PATCH 02/27] gpu: nova-core: extract radix3 page table into its own module John Hubbard
2026-08-19 3:51 ` [PATCH 03/27] gpu: nova-core: set MCTP transport header version to 1 John Hubbard
2026-08-19 3:51 ` [PATCH 04/27] gpu: nova-core: add Falcon helpers for r000 LOAD_EXEC events John Hubbard
2026-08-19 3:51 ` [PATCH 05/27] gpu: nova-core: zero-pad radix3 page table levels to page boundary John Hubbard
2026-08-19 3:51 ` [PATCH 06/27] gpu: nova-core: distinguish async GSP RPC traffic in debug logs John Hubbard
2026-08-19 3:52 ` [PATCH 07/27] gpu: nova-core: add optional ucodes firmware loading John Hubbard
2026-08-19 3:52 ` [PATCH 08/27] gpu: nova-core: add LIBOS3 log buffers and state monitor buffer John Hubbard
2026-08-19 3:52 ` [PATCH 09/27] gpu: nova-core: add build ID headers to debugfs log buffer dumps John Hubbard
2026-08-19 3:52 ` [PATCH 10/27] gpu: nova-core: rename the FbRanges elf field to fw_image John Hubbard
2026-08-19 3:52 ` [PATCH 11/27] gpu: nova-core: regs: add msgq v2 BAR0 register declarations John Hubbard
2026-08-19 3:52 ` [PATCH 12/27] gpu: nova-core: gsp: add msgq v2 internals John Hubbard
2026-08-19 3:52 ` [PATCH 13/27] gpu: nova-core: generalize allocate_command() for variable headers John Hubbard
2026-08-19 3:52 ` [PATCH 14/27] gpu: nova-core: add GMC API message types John Hubbard
2026-08-19 3:52 ` [PATCH 15/27] gpu: nova-core: add GMC send path John Hubbard
2026-08-19 3:52 ` [PATCH 16/27] gpu: nova-core: add GMC transport receive path John Hubbard
2026-08-19 3:52 ` [PATCH 17/27] gpu: nova-core: gsp: add GMC dispatch on receive John Hubbard
2026-08-19 3:52 ` [PATCH 18/27] gpu: nova-core: separate the generic falcon bootloader from FWSEC John Hubbard
2026-08-19 3:52 ` [PATCH 19/27] gpu: nova-core: handle the r000 load-and-execute HS binary event John Hubbard
2026-08-19 3:52 ` John Hubbard [this message]
2026-08-19 3:52 ` [PATCH 21/27] gpu: nova-core: gsp: add the GMC boot event dispatcher John Hubbard
2026-08-19 3:52 ` [PATCH 22/27] gpu: nova-core: gsp: add the GSP_INIT request builder John Hubbard
2026-08-19 3:52 ` [PATCH 23/27] gpu: nova-core: gsp: send GSP_INIT and decode its reply John Hubbard
2026-08-19 3:52 ` [PATCH 24/27] gpu: nova-core: gsp: pass the remaining log buffers to GSP-RM John Hubbard
2026-08-19 3:52 ` [PATCH 25/27] gpu: nova-core: switch to the r000 GSP firmware John Hubbard
2026-08-19 3:52 ` [PATCH 26/27] gpu: nova-core: gsp: remove the retired system-info and static-info RPCs John Hubbard
2026-08-19 3:52 ` [PATCH 27/27] gpu: nova-core: firmware: delete the r570 bindings 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=20260819035221.336390-21-jhubbard@nvidia.com \
--to=jhubbard@nvidia.com \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=apopple@nvidia.com \
--cc=bhelgaas@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=dakr@kernel.org \
--cc=ecourtney@nvidia.com \
--cc=gary@garyguo.net \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=nova-gpu@lists.linux.dev \
--cc=ojeda@kernel.org \
--cc=simona@ffwll.ch \
--cc=tmgross@umich.edu \
--cc=ttabi@nvidia.com \
--cc=zhiw@nvidia.com \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.