1mod cfg;
3
4use core::str::FromStr;
5use std::{fmt::Write, sync::OnceLock};
6
7use anyhow::{Result, bail, ensure};
8use cfg::PeriConfig;
9use indexmap::IndexMap;
10pub use proc_macro2::TokenStream;
11use quote::{format_ident, quote};
12use strum::IntoEnumIterator;
13
14use crate::cfg::{SupportItem, SupportStatus, Value};
15
16macro_rules! include_toml {
17 (Config, $file:expr) => {{
18 static LOADED_TOML: OnceLock<Config> = OnceLock::new();
19 LOADED_TOML.get_or_init(|| {
20 let config: Config = basic_toml::from_str(include_str!($file)).unwrap();
21
22 config.validate().expect("Invalid device configuration");
23
24 config
25 })
26 }};
27}
28
29#[derive(
31 Debug,
32 Clone,
33 Copy,
34 PartialEq,
35 Eq,
36 PartialOrd,
37 Ord,
38 serde::Deserialize,
39 serde::Serialize,
40 strum::Display,
41 strum::EnumIter,
42 strum::EnumString,
43 strum::AsRefStr,
44)]
45#[serde(rename_all = "lowercase")]
46#[strum(serialize_all = "lowercase")]
47pub enum Arch {
48 RiscV,
50 Xtensa,
52}
53
54#[derive(
56 Debug,
57 Clone,
58 Copy,
59 PartialEq,
60 Eq,
61 PartialOrd,
62 Ord,
63 serde::Deserialize,
64 serde::Serialize,
65 strum::Display,
66 strum::EnumIter,
67 strum::EnumString,
68 strum::AsRefStr,
69)]
70pub enum Cores {
71 #[serde(rename = "single_core")]
73 #[strum(serialize = "single_core")]
74 Single,
75 #[serde(rename = "multi_core")]
77 #[strum(serialize = "multi_core")]
78 Multi,
79}
80
81#[derive(
83 Debug,
84 Clone,
85 Copy,
86 PartialEq,
87 Eq,
88 PartialOrd,
89 Ord,
90 Hash,
91 serde::Deserialize,
92 serde::Serialize,
93 strum::Display,
94 strum::EnumIter,
95 strum::EnumString,
96 strum::AsRefStr,
97)]
98#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
99#[serde(rename_all = "kebab-case")]
100#[strum(serialize_all = "kebab-case")]
101pub enum Chip {
102 Esp32,
104 Esp32c2,
106 Esp32c3,
108 Esp32c6,
110 Esp32h2,
112 Esp32s2,
114 Esp32s3,
116}
117
118impl Chip {
119 pub fn from_cargo_feature() -> Result<Self> {
120 let all_chips = Chip::iter().map(|c| c.to_string()).collect::<Vec<_>>();
121
122 let mut chip = None;
123 for c in all_chips.iter() {
124 if std::env::var(format!("CARGO_FEATURE_{}", c.to_uppercase())).is_ok() {
125 if chip.is_some() {
126 bail!(
127 "Expected exactly one of the following features to be enabled: {}",
128 all_chips.join(", ")
129 );
130 }
131 chip = Some(c);
132 }
133 }
134
135 let Some(chip) = chip else {
136 bail!(
137 "Expected exactly one of the following features to be enabled: {}",
138 all_chips.join(", ")
139 );
140 };
141
142 Ok(Self::from_str(chip.as_str()).unwrap())
143 }
144
145 pub fn target(&self) -> String {
146 Config::for_chip(self).device.target.clone()
147 }
148
149 pub fn has_lp_core(&self) -> bool {
150 use Chip::*;
151
152 matches!(self, Esp32c6 | Esp32s2 | Esp32s3)
153 }
154
155 pub fn lp_target(&self) -> Result<&'static str> {
156 match self {
157 Chip::Esp32c6 => Ok("riscv32imac-unknown-none-elf"),
158 Chip::Esp32s2 | Chip::Esp32s3 => Ok("riscv32imc-unknown-none-elf"),
159 _ => bail!("Chip does not contain an LP core: '{self}'"),
160 }
161 }
162
163 pub fn name(&self) -> &str {
164 match self {
165 Chip::Esp32 => "Esp32",
166 Chip::Esp32c2 => "Esp32c2",
167 Chip::Esp32c3 => "Esp32c3",
168 Chip::Esp32c6 => "Esp32c6",
169 Chip::Esp32h2 => "Esp32h2",
170 Chip::Esp32s2 => "Esp32s2",
171 Chip::Esp32s3 => "Esp32s3",
172 }
173 }
174
175 pub fn pretty_name(&self) -> &str {
176 match self {
177 Chip::Esp32 => "ESP32",
178 Chip::Esp32c2 => "ESP32-C2",
179 Chip::Esp32c3 => "ESP32-C3",
180 Chip::Esp32c6 => "ESP32-C6",
181 Chip::Esp32h2 => "ESP32-H2",
182 Chip::Esp32s2 => "ESP32-S2",
183 Chip::Esp32s3 => "ESP32-S3",
184 }
185 }
186
187 pub fn is_xtensa(&self) -> bool {
188 matches!(self, Chip::Esp32 | Chip::Esp32s2 | Chip::Esp32s3)
189 }
190
191 pub fn is_riscv(&self) -> bool {
192 !self.is_xtensa()
193 }
194
195 pub fn list_of_check_cfgs() -> Vec<String> {
196 let mut cfgs = vec![];
197
198 cfgs.push(String::from("cargo:rustc-check-cfg=cfg(not_really_docsrs)"));
200
201 let mut cfg_values: IndexMap<String, Vec<String>> = IndexMap::new();
202
203 for chip in Chip::iter() {
204 let config = Config::for_chip(&chip);
205 for symbol in config.all() {
206 if let Some((symbol_name, symbol_value)) = symbol.split_once('=') {
207 let symbol_name = symbol_name.replace('.', "_");
210 let entry = cfg_values.entry(symbol_name).or_default();
211 if !entry.contains(&symbol_value.to_string()) {
213 entry.push(symbol_value.to_string());
214 }
215 } else {
216 let cfg = format!("cargo:rustc-check-cfg=cfg({})", symbol.replace('.', "_"));
218
219 if !cfgs.contains(&cfg) {
220 cfgs.push(cfg);
221 }
222 }
223 }
224 }
225
226 for (symbol_name, symbol_values) in cfg_values {
228 cfgs.push(format!(
229 "cargo:rustc-check-cfg=cfg({symbol_name}, values({}))",
230 symbol_values.join(",")
231 ));
232 }
233
234 cfgs
235 }
236}
237
238#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
239pub struct MemoryRegion {
240 name: String,
241 start: u32,
242 end: u32,
243}
244
245#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
246pub struct PeripheralDef {
247 name: String,
249 #[serde(default, rename = "pac")]
251 pac_name: Option<String>,
252 #[serde(default, rename = "virtual")]
254 is_virtual: bool,
255 #[serde(default)]
257 interrupts: IndexMap<String, String>,
258}
259
260impl PeripheralDef {
261 fn symbol_name(&self) -> String {
262 format!("soc_has_{}", self.name.to_lowercase())
263 }
264}
265
266#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
267struct Device {
268 name: String,
269 arch: Arch,
270 target: String,
271 cores: usize,
272 trm: String,
273
274 peripherals: Vec<PeripheralDef>,
275 symbols: Vec<String>,
276 memory: Vec<MemoryRegion>,
277
278 #[serde(flatten)]
280 peri_config: PeriConfig,
281}
282
283fn number(n: impl std::fmt::Display) -> TokenStream {
286 TokenStream::from_str(&format!("{n}")).unwrap()
287}
288
289#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
291pub struct Config {
292 device: Device,
293 #[serde(skip)]
294 all_symbols: OnceLock<Vec<String>>,
295}
296
297impl Config {
298 pub fn for_chip(chip: &Chip) -> &Self {
300 match chip {
301 Chip::Esp32 => include_toml!(Config, "../devices/esp32.toml"),
302 Chip::Esp32c2 => include_toml!(Config, "../devices/esp32c2.toml"),
303 Chip::Esp32c3 => include_toml!(Config, "../devices/esp32c3.toml"),
304 Chip::Esp32c6 => include_toml!(Config, "../devices/esp32c6.toml"),
305 Chip::Esp32h2 => include_toml!(Config, "../devices/esp32h2.toml"),
306 Chip::Esp32s2 => include_toml!(Config, "../devices/esp32s2.toml"),
307 Chip::Esp32s3 => include_toml!(Config, "../devices/esp32s3.toml"),
308 }
309 }
310
311 pub fn empty() -> Self {
313 Self {
314 device: Device {
315 name: String::new(),
316 arch: Arch::RiscV,
317 target: String::new(),
318 cores: 1,
319 trm: String::new(),
320 peripherals: Vec::new(),
321 symbols: Vec::new(),
322 memory: Vec::new(),
323 peri_config: PeriConfig::default(),
324 },
325 all_symbols: OnceLock::new(),
326 }
327 }
328
329 fn validate(&self) -> Result<()> {
330 for instance in self.device.peri_config.driver_instances() {
331 let (driver, peri) = instance.split_once('.').unwrap();
332 ensure!(
333 self.device
334 .peripherals
335 .iter()
336 .any(|p| p.name.eq_ignore_ascii_case(peri)),
337 "Driver {driver} marks an implementation for '{peri}' but this peripheral is not defined for '{}'",
338 self.device.name
339 );
340 }
341
342 Ok(())
343 }
344
345 pub fn name(&self) -> String {
347 self.device.name.clone()
348 }
349
350 pub fn arch(&self) -> Arch {
352 self.device.arch
353 }
354
355 pub fn cores(&self) -> Cores {
357 if self.device.cores > 1 {
358 Cores::Multi
359 } else {
360 Cores::Single
361 }
362 }
363
364 pub fn peripherals(&self) -> &[PeripheralDef] {
366 &self.device.peripherals
367 }
368
369 pub fn symbols(&self) -> &[String] {
371 &self.device.symbols
372 }
373
374 pub fn memory(&self) -> &[MemoryRegion] {
379 &self.device.memory
380 }
381
382 pub fn all(&self) -> &[String] {
384 self.all_symbols.get_or_init(|| {
385 let mut all = vec![
386 self.device.name.clone(),
387 self.device.arch.to_string(),
388 match self.cores() {
389 Cores::Single => String::from("single_core"),
390 Cores::Multi => String::from("multi_core"),
391 },
392 ];
393 all.extend(self.device.peripherals.iter().map(|p| p.symbol_name()));
394 all.extend_from_slice(&self.device.symbols);
395 all.extend(
396 self.device
397 .peri_config
398 .driver_names()
399 .map(|name| name.to_string()),
400 );
401 all.extend(self.device.peri_config.driver_instances());
402
403 all.extend(self.device.peri_config.properties().filter_map(
404 |(name, value)| match value {
405 Value::Boolean(true) => Some(name.to_string()),
406 Value::Number(value) => Some(format!("{name}=\"{value}\"")),
407 _ => None,
408 },
409 ));
410 all
411 })
412 }
413
414 pub fn contains(&self, item: &str) -> bool {
416 self.all().iter().any(|i| i == item)
417 }
418
419 pub fn generate_metadata(&self) -> TokenStream {
420 let properties = self.generate_properties();
421 let peris = self.generate_peripherals();
422 let gpios = self.generate_gpios();
423
424 quote! {
425 #properties
426 #peris
427 #gpios
428 }
429 }
430
431 fn generate_properties(&self) -> TokenStream {
432 let mut tokens = TokenStream::new();
433
434 let chip_name = self.name();
435 tokens.extend(quote! {
437 #[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")]
445 #[macro_export]
447 #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
448 macro_rules! chip {
449 () => { #chip_name };
450 }
451 });
452
453 let arch = self.device.arch.as_ref();
455 let cores = number(self.device.cores);
456 let trm = &self.device.trm;
457
458 let peripheral_properties =
459 self.device
460 .peri_config
461 .properties()
462 .flat_map(|(name, value)| match value {
463 Value::Unset => quote! {},
464 Value::Number(value) => {
465 let value = number(value); quote! {
467 (#name) => { #value };
468 (#name, str) => { stringify!(#value) };
469 }
470 }
471 Value::Boolean(value) => quote! {
472 (#name) => { #value };
473 },
474 });
475
476 tokens.extend(quote! {
478 #[macro_export]
480 #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
481 macro_rules! property {
482 ("chip") => { #chip_name };
483 ("arch") => { #arch };
484 ("cores") => { #cores };
485 ("cores", str) => { stringify!(#cores) };
486 ("trm") => { #trm };
487 #(#peripheral_properties)*
488 }
489 });
490
491 let region_branches = self.memory().iter().map(|region| {
492 let name = region.name.to_uppercase();
493 let start = number(region.start as usize);
494 let end = number(region.end as usize);
495
496 quote! {
497 ( #name ) => {
498 #start .. #end
499 };
500 }
501 });
502
503 tokens.extend(quote! {
504 #[macro_export]
506 #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
507 macro_rules! memory_range {
508 #(#region_branches)*
509 }
510 });
511
512 tokens
513 }
514
515 fn generate_gpios(&self) -> TokenStream {
516 let Some(gpio) = self.device.peri_config.gpio.as_ref() else {
517 return quote! {};
519 };
520
521 cfg::generate_gpios(gpio)
522 }
523
524 fn generate_peripherals(&self) -> TokenStream {
525 let mut tokens = TokenStream::new();
526
527 if let Some(peri) = self.device.peri_config.i2c_master.as_ref() {
529 tokens.extend(cfg::generate_i2c_master_peripherals(peri));
530 };
531 if let Some(peri) = self.device.peri_config.uart.as_ref() {
532 tokens.extend(cfg::generate_uart_peripherals(peri));
533 }
534 if let Some(peri) = self.device.peri_config.spi_master.as_ref() {
535 tokens.extend(cfg::generate_spi_master_peripherals(peri));
536 };
537 if let Some(peri) = self.device.peri_config.spi_slave.as_ref() {
538 tokens.extend(cfg::generate_spi_slave_peripherals(peri));
539 };
540
541 tokens.extend(self.generate_peripherals_macro());
542
543 tokens
544 }
545
546 fn generate_peripherals_macro(&self) -> TokenStream {
547 let mut stable = vec![];
548 let mut unstable = vec![];
549 let mut all_peripherals = vec![];
550
551 let mut stable_peris = vec![];
552
553 for item in PeriConfig::drivers() {
554 if self.device.peri_config.support_status(item.config_group)
555 == Some(SupportStatus::Supported)
556 {
557 for p in self.device.peri_config.driver_peris(item.config_group) {
558 if !stable_peris.contains(&p) {
559 stable_peris.push(p);
560 }
561 }
562 }
563 }
564
565 if let Some(gpio) = self.device.peri_config.gpio.as_ref() {
566 for pin in gpio.pins_and_signals.pins.iter() {
567 let pin = format_ident!("GPIO{}", pin.pin);
568 let tokens = quote! {
569 #pin <= virtual ()
570 };
571 all_peripherals.push(quote! { #tokens });
572 stable.push(tokens);
573 }
574 }
575
576 for peri in self.device.peripherals.iter() {
577 let hal = format_ident!("{}", peri.name);
578 let pac = if peri.is_virtual {
579 format_ident!("virtual")
580 } else {
581 format_ident!("{}", peri.pac_name.as_deref().unwrap_or(peri.name.as_str()))
582 };
583 let mut interrupts = peri.interrupts.iter().collect::<Vec<_>>();
585 interrupts.sort_by_key(|(k, _)| k.as_str());
586 let interrupts = interrupts.iter().map(|(k, v)| {
587 let pac_interrupt_name = format_ident!("{v}");
588 let bind = format_ident!("bind_{k}_interrupt");
589 let enable = format_ident!("enable_{k}_interrupt");
590 let disable = format_ident!("disable_{k}_interrupt");
591 quote! { #pac_interrupt_name: { #bind, #enable, #disable } }
592 });
593 let tokens = quote! {
594 #hal <= #pac ( #(#interrupts),* )
595 };
596 if stable_peris
597 .iter()
598 .any(|p| peri.name.eq_ignore_ascii_case(p))
599 {
600 all_peripherals.push(quote! { #tokens });
601 stable.push(tokens);
602 } else {
603 all_peripherals.push(quote! { #tokens (unstable) });
604 unstable.push(tokens);
605 }
606 }
607
608 generate_for_each_macro("peripheral", &[("all", &all_peripherals)])
609 }
610
611 pub fn active_cfgs(&self) -> Vec<String> {
612 let mut cfgs = vec![];
613
614 for symbol in self.all() {
616 cfgs.push(symbol.replace('.', "_"));
617 }
618
619 for memory in self.memory() {
621 cfgs.push(format!("has_{}_region", memory.name.to_lowercase()));
622 }
623
624 cfgs
625 }
626
627 pub fn list_of_cfgs(&self) -> Vec<String> {
628 self.active_cfgs()
629 .iter()
630 .map(|cfg| format!("cargo:rustc-cfg={cfg}"))
631 .collect()
632 }
633}
634
635type Branch<'a> = (&'a str, &'a [TokenStream]);
636
637fn generate_for_each_macro(name: &str, branches: &[Branch<'_>]) -> TokenStream {
638 let macro_name = format_ident!("for_each_{name}");
639
640 let flat_branches = branches.iter().flat_map(|b| b.1.iter());
641 let repeat_names = branches.iter().map(|b| TokenStream::from_str(b.0).unwrap());
642 let repeat_branches = branches.iter().map(|b| b.1);
643
644 quote! {
645 #[macro_export]
649 #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
650 macro_rules! #macro_name {
651 (
652 $($pattern:tt => $code:tt;)*
653 ) => {
654 macro_rules! _for_each_inner {
655 $(($pattern) => $code;)*
656 ($other:tt) => {}
657 }
658
659 #(_for_each_inner!(( #flat_branches ));)*
669
670 #( _for_each_inner!( (#repeat_names #( (#repeat_branches) ),*) ); )*
680 };
681 }
682 }
683}
684
685pub fn generate_build_script_utils() -> TokenStream {
686 let check_cfgs = Chip::list_of_check_cfgs();
687
688 let chip = Chip::iter()
689 .map(|c| format_ident!("{}", c.name()))
690 .collect::<Vec<_>>();
691 let feature_env = Chip::iter().map(|c| format!("CARGO_FEATURE_{}", c.as_ref().to_uppercase()));
692 let name = Chip::iter()
693 .map(|c| c.as_ref().to_string())
694 .collect::<Vec<_>>();
695 let all_chip_features = name.join(", ");
696 let config = Chip::iter().map(|chip| {
697 let config = Config::for_chip(&chip);
698 let symbols = config.active_cfgs();
699 let arch = config.device.arch.to_string();
700 let target = config.device.target.as_str();
701 let cfgs = config.list_of_cfgs();
702 quote! {
703 Config {
704 architecture: #arch,
705 target: #target,
706 symbols: &[
707 #(#symbols,)*
708 ],
709 cfgs: &[
710 #(#cfgs,)*
711 ],
712 }
713 }
714 });
715
716 let bail_message = format!(
717 "Expected exactly one of the following features to be enabled: {all_chip_features}"
718 );
719
720 quote! {
721 #[cfg(docsrs)]
723 macro_rules! println {
724 ($($any:tt)*) => {};
725 }
726
727 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
728 #[cfg_attr(docsrs, doc(cfg(feature = "build-script")))]
729 pub enum Chip {
730 #(#chip),*
731 }
732
733 impl core::str::FromStr for Chip {
734 type Err = ();
735
736 fn from_str(s: &str) -> Result<Self, Self::Err> {
737 match s {
738 #( #name => Ok(Self::#chip),)*
739 _ => Err(()),
740 }
741 }
742 }
743
744 impl Chip {
745 pub fn from_cargo_feature() -> Result<Self, &'static str> {
749 let all_chips = [
750 #(( #feature_env, Self::#chip )),*
751 ];
752
753 let mut chip = None;
754 for (env, c) in all_chips {
755 if std::env::var(env).is_ok() {
756 if chip.is_some() {
757 return Err(#bail_message);
758 }
759 chip = Some(c);
760 }
761 }
762
763 match chip {
764 Some(chip) => Ok(chip),
765 None => Err(#bail_message)
766 }
767 }
768
769 pub fn is_xtensa(self) -> bool {
771 self.config().architecture == "xtensa"
772 }
773
774 pub fn target(self) -> &'static str {
776 self.config().target
777 }
778
779 pub fn name(self) -> &'static str {
787 match self {
788 #( Self::#chip => #name ),*
789 }
790 }
791
792 pub fn contains(self, symbol: &str) -> bool {
802 self.all_symbols().contains(&symbol)
803 }
804
805 pub fn define_cfgs(self) {
807 self.config().define_cfgs()
808 }
809
810 pub fn all_symbols(&self) -> &'static [&'static str] {
818 self.config().symbols
819 }
820
821 pub fn iter() -> impl Iterator<Item = Chip> {
829 [
830 #( Self::#chip ),*
831 ].into_iter()
832 }
833
834 fn config(self) -> Config {
835 match self {
836 #(Self::#chip => #config),*
837 }
838 }
839 }
840
841 struct Config {
842 architecture: &'static str,
843 target: &'static str,
844 symbols: &'static [&'static str],
845 cfgs: &'static [&'static str],
846 }
847
848 impl Config {
849 fn define_cfgs(&self) {
850 #(println!(#check_cfgs);)*
851
852 for cfg in self.cfgs {
853 println!("{cfg}");
854 }
855 }
856 }
857 }
858}
859
860pub fn generate_lib_rs() -> TokenStream {
861 let chips = Chip::iter().map(|c| {
862 let feature = format!("{c}");
863 let file = format!("_generated_{c}.rs");
864 quote! {
865 #[cfg(all(not(feature = "build-script"), feature = #feature))]
866 include!(#file);
867 }
868 });
869
870 quote! {
871 #![cfg_attr(docsrs, feature(doc_cfg))]
967 #![cfg_attr(not(feature = "build-script"), no_std)]
968
969 #(#chips)*
970
971 #[cfg(any(feature = "build-script", docsrs))]
972 include!( "_build_script_utils.rs");
973 }
974}
975
976pub fn generate_chip_support_status(output: &mut impl Write) -> std::fmt::Result {
977 let nothing = "";
978
979 let driver_col_width = std::iter::once("Driver")
981 .chain(PeriConfig::drivers().iter().map(|i| i.name))
982 .map(|c| c.len())
983 .max()
984 .unwrap();
985
986 write!(output, "| {:driver_col_width$} |", "Driver")?;
988 for chip in Chip::iter() {
989 write!(output, " {} |", chip.pretty_name())?;
990 }
991 writeln!(output)?;
992
993 write!(output, "| {nothing:-<driver_col_width$} |")?;
995 for chip in Chip::iter() {
996 write!(
997 output,
998 ":{nothing:-<width$}:|",
999 width = chip.pretty_name().len()
1000 )?;
1001 }
1002 writeln!(output)?;
1003
1004 for SupportItem { name, config_group } in PeriConfig::drivers() {
1006 write!(output, "| {name:driver_col_width$} |")?;
1007 for chip in Chip::iter() {
1008 let config = Config::for_chip(&chip);
1009
1010 let status = config.device.peri_config.support_status(config_group);
1011 let status_icon = match status {
1012 None => " ",
1013 Some(status) => status.icon(),
1014 };
1015 let support_cell_width = chip.pretty_name().len() - status.is_some() as usize;
1018 write!(output, " {status_icon:support_cell_width$} |")?;
1019 }
1020 writeln!(output)?;
1021 }
1022
1023 writeln!(output)?;
1024
1025 writeln!(output, " * Empty cell: not available")?;
1027 for s in [
1028 SupportStatus::NotSupported,
1029 SupportStatus::Partial,
1030 SupportStatus::Supported,
1031 ] {
1032 writeln!(output, " * {}: {}", s.icon(), s.status())?;
1033 }
1034
1035 Ok(())
1036}