use core::fmt;
use std::str::FromStr;
use consortium_cfg_common::{
config::{Codec, VisibleCore},
database::{HardwareDoorbellPeripheralType, MemoryRegionType},
};
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,
}
pub struct ChannelInit {
pub buffer: TokenStream,
pub channel: TokenStream,
}
impl Channel {
pub fn initialize(
&self,
dir: Direction,
chan_ident: &Ident,
transport_ident: &Ident,
codec: Codec,
mtu: u32,
ty_crate: &Path,
) -> ChannelInit {
let direction_ty = match dir {
Direction::Tx => quote::quote! { ::consortium_ipc::Tx },
Direction::Rx => quote::quote! { ::consortium_ipc::Rx },
};
let type_ident = format_ident!("{}", self.type_name);
let codec_token = codec_path(codec, mtu);
let buf_static = format_ident!("{}", self.buffer_name.to_uppercase());
let mtu = Literal::usize_suffixed(mtu as usize);
let buffer = quote::quote! {
static mut #buf_static: [u8; #mtu] = [0u8; #mtu];
};
let channel = quote::quote! {
::consortium_ipc::Channel::<#direction_ty, #ty_crate::#type_ident, _, #codec_token>::new(
#chan_ident,
#transport_ident,
{
unsafe { &mut *::core::ptr::addr_of_mut!(#buf_static) }
},
)
};
ChannelInit { buffer, channel }
}
}
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 enum Direction {
Tx,
Rx,
}
#[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 = self.region.length;
let desc_len = consortium_ipc_transport_memory::DESCRIPTOR_LENGTH as u32;
let header_len = consortium_ipc_transport_memory::slot::SLOT_HEADER as u32;
let available = total_len.saturating_sub(desc_len);
let slot_len = header_len.saturating_add(self.mtu);
let pair_len = slot_len.saturating_mul(2);
if pair_len == 0 {
return 0;
}
let pairs = available / pair_len;
pairs
.saturating_mul(2)
.min(consortium_ipc_transport_memory::CHANNEL_NUM_MAX 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
}
}
pub fn transport_ty(&self, core: VisibleCore) -> TokenStream {
let side = self
.side(core)
.expect("core must be either primary or secondary for the transceiver");
let doorbell_type = self.doorbell.as_type(side);
let region_type = self.region_ty();
let side_type = match side {
DoorbellRingerSide::Primary => quote::quote! { ::consortium_ipc::Primary },
DoorbellRingerSide::Secondary => quote::quote! { ::consortium_ipc::Secondary },
};
quote::quote! {
::consortium_ipc_transport_memory::SharedMemoryTransport<#side_type, #doorbell_type, #region_type>
}
}
fn region_ty(&self) -> TokenStream {
match self.region.r#type {
MemoryRegionType::Sram => {
quote::quote! { ::consortium_shared_memory::regions::SramRegion }
}
}
}
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,
)
}
}}
}
pub fn connect_field_init(
&self,
core: VisibleCore,
field_ident: &Ident,
ty_crate: &Path,
) -> (TokenStream, TokenStream) {
let side = self
.side(core)
.expect("core must be either primary or secondary for the transceiver");
let channel_tx = match side {
DoorbellRingerSide::Primary => &self.channel_primary.1,
DoorbellRingerSide::Secondary => &self.channel_secondary.1,
};
let channel_rx = match side {
DoorbellRingerSide::Primary => &self.channel_secondary.1,
DoorbellRingerSide::Secondary => &self.channel_primary.1,
};
let t_tx = format_ident!("t_tx");
let t_rx = format_ident!("t_rx");
let chan_tx = format_ident!("chan_tx");
let chan_rx = format_ident!("chan_rx");
let tx = channel_tx.initialize(
Direction::Tx,
&chan_tx,
&t_tx,
self.codec,
self.mtu,
ty_crate,
);
let rx = channel_rx.initialize(
Direction::Rx,
&chan_rx,
&t_rx,
self.codec,
self.mtu,
ty_crate,
);
let (tx_buffer, channel_tx_init) = (tx.buffer, tx.channel);
let (rx_buffer, channel_rx_init) = (rx.buffer, rx.channel);
let transfer_size = Literal::usize_suffixed(self.mtu as usize);
let map_err = quote::quote! {
|__e| match __e {
::consortium_ipc_transport_memory::Error::Descriptor(__d) => {
ConsortiumConnectError::Descriptor(__d)
}
::consortium_ipc_transport_memory::Error::Doorbell(_) => {
ConsortiumConnectError::Doorbell
}
_ => ConsortiumConnectError::Other,
}
};
let handshake = quote::quote! {
::consortium_ipc::Connect::connect(
&mut transport,
::consortium_ipc_transport_memory::DescriptorLayout::new(2u8, #transfer_size),
).await.map_err(#map_err)?;
};
let buffers = quote::quote! {
#tx_buffer
#rx_buffer
};
let expr = quote::quote! {{
let mut transport = self.#field_ident;
let (#chan_tx, #chan_rx) = transport.channels();
#handshake
let (#t_tx, #t_rx) = transport.split();
let channel_tx = #channel_tx_init;
let channel_rx = #channel_rx_init;
::consortium_ipc::Transceiver::new(channel_tx, channel_rx)
}};
(buffers, expr)
}
pub fn ty_signature(&self, core: VisibleCore, ty_crate: &Path) -> TokenStream {
let side = self
.side(core)
.expect("core must be either primary or secondary for the transceiver");
let doorbell_type = self.doorbell.as_type(side);
let region_type = self.region_ty();
let side_type = match side {
DoorbellRingerSide::Primary => {
quote::quote! { ::consortium_ipc_transport_memory::Primary }
}
DoorbellRingerSide::Secondary => {
quote::quote! { ::consortium_ipc_transport_memory::Secondary }
}
};
let transport_tx_type = quote::quote! {
::consortium_ipc_transport_memory::SharedMemoryTransportTx<#side_type, #doorbell_type, #region_type>
};
let transport_rx_type = quote::quote! {
::consortium_ipc_transport_memory::SharedMemoryTransportRx<#side_type, #doorbell_type, #region_type>
};
let codec_type = codec_path(self.codec, self.mtu);
let chan_tx = match side {
DoorbellRingerSide::Primary => &self.channel_primary.1,
DoorbellRingerSide::Secondary => &self.channel_secondary.1,
};
let chan_rx = match side {
DoorbellRingerSide::Primary => &self.channel_secondary.1,
DoorbellRingerSide::Secondary => &self.channel_primary.1,
};
let ty_tx = Ident::new(&chan_tx.type_name, proc_macro2::Span::call_site());
let ty_rx = Ident::new(&chan_rx.type_name, proc_macro2::Span::call_site());
quote::quote! {
::consortium_ipc::Transceiver<#ty_crate::#ty_tx, #ty_crate::#ty_rx, #transport_tx_type, #transport_rx_type, #codec_type>
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DoorbellMeta {
pub r#type: HardwareDoorbellPeripheralType,
pub id: Option<u8>,
}
#[derive(Debug, Clone, Copy)]
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 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
}
pub fn initialize(
&self,
core: VisibleCore,
uio_ident: &Ident,
ty_crate: &Path,
hal_crate: &str,
available: &[String],
) -> TokenStream {
let (doorbell_params, sources) = self.doorbell_sources(core, available);
let disconnected_def = self.transceivers.iter().map(|t| {
let ident = Ident::new(t.name.as_str(), Span::call_site());
let ty = t.transport_ty(core);
quote::quote! { #ident: #ty }
});
let connected_def = self.transceivers.iter().map(|t| {
let ident = Ident::new(t.name.as_str(), Span::call_site());
let ty = t.ty_signature(core, ty_crate);
quote::quote! { #ident: #ty }
});
let build_inits = self
.transceivers
.iter()
.zip(sources.iter())
.map(|(t, source)| {
let ident = Ident::new(t.name.as_str(), Span::call_site());
let init = t.build_field_init(core, uio_ident, source.clone(), hal_crate);
quote::quote! { #ident: #init }
})
.collect::<Vec<_>>();
let mut connect_buffers = Vec::new();
let connect_inits = self
.transceivers
.iter()
.map(|t| {
let ident = Ident::new(t.name.as_str(), Span::call_site());
let (buffers, init) = t.connect_field_init(core, &ident, ty_crate);
connect_buffers.push(buffers);
quote::quote! { #ident: #init }
})
.collect::<Vec<_>>();
let (new_sig, new_body) = if core == VisibleCore::Ap {
(
quote::quote! {
fn new(
#uio_ident: &[::consortium_runtime_app::uio::UioDevice],
) -> ::core::result::Result<Self, ::consortium_runtime_app::uio::UioDeviceError>
},
quote::quote! {
::core::result::Result::Ok(Self {
#(#build_inits),*
})
},
)
} 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! { fn new(#(#parameters),*) -> Self },
quote::quote! {
Self {
#(#build_inits),*
}
},
)
};
let connect_error_derive = if core == VisibleCore::Ap {
quote::quote! { #[derive(Debug)] }
} else {
quote::quote! {
#[cfg_attr(target_os = "none", derive(Debug, defmt::Format))]
#[cfg_attr(not(target_os = "none"), derive(Debug))]
}
};
quote::quote! {
#connect_error_derive
#[allow(dead_code)]
enum ConsortiumConnectError {
Descriptor(::consortium_ipc_transport_memory::DescriptorError),
Doorbell,
Other,
}
struct IpcMemoryEndpoints {
#(#disconnected_def),*
}
#(#connect_buffers)*
struct IpcMemoryEndpointsConnected {
#(#connected_def),*
}
impl IpcMemoryEndpoints {
#new_sig {
#new_body
}
async fn connect(
self,
) -> ::core::result::Result<IpcMemoryEndpointsConnected, ConsortiumConnectError> {
::core::result::Result::Ok(IpcMemoryEndpointsConnected {
#(#connect_inits),*
})
}
}
}
}
}
#[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(),
}
}
#[test]
fn channel_tx_uses_tx_direction_marker() {
let chan = format_ident!("chan");
let transport = format_ident!("transport");
let ty_crate: Path = syn::parse_str("::shared").unwrap();
let init = channel(0).initialize(
Direction::Tx,
&chan,
&transport,
Codec::Postcard,
128,
&ty_crate,
);
let buffer = init.buffer.to_string();
let channel = init.channel.to_string();
assert!(channel.contains(":: consortium_ipc :: Channel"));
assert!(channel.contains(":: consortium_ipc :: Tx"));
assert!(buffer.contains("static mut PING_BUF : [u8 ; 128usize]"));
assert!(channel.contains("addr_of_mut ! (PING_BUF)"));
}
#[test]
fn channel_rx_uses_rx_direction_marker() {
let chan = format_ident!("chan");
let transport = format_ident!("transport");
let ty_crate: Path = syn::parse_str("::shared").unwrap();
let channel = channel(1)
.initialize(
Direction::Rx,
&chan,
&transport,
Codec::Postcard,
128,
&ty_crate,
)
.channel
.to_string();
assert!(channel.contains(":: consortium_ipc :: Rx"));
assert!(!channel.contains(":: consortium_ipc :: Tx"));
}
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,
address,
length,
},
channel_primary: (VisibleCore::Ap, channel(0)),
channel_secondary: (cm33, channel(1)),
doorbell: DoorbellMeta {
r#type: HardwareDoorbellPeripheralType::Hsem,
id: None,
},
}
}
const DESC: u32 = consortium_ipc_transport_memory::DESCRIPTOR_LENGTH as u32;
#[test]
fn max_channels_is_zero_when_only_descriptor_fits() {
assert_eq!(transceiver(DESC, 4).max_channels(), 0);
}
#[test]
fn max_channels_counts_slot_pairs() {
let pair_len = (consortium_ipc_transport_memory::slot::SLOT_HEADER as u32 + 4) * 2;
assert_eq!(transceiver(DESC + pair_len * 3, 4).max_channels(), 6);
}
#[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 ap_binds_doorbell_service_to_the_doorbell_uio_device() {
let mut t = transceiver(DESC + 4096, 128);
t.region.address.ap = ApAddress {
uio_id: 1,
mmap_id: 0,
};
t.doorbell_address.ap = ApAddress {
uio_id: 2,
mmap_id: 0,
};
t.doorbell = DoorbellMeta {
r#type: HardwareDoorbellPeripheralType::Ipcc,
id: Some(1),
};
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_doorbell_notifier_to_the_doorbell_uio_device() {
let mut t = transceiver(DESC + 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_notifier"), "{init}");
assert!(!init.contains("uio [1usize] . bind_notifier"), "{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());
}
}