use hal::FalconHal;
use kernel::{
device::{
self,
Device, },
dma::{
Coherent,
CoherentBox,
DmaAddress,
DmaMask, },
io::{
poll::read_poll_timeout,
register::{
RegisterBase,
WithBase, },
Io,
},
prelude::*,
sync::aref::ARef,
time::Delta,
};
use crate::{
bounded_enum,
driver::Bar0,
falcon::hal::LoadMethod,
gpu::Chipset,
num::{
self,
FromSafeCast, },
regs,
};
pub(crate) mod gsp;
mod hal;
pub(crate) mod sec2;
pub(crate) const MEM_BLOCK_ALIGNMENT: usize = 256;
bounded_enum! {
#[derive(Debug, Copy, Clone)]
pub(crate) enum FalconCoreRev with TryFrom<Bounded<u32, 4>> {
Rev1 = 1,
Rev2 = 2,
Rev3 = 3,
Rev4 = 4,
Rev5 = 5,
Rev6 = 6,
Rev7 = 7,
}
}
bounded_enum! {
#[derive(Debug, Copy, Clone)]
pub(crate) enum FalconCoreRevSubversion with From<Bounded<u32, 2>> {
Subversion0 = 0,
Subversion1 = 1,
Subversion2 = 2,
Subversion3 = 3,
}
}
bounded_enum! {
#[derive(Debug, Copy, Clone)]
pub(crate) enum FalconSecurityModel with TryFrom<Bounded<u32, 2>> {
None = 0,
Light = 2,
Heavy = 3,
}
}
bounded_enum! {
#[derive(Debug, Copy, Clone)]
pub(crate) enum FalconModSelAlgo with TryFrom<Bounded<u32, 8>> {
Aes = 0,
Rsa3k = 1,
}
}
bounded_enum! {
#[derive(Debug, Copy, Clone)]
pub(crate) enum DmaTrfCmdSize with TryFrom<Bounded<u32, 3>> {
Size256B = 0x6,
}
}
bounded_enum! {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum PeregrineCoreSelect with From<Bounded<u32, 1>> {
Falcon = 0,
Riscv = 1,
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum FalconMem {
ImemSecure,
#[expect(unused)]
ImemNonSecure,
Dmem,
}
bounded_enum! {
#[derive(Debug, Copy, Clone)]
pub(crate) enum FalconFbifTarget with TryFrom<Bounded<u32, 2>> {
LocalFb = 0,
CoherentSysmem = 1,
NoncoherentSysmem = 2,
}
}
bounded_enum! {
#[derive(Debug, Copy, Clone)]
pub(crate) enum FalconFbifMemType with From<Bounded<u32, 1>> {
Virtual = 0,
Physical = 1,
}
}
pub(crate) struct PFalconBase(());
pub(crate) struct PFalcon2Base(());
pub(crate) trait FalconEngine:
Send + Sync + RegisterBase<PFalconBase> + RegisterBase<PFalcon2Base> + Sized
{
}
#[derive(Debug, Clone)]
pub(crate) struct FalconDmaLoadTarget {
pub(crate) src_start: u32,
pub(crate) dst_start: u32,
pub(crate) len: u32,
}
#[derive(Debug, Clone)]
pub(crate) struct FalconBromParams {
pub(crate) pkc_data_offset: u32,
pub(crate) engine_id_mask: u16,
pub(crate) ucode_id: u8,
}
pub(crate) trait FalconDmaLoadable {
fn as_slice(&self) -> &[u8];
fn imem_sec_load_params(&self) -> FalconDmaLoadTarget;
fn imem_ns_load_params(&self) -> Option<FalconDmaLoadTarget>;
fn dmem_load_params(&self) -> FalconDmaLoadTarget;
fn try_as_pio_loadable(&self) -> Result<FalconDmaFirmwarePioAdapter<'_, Self>> {
let new_pio_imem = |params: FalconDmaLoadTarget, secure| {
let start = usize::from_safe_cast(params.src_start);
let end = start + usize::from_safe_cast(params.len);
let data = self.as_slice().get(start..end).ok_or(EINVAL)?;
let dst_start = u16::try_from(params.dst_start).map_err(|_| EINVAL)?;
Ok::<_, Error>(FalconPioImemLoadTarget {
data,
dst_start,
secure,
start_tag: dst_start >> 8,
})
};
let imem_sec = new_pio_imem(self.imem_sec_load_params(), true)?;
let imem_ns = if let Some(params) = self.imem_ns_load_params() {
Some(new_pio_imem(params, false)?)
} else {
None
};
let dmem = {
let params = self.dmem_load_params();
let start = usize::from_safe_cast(params.src_start);
let end = start + usize::from_safe_cast(params.len);
let data = self.as_slice().get(start..end).ok_or(EINVAL)?;
let dst_start = u16::try_from(params.dst_start).map_err(|_| EINVAL)?;
FalconPioDmemLoadTarget { data, dst_start }
};
Ok(FalconDmaFirmwarePioAdapter {
fw: self,
imem_sec,
imem_ns,
dmem,
})
}
}
#[derive(Clone)]
pub(crate) struct FalconPioImemLoadTarget<'a> {
pub(crate) data: &'a [u8],
pub(crate) dst_start: u16,
pub(crate) secure: bool,
pub(crate) start_tag: u16,
}
#[derive(Clone)]
pub(crate) struct FalconPioDmemLoadTarget<'a> {
pub(crate) data: &'a [u8],
pub(crate) dst_start: u16,
}
pub(crate) trait FalconPioLoadable {
fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>>;
fn imem_ns_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>>;
fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_>;
}
pub(crate) struct FalconDmaFirmwarePioAdapter<'a, T: FalconDmaLoadable + ?Sized> {
fw: &'a T,
imem_sec: FalconPioImemLoadTarget<'a>,
imem_ns: Option<FalconPioImemLoadTarget<'a>>,
dmem: FalconPioDmemLoadTarget<'a>,
}
impl<'a, T> FalconPioLoadable for FalconDmaFirmwarePioAdapter<'a, T>
where
T: FalconDmaLoadable + ?Sized,
{
fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
Some(self.imem_sec.clone())
}
fn imem_ns_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
self.imem_ns.clone()
}
fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> {
self.dmem.clone()
}
}
impl<'a, T> FalconFirmware for FalconDmaFirmwarePioAdapter<'a, T>
where
T: FalconDmaLoadable + FalconFirmware + ?Sized,
{
type Target = <T as FalconFirmware>::Target;
fn brom_params(&self) -> FalconBromParams {
self.fw.brom_params()
}
fn boot_addr(&self) -> u32 {
self.fw.boot_addr()
}
}
pub(crate) trait FalconFirmware {
type Target: FalconEngine;
fn brom_params(&self) -> FalconBromParams;
fn boot_addr(&self) -> u32;
}
pub(crate) struct Falcon<E: FalconEngine> {
hal: KBox<dyn FalconHal<E>>,
dev: ARef<device::Device>,
}
impl<E: FalconEngine + 'static> Falcon<E> {
pub(crate) fn new(dev: &device::Device, chipset: Chipset) -> Result<Self> {
Ok(Self {
hal: hal::falcon_hal(chipset)?,
dev: dev.into(),
})
}
pub(crate) fn dma_reset(&self, bar: &Bar0) {
bar.update(regs::NV_PFALCON_FBIF_CTL::of::<E>(), |v| {
v.with_allow_phys_no_ctx(true)
});
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_DMACTL::zeroed(),
);
}
pub(crate) fn reset(&self, bar: &Bar0) -> Result {
self.hal.reset_eng(bar)?;
self.hal.select_core(self, bar)?;
self.hal.reset_wait_mem_scrubbing(bar)?;
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_RM::from(bar.read(regs::NV_PMC_BOOT_0).into_raw()),
);
Ok(())
}
const PIO_PORT: usize = 0;
fn pio_wr_imem_slice(&self, bar: &Bar0, load_offsets: FalconPioImemLoadTarget<'_>) -> Result {
if load_offsets.data.len() % 4 != 0 {
return Err(EINVAL);
}
bar.write(
WithBase::of::<E>().at(Self::PIO_PORT),
regs::NV_PFALCON_FALCON_IMEMC::zeroed()
.with_secure(load_offsets.secure)
.with_aincw(true)
.with_offs(load_offsets.dst_start),
);
for (n, block) in load_offsets.data.chunks(MEM_BLOCK_ALIGNMENT).enumerate() {
let n = u16::try_from(n)?;
let tag: u16 = load_offsets.start_tag.checked_add(n).ok_or(ERANGE)?;
bar.write(
WithBase::of::<E>().at(Self::PIO_PORT),
regs::NV_PFALCON_FALCON_IMEMT::zeroed().with_tag(tag),
);
for word in block.chunks_exact(4) {
let w = [word[0], word[1], word[2], word[3]];
bar.write(
WithBase::of::<E>().at(Self::PIO_PORT),
regs::NV_PFALCON_FALCON_IMEMD::zeroed().with_data(u32::from_le_bytes(w)),
);
}
}
Ok(())
}
fn pio_wr_dmem_slice(&self, bar: &Bar0, load_offsets: FalconPioDmemLoadTarget<'_>) -> Result {
if load_offsets.data.len() % 4 != 0 {
return Err(EINVAL);
}
bar.write(
WithBase::of::<E>().at(Self::PIO_PORT),
regs::NV_PFALCON_FALCON_DMEMC::zeroed()
.with_aincw(true)
.with_offs(load_offsets.dst_start),
);
for word in load_offsets.data.chunks_exact(4) {
let w = [word[0], word[1], word[2], word[3]];
bar.write(
WithBase::of::<E>().at(Self::PIO_PORT),
regs::NV_PFALCON_FALCON_DMEMD::zeroed().with_data(u32::from_le_bytes(w)),
);
}
Ok(())
}
pub(crate) fn pio_load<F: FalconFirmware<Target = E> + FalconPioLoadable>(
&self,
bar: &Bar0,
fw: &F,
) -> Result {
bar.update(regs::NV_PFALCON_FBIF_CTL::of::<E>(), |v| {
v.with_allow_phys_no_ctx(true)
});
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_DMACTL::zeroed(),
);
if let Some(imem_ns) = fw.imem_ns_load_params() {
self.pio_wr_imem_slice(bar, imem_ns)?;
}
if let Some(imem_sec) = fw.imem_sec_load_params() {
self.pio_wr_imem_slice(bar, imem_sec)?;
}
self.pio_wr_dmem_slice(bar, fw.dmem_load_params())?;
self.hal.program_brom(self, bar, &fw.brom_params())?;
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_BOOTVEC::zeroed().with_value(fw.boot_addr()),
);
Ok(())
}
fn dma_wr(
&self,
bar: &Bar0,
dma_obj: &Coherent<[u8]>,
target_mem: FalconMem,
load_offsets: FalconDmaLoadTarget,
) -> Result {
const DMA_LEN: u32 = num::usize_into_u32::<{ MEM_BLOCK_ALIGNMENT }>();
let (src_start, dma_start) = match target_mem {
FalconMem::ImemSecure | FalconMem::ImemNonSecure => {
(load_offsets.src_start, dma_obj.dma_handle())
}
FalconMem::Dmem => (
0,
dma_obj.dma_handle() + DmaAddress::from(load_offsets.src_start),
),
};
if dma_start % DmaAddress::from(DMA_LEN) > 0 {
dev_err!(
self.dev,
"DMA transfer start addresses must be a multiple of {}\n",
DMA_LEN
);
return Err(EINVAL);
}
if dma_start > DmaMask::new::<49>().value() {
dev_err!(self.dev, "DMA address {:#x} exceeds 49 bits\n", dma_start);
return Err(ERANGE);
}
let num_transfers = load_offsets.len.div_ceil(DMA_LEN);
match num_transfers
.checked_mul(DMA_LEN)
.and_then(|size| size.checked_add(load_offsets.src_start))
{
None => {
dev_err!(self.dev, "DMA transfer length overflow\n");
return Err(EOVERFLOW);
}
Some(upper_bound) if usize::from_safe_cast(upper_bound) > dma_obj.size() => {
dev_err!(self.dev, "DMA transfer goes beyond range of DMA object\n");
return Err(EINVAL);
}
Some(_) => (),
};
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_DMATRFBASE::zeroed().with_base(
(dma_start >> 8) as u32,
),
);
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_DMATRFBASE1::zeroed().try_with_base(dma_start >> 40)?,
);
let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::zeroed()
.with_size(DmaTrfCmdSize::Size256B)
.with_falcon_mem(target_mem);
for pos in (0..num_transfers).map(|i| i * DMA_LEN) {
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_DMATRFMOFFS::zeroed()
.try_with_offs(load_offsets.dst_start + pos)?,
);
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_DMATRFFBOFFS::zeroed().with_offs(src_start + pos),
);
bar.write(WithBase::of::<E>(), cmd);
read_poll_timeout(
|| Ok(bar.read(regs::NV_PFALCON_FALCON_DMATRFCMD::of::<E>())),
|r| r.idle(),
Delta::ZERO,
Delta::from_secs(2),
)?;
}
Ok(())
}
fn dma_load<F: FalconFirmware<Target = E> + FalconDmaLoadable>(
&self,
dev: &Device<device::Bound>,
bar: &Bar0,
fw: &F,
) -> Result {
let dma_obj = {
let fw_slice = fw.as_slice();
let mut dma_obj = CoherentBox::zeroed_slice(
dev,
fw_slice.len().next_multiple_of(MEM_BLOCK_ALIGNMENT),
GFP_KERNEL,
)?;
dma_obj[..fw_slice.len()].copy_from_slice(fw_slice);
dma_obj.into()
};
self.dma_reset(bar);
bar.update(regs::NV_PFALCON_FBIF_TRANSCFG::of::<E>().at(0), |v| {
v.with_target(FalconFbifTarget::CoherentSysmem)
.with_mem_type(FalconFbifMemType::Physical)
});
self.dma_wr(
bar,
&dma_obj,
FalconMem::ImemSecure,
fw.imem_sec_load_params(),
)?;
self.dma_wr(bar, &dma_obj, FalconMem::Dmem, fw.dmem_load_params())?;
self.hal.program_brom(self, bar, &fw.brom_params())?;
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_BOOTVEC::zeroed().with_value(fw.boot_addr()),
);
Ok(())
}
pub(crate) fn wait_till_halted(&self, bar: &Bar0) -> Result<()> {
read_poll_timeout(
|| Ok(bar.read(regs::NV_PFALCON_FALCON_CPUCTL::of::<E>())),
|r| r.halted(),
Delta::ZERO,
Delta::from_secs(2),
)?;
Ok(())
}
pub(crate) fn start(&self, bar: &Bar0) -> Result<()> {
match bar
.read(regs::NV_PFALCON_FALCON_CPUCTL::of::<E>())
.alias_en()
{
true => bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::zeroed().with_startcpu(true),
),
false => bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_CPUCTL::zeroed().with_startcpu(true),
),
}
Ok(())
}
pub(crate) fn write_mailboxes(&self, bar: &Bar0, mbox0: Option<u32>, mbox1: Option<u32>) {
if let Some(mbox0) = mbox0 {
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_MAILBOX0::zeroed().with_value(mbox0),
);
}
if let Some(mbox1) = mbox1 {
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1),
);
}
}
pub(crate) fn read_mailbox0(&self, bar: &Bar0) -> u32 {
bar.read(regs::NV_PFALCON_FALCON_MAILBOX0::of::<E>())
.value()
}
pub(crate) fn read_mailbox1(&self, bar: &Bar0) -> u32 {
bar.read(regs::NV_PFALCON_FALCON_MAILBOX1::of::<E>())
.value()
}
pub(crate) fn read_mailboxes(&self, bar: &Bar0) -> (u32, u32) {
let mbox0 = self.read_mailbox0(bar);
let mbox1 = self.read_mailbox1(bar);
(mbox0, mbox1)
}
pub(crate) fn boot(
&self,
bar: &Bar0,
mbox0: Option<u32>,
mbox1: Option<u32>,
) -> Result<(u32, u32)> {
self.write_mailboxes(bar, mbox0, mbox1);
self.start(bar)?;
self.wait_till_halted(bar)?;
Ok(self.read_mailboxes(bar))
}
pub(crate) fn signature_reg_fuse_version(
&self,
bar: &Bar0,
engine_id_mask: u16,
ucode_id: u8,
) -> Result<u32> {
self.hal
.signature_reg_fuse_version(self, bar, engine_id_mask, ucode_id)
}
pub(crate) fn is_riscv_active(&self, bar: &Bar0) -> bool {
self.hal.is_riscv_active(bar)
}
pub(crate) fn load<F: FalconFirmware<Target = E> + FalconDmaLoadable>(
&self,
dev: &Device<device::Bound>,
bar: &Bar0,
fw: &F,
) -> Result {
match self.hal.load_method() {
LoadMethod::Dma => self.dma_load(dev, bar, fw),
LoadMethod::Pio => self.pio_load(bar, &fw.try_as_pio_loadable()?),
}
}
pub(crate) fn write_os_version(&self, bar: &Bar0, app_version: u32) {
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_OS::zeroed().with_value(app_version),
);
}
}