esp_metadata/
lib.rs

1//! Metadata for Espressif devices, primarily intended for use in build scripts.
2mod 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/// Supported device architectures.
30#[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    /// RISC-V architecture
49    RiscV,
50    /// Xtensa architecture
51    Xtensa,
52}
53
54/// Device core count.
55#[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    /// Single CPU core
72    #[serde(rename = "single_core")]
73    #[strum(serialize = "single_core")]
74    Single,
75    /// Two or more CPU cores
76    #[serde(rename = "multi_core")]
77    #[strum(serialize = "multi_core")]
78    Multi,
79}
80
81/// Supported devices.
82#[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
103    Esp32,
104    /// ESP32-C2, ESP8684
105    Esp32c2,
106    /// ESP32-C3, ESP8685
107    Esp32c3,
108    /// ESP32-C6
109    Esp32c6,
110    /// ESP32-H2
111    Esp32h2,
112    /// ESP32-S2
113    Esp32s2,
114    /// ESP32-S3
115    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        // Used by our documentation builds to prevent the huge red warning banner.
199        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                    // cfg's with values need special syntax, so let's collect all
208                    // of them separately.
209                    let symbol_name = symbol_name.replace('.', "_");
210                    let entry = cfg_values.entry(symbol_name).or_default();
211                    // Avoid duplicates in the same cfg.
212                    if !entry.contains(&symbol_value.to_string()) {
213                        entry.push(symbol_value.to_string());
214                    }
215                } else {
216                    // https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-check-cfg
217                    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        // Now output all cfgs with values.
227        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    /// The name of the esp-hal peripheral singleton
248    name: String,
249    /// When omitted, same as `name`
250    #[serde(default, rename = "pac")]
251    pac_name: Option<String>,
252    /// Whether or not the peripheral has a PAC counterpart
253    #[serde(default, rename = "virtual")]
254    is_virtual: bool,
255    /// List of related interrupt signals
256    #[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    // Peripheral driver configuration:
279    #[serde(flatten)]
280    peri_config: PeriConfig,
281}
282
283// Output a Display-able value as a TokenStream, intended to generate numbers
284// without the type suffix.
285fn number(n: impl std::fmt::Display) -> TokenStream {
286    TokenStream::from_str(&format!("{n}")).unwrap()
287}
288
289/// Device configuration file format.
290#[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    /// The configuration for the specified chip.
299    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    /// Create an empty configuration
312    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    /// The name of the device.
346    pub fn name(&self) -> String {
347        self.device.name.clone()
348    }
349
350    /// The CPU architecture of the device.
351    pub fn arch(&self) -> Arch {
352        self.device.arch
353    }
354
355    /// The core count of the device.
356    pub fn cores(&self) -> Cores {
357        if self.device.cores > 1 {
358            Cores::Multi
359        } else {
360            Cores::Single
361        }
362    }
363
364    /// The peripherals of the device.
365    pub fn peripherals(&self) -> &[PeripheralDef] {
366        &self.device.peripherals
367    }
368
369    /// User-defined symbols for the device.
370    pub fn symbols(&self) -> &[String] {
371        &self.device.symbols
372    }
373
374    /// Memory regions.
375    ///
376    /// Will be available as env-variables `REGION-<NAME>-START` /
377    /// `REGION-<NAME>-END`
378    pub fn memory(&self) -> &[MemoryRegion] {
379        &self.device.memory
380    }
381
382    /// All configuration values for the device.
383    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    /// Does the configuration contain `item`?
415    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        // Public API, can't use a private macro:
436        tokens.extend(quote! {
437            /// The name of the chip as `&str`
438            ///
439            /// # Example
440            ///
441            /// ```rust, no_run
442            /// use esp_hal::chip;
443            /// let chip_name = chip!();
444            #[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")]
445            /// ```
446            #[macro_export]
447            #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
448            macro_rules! chip {
449                () => { #chip_name };
450            }
451        });
452
453        // Translate the chip properties into a macro that can be used in esp-hal:
454        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); // ensure no numeric suffix is added
466                        quote! {
467                            (#name) => { #value };
468                            (#name, str) => { stringify!(#value) };
469                        }
470                    }
471                    Value::Boolean(value) => quote! {
472                        (#name) => { #value };
473                    },
474                });
475
476        // Not public API, can use a private macro:
477        tokens.extend(quote! {
478            /// The properties of this chip and its drivers.
479            #[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 to get the address range of the given memory region.
505            #[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            // No GPIOs defined, nothing to do.
518            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        // TODO: repeat for all drivers that have Instance traits
528        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            // Make sure we have a stable order
584            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        // Define all necessary configuration symbols for the configured device:
615        for symbol in self.all() {
616            cfgs.push(symbol.replace('.', "_"));
617        }
618
619        // Define env-vars for all memory regions
620        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        // This macro is called in esp-hal to implement a driver's
646        // Instance trait for available peripherals. It works by defining, then calling an inner
647        // macro that substitutes the properties into the template provided by the call in esp-hal.
648        #[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                // Generate single macro calls with each branch
660                // Usage:
661                // ```
662                // for_each_x! {
663                //     ( $pattern ) => {
664                //         $code
665                //     }
666                // }
667                // ```
668                #(_for_each_inner!(( #flat_branches ));)*
669
670                // Generate a single macro call with all branches.
671                // Usage:
672                // ```
673                // for_each_x! {
674                //     (all $( ($pattern) ),*) => {
675                //         $( $code )*
676                //     }
677                // }
678                // ```
679                #( _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        // make it possible to build documentation without `std`.
722        #[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            /// Tries to extract the active chip from the active cargo features.
746            ///
747            /// Exactly one device feature must be enabled for this function to succeed.
748            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            /// Returns whether the current chip uses the Tensilica Xtensa ISA.
770            pub fn is_xtensa(self) -> bool {
771                self.config().architecture == "xtensa"
772            }
773
774            /// The target triple of the current chip.
775            pub fn target(self) -> &'static str {
776                self.config().target
777            }
778
779            /// The simple name of the current chip.
780            ///
781            /// ## Example
782            ///
783            /// ```rust
784            /// assert_eq!(Chip::Esp32s3.name(), "esp32s3");
785            /// ```
786            pub fn name(self) -> &'static str {
787                match self {
788                    #( Self::#chip => #name ),*
789                }
790            }
791
792            /// Returns whether the chip configuration contains the given symbol.
793            ///
794            /// This function is a short-hand for `self.all_symbols().contains(&symbol)`.
795            ///
796            /// ## Example
797            ///
798            /// ```rust
799            /// assert!(Chip::Esp32s3.contains("soc_has_pcnt"));
800            /// ```
801            pub fn contains(self, symbol: &str) -> bool {
802                self.all_symbols().contains(&symbol)
803            }
804
805            /// Calling this function will define all cfg symbols for the firmware crate to use.
806            pub fn define_cfgs(self) {
807                self.config().define_cfgs()
808            }
809
810            /// Returns all symbols as a big slice.
811            ///
812            /// ## Example
813            ///
814            /// ```rust
815            /// assert!(Chip::Esp32s3.all_symbols().contains("soc_has_pcnt"));
816            /// ```
817            pub fn all_symbols(&self) -> &'static [&'static str] {
818                self.config().symbols
819            }
820
821            /// Returns an iterator over all chips.
822            ///
823            /// ## Example
824            ///
825            /// ```rust
826            /// assert!(Chip::iter().any(|c| c == Chip::Esp32));
827            /// ```
828            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        //! # (Generated) metadata for Espressif MCUs.
872        //!
873        //! This crate provides properties that are specific to various Espressif microcontrollers,
874        //! and provides macros to work with peripherals, pins, and various other parts of the chips.
875        //!
876        //! This crate can be used both in firmware, as well as in build scripts, but the usage is different.
877        //!
878        //! ## Usage in build scripts
879        //!
880        //! To use the `Chip` enum, add the crate to your `Cargo.toml` build
881        //! dependencies, with the `build-script` feature:
882        //!
883        //! ```toml
884        //! [build-dependencies]
885        //! esp-metadata-generated = { version = "...", features = ["build-script"] }
886        //! ```
887        //!
888        //! ## Usage in firmware
889        //!
890        //! To use the various macros, add the crate to your `Cargo.toml` dependencies.
891        //! A device-specific feature needs to be enabled in order to use the crate, usually
892        //! picked by the user:
893        //!
894        //! ```toml
895        //! [dependencies]
896        //! esp-metadata-generated = { version = "..." }
897        //! # ...
898        //!
899        //! [features]
900        //! esp32 = ["esp-metadata-generated/esp32"]
901        //! esp32c2 = ["esp-metadata-generated/esp32c2"]
902        //! # ...
903        //! ```
904        //!
905        //! ## `for_each` macros
906        //!
907        //! The basic syntax of this macro looks like a macro definition with two distinct syntax options:
908        //!
909        //! ```rust, no_run
910        //! for_each_peripherals! {
911        //!     // Individual matcher, invoked separately for each peripheral instance
912        //!     ( <individual match syntax> ) => { /* some code */ };
913        //!
914        //!     // Repeated matcher, invoked once with all peripheral instances
915        //!     ( all $( (<individual match syntax>) ),* ) => { /* some code */ };
916        //! }
917        //! ```
918        //!
919        //! You can specify any number of matchers in the same invocation.
920        //!
921        //! > The way code is generated, you will need to use the full `return` syntax to return any
922        //! > values from code generated with these macros.
923        //!
924        //! ### Using the individual matcher
925        //!
926        //! In this use case, each item's data is individually passed through the macro. This can be used to
927        //! generate code for each item separately, allowing specializing the implementation where needed.
928        //!
929        //! ```rust,no_run
930        //! for_each_gpio! {
931        //!   // Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] [Output]))`
932        //!   ($n:literal, $gpio:ident ($($digital_input_function:ident => $digital_input_signal:ident)*) ($($digital_output_function:ident => $digital_output_signal:ident)*) ($($pin_attribute:ident)*)) => { /* some code */ };
933        //!
934        //!   // You can create matchers with data filled in. This example will specifically match GPIO2
935        //!   ($n:literal, GPIO2 $input_af:tt $output_af:tt $attributes:tt) => { /* Additional case only for GPIO2 */ };
936        //! }
937        //! ```
938        //!
939        //! Different macros can have multiple different syntax options for their individual matchers, usually
940        //! to provide more detailed information, while preserving simpler syntax for more basic use cases.
941        //! Consult each macro's documentation for available options.
942        //!
943        //! ### Repeated matcher
944        //!
945        //! With this option, all data is passed through the macro all at once. This form can be used to,
946        //! for example, generate struct fields. If the macro has multiple individual matcher options,
947        //! there are separate repeated matchers for each of the options.
948        //!
949        //! To use this option, start the match pattern with the name of the individual matcher option. When
950        //! there is only a single individual matcher option, its repeated matcher is named `all` unless
951        //! otherwise specified by the macro.
952        //!
953        //! ```rust,no_run
954        //! // Example usage to create a struct containing all GPIOs:
955        //! for_each_gpio! {
956        //!     (all $( ($n:literal, $gpio:ident $_af_ins:tt $_af_outs:tt $_attrs:tt) ),*) => {
957        //!         struct Gpios {
958        //!             $(
959        //!                 #[doc = concat!(" The ", stringify!($n), "th GPIO pin")]
960        //!                 pub $gpio: Gpio<$n>,
961        //!             )*
962        //!         }
963        //!     };
964        //! }
965        //! ```
966        #![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    // Calculate the width of the first column.
980    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    // Header
987    write!(output, "| {:driver_col_width$} |", "Driver")?;
988    for chip in Chip::iter() {
989        write!(output, " {} |", chip.pretty_name())?;
990    }
991    writeln!(output)?;
992
993    // Header separator
994    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    // Driver support status
1005    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            // VSCode displays emojis just a bit wider than 2 characters, making this
1016            // approximation a bit too wide but good enough.
1017            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    // Print legend
1026    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}