use std::cmp::Ordering; use std::collections::{BTreeSet, HashMap}; use std::fmt::Write; use std::io::Write as _; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::LazyLock; use std::{env, fs}; use common::CfgSet; use mspm0_metapac::metadata::{ALL_CHIPS, METADATA}; use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::{format_ident, quote}; #[path = "./build_common.rs"] mod common; fn main() { generate_code(); interrupt_group_linker_magic(); } fn generate_code() { let mut cfgs = common::CfgSet::new(); common::set_target_cfgs(&mut cfgs); #[cfg(any(feature = "rt"))] println!( "cargo:rustc-link-search={}", PathBuf::from(env::var_os("OUT_DIR").unwrap()).display(), ); cfgs.declare_all(&["gpio_pb", "gpio_pc", "int_group1"]); let chip_name = match env::vars() .map(|(a, _)| a) .filter(|x| x.starts_with("CARGO_FEATURE_MSPM0") || x.starts_with("CARGO_FEATURE_MSPS")) .get_one() { Ok(x) => x, Err(GetOneError::None) => panic!("No mspm0xx/mspsxx Cargo feature enabled"), Err(GetOneError::Multiple) => panic!("Multiple mspm0xx/mspsxx Cargo features enabled"), } .strip_prefix("CARGO_FEATURE_") .unwrap() .to_ascii_lowercase() .replace('_', "-"); eprintln!("chip: {chip_name}"); cfgs.enable_all(&get_chip_cfgs(&chip_name)); for chip in ALL_CHIPS { cfgs.declare_all(&get_chip_cfgs(&chip)); } let mut singletons = get_singletons(&mut cfgs); time_driver(&mut singletons, &mut cfgs); let mut g = TokenStream::new(); g.extend(generate_singletons(&singletons)); g.extend(generate_pincm_mapping()); g.extend(generate_pin()); g.extend(generate_timers()); g.extend(generate_interrupts()); g.extend(generate_peripheral_instances()); g.extend(generate_pin_trait_impls()); g.extend(generate_groups()); g.extend(generate_dma_channel_count()); let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); fs::write(&out_file, g.to_string()).unwrap(); rustfmt(&out_file); } fn get_chip_cfgs(chip_name: &str) -> Vec { let mut cfgs = Vec::new(); // GPIO on C110x is special as it does not belong to an interrupt group. if chip_name.starts_with("mspm0c110") || chip_name.starts_with("msps003f") { cfgs.push("mspm0c110x".to_string()); } // Family ranges (temporary until int groups are generated) // // TODO: Remove this once int group stuff is generated. if chip_name.starts_with("mspm0g110") { cfgs.push("mspm0g110x".to_string()); } if chip_name.starts_with("mspm0g150") { cfgs.push("mspm0g150x".to_string()); } if chip_name.starts_with("mspm0g151") { cfgs.push("mspm0g151x".to_string()); } if chip_name.starts_with("mspm0g310") { cfgs.push("mspm0g310x".to_string()); } if chip_name.starts_with("mspm0g350") { cfgs.push("mspm0g350x".to_string()); } if chip_name.starts_with("mspm0g351") { cfgs.push("mspm0g351x".to_string()); } if chip_name.starts_with("mspm0l110") { cfgs.push("mspm0l110x".to_string()); } if chip_name.starts_with("mspm0l122") { cfgs.push("mspm0l122x".to_string()); } if chip_name.starts_with("mspm0l130") { cfgs.push("mspm0l130x".to_string()); } if chip_name.starts_with("mspm0l134") { cfgs.push("mspm0l134x".to_string()); } if chip_name.starts_with("mspm0l222") { cfgs.push("mspm0l222x".to_string()); } cfgs } /// Interrupt groups use a weakly linked symbols and #[linkage = "extern_weak"] is nightly we need to /// do some linker magic to create weak linkage. fn interrupt_group_linker_magic() { let mut file = String::new(); for group in METADATA.interrupt_groups { for interrupt in group.interrupts.iter() { let name = interrupt.name; writeln!(&mut file, "PROVIDE({name} = DefaultHandler);").unwrap(); } } let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); let out_file = out_dir.join("interrupt_group.x"); fs::write(&out_file, file).unwrap(); } fn generate_groups() -> TokenStream { let group_vectors = METADATA.interrupt_groups.iter().map(|group| { let vectors = group.interrupts.iter().map(|interrupt| { let fn_name = Ident::new(interrupt.name, Span::call_site()); quote! { pub(crate) fn #fn_name(); } }); quote! { #(#vectors)* } }); let groups = METADATA.interrupt_groups.iter().map(|group| { let interrupt_group_name = Ident::new(group.name, Span::call_site()); let group_enum = Ident::new(&format!("Group{}", &group.name[5..]), Span::call_site()); let group_number = Literal::u32_unsuffixed(group.number); let matches = group.interrupts.iter().map(|interrupt| { let variant = Ident::new(&interrupt.name, Span::call_site()); quote! { #group_enum::#variant => unsafe { group_vectors::#variant() }, } }); quote! { #[cfg(feature = "rt")] #[crate::pac::interrupt] fn #interrupt_group_name() { use crate::pac::#group_enum; let group = crate::pac::CPUSS.int_group(#group_number); // MUST subtract by 1 since 0 is NO_INTR let iidx = group.iidx().read().stat().to_bits() - 1; let Ok(group) = #group_enum::try_from(iidx as u8) else { return; }; match group { #(#matches)* } } } }); quote! { #(#groups)* #[cfg(feature = "rt")] mod group_vectors { extern "Rust" { #(#group_vectors)* } } } } fn generate_dma_channel_count() -> TokenStream { let count = METADATA.dma_channels.len(); quote! { pub const DMA_CHANNELS: usize = #count; } } #[derive(Debug, Clone)] struct Singleton { name: String, cfg: Option, } impl PartialEq for Singleton { fn eq(&self, other: &Self) -> bool { self.name == other.name } } impl Eq for Singleton {} impl PartialOrd for Singleton { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Singleton { fn cmp(&self, other: &Self) -> Ordering { self.name.cmp(&other.name) } } fn get_singletons(cfgs: &mut common::CfgSet) -> Vec { let mut singletons = Vec::::new(); for peripheral in METADATA.peripherals { // Some peripherals do not generate a singleton, but generate a singleton for each pin. let skip_peripheral_singleton = match peripheral.kind { "gpio" => { // Also enable ports that are present. match peripheral.name { "GPIOB" => cfgs.enable("gpio_pb"), "GPIOC" => cfgs.enable("gpio_pc"), _ => (), } true } // Each channel gets a singleton, handled separately. "dma" => true, // These peripherals do not exist as singletons, and have no signals but are managed // by the HAL. "iomux" | "cpuss" => true, _ => false, }; if !skip_peripheral_singleton { singletons.push(Singleton { name: peripheral.name.to_string(), cfg: None, }); } let mut signals = BTreeSet::new(); // Pick out each unique signal. There may be multiple instances of each signal due to // iomux mappings. for pin in peripheral.pins { let signal = if peripheral.name.starts_with("GPIO") || peripheral.name.starts_with("VREF") || peripheral.name.starts_with("RTC") { pin.signal.to_string() } else { format!("{}_{}", peripheral.name, pin.signal) }; // We need to rename some signals to become valid Rust identifiers. let signal = make_valid_identifier(&signal); signals.insert(signal); } singletons.extend(signals); } // DMA channels get their own singletons for dma_channel in METADATA.dma_channels.iter() { singletons.push(Singleton { name: format!("DMA_CH{}", dma_channel.number), cfg: None, }); } singletons.sort_by(|a, b| a.name.cmp(&b.name)); singletons } fn make_valid_identifier(s: &str) -> Singleton { let name = s.replace('+', "_P").replace("-", "_N"); Singleton { name, cfg: None } } fn generate_pincm_mapping() -> TokenStream { let pincms = METADATA.pins.iter().map(|mapping| { let port_letter = mapping.pin.strip_prefix("P").unwrap(); let port_base = (port_letter.chars().next().unwrap() as u8 - b'A') * 32; // This assumes all ports are single letter length. // This is fine unless TI releases a part with 833+ GPIO pins. let pin_number = mapping.pin[2..].parse::().unwrap(); let num = port_base + pin_number; // But subtract 1 since pincm indices start from 0, not 1. let pincm = Literal::u8_unsuffixed(mapping.pincm - 1); quote! { #num => #pincm } }); quote! { #[doc = "Get the mapping from GPIO pin port to IOMUX PINCM index. This is required since the mapping from IO to PINCM index is not consistent across parts."] pub(crate) fn gpio_pincm(pin_port: u8) -> u8 { match pin_port { #(#pincms),*, _ => unreachable!(), } } } } fn generate_pin() -> TokenStream { let pin_impls = METADATA.pins.iter().map(|pin| { let name = Ident::new(&pin.pin, Span::call_site()); let port_letter = pin.pin.strip_prefix("P").unwrap(); let port_letter = port_letter.chars().next().unwrap(); let pin_number = Literal::u8_unsuffixed(pin.pin[2..].parse::().unwrap()); let port = Ident::new(&format!("Port{}", port_letter), Span::call_site()); // TODO: Feature gate pins that can be used as NRST quote! { impl_pin!(#name, crate::gpio::Port::#port, #pin_number); } }); quote! { #(#pin_impls)* } } fn time_driver(singletons: &mut Vec, cfgs: &mut CfgSet) { // Timer features for (timer, _) in TIMERS.iter() { let name = timer.to_lowercase(); cfgs.declare(&format!("time_driver_{}", name)); } let time_driver = match env::vars() .map(|(a, _)| a) .filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_")) .get_one() { Ok(x) => Some( x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_") .unwrap() .to_ascii_lowercase(), ), Err(GetOneError::None) => None, Err(GetOneError::Multiple) => panic!("Multiple time-driver-xxx Cargo features enabled"), }; // Verify the selected timer is available let selected_timer = match time_driver.as_ref().map(|x| x.as_ref()) { None => "", Some("timg0") => "TIMG0", Some("timg1") => "TIMG1", Some("timg2") => "TIMG2", Some("timg3") => "TIMG3", Some("timg4") => "TIMG4", Some("timg5") => "TIMG5", Some("timg6") => "TIMG6", Some("timg7") => "TIMG7", Some("timg8") => "TIMG8", Some("timg9") => "TIMG9", Some("timg10") => "TIMG10", Some("timg11") => "TIMG11", Some("timg14") => "TIMG14", Some("tima0") => "TIMA0", Some("tima1") => "TIMA1", Some("any") => { // Order of timer candidates: // 1. 16-bit, 2 channel // 2. 16-bit, 2 channel with shadow registers // 3. 16-bit, 4 channel // 4. 16-bit with QEI // 5. Advanced timers // // TODO: Select RTC first if available // TODO: 32-bit timers are not considered yet [ // 16-bit, 2 channel "TIMG0", "TIMG1", "TIMG2", "TIMG3", // 16-bit, 2 channel with shadow registers "TIMG4", "TIMG5", "TIMG6", "TIMG7", // 16-bit, 4 channel "TIMG14", // 16-bit with QEI "TIMG8", "TIMG9", "TIMG10", "TIMG11", // Advanced timers "TIMA0", "TIMA1", ] .iter() .find(|tim| singletons.iter().any(|s| s.name == **tim)) .expect("Could not find any timer") } _ => panic!("unknown time_driver {:?}", time_driver), }; if !selected_timer.is_empty() { cfgs.enable(format!("time_driver_{}", selected_timer.to_lowercase())); } // Apply cfgs to each timer and it's pins for singleton in singletons.iter_mut() { if singleton.name.starts_with("TIM") { // Remove suffixes for pin singletons. let name = if singleton.name.contains("_CCP") { singleton.name.split_once("_CCP").unwrap().0 } else if singleton.name.contains("_FAULT") { singleton.name.split_once("_FAULT").unwrap().0 } else if singleton.name.contains("_IDX") { singleton.name.split_once("_IDX").unwrap().0 } else { &singleton.name }; let feature = format!("time-driver-{}", name.to_lowercase()); if singleton.name.contains(selected_timer) { singleton.cfg = Some(quote! { #[cfg(not(all(feature = "time-driver-any", feature = #feature)))] }); } else { singleton.cfg = Some(quote! { #[cfg(not(feature = #feature))] }); } } } } fn generate_singletons(singletons: &[Singleton]) -> TokenStream { let singletons = singletons .iter() .map(|s| { let cfg = s.cfg.clone().unwrap_or_default(); let ident = format_ident!("{}", s.name); quote! { #cfg #ident } }) .collect::>(); quote! { embassy_hal_internal::peripherals_definition!(#(#singletons),*); embassy_hal_internal::peripherals_struct!(#(#singletons),*); } } fn generate_timers() -> TokenStream { // Generate timers let timer_impls = METADATA .peripherals .iter() .filter(|p| p.name.starts_with("TIM")) .map(|peripheral| { let name = Ident::new(&peripheral.name, Span::call_site()); let timers = &*TIMERS; let timer = timers.get(peripheral.name).expect("Timer does not exist"); assert!(timer.bits == 16 || timer.bits == 32); let bits = if timer.bits == 16 { quote! { Bits16 } } else { quote! { Bits32 } }; quote! { impl_timer!(#name, #bits); } }); quote! { #(#timer_impls)* } } fn generate_interrupts() -> TokenStream { // Generate interrupt module let interrupts: Vec = METADATA .interrupts .iter() .map(|interrupt| Ident::new(interrupt.name, Span::call_site())) .collect(); let group_interrupt_enables = METADATA .interrupts .iter() .filter(|interrupt| interrupt.name.contains("GROUP")) .map(|interrupt| { let name = Ident::new(interrupt.name, Span::call_site()); quote! { crate::interrupt::typelevel::#name::enable(); } }); // Generate interrupt enables for groups quote! { embassy_hal_internal::interrupt_mod! { #(#interrupts),* } pub fn enable_group_interrupts(_cs: critical_section::CriticalSection) { use crate::interrupt::typelevel::Interrupt; unsafe { #(#group_interrupt_enables)* } } } } fn generate_peripheral_instances() -> TokenStream { let mut impls = Vec::::new(); for peripheral in METADATA.peripherals { let peri = format_ident!("{}", peripheral.name); let tokens = match peripheral.kind { "uart" => Some(quote! { impl_uart_instance!(#peri); }), _ => None, }; if let Some(tokens) = tokens { impls.push(tokens); } } // DMA channels for dma_channel in METADATA.dma_channels.iter() { let peri = format_ident!("DMA_CH{}", dma_channel.number); let num = dma_channel.number; if dma_channel.full { impls.push(quote! { impl_full_dma_channel!(#peri, #num); }); } else { impls.push(quote! { impl_dma_channel!(#peri, #num); }); } } quote! { #(#impls)* } } fn generate_pin_trait_impls() -> TokenStream { let mut impls = Vec::::new(); for peripheral in METADATA.peripherals { for pin in peripheral.pins { let key = (peripheral.kind, pin.signal); let pin_name = format_ident!("{}", pin.pin); let peri = format_ident!("{}", peripheral.name); let pf = pin.pf; // Will be filled in when uart implementation is finished let _ = pin_name; let _ = peri; let _ = pf; let tokens = match key { ("uart", "TX") => Some(quote! { impl_uart_tx_pin!(#peri, #pin_name, #pf); }), ("uart", "RX") => Some(quote! { impl_uart_rx_pin!(#peri, #pin_name, #pf); }), ("uart", "CTS") => Some(quote! { impl_uart_cts_pin!(#peri, #pin_name, #pf); }), ("uart", "RTS") => Some(quote! { impl_uart_rts_pin!(#peri, #pin_name, #pf); }), _ => None, }; if let Some(tokens) = tokens { impls.push(tokens); } } } quote! { #(#impls)* } } /// rustfmt a given path. /// Failures are logged to stderr and ignored. fn rustfmt(path: impl AsRef) { let path = path.as_ref(); match Command::new("rustfmt").args([path]).output() { Err(e) => { eprintln!("failed to exec rustfmt {:?}: {:?}", path, e); } Ok(out) => { if !out.status.success() { eprintln!("rustfmt {:?} failed:", path); eprintln!("=== STDOUT:"); std::io::stderr().write_all(&out.stdout).unwrap(); eprintln!("=== STDERR:"); std::io::stderr().write_all(&out.stderr).unwrap(); } } } } #[allow(dead_code)] struct TimerDesc { bits: u8, /// Is there an 8-bit prescaler prescaler: bool, /// Is there a repeat counter repeat_counter: bool, ccp_channels_internal: u8, ccp_channels_external: u8, external_pwm_channels: u8, phase_load: bool, shadow_load: bool, shadow_ccs: bool, deadband: bool, fault_handler: bool, qei_hall: bool, } /// Description of all timer instances. const TIMERS: LazyLock> = LazyLock::new(|| { let mut map = HashMap::new(); map.insert( "TIMG0".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG1".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG2".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG3".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG4".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: true, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG5".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: true, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG6".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: true, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG7".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: true, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG8".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: true, }, ); map.insert( "TIMG9".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: true, }, ); map.insert( "TIMG10".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: true, }, ); map.insert( "TIMG11".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: true, }, ); map.insert( "TIMG12".into(), TimerDesc { bits: 32, prescaler: false, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG13".into(), TimerDesc { bits: 32, prescaler: false, repeat_counter: false, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 2, phase_load: false, shadow_load: false, shadow_ccs: true, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMG14".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: false, ccp_channels_internal: 4, ccp_channels_external: 4, external_pwm_channels: 4, phase_load: false, shadow_load: false, shadow_ccs: false, deadband: false, fault_handler: false, qei_hall: false, }, ); map.insert( "TIMA0".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: true, ccp_channels_internal: 4, ccp_channels_external: 2, external_pwm_channels: 8, phase_load: true, shadow_load: true, shadow_ccs: true, deadband: true, fault_handler: true, qei_hall: false, }, ); map.insert( "TIMA1".into(), TimerDesc { bits: 16, prescaler: true, repeat_counter: true, ccp_channels_internal: 2, ccp_channels_external: 2, external_pwm_channels: 4, phase_load: true, shadow_load: true, shadow_ccs: true, deadband: true, fault_handler: true, qei_hall: false, }, ); map }); enum GetOneError { None, Multiple, } trait IteratorExt: Iterator { fn get_one(self) -> Result; } impl IteratorExt for T { fn get_one(mut self) -> Result { match self.next() { None => Err(GetOneError::None), Some(res) => match self.next() { Some(_) => Err(GetOneError::Multiple), None => Ok(res), }, } } }