summaryrefslogtreecommitdiff
path: root/drivers/gpu/nova-core/firmware
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/nova-core/firmware')
-rw-r--r--drivers/gpu/nova-core/firmware/booter.rs87
-rw-r--r--drivers/gpu/nova-core/firmware/fwsec.rs181
-rw-r--r--drivers/gpu/nova-core/firmware/fwsec/bootloader.rs350
-rw-r--r--drivers/gpu/nova-core/firmware/gsp.rs118
-rw-r--r--drivers/gpu/nova-core/firmware/riscv.rs10
5 files changed, 502 insertions, 244 deletions
diff --git a/drivers/gpu/nova-core/firmware/booter.rs b/drivers/gpu/nova-core/firmware/booter.rs
index 86556cee8e67..de2a4536b532 100644
--- a/drivers/gpu/nova-core/firmware/booter.rs
+++ b/drivers/gpu/nova-core/firmware/booter.rs
@@ -4,10 +4,7 @@
//! running on [`Sec2`], that is used on Turing/Ampere to load the GSP firmware into the GSP falcon
//! (and optionally unload it through a separate firmware image).
-use core::{
- marker::PhantomData,
- ops::Deref, //
-};
+use core::marker::PhantomData;
use kernel::{
device,
@@ -16,19 +13,18 @@ use kernel::{
};
use crate::{
- dma::DmaObject,
driver::Bar0,
falcon::{
sec2::Sec2,
Falcon,
FalconBromParams,
- FalconFirmware,
- FalconLoadParams,
- FalconLoadTarget, //
+ FalconDmaLoadTarget,
+ FalconDmaLoadable,
+ FalconFirmware, //
},
firmware::{
BinFirmware,
- FirmwareDmaObject,
+ FirmwareObject,
FirmwareSignature,
Signed,
Unsigned, //
@@ -43,8 +39,9 @@ use crate::{
/// Local convenience function to return a copy of `S` by reinterpreting the bytes starting at
/// `offset` in `slice`.
fn frombytes_at<S: FromBytes + Sized>(slice: &[u8], offset: usize) -> Result<S> {
+ let end = offset.checked_add(size_of::<S>()).ok_or(EINVAL)?;
slice
- .get(offset..offset + size_of::<S>())
+ .get(offset..end)
.and_then(S::from_bytes_copy)
.ok_or(EINVAL)
}
@@ -119,14 +116,21 @@ impl<'a> HsFirmwareV2<'a> {
Some(sig_size) => {
let patch_sig =
frombytes_at::<u32>(self.fw, self.hdr.patch_sig_offset.into_safe_cast())?;
- let signatures_start = usize::from_safe_cast(self.hdr.sig_prod_offset + patch_sig);
+
+ let signatures_start = self
+ .hdr
+ .sig_prod_offset
+ .checked_add(patch_sig)
+ .map(usize::from_safe_cast)
+ .ok_or(EINVAL)?;
+
+ let signatures_end = signatures_start
+ .checked_add(usize::from_safe_cast(self.hdr.sig_prod_size))
+ .ok_or(EINVAL)?;
self.fw
// Get signatures range.
- .get(
- signatures_start
- ..signatures_start + usize::from_safe_cast(self.hdr.sig_prod_size),
- )
+ .get(signatures_start..signatures_end)
.ok_or(EINVAL)?
.chunks_exact(sig_size.into_safe_cast())
}
@@ -252,21 +256,24 @@ impl<'a> FirmwareSignature<BooterFirmware> for BooterSignature<'a> {}
/// The `Booter` loader firmware, responsible for loading the GSP.
pub(crate) struct BooterFirmware {
// Load parameters for Secure `IMEM` falcon memory.
- imem_sec_load_target: FalconLoadTarget,
+ imem_sec_load_target: FalconDmaLoadTarget,
// Load parameters for Non-Secure `IMEM` falcon memory,
// used only on Turing and GA100
- imem_ns_load_target: Option<FalconLoadTarget>,
+ imem_ns_load_target: Option<FalconDmaLoadTarget>,
// Load parameters for `DMEM` falcon memory.
- dmem_load_target: FalconLoadTarget,
+ dmem_load_target: FalconDmaLoadTarget,
// BROM falcon parameters.
brom_params: FalconBromParams,
// Device-mapped firmware image.
- ucode: FirmwareDmaObject<Self, Signed>,
+ ucode: FirmwareObject<Self, Signed>,
}
-impl FirmwareDmaObject<BooterFirmware, Unsigned> {
- fn new_booter(dev: &device::Device<device::Bound>, data: &[u8]) -> Result<Self> {
- DmaObject::from_data(dev, data).map(|ucode| Self(ucode, PhantomData))
+impl FirmwareObject<BooterFirmware, Unsigned> {
+ fn new_booter(data: &[u8]) -> Result<Self> {
+ let mut ucode = KVVec::new();
+ ucode.extend_from_slice(data, GFP_KERNEL)?;
+
+ Ok(Self(ucode, PhantomData))
}
}
@@ -320,7 +327,7 @@ impl BooterFirmware {
let ucode = bin_fw
.data()
.ok_or(EINVAL)
- .and_then(|data| FirmwareDmaObject::<Self, _>::new_booter(dev, data))?;
+ .and_then(FirmwareObject::<Self, _>::new_booter)?;
let ucode_signed = {
let mut signatures = hs_fw.signatures_iter()?.peekable();
@@ -363,7 +370,7 @@ impl BooterFirmware {
let (imem_sec_dst_start, imem_ns_load_target) = if chipset <= Chipset::GA100 {
(
app0.offset,
- Some(FalconLoadTarget {
+ Some(FalconDmaLoadTarget {
src_start: 0,
dst_start: load_hdr.os_code_offset,
len: load_hdr.os_code_size,
@@ -374,13 +381,13 @@ impl BooterFirmware {
};
Ok(Self {
- imem_sec_load_target: FalconLoadTarget {
+ imem_sec_load_target: FalconDmaLoadTarget {
src_start: app0.offset,
dst_start: imem_sec_dst_start,
len: app0.len,
},
imem_ns_load_target,
- dmem_load_target: FalconLoadTarget {
+ dmem_load_target: FalconDmaLoadTarget {
src_start: load_hdr.os_data_offset,
dst_start: 0,
len: load_hdr.os_data_size,
@@ -391,18 +398,26 @@ impl BooterFirmware {
}
}
-impl FalconLoadParams for BooterFirmware {
- fn imem_sec_load_params(&self) -> FalconLoadTarget {
+impl FalconDmaLoadable for BooterFirmware {
+ fn as_slice(&self) -> &[u8] {
+ self.ucode.0.as_slice()
+ }
+
+ fn imem_sec_load_params(&self) -> FalconDmaLoadTarget {
self.imem_sec_load_target.clone()
}
- fn imem_ns_load_params(&self) -> Option<FalconLoadTarget> {
+ fn imem_ns_load_params(&self) -> Option<FalconDmaLoadTarget> {
self.imem_ns_load_target.clone()
}
- fn dmem_load_params(&self) -> FalconLoadTarget {
+ fn dmem_load_params(&self) -> FalconDmaLoadTarget {
self.dmem_load_target.clone()
}
+}
+
+impl FalconFirmware for BooterFirmware {
+ type Target = Sec2;
fn brom_params(&self) -> FalconBromParams {
self.brom_params.clone()
@@ -416,15 +431,3 @@ impl FalconLoadParams for BooterFirmware {
}
}
}
-
-impl Deref for BooterFirmware {
- type Target = DmaObject;
-
- fn deref(&self) -> &Self::Target {
- &self.ucode.0
- }
-}
-
-impl FalconFirmware for BooterFirmware {
- type Target = Sec2;
-}
diff --git a/drivers/gpu/nova-core/firmware/fwsec.rs b/drivers/gpu/nova-core/firmware/fwsec.rs
index bfb7b06b13d1..8810cb49db67 100644
--- a/drivers/gpu/nova-core/firmware/fwsec.rs
+++ b/drivers/gpu/nova-core/firmware/fwsec.rs
@@ -10,10 +10,9 @@
//! - The command to be run, as this firmware can perform several tasks ;
//! - The ucode signature, so the GSP falcon can run FWSEC in HS mode.
-use core::{
- marker::PhantomData,
- ops::Deref, //
-};
+pub(crate) mod bootloader;
+
+use core::marker::PhantomData;
use kernel::{
device::{
@@ -28,27 +27,23 @@ use kernel::{
};
use crate::{
- dma::DmaObject,
driver::Bar0,
falcon::{
gsp::Gsp,
Falcon,
FalconBromParams,
- FalconFirmware,
- FalconLoadParams,
- FalconLoadTarget, //
+ FalconDmaLoadTarget,
+ FalconDmaLoadable,
+ FalconFirmware, //
},
firmware::{
FalconUCodeDesc,
- FirmwareDmaObject,
+ FirmwareObject,
FirmwareSignature,
Signed,
Unsigned, //
},
- num::{
- FromSafeCast,
- IntoSafeCast, //
- },
+ num::FromSafeCast,
vbios::Vbios,
};
@@ -177,63 +172,36 @@ impl AsRef<[u8]> for Bcrt30Rsa3kSignature {
impl FirmwareSignature<FwsecFirmware> for Bcrt30Rsa3kSignature {}
-/// Reinterpret the area starting from `offset` in `fw` as an instance of `T` (which must implement
-/// [`FromBytes`]) and return a reference to it.
-///
-/// # Safety
-///
-/// * Callers must ensure that the device does not read/write to/from memory while the returned
-/// reference is live.
-/// * Callers must ensure that this call does not race with a write to the same region while
-/// the returned reference is live.
-unsafe fn transmute<T: Sized + FromBytes>(fw: &DmaObject, offset: usize) -> Result<&T> {
- // SAFETY: The safety requirements of the function guarantee the device won't read
- // or write to memory while the reference is alive and that this call won't race
- // with writes to the same memory region.
- T::from_bytes(unsafe { fw.as_slice(offset, size_of::<T>())? }).ok_or(EINVAL)
-}
-
-/// Reinterpret the area starting from `offset` in `fw` as a mutable instance of `T` (which must
-/// implement [`FromBytes`]) and return a reference to it.
-///
-/// # Safety
-///
-/// * Callers must ensure that the device does not read/write to/from memory while the returned
-/// slice is live.
-/// * Callers must ensure that this call does not race with a read or write to the same region
-/// while the returned slice is live.
-unsafe fn transmute_mut<T: Sized + FromBytes + AsBytes>(
- fw: &mut DmaObject,
- offset: usize,
-) -> Result<&mut T> {
- // SAFETY: The safety requirements of the function guarantee the device won't read
- // or write to memory while the reference is alive and that this call won't race
- // with writes or reads to the same memory region.
- T::from_bytes_mut(unsafe { fw.as_slice_mut(offset, size_of::<T>())? }).ok_or(EINVAL)
-}
-
/// 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.
pub(crate) struct FwsecFirmware {
/// Descriptor of the firmware.
desc: FalconUCodeDesc,
- /// GPU-accessible DMA object containing the firmware.
- ucode: FirmwareDmaObject<Self, Signed>,
+ /// Object containing the firmware binary.
+ ucode: FirmwareObject<Self, Signed>,
}
-impl FalconLoadParams for FwsecFirmware {
- fn imem_sec_load_params(&self) -> FalconLoadTarget {
+impl FalconDmaLoadable for FwsecFirmware {
+ fn as_slice(&self) -> &[u8] {
+ self.ucode.0.as_slice()
+ }
+
+ fn imem_sec_load_params(&self) -> FalconDmaLoadTarget {
self.desc.imem_sec_load_params()
}
- fn imem_ns_load_params(&self) -> Option<FalconLoadTarget> {
+ fn imem_ns_load_params(&self) -> Option<FalconDmaLoadTarget> {
self.desc.imem_ns_load_params()
}
- fn dmem_load_params(&self) -> FalconLoadTarget {
+ fn dmem_load_params(&self) -> FalconDmaLoadTarget {
self.desc.dmem_load_params()
}
+}
+
+impl FalconFirmware for FwsecFirmware {
+ type Target = Gsp;
fn brom_params(&self) -> FalconBromParams {
FalconBromParams {
@@ -248,27 +216,23 @@ impl FalconLoadParams for FwsecFirmware {
}
}
-impl Deref for FwsecFirmware {
- type Target = DmaObject;
-
- fn deref(&self) -> &Self::Target {
- &self.ucode.0
- }
-}
-
-impl FalconFirmware for FwsecFirmware {
- type Target = Gsp;
-}
-
-impl FirmwareDmaObject<FwsecFirmware, Unsigned> {
- fn new_fwsec(dev: &Device<device::Bound>, bios: &Vbios, cmd: FwsecCommand) -> Result<Self> {
+impl FirmwareObject<FwsecFirmware, Unsigned> {
+ fn new_fwsec(bios: &Vbios, cmd: FwsecCommand) -> Result<Self> {
let desc = bios.fwsec_image().header()?;
- let ucode = bios.fwsec_image().ucode(&desc)?;
- let mut dma_object = DmaObject::from_data(dev, ucode)?;
+ let mut ucode = KVVec::new();
+ ucode.extend_from_slice(bios.fwsec_image().ucode(&desc)?, GFP_KERNEL)?;
- let hdr_offset = usize::from_safe_cast(desc.imem_load_size() + desc.interface_offset());
- // SAFETY: we have exclusive access to `dma_object`.
- let hdr: &FalconAppifHdrV1 = unsafe { transmute(&dma_object, hdr_offset) }?;
+ let hdr_offset = desc
+ .imem_load_size()
+ .checked_add(desc.interface_offset())
+ .map(usize::from_safe_cast)
+ .ok_or(EINVAL)?;
+
+ let hdr = ucode
+ .get(hdr_offset..)
+ .and_then(FalconAppifHdrV1::from_bytes_prefix)
+ .ok_or(EINVAL)?
+ .0;
if hdr.version != 1 {
return Err(EINVAL);
@@ -276,26 +240,34 @@ impl FirmwareDmaObject<FwsecFirmware, Unsigned> {
// Find the DMEM mapper section in the firmware.
for i in 0..usize::from(hdr.entry_count) {
- // SAFETY: we have exclusive access to `dma_object`.
- let app: &FalconAppifV1 = unsafe {
- transmute(
- &dma_object,
- hdr_offset + usize::from(hdr.header_size) + i * usize::from(hdr.entry_size),
- )
- }?;
+ // CALC: hdr_offset + header_size + i * entry_size.
+ let entry_offset = hdr_offset
+ .checked_add(usize::from(hdr.header_size))
+ .and_then(|o| o.checked_add(i.checked_mul(usize::from(hdr.entry_size))?))
+ .ok_or(EINVAL)?;
+
+ let app = ucode
+ .get(entry_offset..)
+ .and_then(FalconAppifV1::from_bytes_prefix)
+ .ok_or(EINVAL)?
+ .0;
if app.id != NVFW_FALCON_APPIF_ID_DMEMMAPPER {
continue;
}
let dmem_base = app.dmem_base;
- // SAFETY: we have exclusive access to `dma_object`.
- let dmem_mapper: &mut FalconAppifDmemmapperV3 = unsafe {
- transmute_mut(
- &mut dma_object,
- (desc.imem_load_size() + dmem_base).into_safe_cast(),
- )
- }?;
+ let dmem_mapper_offset = desc
+ .imem_load_size()
+ .checked_add(dmem_base)
+ .map(usize::from_safe_cast)
+ .ok_or(EINVAL)?;
+
+ let dmem_mapper = ucode
+ .get_mut(dmem_mapper_offset..)
+ .and_then(FalconAppifDmemmapperV3::from_bytes_mut_prefix)
+ .ok_or(EINVAL)?
+ .0;
dmem_mapper.init_cmd = match cmd {
FwsecCommand::Frts { .. } => NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS,
@@ -303,13 +275,17 @@ impl FirmwareDmaObject<FwsecFirmware, Unsigned> {
};
let cmd_in_buffer_offset = dmem_mapper.cmd_in_buffer_offset;
- // SAFETY: we have exclusive access to `dma_object`.
- let frts_cmd: &mut FrtsCmd = unsafe {
- transmute_mut(
- &mut dma_object,
- (desc.imem_load_size() + cmd_in_buffer_offset).into_safe_cast(),
- )
- }?;
+ let frts_cmd_offset = desc
+ .imem_load_size()
+ .checked_add(cmd_in_buffer_offset)
+ .map(usize::from_safe_cast)
+ .ok_or(EINVAL)?;
+
+ let frts_cmd = ucode
+ .get_mut(frts_cmd_offset..)
+ .and_then(FrtsCmd::from_bytes_mut_prefix)
+ .ok_or(EINVAL)?
+ .0;
frts_cmd.read_vbios = ReadVbios {
ver: 1,
@@ -333,7 +309,7 @@ impl FirmwareDmaObject<FwsecFirmware, Unsigned> {
}
// Return early as we found and patched the DMEMMAPPER region.
- return Ok(Self(dma_object, PhantomData));
+ return Ok(Self(ucode, PhantomData));
}
Err(ENOTSUPP)
@@ -350,13 +326,16 @@ impl FwsecFirmware {
bios: &Vbios,
cmd: FwsecCommand,
) -> Result<Self> {
- let ucode_dma = FirmwareDmaObject::<Self, _>::new_fwsec(dev, bios, cmd)?;
+ let ucode_dma = FirmwareObject::<Self, _>::new_fwsec(bios, cmd)?;
// Patch signature if needed.
let desc = bios.fwsec_image().header()?;
let ucode_signed = if desc.signature_count() != 0 {
- let sig_base_img =
- usize::from_safe_cast(desc.imem_load_size() + desc.pkc_data_offset());
+ let sig_base_img = desc
+ .imem_load_size()
+ .checked_add(desc.pkc_data_offset())
+ .map(usize::from_safe_cast)
+ .ok_or(EINVAL)?;
let desc_sig_versions = u32::from(desc.signature_versions());
let reg_fuse_version =
falcon.signature_reg_fuse_version(bar, desc.engine_id_mask(), desc.ucode_id())?;
@@ -408,6 +387,10 @@ impl FwsecFirmware {
}
/// Loads the FWSEC firmware into `falcon` and execute it.
+ ///
+ /// This must only be called on chipsets that do not need the FWSEC bootloader (i.e., where
+ /// [`Chipset::needs_fwsec_bootloader()`](crate::gpu::Chipset::needs_fwsec_bootloader) returns
+ /// `false`). On chipsets that do, use [`bootloader::FwsecFirmwareWithBl`] instead.
pub(crate) fn run(
&self,
dev: &Device<device::Bound>,
@@ -419,7 +402,7 @@ impl FwsecFirmware {
.reset(bar)
.inspect_err(|e| dev_err!(dev, "Failed to reset GSP falcon: {:?}\n", e))?;
falcon
- .load(bar, self)
+ .load(dev, bar, self)
.inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?;
let (mbox0, _) = falcon
.boot(bar, Some(0), None)
diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
new file mode 100644
index 000000000000..bcb713a868e2
--- /dev/null
+++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Bootloader support for the FWSEC firmware.
+//!
+//! On Turing, the FWSEC firmware is not loaded directly, but is instead loaded through a small
+//! bootloader program that performs the required DMA operations. This bootloader itself needs to
+//! be loaded using PIO.
+
+use kernel::{
+ alloc::KVec,
+ device::{
+ self,
+ Device, //
+ },
+ dma::Coherent,
+ io::{
+ register::WithBase, //
+ Io,
+ },
+ prelude::*,
+ ptr::{
+ Alignable,
+ Alignment, //
+ },
+ sizes,
+ transmute::{
+ AsBytes,
+ FromBytes, //
+ },
+};
+
+use crate::{
+ driver::Bar0,
+ falcon::{
+ self,
+ gsp::Gsp,
+ Falcon,
+ FalconBromParams,
+ FalconDmaLoadable,
+ FalconFbifMemType,
+ FalconFbifTarget,
+ FalconFirmware,
+ FalconPioDmemLoadTarget,
+ FalconPioImemLoadTarget,
+ FalconPioLoadable, //
+ },
+ firmware::{
+ fwsec::FwsecFirmware,
+ request_firmware,
+ BinHdr,
+ FIRMWARE_VERSION, //
+ },
+ gpu::Chipset,
+ num::FromSafeCast,
+ regs,
+};
+
+/// Descriptor used by RM to figure out the requirements of the boot loader.
+///
+/// Most of its fields appear to be legacy and carry incorrect values, so they are left unused.
+#[repr(C)]
+#[derive(Debug, Clone)]
+struct BootloaderDesc {
+ /// Starting tag of bootloader.
+ start_tag: u32,
+ /// DMEM load offset - unused here as we always load at offset `0`.
+ _dmem_load_off: u32,
+ /// Offset of code section in the image. Unused as there is only one section in the bootloader
+ /// binary.
+ _code_off: u32,
+ /// Size of code section in the image.
+ code_size: u32,
+ /// Offset of data section in the image. Unused as we build the data section ourselves.
+ _data_off: u32,
+ /// Size of data section in the image. Unused as we build the data section ourselves.
+ _data_size: u32,
+}
+// SAFETY: any byte sequence is valid for this struct.
+unsafe impl FromBytes for BootloaderDesc {}
+
+/// 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 {
+ /// 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>,
+ /// 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 {
+ /// Loads the bootloader firmware for `dev` and `chipset`, and wrap `firmware` so it can be
+ /// loaded using it.
+ pub(crate) fn new(
+ firmware: FwsecFirmware,
+ dev: &Device<device::Bound>,
+ chipset: Chipset,
+ ) -> Result<Self> {
+ let fw = request_firmware(dev, chipset, "gen_bootloader", FIRMWARE_VERSION)?;
+ let hdr = fw
+ .data()
+ .get(0..size_of::<BinHdr>())
+ .and_then(BinHdr::from_bytes_copy)
+ .ok_or(EINVAL)?;
+
+ let desc = {
+ let desc_offset = usize::from_safe_cast(hdr.header_offset);
+
+ fw.data()
+ .get(desc_offset..)
+ .and_then(BootloaderDesc::from_bytes_copy_prefix)
+ .ok_or(EINVAL)?
+ .0
+ };
+
+ let ucode = {
+ let ucode_start = usize::from_safe_cast(hdr.data_offset);
+ let code_size = usize::from_safe_cast(desc.code_size);
+ // Align to falcon block size (256 bytes).
+ 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(
+ fw.data()
+ .get(ucode_start..ucode_start + code_size)
+ .ok_or(EINVAL)?,
+ GFP_KERNEL,
+ )?;
+ ucode.resize(aligned_code_size, 0, GFP_KERNEL)?;
+
+ ucode
+ };
+
+ // `BootloaderDmemDescV2` expects the source to be a mirror image of the destination and
+ // uses the same offset parameter for both.
+ //
+ // Thus, the start of the source object needs to be padded with the difference between the
+ // destination and source offsets.
+ //
+ // In practice, this is expected to always be zero but is required for code correctness.
+ let (align_padding, firmware_dma) = {
+ let align_padding = {
+ let imem_sec = firmware.imem_sec_load_params();
+
+ imem_sec
+ .dst_start
+ .checked_sub(imem_sec.src_start)
+ .map(usize::from_safe_cast)
+ .ok_or(EOVERFLOW)?
+ };
+
+ let mut firmware_obj = KVVec::new();
+ firmware_obj.extend_with(align_padding, 0u8, GFP_KERNEL)?;
+ firmware_obj.extend_from_slice(firmware.ucode.0.as_slice(), GFP_KERNEL)?;
+
+ (
+ align_padding,
+ Coherent::from_slice(dev, firmware_obj.as_slice(), GFP_KERNEL)?,
+ )
+ };
+
+ 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();
+
+ // The bootloader does not have a data destination offset field and copies the data at
+ // the start of DMEM, so it can only be used if the destination offset of the firmware
+ // is 0.
+ if dmem.dst_start != 0 {
+ return Err(EINVAL);
+ }
+
+ BootloaderDmemDescV2 {
+ reserved: [0; 4],
+ signature: [0; 4],
+ ctx_dma: FALCON_DMAIDX_PHYS_SYS_NCOH,
+ code_dma_base: firmware_dma.dma_handle(),
+ // `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_handle()
+ .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,
+ }
+ };
+
+ // 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 {
+ _firmware_dma: firmware_dma,
+ ucode,
+ dmem_desc,
+ brom_params: firmware.brom_params(),
+ imem_dst_start: u16::try_from(imem_dst_start)?,
+ start_tag: u16::try_from(desc.start_tag)?,
+ })
+ }
+
+ /// Loads the bootloader into `falcon` and execute it.
+ ///
+ /// 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>,
+ bar: &Bar0,
+ ) -> Result<()> {
+ // Reset falcon, load the firmware, and run it.
+ falcon
+ .reset(bar)
+ .inspect_err(|e| dev_err!(dev, "Failed to reset GSP falcon: {:?}\n", e))?;
+ falcon
+ .pio_load(bar, 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.
+ bar.update(
+ regs::NV_PFALCON_FBIF_TRANSCFG::of::<Gsp>()
+ .try_at(usize::from_safe_cast(self.dmem_desc.ctx_dma))
+ .ok_or(EINVAL)?,
+ |v| {
+ v.with_target(FalconFbifTarget::CoherentSysmem)
+ .with_mem_type(FalconFbifMemType::Physical)
+ },
+ );
+
+ let (mbox0, _) = falcon
+ .boot(bar, Some(0), None)
+ .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);
+ Err(EIO)
+ } else {
+ Ok(())
+ }
+ }
+}
+
+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.
+ u32::from(self.start_tag) << 8
+ }
+}
+
+impl FalconPioLoadable for FwsecFirmwareWithBl {
+ fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
+ None
+ }
+
+ 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,
+ })
+ }
+
+ fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> {
+ FalconPioDmemLoadTarget {
+ data: self.dmem_desc.as_bytes(),
+ dst_start: 0,
+ }
+ }
+}
diff --git a/drivers/gpu/nova-core/firmware/gsp.rs b/drivers/gpu/nova-core/firmware/gsp.rs
index 9488a626352f..2fcc255c3bc8 100644
--- a/drivers/gpu/nova-core/firmware/gsp.rs
+++ b/drivers/gpu/nova-core/firmware/gsp.rs
@@ -3,10 +3,11 @@
use kernel::{
device,
dma::{
+ Coherent,
+ CoherentBox,
DataDirection,
DmaAddress, //
},
- kvec,
prelude::*,
scatterlist::{
Owned,
@@ -15,8 +16,10 @@ use kernel::{
};
use crate::{
- dma::DmaObject,
- firmware::riscv::RiscvFirmware,
+ firmware::{
+ elf,
+ riscv::RiscvFirmware, //
+ },
gpu::{
Architecture,
Chipset, //
@@ -25,92 +28,6 @@ use crate::{
num::FromSafeCast,
};
-/// Ad-hoc and temporary module to extract sections from ELF images.
-///
-/// Some firmware images are currently packaged as ELF files, where sections names are used as keys
-/// to specific and related bits of data. Future firmware versions are scheduled to move away from
-/// that scheme before nova-core becomes stable, which means this module will eventually be
-/// removed.
-mod elf {
- use kernel::{
- bindings,
- prelude::*,
- transmute::FromBytes, //
- };
-
- /// Newtype to provide a [`FromBytes`] implementation.
- #[repr(transparent)]
- struct Elf64Hdr(bindings::elf64_hdr);
- // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
- unsafe impl FromBytes for Elf64Hdr {}
-
- #[repr(transparent)]
- struct Elf64SHdr(bindings::elf64_shdr);
- // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
- unsafe impl FromBytes for Elf64SHdr {}
-
- /// Tries to extract section with name `name` from the ELF64 image `elf`, and returns it.
- pub(super) fn elf64_section<'a, 'b>(elf: &'a [u8], name: &'b str) -> Option<&'a [u8]> {
- let hdr = &elf
- .get(0..size_of::<bindings::elf64_hdr>())
- .and_then(Elf64Hdr::from_bytes)?
- .0;
-
- // Get all the section headers.
- let mut shdr = {
- let shdr_num = usize::from(hdr.e_shnum);
- let shdr_start = usize::try_from(hdr.e_shoff).ok()?;
- let shdr_end = shdr_num
- .checked_mul(size_of::<Elf64SHdr>())
- .and_then(|v| v.checked_add(shdr_start))?;
-
- elf.get(shdr_start..shdr_end)
- .map(|slice| slice.chunks_exact(size_of::<Elf64SHdr>()))?
- };
-
- // Get the strings table.
- let strhdr = shdr
- .clone()
- .nth(usize::from(hdr.e_shstrndx))
- .and_then(Elf64SHdr::from_bytes)?;
-
- // Find the section which name matches `name` and return it.
- shdr.find(|&sh| {
- let Some(hdr) = Elf64SHdr::from_bytes(sh) else {
- return false;
- };
-
- let Some(name_idx) = strhdr
- .0
- .sh_offset
- .checked_add(u64::from(hdr.0.sh_name))
- .and_then(|idx| usize::try_from(idx).ok())
- else {
- return false;
- };
-
- // Get the start of the name.
- elf.get(name_idx..)
- .and_then(|nstr| CStr::from_bytes_until_nul(nstr).ok())
- // Convert into str.
- .and_then(|c_str| c_str.to_str().ok())
- // Check that the name matches.
- .map(|str| str == name)
- .unwrap_or(false)
- })
- // Return the slice containing the section.
- .and_then(|sh| {
- let hdr = Elf64SHdr::from_bytes(sh)?;
- let start = usize::try_from(hdr.0.sh_offset).ok()?;
- let end = usize::try_from(hdr.0.sh_size)
- .ok()
- .and_then(|sh_size| start.checked_add(sh_size))?;
-
- elf.get(start..end)
- })
- }
-}
-
/// GSP firmware with 3-level radix page tables for the GSP bootloader.
///
/// The bootloader expects firmware to be mapped starting at address 0 in GSP's virtual address
@@ -136,11 +53,11 @@ pub(crate) struct GspFirmware {
#[pin]
level1: SGTable<Owned<VVec<u8>>>,
/// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page.
- level0: DmaObject,
+ level0: Coherent<[u64]>,
/// Size in bytes of the firmware contained in [`Self::fw`].
pub(crate) size: usize,
/// Device-mapped GSP signatures matching the GPU's [`Chipset`].
- pub(crate) signatures: DmaObject,
+ pub(crate) signatures: Coherent<[u8]>,
/// GSP bootloader, verifies the GSP firmware before loading and running it.
pub(crate) bootloader: RiscvFirmware,
}
@@ -197,17 +114,20 @@ impl GspFirmware {
// Allocate the level 0 page table as a device-visible DMA object, and map the
// level 1 page table onto it.
- // Level 0 page table data.
- let mut level0_data = kvec![0u8; GSP_PAGE_SIZE]?;
-
// Fill level 1 page entry.
let level1_entry = level1.iter().next().ok_or(EINVAL)?;
let level1_entry_addr = level1_entry.dma_address();
- let dst = &mut level0_data[..size_of_val(&level1_entry_addr)];
- dst.copy_from_slice(&level1_entry_addr.to_le_bytes());
- // Turn the level0 page table into a [`DmaObject`].
- DmaObject::from_data(dev, &level0_data)?
+ // Create level 0 page table data and fill its first entry with the level 1
+ // table.
+ let mut level0 = CoherentBox::<[u64]>::zeroed_slice(
+ dev,
+ GSP_PAGE_SIZE / size_of::<u64>(),
+ GFP_KERNEL
+ )?;
+ level0[0] = level1_entry_addr.to_le();
+
+ level0.into()
},
size,
signatures: {
@@ -226,7 +146,7 @@ impl GspFirmware {
elf::elf64_section(firmware.data(), sigs_section)
.ok_or(EINVAL)
- .and_then(|data| DmaObject::from_data(dev, data))?
+ .and_then(|data| Coherent::from_slice(dev, data, GFP_KERNEL))?
},
bootloader: {
let bl = super::request_firmware(dev, chipset, "bootloader", ver)?;
diff --git a/drivers/gpu/nova-core/firmware/riscv.rs b/drivers/gpu/nova-core/firmware/riscv.rs
index 4bdd89bd0757..2afa7f36404e 100644
--- a/drivers/gpu/nova-core/firmware/riscv.rs
+++ b/drivers/gpu/nova-core/firmware/riscv.rs
@@ -5,13 +5,13 @@
use kernel::{
device,
+ dma::Coherent,
firmware::Firmware,
prelude::*,
transmute::FromBytes, //
};
use crate::{
- dma::DmaObject,
firmware::BinFirmware,
num::FromSafeCast, //
};
@@ -45,10 +45,11 @@ impl RmRiscvUCodeDesc {
/// Fails if the header pointed at by `bin_fw` is not within the bounds of the firmware image.
fn new(bin_fw: &BinFirmware<'_>) -> Result<Self> {
let offset = usize::from_safe_cast(bin_fw.hdr.header_offset);
+ let end = offset.checked_add(size_of::<Self>()).ok_or(EINVAL)?;
bin_fw
.fw
- .get(offset..offset + size_of::<Self>())
+ .get(offset..end)
.and_then(Self::from_bytes_copy)
.ok_or(EINVAL)
}
@@ -65,7 +66,7 @@ pub(crate) struct RiscvFirmware {
/// Application version.
pub(crate) app_version: u32,
/// Device-mapped firmware image.
- pub(crate) ucode: DmaObject,
+ pub(crate) ucode: Coherent<[u8]>,
}
impl RiscvFirmware {
@@ -78,8 +79,9 @@ impl RiscvFirmware {
let ucode = {
let start = usize::from_safe_cast(bin_fw.hdr.data_offset);
let len = usize::from_safe_cast(bin_fw.hdr.data_size);
+ let end = start.checked_add(len).ok_or(EINVAL)?;
- DmaObject::from_data(dev, fw.data().get(start..start + len).ok_or(EINVAL)?)?
+ Coherent::from_slice(dev, fw.data().get(start..end).ok_or(EINVAL)?, GFP_KERNEL)?
};
Ok(Self {