use proc_macro2::TokenStream;
use quote::quote;
use syn::parse_str;
#[derive(Clone, Copy)]
enum Family {
Stm32mp2,
Imx9,
}
impl Family {
fn from_hal_crate(hal_crate: &str) -> Option<Self> {
if hal_crate.contains("stm32mp2") {
Some(Family::Stm32mp2)
} else if hal_crate.contains("imx9") {
Some(Family::Imx9)
} else {
None
}
}
fn timer(self) -> &'static str {
match self {
Family::Stm32mp2 => "TIM2",
Family::Imx9 => "LPIT1",
}
}
}
pub struct TimeDriverCodegen {
pub timestamp: TokenStream,
pub isr: TokenStream,
pub bringup: TokenStream,
}
impl TimeDriverCodegen {
fn empty() -> Self {
Self {
timestamp: quote! {},
isr: quote! {},
bringup: quote! {},
}
}
}
pub fn time_driver_codegen(
hal_crate: &str,
enabled: bool,
dbg_present: bool,
available: Option<&[String]>,
) -> TimeDriverCodegen {
if !enabled {
return TimeDriverCodegen::empty();
}
let Some(family) = Family::from_hal_crate(hal_crate) else {
return TimeDriverCodegen::empty();
};
let timer = family.timer();
if available.is_some_and(|names| !names.iter().any(|name| name == timer)) {
return TimeDriverCodegen::empty();
}
let hal: syn::Path = parse_str(hal_crate).expect("hal_crate must be a valid path");
let timer = quote::format_ident!("{timer}");
let bringup = quote! {
#hal::timer_driver::bringup(__p.#timer);
::consortium_runtime_mcu::log::trace!("consortium controller init: time driver up");
};
let isr = quote! {
#[#hal::interrupt]
fn #timer() {
#hal::timer_driver::on_timer_interrupt();
}
};
let timestamp = if dbg_present {
quote! {
#[cfg(target_arch = "arm")]
defmt::timestamp!("{=u32}", {
use ::core::sync::atomic::{AtomicU32, Ordering};
static COUNT: AtomicU32 = AtomicU32::new(0);
COUNT.fetch_add(1, Ordering::Relaxed)
});
}
} else {
quote! {}
};
TimeDriverCodegen {
timestamp,
isr,
bringup,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn names(list: &[&str]) -> Vec<String> {
list.iter().map(|name| (*name).to_owned()).collect()
}
#[test]
fn emits_when_the_core_reaches_the_timer() {
let time = time_driver_codegen(
"::consortium_hal_stm32mp2",
true,
true,
Some(&names(&["IPCC1", "TIM2"])),
);
assert!(time.bringup.to_string().contains("bringup (__p . TIM2)"));
assert!(time.isr.to_string().contains("fn TIM2 ()"));
assert!(!time.timestamp.is_empty());
}
#[test]
fn skips_a_core_without_the_timer() {
let time = time_driver_codegen(
"::consortium_hal_stm32mp2",
true,
true,
Some(&names(&["IPCC2", "LPTIM3", "LPTIM4", "LPTIM5"])),
);
assert!(time.bringup.is_empty());
assert!(time.isr.is_empty());
assert!(time.timestamp.is_empty());
}
#[test]
fn keeps_the_default_for_a_core_with_no_chip_map() {
let time = time_driver_codegen("::consortium_hal_imx9", true, false, None);
assert!(time.bringup.to_string().contains("bringup (__p . LPIT1)"));
assert!(time.timestamp.is_empty());
}
}