use core::fmt;
use std::str::FromStr;
use consortium_cfg_common::{
config::{Codec, VisibleCore},
database::HardwareDoorbellPeripheralType,
};
use proc_macro2::{Literal, Span, TokenStream};
use quote::format_ident;
use syn::{Ident, Path};
use crate::{
components::{address::Address, region::Region},
error::CfgError,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Channel {
pub doorbell_chan: u8,
pub type_name: String,
pub buffer_name: String,
}
fn codec_path(codec: Codec, scratch: u32) -> TokenStream {
match codec {
Codec::Postcard => quote::quote! { ::consortium_codec::PostcardCodec },
Codec::Prost => quote::quote! { ::consortium_codec::ProstCodec },
Codec::Rkyv => {
let scratch = Literal::usize_suffixed(scratch as usize);
quote::quote! { ::consortium_codec::RkyvCodec<#scratch> }
}
}
}
pub fn initialize_instances(
shuhan: &ShuHan,
core: VisibleCore,
uio_ident: &Ident,
ty_crate: &Path,
hal_crate: &str,
available: &[String],
) -> TokenStream {
let (doorbell_params, sources) = shuhan.doorbell_sources(core, available);
let markers = shuhan
.transceivers
.iter()
.map(|t| Ident::new(&t.name.to_ascii_uppercase(), Span::call_site()))
.collect::<Vec<_>>();
let instance_impls = shuhan
.transceivers
.iter()
.zip(markers.iter())
.map(|(t, marker)| {
let side = t
.side(core)
.expect("core must participate in generated transceiver");
let side_ty = match side {
DoorbellRingerSide::Primary => quote::quote! { ::consortium_ipc::Primary },
DoorbellRingerSide::Secondary => quote::quote! { ::consortium_ipc::Secondary },
};
let doorbell_ty = t.doorbell.as_type(side);
let region_ty = t.region_ty(core);
let codec_ty = codec_path(t.codec, t.mtu);
let (tx, rx) = match side {
DoorbellRingerSide::Primary => (&t.channel_primary.1, &t.channel_secondary.1),
DoorbellRingerSide::Secondary => (&t.channel_secondary.1, &t.channel_primary.1),
};
let tx_ty = Ident::new(&tx.type_name, Span::call_site());
let rx_ty = Ident::new(&rx.type_name, Span::call_site());
let chan_tx = tx.doorbell_chan;
let chan_rx = rx.doorbell_chan;
let mtu = Literal::usize_suffixed(t.mtu as usize);
quote::quote! {
::consortium_ipc_transport_memory::impl_channel!(
#marker,
#side_ty,
#doorbell_ty,
#region_ty,
#ty_crate::#tx_ty,
#ty_crate::#rx_ty,
#codec_ty,
#chan_tx,
#chan_rx,
#mtu,
);
}
})
.collect::<Vec<_>>();
let installs = shuhan
.transceivers
.iter()
.zip(sources.iter())
.zip(markers.iter())
.map(|((t, source), marker)| {
let transport = t.build_field_init(core, uio_ident, source.clone(), hal_crate);
if core == VisibleCore::Ap {
let region_uio = t.region.address.ap.uio_id;
let doorbell_uio = t.doorbell_address.ap.uio_id;
quote::quote! {
let mut transport = #transport;
transport.keep_alive(#uio_ident[#region_uio].clone());
transport.keep_alive(#uio_ident[#doorbell_uio].clone());
::consortium_ipc_transport_memory::install::<channels::#marker>(transport);
}
} else {
quote::quote! {
::consortium_ipc_transport_memory::install::<channels::#marker>(#transport);
}
}
})
.collect::<Vec<_>>();
let installer = if core == VisibleCore::Ap {
quote::quote! {
fn __consortium_install_channels(
#uio_ident: &[::std::sync::Arc<::consortium_runtime_app::uio::UioDevice>],
) -> ::core::result::Result<(), ::consortium_runtime_app::uio::UioDeviceError> {
#(#installs)*
::core::result::Result::Ok(())
}
}
} else {
let hal: syn::Path = syn::parse_str(hal_crate).expect("hal_crate must be a valid path");
let parameters = doorbell_params.iter().map(|singleton| {
let param = doorbell_param(singleton);
quote::quote! {
#param: #hal::Peri<'static, #hal::peripherals::#singleton>
}
});
quote::quote! {
#[cfg(target_arch = "arm")]
fn __consortium_install_channels(#(#parameters),*) {
#(#installs)*
}
}
};
quote::quote! {
::consortium_ipc_transport_memory::channels!(#(#markers),*);
#(#instance_impls)*
#installer
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Transceiver {
pub name: String,
pub doorbell_address: Address,
pub mtu: u32,
pub codec: Codec,
pub region: Region,
pub channel_primary: (VisibleCore, Channel),
pub channel_secondary: (VisibleCore, Channel),
pub doorbell: DoorbellMeta,
}
impl Transceiver {
pub fn max_channels(&self) -> u32 {
let total_len = u64::from(self.region.length);
let slot_base = consortium_ipc_transport_memory::slot_offset(0, self.mtu as usize) as u64;
let slot_size = u64::from(consortium_ipc_transport_memory::slot::SLOT_HEADER as u32)
+ u64::from(self.mtu);
let first_slot_end = slot_base.saturating_add(slot_size);
if total_len < first_slot_end {
return 0;
}
let stride = consortium_ipc_transport_memory::slot_stride(self.mtu as usize) as u64;
let slots = 1 + (total_len - first_slot_end) / stride;
let slots = slots.min(consortium_ipc_transport_memory::CHANNEL_NUM_MAX as u64);
(slots / 2 * 2) as u32
}
fn side(&self, core: VisibleCore) -> Option<DoorbellRingerSide> {
if self.channel_primary.0 == core {
Some(DoorbellRingerSide::Primary)
} else if self.channel_secondary.0 == core {
Some(DoorbellRingerSide::Secondary)
} else {
None
}
}
fn region_ty(&self, core: VisibleCore) -> TokenStream {
match (core, self.region.cache) {
(VisibleCore::Ap, _) | (_, false) => {
quote::quote! { ::consortium_shared_memory::regions::UncachedRegion }
}
(_, true) => quote::quote! { ::consortium_shared_memory::regions::CachedRegion },
}
}
pub fn build_field_init(
&self,
core: VisibleCore,
uio_ident: &Ident,
source: DoorbellSource,
hal_crate: &str,
) -> TokenStream {
let side = self
.side(core)
.expect("core must be either primary or secondary for the transceiver");
let region_ident = format_ident!("region");
let region_init = self.region.initialize(core, uio_ident);
let doorbell_ident = format_ident!("doorbell");
let doorbell_code = self.doorbell.initialize(&DoorbellSite {
side,
core,
uio_ident,
uio_id: self.doorbell_address.ap.uio_id,
base: self.doorbell_address.memory_lookup(core, uio_ident),
ident: &doorbell_ident,
source: &source,
hal_crate,
});
let chan_id_tx = match side {
DoorbellRingerSide::Primary => &self.channel_primary.1.doorbell_chan,
DoorbellRingerSide::Secondary => &self.channel_secondary.1.doorbell_chan,
};
let chan_id_rx = match side {
DoorbellRingerSide::Primary => &self.channel_secondary.1.doorbell_chan,
DoorbellRingerSide::Secondary => &self.channel_primary.1.doorbell_chan,
};
let transport_side_ty = match side {
DoorbellRingerSide::Primary => quote::quote! { ::consortium_ipc::Primary },
DoorbellRingerSide::Secondary => quote::quote! { ::consortium_ipc::Secondary },
};
let transport_address = self.region.address.memory_lookup(core, uio_ident);
quote::quote! {{
let #region_ident = #region_init;
#doorbell_code
let chan_tx = unsafe { ::consortium_ipc::Chan::new_unchecked(#chan_id_tx) };
let chan_rx = unsafe { ::consortium_ipc::Chan::new_unchecked(#chan_id_rx) };
unsafe {
::consortium_ipc_transport_memory::SharedMemoryTransport::<#transport_side_ty, _, _>::new(
chan_tx,
chan_rx,
#doorbell_ident,
#region_ident,
(#transport_address) as *mut u8,
)
}
}}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DoorbellMeta {
pub r#type: HardwareDoorbellPeripheralType,
pub id: Option<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DoorbellRingerSide {
Primary,
Secondary,
}
pub struct DoorbellSite<'a> {
pub side: DoorbellRingerSide,
pub core: VisibleCore,
pub uio_ident: &'a Ident,
pub uio_id: usize,
pub base: TokenStream,
pub ident: &'a Ident,
pub source: &'a DoorbellSource,
pub hal_crate: &'a str,
}
#[derive(Debug, Clone)]
pub enum DoorbellSource {
Uio,
Owned(Ident),
Shared(Ident),
}
impl DoorbellMeta {
pub fn singleton(&self, side: DoorbellRingerSide) -> Ident {
let id = self.id.unwrap_or(1);
match self.r#type {
HardwareDoorbellPeripheralType::Mu => {
let window = match side {
DoorbellRingerSide::Primary => "MUA",
DoorbellRingerSide::Secondary => "MUB",
};
format_ident!("MU{id}_{window}")
}
HardwareDoorbellPeripheralType::Hsem => format_ident!("HSEM"),
HardwareDoorbellPeripheralType::Ipcc => format_ident!("IPCC{id}"),
}
}
fn idents(&self, side: DoorbellRingerSide) -> (Ident, Ident, Ident) {
let crate_ident = format_ident!(
"consortium_ipc_doorbell_{}",
self.r#type.to_string().to_lowercase()
);
let type_ident = format_ident!("{}Doorbell", self.r#type.to_string());
let side_ident = format_ident!(
"{}",
match side {
DoorbellRingerSide::Primary => "Primary",
DoorbellRingerSide::Secondary => "Secondary",
}
);
(crate_ident, type_ident, side_ident)
}
pub fn as_path(&self, side: DoorbellRingerSide) -> TokenStream {
let id = self.id.unwrap_or(1) as usize;
let (crate_ident, type_ident, side_ident) = self.idents(side);
quote::quote! {
::#crate_ident::#type_ident::<::consortium_ipc::#side_ident, #id>
}
}
pub fn service_call(&self, side: DoorbellRingerSide) -> TokenStream {
let id = self.id.unwrap_or(1) as usize;
let (crate_ident, _, side_ident) = self.idents(side);
match self.r#type {
HardwareDoorbellPeripheralType::Hsem => quote::quote! {
::#crate_ident::notify();
},
HardwareDoorbellPeripheralType::Ipcc => quote::quote! {
::#crate_ident::notify_rx_occupied_interrupt::<
::consortium_ipc::#side_ident, #id
>();
},
HardwareDoorbellPeripheralType::Mu => quote::quote! {
::#crate_ident::notify_gi_interrupt::<
::consortium_ipc::#side_ident, #id
>();
},
}
}
pub fn as_type(&self, side: DoorbellRingerSide) -> TokenStream {
let id = self.id.unwrap_or(1) as usize;
let (crate_ident, type_ident, side_ident) = self.idents(side);
quote::quote! {
::#crate_ident::#type_ident<::consortium_ipc::#side_ident, #id>
}
}
pub fn initialize(&self, site: &DoorbellSite<'_>) -> TokenStream {
let DoorbellSite {
side,
core,
uio_ident,
uio_id: doorbell_uio_id,
base: doorbell_base,
ident: doorbell_ident,
source,
hal_crate,
} = site;
let (side, core) = (*side, *core);
let doorbell = self.as_path(side);
let binding_uio_stmt = match core {
VisibleCore::Ap => {
let bind = match self.r#type {
HardwareDoorbellPeripheralType::Ipcc => quote::quote! {
let __service_db = #doorbell_ident.clone();
#uio_ident[#doorbell_uio_id].bind_service(::std::sync::Arc::new(
move || __service_db.notify_rx_occupied_interrupt(),
));
},
HardwareDoorbellPeripheralType::Mu => quote::quote! {
let __service_db = #doorbell_ident.clone();
#uio_ident[#doorbell_uio_id].bind_service(::std::sync::Arc::new(
move || __service_db.notify_gi_interrupt(),
));
},
HardwareDoorbellPeripheralType::Hsem => quote::quote! {
#uio_ident[#doorbell_uio_id].bind_notifier(#doorbell_ident.notifier());
},
};
Some(bind)
}
_ => None,
};
let construct = match source {
DoorbellSource::Uio => quote::quote! {
let #doorbell_ident = unsafe { #doorbell::new_mapped(#doorbell_base) };
},
DoorbellSource::Owned(param) => quote::quote! {
let #doorbell_ident = #doorbell::new(#param);
},
DoorbellSource::Shared(singleton) => {
let hal: syn::Path =
syn::parse_str(hal_crate).expect("hal_crate must be a valid path");
quote::quote! {
let #doorbell_ident = #doorbell::new(unsafe {
#hal::peripherals::#singleton::steal()
});
}
}
};
quote::quote! {
#construct
#binding_uio_stmt
}
}
}
impl FromStr for DoorbellMeta {
type Err = CfgError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let split = s.find(|ch: char| ch.is_ascii_digit()).unwrap_or(s.len());
let (prefix, suffix) = s.split_at(split);
let r#type = match prefix {
"mu" => HardwareDoorbellPeripheralType::Mu,
"hsem" => HardwareDoorbellPeripheralType::Hsem,
"ipcc" => HardwareDoorbellPeripheralType::Ipcc,
_ => return Err(CfgError::Manifest(format!("unknown doorbell type '{s}'"))),
};
let id = if suffix.is_empty() {
None
} else {
Some(
suffix
.parse()
.map_err(|_| CfgError::Manifest(format!("invalid doorbell id in '{s}'")))?,
)
};
Ok(Self { r#type, id })
}
}
impl fmt::Display for DoorbellMeta {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.r#type.to_string().to_lowercase())?;
if let Some(id) = self.id {
write!(f, "{id}")?;
}
Ok(())
}
}
fn doorbell_param(singleton: &Ident) -> Ident {
format_ident!("__db_{}", singleton.to_string().to_lowercase())
}
#[derive(Debug, Clone)]
pub struct ShuHan {
transceivers: Vec<Transceiver>,
}
impl ShuHan {
pub fn new(transceivers: Vec<Transceiver>) -> Self {
Self { transceivers }
}
pub fn transceivers(&self) -> &[Transceiver] {
&self.transceivers
}
fn doorbell_sources(
&self,
core: VisibleCore,
available: &[String],
) -> (Vec<Ident>, Vec<DoorbellSource>) {
let mut owned: Vec<Ident> = Vec::new();
let sources = self
.transceivers
.iter()
.map(|t| {
if core == VisibleCore::Ap {
return DoorbellSource::Uio;
}
let side = t
.side(core)
.expect("core must be either primary or secondary for the transceiver");
let singleton = t.doorbell.singleton(side);
if !available
.iter()
.any(|name| name.eq_ignore_ascii_case(&singleton.to_string()))
{
return DoorbellSource::Uio;
}
if owned.contains(&singleton) {
return DoorbellSource::Shared(singleton);
}
owned.push(singleton.clone());
DoorbellSource::Owned(doorbell_param(&singleton))
})
.collect();
(owned, sources)
}
pub fn doorbell_singletons(&self, core: VisibleCore, available: &[String]) -> Vec<Ident> {
self.doorbell_sources(core, available).0
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use crate::components::address::ApAddress;
use consortium_cfg_common::database::MemoryRegionType;
use quote::format_ident;
fn channel(doorbell_chan: u8) -> Channel {
Channel {
doorbell_chan,
type_name: "Ping".to_string(),
buffer_name: "ping_buf".to_string(),
}
}
fn transceiver(length: u32, mtu: u32) -> Transceiver {
let cm33 = VisibleCore::Mcore(consortium_cfg_common::config::McoreName::Cm33);
let address = Address {
ap: ApAddress {
uio_id: 0,
mmap_id: 0,
},
cp: HashMap::from([(cm33, 0x3400_0000)]),
};
Transceiver {
name: "ping".to_string(),
doorbell_address: address.clone(),
mtu,
codec: Codec::Postcard,
region: Region {
r#type: MemoryRegionType::Sram,
cache: false,
address,
length,
},
channel_primary: (VisibleCore::Ap, channel(0)),
channel_secondary: (cm33, channel(1)),
doorbell: DoorbellMeta {
r#type: HardwareDoorbellPeripheralType::Ipcc,
id: Some(1),
},
}
}
const SLOT_BASE: u32 = consortium_ipc_transport_memory::slot_offset(0, 4) as u32;
const SLOT_STRIDE: u32 = consortium_ipc_transport_memory::slot_stride(4) as u32;
#[test]
fn max_channels_is_zero_when_only_descriptor_fits() {
assert_eq!(transceiver(SLOT_BASE, 4).max_channels(), 0);
}
#[test]
fn max_channels_counts_slot_pairs() {
let pair_len = SLOT_STRIDE * 2;
assert_eq!(transceiver(SLOT_BASE + pair_len * 3, 4).max_channels(), 6);
}
#[test]
fn max_channels_counts_pair_without_trailing_padding() {
let exact = consortium_ipc_transport_memory::required_region_len(2, 128) as u32;
assert_eq!(exact, 0x24c);
assert_eq!(transceiver(exact, 128).max_channels(), 2);
assert_eq!(transceiver(exact - 1, 128).max_channels(), 0);
}
#[test]
fn max_channels_saturates_at_channel_num_max() {
let max = consortium_ipc_transport_memory::CHANNEL_NUM_MAX as u32;
assert_eq!(transceiver(u32::MAX, 4).max_channels(), max);
}
#[test]
fn cached_region_type_is_controller_only() {
let mut transceiver = transceiver(SLOT_BASE + 4096, 128);
transceiver.region.cache = true;
let cm33 = VisibleCore::Mcore(consortium_cfg_common::config::McoreName::Cm33);
assert!(
transceiver
.region_ty(cm33)
.to_string()
.contains("CachedRegion")
);
assert!(
transceiver
.region_ty(VisibleCore::Ap)
.to_string()
.contains("UncachedRegion")
);
}
#[test]
fn ap_binds_doorbell_service_to_the_doorbell_uio_device() {
let mut t = transceiver(SLOT_BASE + 4096, 128);
t.region.address.ap = ApAddress {
uio_id: 1,
mmap_id: 0,
};
t.doorbell_address.ap = ApAddress {
uio_id: 2,
mmap_id: 0,
};
let uio = format_ident!("uio");
let init = t
.build_field_init(
VisibleCore::Ap,
&uio,
DoorbellSource::Uio,
"::consortium_hal_stm32mp2",
)
.to_string();
assert!(init.contains("uio [2usize] . bind_service"), "{init}");
assert!(!init.contains("uio [1usize] . bind_service"), "{init}");
assert!(init.contains("uio [1usize] . mmap_zeroed"), "{init}");
assert!(init.contains("uio [2usize] . mmap"), "{init}");
}
#[test]
fn ap_binds_a_plain_notifier_for_hsem() {
let mut t = transceiver(SLOT_BASE + 4096, 128);
t.doorbell_address.ap = ApAddress {
uio_id: 2,
mmap_id: 0,
};
t.doorbell = DoorbellMeta {
r#type: HardwareDoorbellPeripheralType::Hsem,
id: None,
};
let uio = format_ident!("uio");
let init = t
.build_field_init(
VisibleCore::Ap,
&uio,
DoorbellSource::Uio,
"::consortium_hal_stm32mp2",
)
.to_string();
assert!(init.contains("uio [2usize] . bind_notifier"), "{init}");
assert!(!init.contains("bind_service"), "{init}");
}
#[test]
fn doorbell_path_resolves_crate_type_and_side() {
let doorbell = DoorbellMeta {
r#type: HardwareDoorbellPeripheralType::Mu,
id: Some(7),
};
let tokens = doorbell.as_path(DoorbellRingerSide::Primary).to_string();
assert!(tokens.contains(":: consortium_ipc_doorbell_mu :: MuDoorbell"));
assert!(tokens.contains(":: consortium_ipc :: Primary"));
assert!(tokens.contains("7usize"));
}
#[test]
fn doorbell_path_defaults_missing_id_to_one() {
let doorbell = DoorbellMeta {
r#type: HardwareDoorbellPeripheralType::Hsem,
id: None,
};
let tokens = doorbell.as_path(DoorbellRingerSide::Secondary).to_string();
assert!(tokens.contains(":: consortium_ipc_doorbell_hsem :: HsemDoorbell"));
assert!(tokens.contains(":: consortium_ipc :: Secondary"));
assert!(tokens.contains("1usize"));
}
#[test]
fn display_round_trips_through_from_str() {
for s in ["mu7", "hsem", "ipcc1", "mu0"] {
let doorbell: DoorbellMeta = s.parse().unwrap();
assert_eq!(doorbell.to_string(), s);
}
}
#[test]
fn rejects_invalid_doorbell_id() {
assert!("mu999".parse::<DoorbellMeta>().is_err());
}
#[test]
fn parses_typed_doorbell_with_id() {
assert_eq!(
"mu7".parse::<DoorbellMeta>().unwrap(),
DoorbellMeta {
r#type: HardwareDoorbellPeripheralType::Mu,
id: Some(7),
}
);
assert_eq!(
"ipcc1".parse::<DoorbellMeta>().unwrap(),
DoorbellMeta {
r#type: HardwareDoorbellPeripheralType::Ipcc,
id: Some(1),
}
);
}
#[test]
fn parses_typed_doorbell_without_id() {
assert_eq!(
"hsem".parse::<DoorbellMeta>().unwrap(),
DoorbellMeta {
r#type: HardwareDoorbellPeripheralType::Hsem,
id: None,
}
);
}
#[test]
fn rejects_unknown_doorbell_type() {
assert!("foo3".parse::<DoorbellMeta>().is_err());
}
}