use consortium_cfg_common::config::VisibleCore;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use crate::components::irq::InterruptLine;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PeripheralKind {
I2c {
clock_hz: u32,
frequency: u32,
irq: InterruptLine,
},
Gpio,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PeripheralInstance {
pub owner: VisibleCore,
pub key: String,
pub singleton: String,
pub kind: PeripheralKind,
}
#[derive(Debug, Clone)]
pub struct Peripherals {
pub hal_crate: String,
pub instances: Vec<PeripheralInstance>,
}
impl Peripherals {
pub fn initialize(&self, core: VisibleCore) -> Option<TokenStream> {
let owned: Vec<&PeripheralInstance> =
self.instances.iter().filter(|p| p.owner == core).collect();
let hal: syn::Path =
syn::parse_str(&self.hal_crate).expect("hal_crate must be a valid path");
let mut fields = Vec::new();
let mut constants = Vec::new();
for instance in owned {
let singleton = ident(&instance.singleton);
fields.push(quote! {
pub #singleton: #hal::Peri<'static, #hal::peripherals::#singleton>
});
match &instance.kind {
PeripheralKind::I2c {
clock_hz,
frequency,
..
} => {
let clock_const = ident(&format!("{}_CLOCK_HZ", instance.singleton));
let frequency_const = ident(&format!("{}_FREQUENCY", instance.singleton));
constants.push(quote! {
pub const #clock_const: u32 = #clock_hz;
pub const #frequency_const: u32 = #frequency;
});
}
PeripheralKind::Gpio | PeripheralKind::Other => {}
}
}
Some(quote! {
#[cfg(target_arch = "arm")]
#[allow(non_snake_case)]
pub struct Peripherals {
#(#fields,)*
}
#(#constants)*
})
}
pub fn vectors(&self, core: VisibleCore) -> Vec<TokenStream> {
let hal: syn::Path =
syn::parse_str(&self.hal_crate).expect("hal_crate must be a valid path");
let hal_tokens: TokenStream = self
.hal_crate
.parse()
.expect("hal crate path is a valid token stream");
self.instances
.iter()
.filter(|instance| instance.owner == core)
.filter_map(|instance| {
let PeripheralKind::I2c { irq, .. } = &instance.kind else {
return None;
};
let line = ident(irq.variant.as_ref()?);
let singleton = ident(&instance.singleton);
let guard = irq.guard(&hal_tokens);
Some(quote! {
#guard
#[#hal::interrupt]
fn #line() {
#hal::i2c::on_interrupt::<#hal::peripherals::#singleton>();
}
})
})
.collect()
}
pub fn nvic(&self, core: VisibleCore) -> TokenStream {
let hal: TokenStream = self
.hal_crate
.parse()
.expect("hal crate path is a valid token stream");
let statements = self.instances.iter().filter_map(|instance| {
if instance.owner != core {
return None;
}
let PeripheralKind::I2c { irq, .. } = &instance.kind else {
return None;
};
let (line, _) = irq.tokens(&hal)?;
Some(quote! {
unsafe {
#hal::hal::nvic::set_priority(#line, 0x80u8);
#hal::hal::nvic::enable(#line);
}
})
});
quote! { #(#statements)* }
}
pub fn arguments(&self, core: VisibleCore) -> Vec<Ident> {
self.instances
.iter()
.filter(|p| p.owner == core)
.map(|p| ident(&p.singleton))
.collect()
}
}
fn ident(raw: &str) -> Ident {
let mut out = String::with_capacity(raw.len());
for ch in raw.chars() {
if ch.is_ascii_alphanumeric() || ch == '_' {
out.push(ch);
} else {
out.push('_');
}
}
if out.is_empty() || out.starts_with(|ch: char| ch.is_ascii_digit()) {
out.insert(0, '_');
}
Ident::new(&out, Span::call_site())
}
#[cfg(test)]
mod tests {
use super::*;
use consortium_cfg_common::config::McoreName;
fn cm33() -> VisibleCore {
VisibleCore::Mcore(McoreName::Cm33)
}
fn cm0p() -> VisibleCore {
VisibleCore::Mcore(McoreName::Cm0p)
}
fn peripherals(owner: VisibleCore, irq: InterruptLine) -> Peripherals {
Peripherals {
hal_crate: "::consortium_hal_stm32mp2".to_string(),
instances: vec![PeripheralInstance {
owner,
key: "i2c8".to_string(),
singleton: "I2C8".to_string(),
kind: PeripheralKind::I2c {
clock_hz: 24_000_000,
frequency: 100_000,
irq,
},
}],
}
}
#[test]
fn emits_a_guarded_vector_for_a_pac_covered_core() {
let line = InterruptLine {
number: 212,
variant: Some("I2C8".to_string()),
};
let vectors = peripherals(cm33(), line).vectors(cm33());
assert_eq!(vectors.len(), 1);
let tokens = vectors[0].to_string();
assert!(tokens.contains("[:: consortium_hal_stm32mp2 :: interrupt] fn I2C8 ()"));
assert!(
tokens.contains(":: consortium_hal_stm32mp2 :: Interrupt :: I2C8 as u16 == 212u16")
);
}
#[test]
fn emits_no_vector_for_a_core_the_pac_does_not_describe() {
let ir = peripherals(cm0p(), InterruptLine::new(15));
assert!(ir.vectors(cm0p()).is_empty());
let nvic = ir.nvic(cm0p()).to_string();
assert!(nvic.contains("hal :: nvic :: enable"), "{nvic}");
assert!(nvic.contains("Irq (15u16)"), "{nvic}");
assert!(!nvic.contains("Interrupt ::"), "{nvic}");
}
#[test]
fn vectors_are_scoped_to_the_owning_core() {
let line = InterruptLine {
number: 212,
variant: Some("I2C8".to_string()),
};
assert!(peripherals(cm33(), line).vectors(cm0p()).is_empty());
}
}