On Thu Dec 18, 2025 at 12:29 PM JST, Timur Tabi wrote:
<snip>
> + fn pio_wr<F: FalconFirmware<Target = E>>(
> + &self,
> + bar: &Bar0,
> + fw: &F,
> + target_mem: FalconMem,
> + load_offsets: &FalconLoadTarget,
> + port: u8,
> + tag: u16,
> + ) -> Result {
> + let start = usize::from_safe_cast(load_offsets.src_start);
> + let len = usize::from_safe_cast(load_offsets.len);
> + let mem_base = u16::try_from(load_offsets.dst_start)?;
> +
> + // SAFETY: as_slice() ensures that start+len is within range
That's not the safety concern - check the documentation for `as_slice`.
We need to ensure that there won't be any concurrent access to the DMA
object. Since we are the only user of it at this stage, that's a
guarantee we can indeed provide.
> + let data = unsafe { fw.as_slice(start, len).map_err(|_| EINVAL)? };
> +
> + self.pio_wr_bytes(bar, data, mem_base, target_mem, port, tag)
> + }
> +
> + /// Perform a PIO copy into `IMEM` and `DMEM` of `fw`, and prepare the
> falcon to run it.
> + pub(crate) fn pio_load<F: FalconFirmware<Target = E>>(
> + &self,
> + bar: &Bar0,
> + fw: &F,
> + gbl: Option<&GenericBootloader>,
> + ) -> Result {
> + let imem_sec = fw.imem_sec_load_params();
> + let imem_ns = fw.imem_ns_load_params().ok_or(EINVAL)?;
> + let dmem = fw.dmem_load_params();
> +
> + regs::NV_PFALCON_FBIF_CTL::read(bar, &E::ID)
> + .set_allow_phys_no_ctx(true)
> + .write(bar, &E::ID);
> +
> + regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, &E::ID);
> +
> + // If the Generic Bootloader was passed, then use it to boot FRTS
> + if let Some(gbl) = gbl {
> + let dst_start = u16::try_from(0x10000 - gbl.desc.code_size)?;
> + let data =
> &gbl.ucode[..usize::from_safe_cast(gbl.desc.code_size)];
> + let tag = u16::try_from(gbl.desc.start_tag)?;
> +
> + self.pio_wr_bytes(bar, data, dst_start,
> FalconMem::ImemNonSecure, 0, tag)?;
> +
> + // This structure tells the generic bootloader where to find the
> FWSEC
> + // image.
> + let dmem_desc = BootloaderDmemDescV2 {
> + reserved: [0; 4],
> + signature: [0; 4],
> + ctx_dma: 4, // FALCON_DMAIDX_PHYS_SYS_NCOH
> + code_dma_base: fw.dma_handle(),
> + 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: fw.dma_handle() + u64::from(dmem.src_start),
> + data_size: dmem.len,
> + argc: 0,
> + argv: 0,
> + };
> +
> + regs::NV_PFALCON_FBIF_TRANSCFG::update(bar, &E::ID, 4, |v| {
> + v.set_target(FalconFbifTarget::CoherentSysmem)
> + .set_mem_type(FalconFbifMemType::Physical)
> + });
> +
> + self.pio_wr_bytes(bar, dmem_desc.as_bytes(), 0, FalconMem::Dmem,
> 0, 0)?;
So this `if` branch is really special-casing the generic bootloader. But
at the end of the day it just does these things:
- Write an `ImemNonSecure` section,
- Write an `Dmem` section,
- Program the `TRANSCFG` register so the bootloader can initiate the DMA
transfer.
The first two steps can be expressed as a set of `FalconLoadTarget`s.
That way they can be handled by the non-generic-bootloader path, and we
can remove the `gbl` argument.
So `FwsecFirmware` could have an optional member that contains both the
generic bootloader and the `BootloaderDmemDescV2` corresponding to it.
If that optional member is `Some`, then it returns the `FalconLoadTarget`s
corresponding to the generic bootloader. Otherwise, it behaves as
before.
Interestingly there is no `ImemSecure` section to write so I guess we
will have to make `imem_sec_load_params` return an `Option` as well.
And `NV_PFALCON_FBIF_TRANSCFG` is always programmed as the worst thing
that can happen is that we don't use the DMA engine if there is no
generic bootloader.
> + } else {
> + self.pio_wr(
> + bar,
> + fw,
> + FalconMem::ImemNonSecure,
> + &imem_ns,
> + 0,
> + u16::try_from(imem_ns.dst_start >> 8)?,
> + )?;
> + self.pio_wr(
> + bar,
> + fw,
> + FalconMem::ImemSecure,
> + &imem_sec,
> + 0,
> + u16::try_from(imem_sec.dst_start >> 8)?,
> + )?;
> + self.pio_wr(bar, fw, FalconMem::Dmem, &dmem, 0, 0)?;
> + }
> +
> + self.hal.program_brom(self, bar, &fw.brom_params())?;
> +
> + // Set `BootVec` to start of non-secure code.
> + regs::NV_PFALCON_FALCON_BOOTVEC::default()
> + .set_value(fw.boot_addr())
> + .write(bar, &E::ID);
> +
> + Ok(())
> + }
> +
> /// Perform a DMA write according to `load_offsets` from `dma_handle`
> into the falcon's
> /// `target_mem`.
> ///
> diff --git a/drivers/gpu/nova-core/firmware.rs
> b/drivers/gpu/nova-core/firmware.rs
> index 44897feb82a4..26efbf4f6760 100644
> --- a/drivers/gpu/nova-core/firmware.rs
> +++ b/drivers/gpu/nova-core/firmware.rs
> @@ -31,7 +31,7 @@
> pub(crate) const FIRMWARE_VERSION: &str = "570.144";
>
> /// Requests the GPU firmware `name` suitable for `chipset`, with version
> `ver`.
> -fn request_firmware(
> +pub(crate) fn request_firmware(
There is no need to change the visibility of this function.
> dev: &device::Device,
> chipset: gpu::Chipset,
> name: &str,
> @@ -321,7 +321,7 @@ fn no_patch_signature(self) -> FirmwareDmaObject<F,
> Signed> {
> /// Header common to most firmware files.
> #[repr(C)]
> #[derive(Debug, Clone)]
> -struct BinHdr {
> +pub(crate) struct BinHdr {
Same for this type.
> /// Magic number, must be `0x10de`.
> bin_magic: u32,
> /// Version of the header.
> diff --git a/drivers/gpu/nova-core/firmware/fwsec.rs
> b/drivers/gpu/nova-core/firmware/fwsec.rs
> index 1c1dcdacf5f5..4c26257bbfe0 100644
> --- a/drivers/gpu/nova-core/firmware/fwsec.rs
> +++ b/drivers/gpu/nova-core/firmware/fwsec.rs
> @@ -40,12 +40,15 @@
> FalconLoadTarget, //
> },
> firmware::{
> + BinHdr,
> FalconUCodeDesc,
> FirmwareDmaObject,
> FirmwareSignature,
> Signed,
> Unsigned, //
> + FIRMWARE_VERSION,
> },
> + gpu::Chipset,
> num::{
> FromSafeCast,
> IntoSafeCast, //
> @@ -213,6 +216,72 @@ unsafe fn transmute_mut<T: Sized + FromBytes + AsBytes>(
> T::from_bytes_mut(unsafe { fw.as_slice_mut(offset, size_of::<T>())?
> }).ok_or(EINVAL)
> }
>
> +/// Descriptor used by RM to figure out the requirements of the boot loader.
> +#[repr(C)]
> +#[derive(Debug, Clone)]
> +pub(crate) struct BootloaderDesc {
> + /// Starting tag of bootloader.
> + pub start_tag: u32,
> + /// DMEM offset where [`BootloaderDmemDescV2`] is to be loaded.
> + pub dmem_load_off: u32,
> + /// Offset of code section in the image.
> + pub code_off: u32,
> + /// Size of code section in the image.
> + pub code_size: u32,
> + /// Offset of data section in the image.
> + pub data_off: u32,
> + /// Size of data section in the image.
> + pub data_size: u32,
> +}
> +// SAFETY: any byte sequence is valid for this struct.
> +unsafe impl FromBytes for BootloaderDesc {}
> +// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have
> interior mutability.
> +unsafe impl AsBytes for BootloaderDesc {}
We only need to implement `FromBytes` for this type, `AsBytes` is not
needed.
> +
> +/// 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.
> + pub reserved: [u32; 4],
> + /// 16B signature for secure code, 0s if no secure code.
> + pub signature: [u32; 4],
> + /// DMA context used by the bootloader while loading code/data.
> + pub ctx_dma: u32,
> + /// 256B-aligned physical FB address where code is located.
> + pub code_dma_base: u64,
> + /// Offset from `code_dma_base` where the non-secure code is located
> (must be multiple of 256).
> + pub non_sec_code_off: u32,
> + /// Size of the non-secure code part.
> + pub non_sec_code_size: u32,
> + /// Offset from `code_dma_base` where the secure code is located (must
> be multiple of 256).
> + pub sec_code_off: u32,
> + /// Size of the secure code part.
> + pub sec_code_size: u32,
> + /// Code entry point invoked by the bootloader after code is loaded.
> + pub code_entry_point: u32,
> + /// 256B-aligned physical FB address where data is located.
> + pub data_dma_base: u64,
> + /// Size of data block (should be multiple of 256B).
> + pub data_size: u32,
> + /// Arguments to be passed to the target firmware being loaded.
> + pub argc: u32,
> + /// Number of arguments to be passed to the target firmware being loaded.
> + pub argv: u32,
> +}
> +// SAFETY: any byte sequence is valid for this struct.
> +unsafe impl FromBytes for BootloaderDmemDescV2 {}
> +// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have
> interior mutability.
> +unsafe impl AsBytes for BootloaderDmemDescV2 {}
Here we can do without `FromBytes`.
> +
> +pub(crate) struct GenericBootloader {
> + pub desc: BootloaderDesc,
> + pub ucode: Vec<u8, kernel::alloc::allocator::Kmalloc>,
> +}
> +
> /// The FWSEC microcode, extracted from the BIOS and to be run on the GSP
> falcon.
> ///
> /// It is responsible for e.g. carving out the WPR2 region as the first step
> of the GSP bootflow.
> @@ -221,6 +290,8 @@ pub(crate) struct FwsecFirmware {
> desc: FalconUCodeDesc,
> /// GPU-accessible DMA object containing the firmware.
> ucode: FirmwareDmaObject<Self, Signed>,
> + /// Generic bootloader
> + gen_bootloader: Option<GenericBootloader>,
> }
>
> impl FalconLoadParams for FwsecFirmware {
> @@ -275,7 +346,19 @@ fn brom_params(&self) -> FalconBromParams {
> }
>
> fn boot_addr(&self) -> u32 {
> - 0
> + match &self.desc {
> + FalconUCodeDesc::V2(_v2) => {
> + // 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.
> + if let Some(ref gbl) = self.gen_bootloader {
> + gbl.desc.start_tag << 8
> + } else {
> + 0
> + }
> + }
> + FalconUCodeDesc::V3(_v3) => 0,
> + }
> }
> }
>
> @@ -376,6 +459,7 @@ impl FwsecFirmware {
> /// command.
> pub(crate) fn new(
> dev: &Device<device::Bound>,
> + chipset: Chipset,
> falcon: &Falcon<Gsp>,
> bar: &Bar0,
> bios: &Vbios,
> @@ -432,9 +516,49 @@ pub(crate) fn new(
> ucode_dma.no_patch_signature()
> };
>
> + // The Generic Bootloader exists only on Turing and GA100. To avoid
> a bogus
> + // console error message on other platforms, only try to load it if
> it's
> + // supposed to be there.
> + let gbl_fw = if chipset < Chipset::GA102 {
> + super::request_firmware(dev, chipset, "gen_bootloader",
> FIRMWARE_VERSION)
> + } else {
> + Err(ENOENT)
> + };
Using `Err` to indicate no firmware means that we will proceed even if
`request_firmware` returns an error. This should be:
let gbl_fw = if chipset < Chipset::GA102 {
Some(super::request_firmware(dev, chipset, "gen_bootloader",
FIRMWARE_VERSION)?)
} else {
None
};
> +
> + let gbl = match gbl_fw {
> + Ok(fw) => {
> + let hdr = fw
> + .data()
> + .get(0..size_of::<BinHdr>())
> + .and_then(BinHdr::from_bytes_copy)
> + .ok_or(EINVAL)?;
> +
> + let desc_offset = usize::from_safe_cast(hdr.header_offset);
> + let desc = fw
> + .data()
> + .get(desc_offset..desc_offset +
> size_of::<BootloaderDesc>())
> + .and_then(BootloaderDesc::from_bytes_copy)
> + .ok_or(EINVAL)?;
> +
> + let ucode_start = usize::from_safe_cast(hdr.data_offset);
> + let ucode_size = usize::from_safe_cast(hdr.data_size);
> + let ucode_data = fw
> + .data()
> + .get(ucode_start..ucode_start + ucode_size)
> + .ok_or(EINVAL)?;
> +
> + let mut ucode = KVec::new();
> + ucode.extend_from_slice(ucode_data, GFP_KERNEL)?;
> +
> + Some(GenericBootloader { desc, ucode })
> + }
> + Err(_) => None,
> + };
> +
Actually, let's put that code into a new `GenBootloader` type. You can
follow the example of `BooterFirmware`, which is quite similar (only a
bit more complex).