Re: [PATCH 18/27] gpu: nova-core: separate the generic falcon bootloader from FWSEC

Zhi Wang <[email protected]>
Newsgroups dev.linux.lists.nova-gpu,org.kernel.vger.linux-kernel
Message-ID <20260820160254.3dbc9191@inno-dell>
On Tue, 18 Aug 2026 20:52:11 -0700
John Hubbard <[email protected]> wrote:

Looking good to me.

Reviewed-by: Zhi Wang <[email protected]>

> The r000 GSP boot protocol sends a load-and-execute event for the
> generic falcon bootloader.
> 
> Nova-core stored the image and its load parameters in the FWSEC
> firmware wrapper, which kept the r000 boot path from loading it
> independently.
> 
> Separate the bootloader image and load parameters from the FWSEC
> wrapper. Place the bootloader at the end of the target falcon's
> HWCFG-reported IMEM rather than below a fixed 64 KiB ceiling, so its
> placement remains within bounds on falcons with less IMEM.
> 
> Assisted-by: Cursor:claude-opus-5
> Signed-off-by: John Hubbard <[email protected]>
> ---
>  drivers/gpu/nova-core/falcon.rs               |  15 +++
>  drivers/gpu/nova-core/firmware.rs             |   1 +
>  .../nova-core/firmware/fwsec/bootloader.rs    |  64 ++---------
>  .../gpu/nova-core/firmware/gen_bootloader.rs  | 102
> ++++++++++++++++++ drivers/gpu/nova-core/gsp/hal/tu102.rs        |
> 6 +- drivers/gpu/nova-core/regs.rs                 |   5 +
>  6 files changed, 135 insertions(+), 58 deletions(-)
>  create mode 100644 drivers/gpu/nova-core/firmware/gen_bootloader.rs
> 
> diff --git a/drivers/gpu/nova-core/falcon.rs
> b/drivers/gpu/nova-core/falcon.rs index 20a288050c37..b8013d2eb582
> 100644 --- a/drivers/gpu/nova-core/falcon.rs
> +++ b/drivers/gpu/nova-core/falcon.rs
> @@ -387,6 +387,21 @@ pub(crate) fn new(
>          })
>      }
>  
> +    /// Returns the size of this falcon's IMEM, in bytes.
> +    ///
> +    /// `NV_PFALCON_FALCON_IMEMC` addresses IMEM with a 16-bit byte
> offset, so the result never
> +    /// exceeds 64KiB.
> +    pub(crate) fn imem_size(&self) -> usize {
> +        let blocks = usize::from_safe_cast(
> +            *self
> +                .bar
> +                .read(regs::NV_PFALCON_FALCON_HWCFG::of::<E>())
> +                .imem_size(),
> +        );
> +
> +        blocks * MEM_BLOCK_ALIGNMENT
> +    }
> +
>      /// Resets DMA-related registers.
>      pub(crate) fn dma_reset(&self) {
>          self.bar.update(regs::NV_PFALCON_FBIF_CTL::of::<E>(), |v| {
> diff --git a/drivers/gpu/nova-core/firmware.rs
> b/drivers/gpu/nova-core/firmware.rs index e0befe84aa3e..9ff764930bcd
> 100644 --- a/drivers/gpu/nova-core/firmware.rs
> +++ b/drivers/gpu/nova-core/firmware.rs
> @@ -26,6 +26,7 @@
>  pub(crate) mod booter;
>  pub(crate) mod fsp;
>  pub(crate) mod fwsec;
> +pub(crate) mod gen_bootloader;
>  pub(crate) mod gsp;
>  pub(crate) mod radix3;
>  pub(crate) mod riscv;
> diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
> b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs index
> ec4d92317a93..6670d17b4eeb 100644 ---
> a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs +++
> b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs @@ -14,18 +14,12
> @@ dma::Coherent,
>      io::{register::WithBase, Io},
>      prelude::*,
> -    ptr::{
> -        Alignable,
> -        Alignment, //
> -    },
> -    sizes,
>      transmute::AsBytes,
>  };
>  
>  use crate::{
>      driver::Bar0,
>      falcon::{
> -        self,
>          gsp::Gsp,
>          Falcon,
>          FalconBromParams,
> @@ -39,10 +33,7 @@
>      },
>      firmware::{
>          fwsec::FwsecFirmware,
> -        tlv::{
> -            request_tlv, //
> -            Tlv,
> -        },
> +        gen_bootloader::GenericBootloader, //
>      },
>      gpu::Chipset,
>      num::FromSafeCast, //
> @@ -101,16 +92,12 @@ unsafe impl AsBytes for BootloaderDmemDescV2 {}
>  pub(crate) struct FwsecFirmwareWithBl {
>      /// DMA object the bootloader will copy the firmware from.
>      _firmware_dma: Coherent<[u8]>,
> -    /// Code of the bootloader to be loaded into non-secure IMEM.
> -    ucode: KVec<u8>,
> +    /// Bootloader that performs the load.
> +    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 FwsecFirmwareWithBl {
> @@ -120,29 +107,9 @@ pub(crate) fn new(
>          firmware: FwsecFirmware,
>          dev: &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!(
> -            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(Alignment::new::<{
> 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,
> falcon)?; 
>          // `BootloaderDmemDescV2` expects the source to be a mirror
> image of the destination and // uses the same offset parameter for
> both. @@ -213,21 +180,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,
>          })
>      }
>  
> @@ -282,7 +239,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()
>      }
>  }
>  
> @@ -292,12 +249,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..f949223af2d0
> --- /dev/null
> +++ b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
> @@ -0,0 +1,102 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! The generic falcon bootloader.
> +//!
> +//! A small program loaded into falcon IMEM using PIO, which then
> DMAs a larger image into IMEM and +//! DMEM from a descriptor the
> driver leaves in DMEM at offset 0. Open RM's +//!
> `ksec2GetGenericBlUcode` supplies the same image for both SEC2 and
> GSP. + +use kernel::{
> +    device,
> +    prelude::*,
> +    ptr::{
> +        Alignable,
> +        Alignment, //
> +    },
> +};
> +
> +use crate::{
> +    falcon::{
> +        self,
> +        Falcon,
> +        FalconEngine,
> +        FalconPioImemLoadTarget, //
> +    },
> +    firmware::tlv::{
> +        request_tlv, //
> +        Tlv,
> +    },
> +    gpu::Chipset,
> +    num::FromSafeCast, //
> +};
> +
> +/// 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.
> +    ucode: KVec<u8>,
> +    /// Byte offset in IMEM the code is loaded at.
> +    imem_dst_start: u16,
> +    /// Tag the first code block is loaded under.
> +    start_tag: u16,
> +}
> +
> +impl GenericBootloader {
> +    /// Loads the generic bootloader image for `chipset`, placed in
> the last blocks of `falcon`'s
> +    /// IMEM so the image it goes on to load has the rest to itself.
> +    ///
> +    /// # Errors
> +    ///
> +    /// - `EINVAL` if a required TLV field is absent or the image
> does not fit in IMEM.
> +    /// - `ENOMEM` if the padded copy of the code cannot be
> allocated.
> +    pub(crate) fn new<E: FalconEngine + 'static>(
> +        dev: &device::Device<device::Bound>,
> +        chipset: Chipset,
> +        falcon: &Falcon<'_, E>,
> +    ) -> 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(Alignment::new::<{
> 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 imem_dst_start =
> falcon.imem_size().checked_sub(ucode.len()).ok_or(EINVAL)?; +
> +        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 the falcon must boot from to run this
> bootloader.
> +    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,
> +        }
> +    }
> +}
> diff --git a/drivers/gpu/nova-core/gsp/hal/tu102.rs
> b/drivers/gpu/nova-core/gsp/hal/tu102.rs index
> a5c0ca355493..68a48c882c0f 100644 ---
> a/drivers/gpu/nova-core/gsp/hal/tu102.rs +++
> b/drivers/gpu/nova-core/gsp/hal/tu102.rs @@ -166,7 +166,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, bar)?;
>          } else {
> @@ -224,7 +224,9 @@ fn build_unload_bundle(
>          // 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 5d265b5788e3..5501c36a56af
> 100644 --- a/drivers/gpu/nova-core/regs.rs
> +++ b/drivers/gpu/nova-core/regs.rs
> @@ -247,6 +247,11 @@ pub(crate) fn usable_fb_size(self) -> u64 {
>          31:0    value => u32;
>      }
>  
> +    pub(crate) NV_PFALCON_FALCON_HWCFG(u32) @ PFalconBase +
> 0x00000108 {
> +        /// Size of this falcon's IMEM, in blocks of
> [`crate::falcon::MEM_BLOCK_ALIGNMENT`] bytes.
> +        8:0     imem_size;
> +    }
> +
>      pub(crate) NV_PFALCON_FALCON_DMACTL(u32) @ PFalconBase +
> 0x0000010c { 7:7     secure_stat => bool;
>          6:3     dmaq_num;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.