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,
};
#[repr(C)]
#[derive(Debug, Clone)]
struct BootloaderDesc {
start_tag: u32,
_dmem_load_off: u32,
_code_off: u32,
code_size: u32,
_data_off: u32,
_data_size: u32,
}
unsafe impl FromBytes for BootloaderDesc {}
#[repr(C, packed)]
#[derive(Debug, Clone)]
struct BootloaderDmemDescV2 {
reserved: [u32; 4],
signature: [u32; 4],
ctx_dma: u32,
code_dma_base: u64,
non_sec_code_off: u32,
non_sec_code_size: u32,
sec_code_off: u32,
sec_code_size: u32,
code_entry_point: u32,
data_dma_base: u64,
data_size: u32,
argc: u32,
argv: u32,
}
unsafe impl AsBytes for BootloaderDmemDescV2 {}
pub(crate) struct FwsecFirmwareWithBl {
_firmware_dma: Coherent<[u8]>,
ucode: KVec<u8>,
dmem_desc: BootloaderDmemDescV2,
imem_dst_start: u16,
brom_params: FalconBromParams,
start_tag: u16,
}
impl FwsecFirmwareWithBl {
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);
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
};
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 = {
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();
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(),
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: 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,
}
};
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)?,
})
}
pub(crate) fn run(
&self,
dev: &Device<device::Bound>,
falcon: &Falcon<Gsp>,
bar: &Bar0,
) -> Result<()> {
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))?;
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 {
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,
}
}
}