1mod cfg;
3
4use core::str::FromStr;
5use std::{fmt::Write, sync::OnceLock};
6
7use anyhow::{Context, Result, bail, ensure};
8use cfg::PeriConfig;
9use indexmap::IndexMap;
10pub use proc_macro2::TokenStream;
11use quote::{format_ident, quote};
12use strum::IntoEnumIterator;
13
14mod support_status;
15
16use crate::{
17 cfg::{SupportItem, Value},
18 support_status::SupportStatusLevel,
19};
20
21macro_rules! include_toml {
22 (Config, $file:expr) => {{
23 static LOADED_TOML: OnceLock<Config> = OnceLock::new();
24 LOADED_TOML.get_or_init(|| {
25 let config: Config = basic_toml::from_str(include_str!($file))
26 .with_context(|| format!("Failed to load device configuration: {}", $file))
27 .unwrap();
28
29 config
30 .validate()
31 .with_context(|| format!("Failed to validate device configuration: {}", $file))
32 .unwrap();
33
34 config
35 })
36 }};
37}
38
39#[derive(
41 Debug,
42 Clone,
43 Copy,
44 PartialEq,
45 Eq,
46 PartialOrd,
47 Ord,
48 serde::Deserialize,
49 serde::Serialize,
50 strum::Display,
51 strum::EnumIter,
52 strum::EnumString,
53 strum::AsRefStr,
54)]
55#[serde(rename_all = "lowercase")]
56#[strum(serialize_all = "lowercase")]
57pub enum Arch {
58 RiscV,
60 Xtensa,
62}
63
64#[derive(
66 Debug,
67 Clone,
68 Copy,
69 PartialEq,
70 Eq,
71 PartialOrd,
72 Ord,
73 serde::Deserialize,
74 serde::Serialize,
75 strum::Display,
76 strum::EnumIter,
77 strum::EnumString,
78 strum::AsRefStr,
79)]
80pub enum Cores {
81 #[serde(rename = "single_core")]
83 #[strum(serialize = "single_core")]
84 Single,
85 #[serde(rename = "multi_core")]
87 #[strum(serialize = "multi_core")]
88 Multi,
89}
90
91#[derive(
93 Debug,
94 Clone,
95 Copy,
96 PartialEq,
97 Eq,
98 PartialOrd,
99 Ord,
100 Hash,
101 serde::Deserialize,
102 serde::Serialize,
103 strum::Display,
104 strum::EnumIter,
105 strum::EnumString,
106 strum::AsRefStr,
107)]
108#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
109#[serde(rename_all = "kebab-case")]
110#[strum(serialize_all = "kebab-case")]
111pub enum Chip {
112 Esp32,
114 Esp32c2,
116 Esp32c3,
118 Esp32c5,
120 Esp32c6,
122 Esp32c61,
124 Esp32h2,
126 Esp32s2,
128 Esp32s3,
130}
131
132impl Chip {
133 pub fn from_cargo_feature() -> Result<Self> {
134 let all_chips = Chip::iter().map(|c| c.to_string()).collect::<Vec<_>>();
135
136 let mut chip = None;
137 for c in all_chips.iter() {
138 if std::env::var(format!("CARGO_FEATURE_{}", c.to_uppercase())).is_ok() {
139 if chip.is_some() {
140 bail!(
141 "Expected exactly one of the following features to be enabled: {}",
142 all_chips.join(", ")
143 );
144 }
145 chip = Some(c);
146 }
147 }
148
149 let Some(chip) = chip else {
150 bail!(
151 "Expected exactly one of the following features to be enabled: {}",
152 all_chips.join(", ")
153 );
154 };
155
156 Ok(Self::from_str(chip.as_str()).unwrap())
157 }
158
159 pub fn target(&self) -> String {
160 Config::for_chip(self).device.target.clone()
161 }
162
163 pub fn has_lp_core(&self) -> bool {
164 use Chip::*;
165 matches!(self, Esp32c6 | Esp32s2 | Esp32s3)
167 }
168
169 pub fn lp_target(&self) -> Result<&'static str> {
170 match self {
171 Chip::Esp32c5 | Chip::Esp32c6 => Ok("riscv32imac-unknown-none-elf"),
172 Chip::Esp32s2 | Chip::Esp32s3 => Ok("riscv32imc-unknown-none-elf"),
173 _ => bail!("Chip does not contain an LP core: '{self}'"),
174 }
175 }
176
177 pub fn name(&self) -> &str {
178 match self {
179 Chip::Esp32 => "Esp32",
180 Chip::Esp32c2 => "Esp32c2",
181 Chip::Esp32c3 => "Esp32c3",
182 Chip::Esp32c5 => "Esp32c5",
183 Chip::Esp32c6 => "Esp32c6",
184 Chip::Esp32c61 => "Esp32c61",
185 Chip::Esp32h2 => "Esp32h2",
186 Chip::Esp32s2 => "Esp32s2",
187 Chip::Esp32s3 => "Esp32s3",
188 }
189 }
190
191 pub fn pretty_name(&self) -> &str {
192 match self {
193 Chip::Esp32 => "ESP32",
194 Chip::Esp32c2 => "ESP32-C2",
195 Chip::Esp32c3 => "ESP32-C3",
196 Chip::Esp32c5 => "ESP32-C5",
197 Chip::Esp32c6 => "ESP32-C6",
198 Chip::Esp32c61 => "ESP32-C61",
199 Chip::Esp32h2 => "ESP32-H2",
200 Chip::Esp32s2 => "ESP32-S2",
201 Chip::Esp32s3 => "ESP32-S3",
202 }
203 }
204
205 pub fn is_xtensa(&self) -> bool {
206 matches!(self, Chip::Esp32 | Chip::Esp32s2 | Chip::Esp32s3)
207 }
208
209 pub fn is_riscv(&self) -> bool {
210 !self.is_xtensa()
211 }
212
213 pub fn list_of_possible_symbols() -> &'static IndexMap<String, Option<Vec<String>>> {
214 type SymbolMap = IndexMap<String, Option<Vec<String>>>;
215 static CACHED_SYMBOLS: OnceLock<SymbolMap> = OnceLock::new();
216 CACHED_SYMBOLS.get_or_init(|| {
217 let mut cfgs: SymbolMap = SymbolMap::new();
218
219 for chip in Chip::iter() {
220 let config = Config::for_chip(&chip);
221 for symbol in config.all() {
222 if let Some((symbol_name, symbol_value)) = symbol.split_once('=') {
223 let symbol_name = symbol_name.replace('.', "_");
224 let entry = cfgs.entry(symbol_name).or_default();
225 let vec = entry.get_or_insert_with(Vec::new);
226
227 if !vec.contains(&symbol_value.to_string()) {
229 vec.push(symbol_value.to_string());
230 }
231 } else {
232 let cfg = symbol.replace('.', "_");
234
235 if !cfgs.contains_key(&cfg) {
236 cfgs.insert(cfg, None);
237 }
238 }
239 }
240 }
241
242 cfgs
243 })
244 }
245
246 pub fn list_of_check_cfgs() -> Vec<String> {
247 let mut cfgs = vec![];
248
249 cfgs.push(String::from("cargo:rustc-check-cfg=cfg(not_really_docsrs)"));
251 cfgs.push(String::from("cargo:rustc-check-cfg=cfg(semver_checks)"));
252
253 let possible_symbols = Self::list_of_possible_symbols();
254 for (sym, values) in possible_symbols.iter() {
255 if values.is_none() {
256 cfgs.push(format!("cargo:rustc-check-cfg=cfg({})", sym));
257 }
258 }
259
260 for (sym, values) in possible_symbols.iter() {
261 if let Some(values) = values {
262 cfgs.push(format!(
263 "cargo:rustc-check-cfg=cfg({sym}, values({}))",
264 values.join(",")
265 ));
266 }
267 }
268
269 cfgs
270 }
271}
272
273#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
274pub struct MemoryRegion {
275 name: String,
276 start: u32,
277 end: u32,
278}
279
280#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
281pub struct PeripheralDef {
282 name: String,
284 #[serde(default, rename = "pac")]
286 pac_name: Option<String>,
287 #[serde(default, rename = "virtual")]
289 is_virtual: bool,
290 #[serde(default)]
292 interrupts: IndexMap<String, String>,
293 #[serde(default)]
295 dma_peripheral: Option<u32>,
296 #[serde(default)]
298 hidden: bool,
299 #[serde(default)]
302 stable: bool,
303
304 #[serde(default)]
306 clock_group: Option<String>,
307}
308
309impl PeripheralDef {
310 fn symbol_name(&self) -> String {
311 format!("soc_has_{}", self.name.to_lowercase())
312 }
313}
314
315#[derive(Debug, Clone, serde::Deserialize)]
316struct Device {
317 name: String,
318 arch: Arch,
319 target: String,
320 cores: usize,
321 trm: String,
322
323 symbols: Vec<String>,
324
325 #[serde(flatten)]
327 peri_config: PeriConfig,
328}
329
330fn number(n: impl std::fmt::Display) -> TokenStream {
333 TokenStream::from_str(&format!("{n}")).unwrap()
334}
335fn number_hex(n: impl std::fmt::Display + std::fmt::UpperHex) -> TokenStream {
336 TokenStream::from_str(&format!("{n:#X}")).unwrap()
337}
338
339#[derive(Debug, Clone, serde::Deserialize)]
341pub struct Config {
342 device: Device,
343 #[serde(skip)]
344 all_symbols: OnceLock<Vec<String>>,
345}
346
347impl Config {
348 pub fn for_chip(chip: &Chip) -> &Self {
350 match chip {
351 Chip::Esp32 => include_toml!(Config, "../devices/esp32.toml"),
352 Chip::Esp32c2 => include_toml!(Config, "../devices/esp32c2.toml"),
353 Chip::Esp32c3 => include_toml!(Config, "../devices/esp32c3.toml"),
354 Chip::Esp32c5 => include_toml!(Config, "../devices/esp32c5.toml"),
355 Chip::Esp32c6 => include_toml!(Config, "../devices/esp32c6.toml"),
356 Chip::Esp32c61 => include_toml!(Config, "../devices/esp32c61.toml"),
357 Chip::Esp32h2 => include_toml!(Config, "../devices/esp32h2.toml"),
358 Chip::Esp32s2 => include_toml!(Config, "../devices/esp32s2.toml"),
359 Chip::Esp32s3 => include_toml!(Config, "../devices/esp32s3.toml"),
360 }
361 }
362
363 pub fn empty() -> Self {
365 Self {
366 device: Device {
367 name: String::new(),
368 arch: Arch::RiscV,
369 target: String::new(),
370 cores: 1,
371 trm: String::new(),
372 symbols: Vec::new(),
373 peri_config: PeriConfig::default(),
374 },
375 all_symbols: OnceLock::new(),
376 }
377 }
378
379 fn validate(&self) -> Result<()> {
380 for instance in self.device.peri_config.driver_instances() {
381 let (driver, peri) = instance.split_once('.').unwrap();
382 ensure!(
383 self.peripherals()
384 .iter()
385 .any(|p| p.name.eq_ignore_ascii_case(peri)),
386 "Driver {driver} marks an implementation for '{peri}' but this peripheral is not defined for '{}'",
387 self.device.name
388 );
389 }
390
391 Ok(())
392 }
393
394 pub fn name(&self) -> String {
396 self.device.name.clone()
397 }
398
399 pub fn arch(&self) -> Arch {
401 self.device.arch
402 }
403
404 pub fn cores(&self) -> Cores {
406 if self.device.cores > 1 {
407 Cores::Multi
408 } else {
409 Cores::Single
410 }
411 }
412
413 pub fn peripherals(&self) -> &[PeripheralDef] {
415 self.device
416 .peri_config
417 .soc
418 .as_ref()
419 .map(|props| props.config.peripherals.as_slice())
420 .unwrap_or(&[])
421 }
422
423 pub fn symbols(&self) -> &[String] {
425 &self.device.symbols
426 }
427
428 pub fn all(&self) -> &[String] {
430 self.all_symbols.get_or_init(|| {
431 let mut all = vec![
432 self.device.name.clone(),
433 self.device.arch.to_string(),
434 match self.cores() {
435 Cores::Single => String::from("single_core"),
436 Cores::Multi => String::from("multi_core"),
437 },
438 ];
439 all.extend(self.peripherals().iter().map(|p| p.symbol_name()));
440 all.extend_from_slice(&self.device.symbols);
441 all.extend(
442 self.device
443 .peri_config
444 .driver_names()
445 .map(|name| format!("{name}_driver_supported")),
446 );
447 all.extend(self.device.peri_config.driver_instances());
448
449 all.extend(
450 self.device
451 .peri_config
452 .properties()
453 .filter_map(|(name, optional, value)| {
454 let is_unset = matches!(value, Value::Unset);
455 let mut syms = match value {
456 Value::Boolean(true) => Some(vec![name.to_string()]),
457 Value::NumberList(_) => None,
458 Value::String(value) => Some(vec![format!("{name}=\"{value}\"")]),
459 Value::Generic(v) => v.cfgs(),
460 Value::StringList(values) => Some(
461 values
462 .iter()
463 .map(|val| {
464 format!(
465 "{name}_{}",
466 val.to_lowercase().replace("-", "_").replace("/", "_")
467 )
468 })
469 .collect(),
470 ),
471 Value::Number(value) => Some(vec![format!("{name}=\"{value}\"")]),
472 _ => None,
473 };
474
475 if optional && !is_unset {
476 syms.get_or_insert_default().push(format!("{name}_is_set"));
477 }
478
479 syms
480 })
481 .flatten(),
482 );
483 all
484 })
485 }
486
487 pub fn contains(&self, item: &str) -> bool {
489 self.all().iter().any(|i| i == item)
490 }
491
492 pub fn generate_metadata(&self) -> TokenStream {
493 let properties = self.generate_properties();
494 let peris = self.generate_peripherals();
495 let gpios = self.generate_gpios();
496
497 quote! {
498 #properties
499 #peris
500 #gpios
501 }
502 }
503
504 fn generate_properties(&self) -> TokenStream {
505 let chip_name = self.name();
506 let chip_pretty_name = Chip::from_str(&chip_name)
507 .expect("Valid chip name")
508 .pretty_name()
509 .to_string();
510
511 let arch = self.device.arch.as_ref();
513 let cores = number(self.device.cores);
514 let trm = &self.device.trm;
515
516 let mut macros = vec![];
517
518 let peripheral_properties =
519 self.device
520 .peri_config
521 .properties()
522 .flat_map(|(name, _optional, value)| match value {
523 Value::Number(value) => {
524 let value = number(value); quote! {
526 (#name) => { #value };
527 (#name, str) => { stringify!(#value) };
528 }
529 }
530 Value::Boolean(value) => quote! {
531 (#name) => { #value };
532 },
533 Value::String(value) => quote! {
534 (#name) => { #value };
535 },
536 Value::NumberList(numbers) => {
537 let numbers = numbers.into_iter().map(number).collect::<Vec<_>>();
538 macros.push(generate_for_each_macro(
539 &name.replace(".", "_"),
540 &[("all", &numbers)],
541 ));
542 quote! {}
543 }
544 Value::Generic(v) => {
545 if let Some(for_each) = v.macros() {
546 macros.push(for_each);
547 }
548 v.property_macro_branches()
549 }
550 Value::Unset | Value::StringList(_) => {
551 quote! {}
552 }
553 });
554
555 quote! {
556 #[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")]
564 #[macro_export]
566 #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
567 macro_rules! chip {
568 () => { #chip_name };
569 }
570
571 #[doc = concat!("assert_eq!(chip_name, ", chip_pretty!(), ")")]
579 #[macro_export]
581 #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
582 macro_rules! chip_pretty {
583 () => { #chip_pretty_name };
584 }
585
586 #[macro_export]
588 #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
589 macro_rules! property {
590 ("chip") => { #chip_name };
591 ("arch") => { #arch };
592 ("cores") => { #cores };
593 ("cores", str) => { stringify!(#cores) };
594 ("trm") => { #trm };
595 #(#peripheral_properties)*
596 }
597
598 #(#macros)*
599 }
600 }
601
602 fn generate_gpios(&self) -> TokenStream {
603 let Some(gpio) = self.device.peri_config.gpio.as_ref() else {
604 return quote! {};
606 };
607
608 cfg::generate_gpios(gpio)
609 }
610
611 fn generate_peripherals(&self) -> TokenStream {
612 let mut tokens = TokenStream::new();
613
614 if let Some(peri) = self.device.peri_config.i2c_master.as_ref() {
616 tokens.extend(cfg::generate_i2c_master_peripherals(peri));
617 };
618 if let Some(peri) = self.device.peri_config.uart.as_ref() {
619 tokens.extend(cfg::generate_uart_peripherals(peri));
620 }
621 if let Some(peri) = self.device.peri_config.spi_master.as_ref() {
622 tokens.extend(cfg::generate_spi_master_peripherals(peri));
623 };
624 if let Some(peri) = self.device.peri_config.spi_slave.as_ref() {
625 tokens.extend(cfg::generate_spi_slave_peripherals(peri));
626 };
627
628 tokens.extend(self.generate_peripherals_macro());
629
630 tokens
631 }
632
633 fn generate_peripherals_macro(&self) -> TokenStream {
634 let mut all_peripherals = vec![];
635 let mut singleton_peripherals = vec![];
636 let mut dma_peripherals = vec![];
637
638 let mut stable_peris = vec![];
639
640 for p in self.peripherals().iter() {
641 if p.stable && !stable_peris.contains(&p.name.as_str()) {
642 stable_peris.push(p.name.as_str());
643 }
644 }
645
646 if let Some(gpio) = self.device.peri_config.gpio.as_ref() {
647 for gpio in gpio.pins_and_signals.pins.iter() {
648 let pin = format_ident!("GPIO{}", gpio.pin);
649 let mut docs = format!("GPIO{} peripheral singleton", gpio.pin);
650
651 let limitations = gpio.limitations();
652
653 if !limitations.is_empty() {
654 let limitations = limitations
656 .iter()
657 .map(|limitation| format!("<li>{}</li>", limitation))
658 .collect::<Vec<_>>()
659 .join("\n");
660 write!(
661 &mut docs,
662 r#" (Limitations exist)
663
664<section class="warning">
665This pin may be available with certain limitations. Check your hardware to make sure whether you can use it.
666<ul>
667{limitations}
668</ul>
669</section>"#,
670 )
671 .unwrap();
672 }
673 let docs = docs.lines();
674 let tokens = quote! {
675 #(#[doc = #docs])* #pin <= virtual ()
676 };
677 all_peripherals.push(quote! { @peri_type #tokens });
678 singleton_peripherals.push(quote! { #pin });
679 }
680 }
681
682 for peri in self.peripherals().iter() {
683 let hal = format_ident!("{}", peri.name);
684 let pac = if peri.is_virtual {
685 format_ident!("virtual")
686 } else {
687 format_ident!("{}", peri.pac_name.as_deref().unwrap_or(peri.name.as_str()))
688 };
689 let mut interrupts = peri.interrupts.iter().collect::<Vec<_>>();
691 interrupts.sort_by_key(|(k, _)| k.as_str());
692 let interrupts = interrupts.iter().map(|(k, v)| {
693 let pac_interrupt_name = format_ident!("{v}");
694 let bind = format_ident!("bind_{k}_interrupt");
695 let enable = format_ident!("enable_{k}_interrupt");
696 let disable = format_ident!("disable_{k}_interrupt");
697 quote! { #pac_interrupt_name: { #bind, #enable, #disable } }
698 });
699 let singleton_doc = format!("{} peripheral singleton", peri.name);
700 let tokens = quote! {
701 #[doc = #singleton_doc] #hal <= #pac ( #(#interrupts),* )
702 };
703 if stable_peris
704 .iter()
705 .any(|p| peri.name.eq_ignore_ascii_case(p))
706 {
707 all_peripherals.push(quote! { @peri_type #tokens });
708 if !peri.hidden {
709 singleton_peripherals.push(quote! { #hal });
710 }
711 } else {
712 all_peripherals.push(quote! { @peri_type #tokens (unstable) });
713 if !peri.hidden {
714 singleton_peripherals.push(quote! { #hal (unstable) });
715 }
716 }
717
718 if let Some(dma_peripheral) = peri.dma_peripheral {
719 dma_peripherals.push((peri.name.as_str(), dma_peripheral));
720 }
721 }
722
723 dma_peripherals.sort_by_key(|(_, dma_peripheral)| *dma_peripheral);
724
725 let dma_peripherals = dma_peripherals
726 .into_iter()
727 .map(|(name, dma_peripheral)| {
728 use convert_case::{Boundary, Case, Casing, pattern};
729
730 let peri = format_ident!("{}", name);
731 let dma_peripheral = number(dma_peripheral);
732 let variant_name = format_ident!(
733 "{}",
734 name.from_case(Case::Custom {
735 boundaries: &[Boundary::LOWER_UPPER, Boundary::UNDERSCORE],
736 pattern: pattern::capital,
737 delim: "",
738 })
739 .to_case(Case::Pascal)
740 );
741 quote! { #peri, #variant_name, #dma_peripheral }
742 })
743 .collect::<Vec<_>>();
744
745 generate_for_each_macro(
746 "peripheral",
747 &[
748 ("all", &all_peripherals),
749 ("singletons", &singleton_peripherals),
750 ("dma_eligible", &dma_peripherals),
751 ],
752 )
753 }
754
755 pub fn active_cfgs(&self) -> Vec<String> {
756 let mut cfgs = vec![];
757
758 for symbol in self.all() {
760 cfgs.push(symbol.replace('.', "_"));
761 }
762
763 cfgs
764 }
765
766 pub fn list_of_cfgs(&self) -> Vec<String> {
768 self.active_cfgs()
769 .iter()
770 .map(|cfg| format!("cargo:rustc-cfg={cfg}"))
771 .collect()
772 }
773}
774
775type Branch<'a> = (&'a str, &'a [TokenStream]);
776
777fn generate_for_each_macro(name: &str, branches: &[Branch<'_>]) -> TokenStream {
778 let macro_name = format_ident!("for_each_{name}");
779
780 let flat_branches = branches.iter().flat_map(|b| b.1.iter());
781 let repeat_names = branches.iter().map(|b| TokenStream::from_str(b.0).unwrap());
782 let repeat_branches = branches.iter().map(|b| b.1);
783 let inner = format_ident!("_for_each_inner_{name}");
784
785 quote! {
786 #[macro_export]
790 #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
791 macro_rules! #macro_name {
792 (
793 $($pattern:tt => $code:tt;)*
794 ) => {
795 macro_rules! #inner {
796 $(($pattern) => $code;)*
797 ($other:tt) => {}
798 }
799
800 #( #inner!(( #flat_branches ));)*
810
811 #( #inner!( (#repeat_names #( (#repeat_branches) ),*) ); )*
821 };
822 }
823 }
824}
825
826pub fn generate_build_script_utils() -> TokenStream {
827 let check_cfgs = Chip::list_of_check_cfgs();
828
829 let chip = Chip::iter()
830 .map(|c| format_ident!("{}", c.name()))
831 .collect::<Vec<_>>();
832 let feature_env = Chip::iter().map(|c| format!("CARGO_FEATURE_{}", c.as_ref().to_uppercase()));
833 let name = Chip::iter()
834 .map(|c| c.as_ref().to_string())
835 .collect::<Vec<_>>();
836 let all_chip_features = name.join(", ");
837 let config = Chip::iter().map(|chip| {
838 let config = Config::for_chip(&chip);
839 let symbols = config.active_cfgs();
840 let arch = config.device.arch.to_string();
841 let target = config.device.target.as_str();
842 let cfgs = config.list_of_cfgs();
843 let soc_config = config.device.peri_config.soc.as_ref().unwrap();
844 let memory_regions = soc_config.config.memory_map.ranges.iter().map(|r| {
845 let name = r.name.as_str();
846 let start = number_hex(r.range.start);
847 let end = number_hex(r.range.end);
848 quote! {
849 (#name, MemoryRegion {
850 address_range: #start .. #end,
851 })
852 }
853 });
854 let pins = config
855 .device
856 .peri_config
857 .gpio
858 .as_ref()
859 .map(|gpio| {
860 gpio.pins_and_signals
861 .pins
862 .iter()
863 .map(|pin| {
864 let num = number(pin.pin);
865 let limitations = pin.limitations().into_iter().map(|limitation| {
866 TokenStream::from_str(
867 &basic_toml::to_string(&limitation)
868 .expect("Serializing limitations should be infallible"),
869 )
870 .expect("Valid TOML string can be re-parsed as Rust strings")
871 });
872 quote! {
873 PinInfo {
874 pin: #num,
875 limitations: &[#(#limitations,)*],
876 }
877 }
878 })
879 .collect::<Vec<_>>()
880 })
881 .unwrap_or_default();
882 quote! {
883 Config {
884 architecture: #arch,
885 target: #target,
886 symbols: &[
887 #(#symbols,)*
888 ],
889 cfgs: &[
890 #(#cfgs,)*
891 ],
892 memory_layout: &MemoryLayout {
893 regions: &[
894 #(#memory_regions,)*
895 ],
896 },
897 pins: &[
898 #(#pins,)*
899 ]
900 }
901 }
902 });
903
904 let bail_message = format!(
905 "Expected exactly one of the following features to be enabled: {all_chip_features}"
906 );
907
908 let from_str_err = format!("Unknown chip {{s}}. Possible options: {all_chip_features}");
909
910 quote! {
911 use core::ops::Range;
912
913 extern crate alloc;
914
915 #[cfg(docsrs)]
917 macro_rules! println {
918 ($($any:tt)*) => {};
919 }
920
921 #[doc(hidden)]
922 #[macro_export]
923 macro_rules! __assert_features_logic {
924 ($op:tt, $limit:expr, $msg:literal, $($feature:literal),+ $(,)?) => {{
925 let enabled: Vec<&str> = [
926 $( if cfg!(feature = $feature) { Some($feature) } else { None }, )+
927 ]
928 .into_iter()
929 .flatten()
930 .collect();
931
932 assert!(
933 enabled.len() $op $limit,
934 concat!($msg, ": {}.\nCurrently enabled: {}. This might be caused by enabled default features.\n"),
935 [$($feature),+].join(", "),
936 if enabled.is_empty() {
937 "none".to_string()
938 } else {
939 enabled.join(", ")
940 }
941 );
942 }};
943 }
944
945 #[macro_export]
946 macro_rules! assert_unique_features {
947 ($($f:literal),+ $(,)?) => {
948 $crate::__assert_features_logic!(
949 <=,
950 1,
951 "\nAt most one of the following features must be enabled",
952 $($f),+
953 );
954 };
955 }
956
957 #[macro_export]
958 macro_rules! assert_unique_used_features {
959 ($($f:literal),+ $(,)?) => {
960 $crate::__assert_features_logic!(
961 ==,
962 1,
963 "\nExactly one of the following features must be enabled",
964 $($f),+
965 );
966 };
967 }
968
969 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
970 #[cfg_attr(docsrs, doc(cfg(feature = "build-script")))]
971 pub enum Chip {
972 #(#chip),*
973 }
974
975 impl core::str::FromStr for Chip {
976 type Err = alloc::string::String;
977
978 fn from_str(s: &str) -> Result<Self, Self::Err> {
979 match s {
980 #( #name => Ok(Self::#chip), )*
981 _ => Err(alloc::format!(#from_str_err)),
982 }
983 }
984 }
985
986 impl Chip {
987 pub fn from_cargo_feature() -> Result<Self, &'static str> {
991 let all_chips = [
992 #((#feature_env, Self::#chip)),*
993 ];
994
995 let mut chip = None;
996
997 for (env, c) in all_chips {
998 if std::env::var(env).is_ok() {
999 if chip.is_some() {
1000 return Err(#bail_message);
1001 }
1002 chip = Some(c);
1003 }
1004 }
1005
1006 match chip {
1007 Some(chip) => Ok(chip),
1008 None => Err(#bail_message),
1009 }
1010 }
1011
1012 pub fn is_xtensa(self) -> bool {
1014 self.config().architecture == "xtensa"
1015 }
1016
1017 pub fn target(self) -> &'static str {
1019 self.config().target
1020 }
1021
1022 pub fn name(self) -> &'static str {
1030 match self {
1031 #( Self::#chip => #name ),*
1032 }
1033 }
1034
1035 pub fn contains(self, symbol: &str) -> bool {
1045 self.all_symbols().contains(&symbol)
1046 }
1047
1048 pub fn define_cfgs(self) {
1050 self.config().define_cfgs()
1051 }
1052
1053 pub fn all_symbols(&self) -> &'static [&'static str] {
1061 self.config().symbols
1062 }
1063
1064 pub fn memory_layout(&self) -> &'static MemoryLayout {
1066 self.config().memory_layout
1067 }
1068
1069 pub fn pins(&self) -> &'static [PinInfo] {
1071 self.config().pins
1072 }
1073
1074 pub fn iter() -> impl Iterator<Item = Chip> {
1082 [
1083 #( Self::#chip ),*
1084 ]
1085 .into_iter()
1086 }
1087
1088 fn config(self) -> Config {
1089 match self {
1090 #( Self::#chip => #config ),*
1091 }
1092 }
1093 }
1094
1095 pub struct MemoryRegion {
1097 address_range: Range<u32>,
1098 }
1099
1100 impl MemoryRegion {
1101 pub fn range(&self) -> Range<u32> {
1103 self.address_range.clone()
1104 }
1105
1106 pub fn size(&self) -> u32 {
1108 self.address_range.end - self.address_range.start
1109 }
1110 }
1111
1112 pub struct MemoryLayout {
1114 regions: &'static [(&'static str, MemoryRegion)],
1115 }
1116
1117 impl MemoryLayout {
1118 pub fn region(&self, name: &str) -> Option<&'static MemoryRegion> {
1120 self.regions
1121 .iter()
1122 .find_map(|(n, r)| if *n == name { Some(r) } else { None })
1123 }
1124 }
1125
1126 #[non_exhaustive]
1128 pub struct PinInfo {
1129 pub pin: usize,
1131
1132 pub limitations: &'static [&'static str],
1136 }
1137
1138 struct Config {
1139 architecture: &'static str,
1140 target: &'static str,
1141 symbols: &'static [&'static str],
1142 cfgs: &'static [&'static str],
1143 memory_layout: &'static MemoryLayout,
1144 pins: &'static [PinInfo],
1145 }
1146
1147 impl Config {
1148 fn define_cfgs(&self) {
1149 emit_check_cfg_directives();
1150 for cfg in self.cfgs {
1151 println!("{cfg}");
1152 }
1153 }
1154 }
1155
1156 pub fn emit_check_cfg_directives() {
1158 #( println!(#check_cfgs); )*
1159 }
1160 }
1161}
1162
1163pub fn generate_lib_rs() -> TokenStream {
1164 let chips = Chip::iter().map(|c| {
1165 let feature = format!("{c}");
1166 let file = format!("_generated_{c}.rs");
1167 quote! {
1168 #[cfg(feature = #feature)]
1169 include!(#file);
1170 }
1171 });
1172
1173 quote! {
1174 #![cfg_attr(docsrs, feature(doc_cfg))]
1270 #![cfg_attr(not(feature = "build-script"), no_std)]
1271
1272 #(#chips)*
1273
1274 #[cfg(any(feature = "build-script", docsrs))]
1275 include!( "_build_script_utils.rs");
1276 }
1277}
1278
1279pub fn generate_chip_support_status(output: &mut impl Write) -> std::fmt::Result {
1280 let nothing = "";
1281
1282 let driver_col_width = std::iter::once("Driver")
1284 .chain(
1285 PeriConfig::drivers()
1286 .iter()
1287 .filter(|i| !i.hide_from_peri_table)
1288 .map(|i| i.name),
1289 )
1290 .map(|c| c.len())
1291 .max()
1292 .unwrap();
1293
1294 write!(output, "| {:driver_col_width$} |", "Driver")?;
1296 for chip in Chip::iter() {
1297 write!(output, " {} |", chip.pretty_name())?;
1298 }
1299 writeln!(output)?;
1300
1301 write!(output, "| {nothing:-<driver_col_width$} |")?;
1303 for chip in Chip::iter() {
1304 write!(
1305 output,
1306 ":{nothing:-<width$}:|",
1307 width = chip.pretty_name().len()
1308 )?;
1309 }
1310 writeln!(output)?;
1311
1312 let mut issues = Vec::new();
1314 for SupportItem {
1315 name,
1316 config_group,
1317 hide_from_peri_table,
1318 } in PeriConfig::drivers()
1319 {
1320 if *hide_from_peri_table {
1321 continue;
1322 }
1323 write!(output, "| {name:driver_col_width$} |")?;
1324 for chip in Chip::iter() {
1325 let config = Config::for_chip(&chip);
1326
1327 let status = config.device.peri_config.support_status(config_group);
1328 let support_cell_width =
1331 chip.pretty_name().len() - !status.status.icon().is_empty() as usize;
1332 if let Some(issue) = status.issue {
1333 write!(output, " [{}][{issue}] [^1] |", status.status.icon())?;
1334 issues.push(issue);
1335 } else {
1336 write!(output, " {:support_cell_width$} |", status.status.icon())?;
1337 }
1338 }
1339 writeln!(output)?;
1340 }
1341
1342 writeln!(output)?;
1343 SupportStatusLevel::write_legend(output)?;
1344 writeln!(output)?;
1345
1346 issues.sort();
1348 issues.dedup();
1349
1350 if !issues.is_empty() {
1351 writeln!(
1352 output,
1353 "[^1]: This cell is clickable and will open the peripheral's issue on GitHub"
1354 )?;
1355 writeln!(output)?;
1356 }
1357 for issue in issues {
1358 writeln!(
1359 output,
1360 "[{issue}]: https://github.com/esp-rs/esp-hal/issues/{issue}"
1361 )?;
1362 }
1363
1364 Ok(())
1365}