use std::collections::HashMap;
use consortium_cfg_common::{
config::{Party, VisibleCore},
database::{HardwareDoorbellPeripheral, HardwareDoorbellPeripheralType},
};
use consortium_dts::utils::irq_method::IrqMethod;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use crate::components::address::Address;
use crate::components::ipc_shm::{DoorbellMeta, DoorbellRingerSide};
use crate::components::irq::InterruptLine;
use crate::components::uio::{ToUio, UioBacking, UioClock, UioDevice};
use crate::error::CfgError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HardwareDoorbell {
pub name: String,
pub address: Address,
pub r#type: HardwareDoorbellPeripheralType,
pub id: Option<u8>,
pub channels: Vec<u8>,
pub irq: HashMap<VisibleCore, InterruptLine>,
pub side: HashMap<VisibleCore, DoorbellRingerSide>,
}
impl HardwareDoorbell {
fn meta(&self) -> DoorbellMeta {
DoorbellMeta {
r#type: self.r#type,
id: self.id,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BellA {
pub bells: Vec<HardwareDoorbell>,
}
impl BellA {
pub fn resolve_variants(
&mut self,
mut resolve: impl FnMut(VisibleCore, u16) -> Option<String>,
) {
for bell in &mut self.bells {
for (core, irq) in &mut bell.irq {
if !matches!(core, VisibleCore::Mcore(_)) {
continue;
}
irq.variant = u16::try_from(irq.number)
.ok()
.and_then(|number| resolve(*core, number));
}
}
}
pub fn vectors(&self, core: &VisibleCore, hal_crate: &str) -> Vec<TokenStream> {
let hal: TokenStream = hal_crate
.parse()
.expect("hal crate path is a valid token stream");
let mut lines = self
.bells
.iter()
.filter_map(|bell| {
let irq = bell.irq.get(core)?;
let variant = irq.variant.as_ref()?;
let side = bell.side.get(core)?;
Some((
variant.clone(),
bell.meta().service_call(*side),
irq.guard(&hal),
))
})
.collect::<Vec<_>>();
lines.sort_by(|(left, ..), (right, ..)| left.cmp(right));
lines.dedup_by(|(left, ..), (right, ..)| left == right);
lines
.into_iter()
.map(|(variant, service, guard)| {
let line = Ident::new(&variant, Span::call_site());
quote! {
#guard
#[#hal::interrupt]
fn #line() {
#service
}
}
})
.collect()
}
pub fn nvic_enable(&self, core: &VisibleCore, hal_crate: &str) -> Option<TokenStream> {
let hal: TokenStream = hal_crate
.parse()
.expect("hal crate path is a valid token stream");
let mut interrupts = self
.bells
.iter()
.filter_map(|bell| bell.irq.get(core))
.filter_map(|irq| irq.tokens(&hal).map(|named| (irq.number, named)))
.collect::<Vec<_>>();
if interrupts.is_empty() {
return None;
}
interrupts.sort_by_key(|(number, _)| *number);
interrupts.dedup_by_key(|(number, _)| *number);
let statements = interrupts.iter().map(|(number, (line, label))| {
quote! {
#hal::hal::nvic::unpend(#line);
unsafe {
#hal::hal::nvic::set_priority(#line, 0x80u8);
#hal::hal::nvic::enable(#line);
}
::consortium_runtime_mcu::log::trace!(
"consortium controller init: NVIC enabled {} at priority 0x80 (id={})",
#label,
#number
);
}
});
Some(quote! { #(#statements)* })
}
}
impl ToUio for HardwareDoorbellPeripheral {
fn to_uio(&self) -> Result<Option<UioDevice>, CfgError> {
let linux = self
.parties
.iter()
.find(|party| party.core == Party::AcoreNonSecure);
let irq = linux.map(|party| (party.irq, IrqMethod::LevelHigh));
let clock = self.clock.as_ref().map(|clock| UioClock {
provider: clock.provider.clone(),
id: clock.id.clone(),
});
let Some(party) = linux.or_else(|| self.parties.first()) else {
return Ok(None);
};
let window = (party.memory, self.party_window_length());
Ok(Some(UioDevice {
mmap: vec![window],
irq,
backing: UioBacking::Registers,
dma_coherent: Vec::new(),
interrupt_parent: None,
clock,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use consortium_cfg_common::config::McoreName;
use consortium_cfg_common::database::{
DoorbellWindow, HardwareDoorbellParty, HardwareDoorbellPeripheralType,
};
fn bell(name: &str, core: VisibleCore, irq: u32) -> HardwareDoorbell {
typed_bell(
name,
core,
irq,
HardwareDoorbellPeripheralType::Ipcc,
Some(1),
)
}
fn typed_bell(
name: &str,
core: VisibleCore,
irq: u32,
r#type: HardwareDoorbellPeripheralType,
id: Option<u8>,
) -> HardwareDoorbell {
HardwareDoorbell {
name: name.to_string(),
address: Address {
ap: crate::components::address::ApAddress {
uio_id: 0,
mmap_id: 0,
},
cp: HashMap::new(),
},
r#type,
id,
channels: vec![0],
irq: HashMap::from([(core, InterruptLine::new(irq))]),
side: HashMap::from([(core, DoorbellRingerSide::Secondary)]),
}
}
#[test]
fn generates_nvic_enablement_for_core_irqs() {
let core = VisibleCore::Mcore(McoreName::Cm33);
let mut bella = BellA {
bells: vec![bell("ipcc1", core, 171), bell("ipcc2", core, 175)],
};
bella.resolve_variants(|_, number| match number {
171 => Some("IPCC1_RX".to_string()),
175 => Some("IPCC2_RX".to_string()),
_ => None,
});
let tokens = bella
.nvic_enable(&core, "::consortium_hal_stm32mp2")
.unwrap()
.to_string();
assert!(!tokens.contains("BellaInterrupt"));
assert!(!tokens.contains("cortex_m"));
assert!(tokens.contains(":: consortium_hal_stm32mp2 :: Interrupt :: IPCC1_RX"));
assert!(tokens.contains(":: consortium_hal_stm32mp2 :: Interrupt :: IPCC2_RX"));
assert!(tokens.contains(
":: hal :: nvic :: set_priority (:: consortium_hal_stm32mp2 :: Interrupt :: IPCC1_RX , \
0x80u8)"
));
}
#[test]
fn generates_doorbell_vectors_forwarding_to_each_crate() {
let core = VisibleCore::Mcore(McoreName::Cm33);
let mut bella = BellA {
bells: vec![
typed_bell(
"ipcc1",
core,
171,
HardwareDoorbellPeripheralType::Ipcc,
Some(1),
),
typed_bell(
"ipcc2",
core,
175,
HardwareDoorbellPeripheralType::Ipcc,
Some(2),
),
],
};
bella.resolve_variants(|_, number| match number {
171 => Some("IPCC1_RX".to_string()),
175 => Some("IPCC2_RX".to_string()),
_ => None,
});
let tokens = bella
.vectors(&core, "::consortium_hal_stm32mp2")
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>();
assert_eq!(tokens.len(), 2);
assert!(tokens[0].contains("[:: consortium_hal_stm32mp2 :: interrupt] fn IPCC1_RX ()"));
assert!(tokens[0].contains(
":: consortium_ipc_doorbell_ipcc :: notify_rx_occupied_interrupt :: < :: \
consortium_ipc :: Secondary , 1usize > ()"
));
assert!(tokens[1].contains("fn IPCC2_RX ()"));
assert!(tokens[1].contains(
":: consortium_ipc_doorbell_ipcc :: notify_rx_occupied_interrupt :: < :: \
consortium_ipc :: Secondary , 2usize > ()"
));
}
#[test]
fn generates_hsem_doorbell_vector_without_an_instance() {
let core = VisibleCore::Mcore(McoreName::Cm33);
let mut bella = BellA {
bells: vec![typed_bell(
"hsem",
core,
199,
HardwareDoorbellPeripheralType::Hsem,
None,
)],
};
bella.resolve_variants(|_, _| Some("HSEM".to_string()));
let tokens = bella.vectors(&core, "::consortium_hal_stm32mp2")[0].to_string();
assert!(tokens.contains("fn HSEM ()"));
assert!(tokens.contains(":: consortium_ipc_doorbell_hsem :: notify ()"));
}
#[test]
fn generates_mu_doorbell_vector_with_its_instance() {
let core = VisibleCore::Mcore(McoreName::Cm7);
let mut bella = BellA {
bells: vec![typed_bell(
"mu7",
core,
207,
HardwareDoorbellPeripheralType::Mu,
Some(7),
)],
};
bella.resolve_variants(|_, _| Some("MU7_B".to_string()));
let tokens = bella.vectors(&core, "::consortium_hal_imx9")[0].to_string();
assert!(tokens.contains("fn MU7_B ()"));
assert!(tokens.contains(
":: consortium_ipc_doorbell_mu :: notify_gi_interrupt :: < :: consortium_ipc :: \
Secondary , 7usize > ()"
));
}
#[test]
fn deduplicates_a_shared_doorbell_line() {
let core = VisibleCore::Mcore(McoreName::Cm33);
let mut bella = BellA {
bells: vec![bell("ipcc1", core, 171), bell("ipcc1", core, 171)],
};
bella.resolve_variants(|_, _| Some("IPCC1_RX".to_string()));
assert_eq!(bella.vectors(&core, "::consortium_hal_stm32mp2").len(), 1);
}
#[test]
fn emits_no_vector_without_a_pac_variant() {
let core = VisibleCore::Mcore(McoreName::Cm0p);
let bella = BellA {
bells: vec![bell("ipcc2", core, 13)],
};
assert!(bella.vectors(&core, "::consortium_hal_stm32mp2").is_empty());
}
#[test]
fn falls_back_to_the_raw_line_without_a_pac_variant() {
let core = VisibleCore::Mcore(McoreName::Cm0p);
let mut bella = BellA {
bells: vec![bell("ipcc2", core, 13)],
};
bella.resolve_variants(|_, _| None);
let tokens = bella
.nvic_enable(&core, "::consortium_hal_stm32mp2")
.unwrap()
.to_string();
assert!(!tokens.contains("Interrupt ::"));
assert!(tokens.contains(
":: hal :: nvic :: enable (:: consortium_hal_stm32mp2 :: hal :: nvic :: Irq (13u16))"
));
}
#[test]
fn resolve_variants_skips_the_ap() {
let mut bella = BellA {
bells: vec![bell("ipcc1", VisibleCore::Ap, 171)],
};
bella.resolve_variants(|_, _| Some("IPCC1_RX".to_string()));
assert!(bella.bells[0].irq[&VisibleCore::Ap].variant.is_none());
}
#[test]
fn skips_unavailable_core_irqs() {
let cm33 = VisibleCore::Mcore(McoreName::Cm33);
let cm7 = VisibleCore::Mcore(McoreName::Cm7);
let bella = BellA {
bells: vec![bell("ipcc1", cm33, 171)],
};
assert!(
bella
.nvic_enable(&cm7, "::consortium_hal_stm32mp2")
.is_none()
);
}
fn party(core: &str, irq: u32) -> HardwareDoorbellParty {
HardwareDoorbellParty {
core: core.parse().unwrap(),
irq,
window: DoorbellWindow::A,
memory: 0x0049_0000,
page_memory: 0,
page_irq: 0,
}
}
fn database_doorbell(parties: Vec<HardwareDoorbellParty>) -> HardwareDoorbellPeripheral {
HardwareDoorbellPeripheral {
name: "IPCC1".to_string(),
page: 1305,
r#type: HardwareDoorbellPeripheralType::Ipcc,
length: 0x400,
parties,
party_page: 1305,
channels: 32,
channel_page: 1305,
clock: None,
window_length: None,
}
}
#[test]
fn doorbell_uio_uses_linux_party_interrupt() {
let uio = database_doorbell(vec![
party("optee", 200),
party("linux", 199),
party("cm33", 199),
])
.to_uio()
.unwrap()
.unwrap();
assert_eq!(uio.mmap, vec![(0x0049_0000, 0x400)]);
assert_eq!(uio.irq, Some((199, IrqMethod::LevelHigh)));
}
#[test]
fn doorbell_uio_is_memory_only_without_linux_party() {
let uio = database_doorbell(vec![party("cm33", 199), party("cm0p", 21)])
.to_uio()
.unwrap()
.unwrap();
assert_eq!(uio.mmap, vec![(0x0049_0000, 0x400)]);
assert!(uio.irq.is_none());
}
}