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::{Context, 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))
21                .with_context(|| format!("Failed to load device configuration: {}", $file))
22                .unwrap();
23
24            config
25                .validate()
26                .with_context(|| format!("Failed to validate device configuration: {}", $file))
27                .unwrap();
28
29            config
30        })
31    }};
32}
33
34/// Supported device architectures.
35#[derive(
36    Debug,
37    Clone,
38    Copy,
39    PartialEq,
40    Eq,
41    PartialOrd,
42    Ord,
43    serde::Deserialize,
44    serde::Serialize,
45    strum::Display,
46    strum::EnumIter,
47    strum::EnumString,
48    strum::AsRefStr,
49)]
50#[serde(rename_all = "lowercase")]
51#[strum(serialize_all = "lowercase")]
52pub enum Arch {
53    /// RISC-V architecture
54    RiscV,
55    /// Xtensa architecture
56    Xtensa,
57}
58
59/// Device core count.
60#[derive(
61    Debug,
62    Clone,
63    Copy,
64    PartialEq,
65    Eq,
66    PartialOrd,
67    Ord,
68    serde::Deserialize,
69    serde::Serialize,
70    strum::Display,
71    strum::EnumIter,
72    strum::EnumString,
73    strum::AsRefStr,
74)]
75pub enum Cores {
76    /// Single CPU core
77    #[serde(rename = "single_core")]
78    #[strum(serialize = "single_core")]
79    Single,
80    /// Two or more CPU cores
81    #[serde(rename = "multi_core")]
82    #[strum(serialize = "multi_core")]
83    Multi,
84}
85
86/// Supported devices.
87#[derive(
88    Debug,
89    Clone,
90    Copy,
91    PartialEq,
92    Eq,
93    PartialOrd,
94    Ord,
95    Hash,
96    serde::Deserialize,
97    serde::Serialize,
98    strum::Display,
99    strum::EnumIter,
100    strum::EnumString,
101    strum::AsRefStr,
102)]
103#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
104#[serde(rename_all = "kebab-case")]
105#[strum(serialize_all = "kebab-case")]
106pub enum Chip {
107    /// ESP32
108    Esp32,
109    /// ESP32-C2, ESP8684
110    Esp32c2,
111    /// ESP32-C3, ESP8685
112    Esp32c3,
113    /// ESP32-C6
114    Esp32c6,
115    /// ESP32-H2
116    Esp32h2,
117    /// ESP32-S2
118    Esp32s2,
119    /// ESP32-S3
120    Esp32s3,
121}
122
123impl Chip {
124    pub fn from_cargo_feature() -> Result<Self> {
125        let all_chips = Chip::iter().map(|c| c.to_string()).collect::<Vec<_>>();
126
127        let mut chip = None;
128        for c in all_chips.iter() {
129            if std::env::var(format!("CARGO_FEATURE_{}", c.to_uppercase())).is_ok() {
130                if chip.is_some() {
131                    bail!(
132                        "Expected exactly one of the following features to be enabled: {}",
133                        all_chips.join(", ")
134                    );
135                }
136                chip = Some(c);
137            }
138        }
139
140        let Some(chip) = chip else {
141            bail!(
142                "Expected exactly one of the following features to be enabled: {}",
143                all_chips.join(", ")
144            );
145        };
146
147        Ok(Self::from_str(chip.as_str()).unwrap())
148    }
149
150    pub fn target(&self) -> String {
151        Config::for_chip(self).device.target.clone()
152    }
153
154    pub fn has_lp_core(&self) -> bool {
155        use Chip::*;
156
157        matches!(self, Esp32c6 | Esp32s2 | Esp32s3)
158    }
159
160    pub fn lp_target(&self) -> Result<&'static str> {
161        match self {
162            Chip::Esp32c6 => Ok("riscv32imac-unknown-none-elf"),
163            Chip::Esp32s2 | Chip::Esp32s3 => Ok("riscv32imc-unknown-none-elf"),
164            _ => bail!("Chip does not contain an LP core: '{self}'"),
165        }
166    }
167
168    pub fn name(&self) -> &str {
169        match self {
170            Chip::Esp32 => "Esp32",
171            Chip::Esp32c2 => "Esp32c2",
172            Chip::Esp32c3 => "Esp32c3",
173            Chip::Esp32c6 => "Esp32c6",
174            Chip::Esp32h2 => "Esp32h2",
175            Chip::Esp32s2 => "Esp32s2",
176            Chip::Esp32s3 => "Esp32s3",
177        }
178    }
179
180    pub fn pretty_name(&self) -> &str {
181        match self {
182            Chip::Esp32 => "ESP32",
183            Chip::Esp32c2 => "ESP32-C2",
184            Chip::Esp32c3 => "ESP32-C3",
185            Chip::Esp32c6 => "ESP32-C6",
186            Chip::Esp32h2 => "ESP32-H2",
187            Chip::Esp32s2 => "ESP32-S2",
188            Chip::Esp32s3 => "ESP32-S3",
189        }
190    }
191
192    pub fn is_xtensa(&self) -> bool {
193        matches!(self, Chip::Esp32 | Chip::Esp32s2 | Chip::Esp32s3)
194    }
195
196    pub fn is_riscv(&self) -> bool {
197        !self.is_xtensa()
198    }
199
200    pub fn list_of_check_cfgs() -> Vec<String> {
201        let mut cfgs = vec![];
202
203        // Used by our documentation builds to prevent the huge red warning banner.
204        cfgs.push(String::from("cargo:rustc-check-cfg=cfg(not_really_docsrs)"));
205        cfgs.push(String::from("cargo:rustc-check-cfg=cfg(semver_checks)"));
206
207        let mut cfg_values: IndexMap<String, Vec<String>> = IndexMap::new();
208
209        for chip in Chip::iter() {
210            let config = Config::for_chip(&chip);
211            for symbol in config.all() {
212                if let Some((symbol_name, symbol_value)) = symbol.split_once('=') {
213                    // cfg's with values need special syntax, so let's collect all
214                    // of them separately.
215                    let symbol_name = symbol_name.replace('.', "_");
216                    let entry = cfg_values.entry(symbol_name).or_default();
217                    // Avoid duplicates in the same cfg.
218                    if !entry.contains(&symbol_value.to_string()) {
219                        entry.push(symbol_value.to_string());
220                    }
221                } else {
222                    // https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-check-cfg
223                    let cfg = format!("cargo:rustc-check-cfg=cfg({})", symbol.replace('.', "_"));
224
225                    if !cfgs.contains(&cfg) {
226                        cfgs.push(cfg);
227                    }
228                }
229            }
230        }
231
232        // Now output all cfgs with values.
233        for (symbol_name, symbol_values) in cfg_values {
234            cfgs.push(format!(
235                "cargo:rustc-check-cfg=cfg({symbol_name}, values({}))",
236                symbol_values.join(",")
237            ));
238        }
239
240        cfgs
241    }
242}
243
244#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
245pub struct MemoryRegion {
246    name: String,
247    start: u32,
248    end: u32,
249}
250
251#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
252pub struct PeripheralDef {
253    /// The name of the esp-hal peripheral singleton
254    name: String,
255    /// When omitted, same as `name`
256    #[serde(default, rename = "pac")]
257    pac_name: Option<String>,
258    /// Whether or not the peripheral has a PAC counterpart
259    #[serde(default, rename = "virtual")]
260    is_virtual: bool,
261    /// List of related interrupt signals
262    #[serde(default)]
263    interrupts: IndexMap<String, String>,
264}
265
266impl PeripheralDef {
267    fn symbol_name(&self) -> String {
268        format!("soc_has_{}", self.name.to_lowercase())
269    }
270}
271
272#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
273struct Device {
274    name: String,
275    arch: Arch,
276    target: String,
277    cores: usize,
278    trm: String,
279
280    peripherals: Vec<PeripheralDef>,
281    symbols: Vec<String>,
282
283    // Peripheral driver configuration:
284    #[serde(flatten)]
285    peri_config: PeriConfig,
286}
287
288// Output a Display-able value as a TokenStream, intended to generate numbers
289// without the type suffix.
290fn number(n: impl std::fmt::Display) -> TokenStream {
291    TokenStream::from_str(&format!("{n}")).unwrap()
292}
293
294/// Device configuration file format.
295#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
296pub struct Config {
297    device: Device,
298    #[serde(skip)]
299    all_symbols: OnceLock<Vec<String>>,
300}
301
302impl Config {
303    /// The configuration for the specified chip.
304    pub fn for_chip(chip: &Chip) -> &Self {
305        match chip {
306            Chip::Esp32 => include_toml!(Config, "../devices/esp32.toml"),
307            Chip::Esp32c2 => include_toml!(Config, "../devices/esp32c2.toml"),
308            Chip::Esp32c3 => include_toml!(Config, "../devices/esp32c3.toml"),
309            Chip::Esp32c6 => include_toml!(Config, "../devices/esp32c6.toml"),
310            Chip::Esp32h2 => include_toml!(Config, "../devices/esp32h2.toml"),
311            Chip::Esp32s2 => include_toml!(Config, "../devices/esp32s2.toml"),
312            Chip::Esp32s3 => include_toml!(Config, "../devices/esp32s3.toml"),
313        }
314    }
315
316    /// Create an empty configuration
317    pub fn empty() -> Self {
318        Self {
319            device: Device {
320                name: String::new(),
321                arch: Arch::RiscV,
322                target: String::new(),
323                cores: 1,
324                trm: String::new(),
325                peripherals: Vec::new(),
326                symbols: Vec::new(),
327                peri_config: PeriConfig::default(),
328            },
329            all_symbols: OnceLock::new(),
330        }
331    }
332
333    fn validate(&self) -> Result<()> {
334        for instance in self.device.peri_config.driver_instances() {
335            let (driver, peri) = instance.split_once('.').unwrap();
336            ensure!(
337                self.device
338                    .peripherals
339                    .iter()
340                    .any(|p| p.name.eq_ignore_ascii_case(peri)),
341                "Driver {driver} marks an implementation for '{peri}' but this peripheral is not defined for '{}'",
342                self.device.name
343            );
344        }
345
346        Ok(())
347    }
348
349    /// The name of the device.
350    pub fn name(&self) -> String {
351        self.device.name.clone()
352    }
353
354    /// The CPU architecture of the device.
355    pub fn arch(&self) -> Arch {
356        self.device.arch
357    }
358
359    /// The core count of the device.
360    pub fn cores(&self) -> Cores {
361        if self.device.cores > 1 {
362            Cores::Multi
363        } else {
364            Cores::Single
365        }
366    }
367
368    /// The peripherals of the device.
369    pub fn peripherals(&self) -> &[PeripheralDef] {
370        &self.device.peripherals
371    }
372
373    /// User-defined symbols for the device.
374    pub fn symbols(&self) -> &[String] {
375        &self.device.symbols
376    }
377
378    /// All configuration values for the device.
379    pub fn all(&self) -> &[String] {
380        self.all_symbols.get_or_init(|| {
381            let mut all = vec![
382                self.device.name.clone(),
383                self.device.arch.to_string(),
384                match self.cores() {
385                    Cores::Single => String::from("single_core"),
386                    Cores::Multi => String::from("multi_core"),
387                },
388            ];
389            all.extend(self.device.peripherals.iter().map(|p| p.symbol_name()));
390            all.extend_from_slice(&self.device.symbols);
391            all.extend(
392                self.device
393                    .peri_config
394                    .driver_names()
395                    .map(|name| name.to_string()),
396            );
397            all.extend(self.device.peri_config.driver_instances());
398
399            all.extend(
400                self.device
401                    .peri_config
402                    .properties()
403                    .filter_map(|(name, optional, value)| {
404                        let is_unset = matches!(value, Value::Unset);
405                        let mut syms = match value {
406                            Value::Boolean(true) => Some(vec![name.to_string()]),
407                            Value::NumberList(_) => None,
408                            Value::String(value) => Some(vec![format!("{name}=\"{value}\"")]),
409                            Value::Generic(v) => v.cfgs(),
410                            Value::StringList(values) => Some(
411                                values
412                                    .iter()
413                                    .map(|val| {
414                                        format!(
415                                            "{name}_{}",
416                                            val.to_lowercase().replace("-", "_").replace("/", "_")
417                                        )
418                                    })
419                                    .collect(),
420                            ),
421                            Value::Number(value) => Some(vec![format!("{name}=\"{value}\"")]),
422                            _ => None,
423                        };
424
425                        if optional && !is_unset {
426                            syms.get_or_insert_default().push(format!("{name}_is_set"));
427                        }
428
429                        syms
430                    })
431                    .flatten(),
432            );
433            all
434        })
435    }
436
437    /// Does the configuration contain `item`?
438    pub fn contains(&self, item: &str) -> bool {
439        self.all().iter().any(|i| i == item)
440    }
441
442    pub fn generate_metadata(&self) -> TokenStream {
443        let properties = self.generate_properties();
444        let peris = self.generate_peripherals();
445        let gpios = self.generate_gpios();
446
447        quote! {
448            #properties
449            #peris
450            #gpios
451        }
452    }
453
454    fn generate_properties(&self) -> TokenStream {
455        let chip_name = self.name();
456
457        // Translate the chip properties into a macro that can be used in esp-hal:
458        let arch = self.device.arch.as_ref();
459        let cores = number(self.device.cores);
460        let trm = &self.device.trm;
461
462        let mut macros = vec![];
463
464        let peripheral_properties =
465            self.device
466                .peri_config
467                .properties()
468                .flat_map(|(name, _optional, value)| match value {
469                    Value::Number(value) => {
470                        let value = number(value); // ensure no numeric suffix is added
471                        quote! {
472                            (#name) => { #value };
473                            (#name, str) => { stringify!(#value) };
474                        }
475                    }
476                    Value::Boolean(value) => quote! {
477                        (#name) => { #value };
478                    },
479                    Value::String(value) => quote! {
480                        (#name) => { #value };
481                    },
482                    Value::NumberList(numbers) => {
483                        let numbers = numbers.into_iter().map(number).collect::<Vec<_>>();
484                        macros.push(generate_for_each_macro(
485                            &name.replace(".", "_"),
486                            &[("all", &numbers)],
487                        ));
488                        quote! {}
489                    }
490                    Value::Generic(v) => {
491                        if let Some(for_each) = v.macros() {
492                            macros.push(for_each);
493                        }
494                        v.property_macro_branches()
495                    }
496                    Value::Unset | Value::StringList(_) => {
497                        quote! {}
498                    }
499                });
500
501        quote! {
502            /// The name of the chip as `&str`
503            ///
504            /// # Example
505            ///
506            /// ```rust, no_run
507            /// use esp_hal::chip;
508            /// let chip_name = chip!();
509            #[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")]
510            /// ```
511            #[macro_export]
512            #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
513            macro_rules! chip {
514                () => { #chip_name };
515            }
516
517            /// The properties of this chip and its drivers.
518            #[macro_export]
519            #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
520            macro_rules! property {
521                ("chip") => { #chip_name };
522                ("arch") => { #arch };
523                ("cores") => { #cores };
524                ("cores", str) => { stringify!(#cores) };
525                ("trm") => { #trm };
526                #(#peripheral_properties)*
527            }
528
529            #(#macros)*
530        }
531    }
532
533    fn generate_gpios(&self) -> TokenStream {
534        let Some(gpio) = self.device.peri_config.gpio.as_ref() else {
535            // No GPIOs defined, nothing to do.
536            return quote! {};
537        };
538
539        cfg::generate_gpios(gpio)
540    }
541
542    fn generate_peripherals(&self) -> TokenStream {
543        let mut tokens = TokenStream::new();
544
545        // TODO: repeat for all drivers that have Instance traits
546        if let Some(peri) = self.device.peri_config.i2c_master.as_ref() {
547            tokens.extend(cfg::generate_i2c_master_peripherals(peri));
548        };
549        if let Some(peri) = self.device.peri_config.uart.as_ref() {
550            tokens.extend(cfg::generate_uart_peripherals(peri));
551        }
552        if let Some(peri) = self.device.peri_config.spi_master.as_ref() {
553            tokens.extend(cfg::generate_spi_master_peripherals(peri));
554        };
555        if let Some(peri) = self.device.peri_config.spi_slave.as_ref() {
556            tokens.extend(cfg::generate_spi_slave_peripherals(peri));
557        };
558
559        tokens.extend(self.generate_peripherals_macro());
560
561        tokens
562    }
563
564    fn generate_peripherals_macro(&self) -> TokenStream {
565        let mut stable = vec![];
566        let mut unstable = vec![];
567        let mut all_peripherals = vec![];
568
569        let mut stable_peris = vec![];
570
571        for item in PeriConfig::drivers() {
572            if self.device.peri_config.support_status(item.config_group)
573                == Some(SupportStatus::Supported)
574            {
575                for p in self.device.peri_config.driver_peris(item.config_group) {
576                    if !stable_peris.contains(&p) {
577                        stable_peris.push(p);
578                    }
579                }
580            }
581        }
582
583        if let Some(gpio) = self.device.peri_config.gpio.as_ref() {
584            for pin in gpio.pins_and_signals.pins.iter() {
585                let pin = format_ident!("GPIO{}", pin.pin);
586                let tokens = quote! {
587                    #pin <= virtual ()
588                };
589                all_peripherals.push(quote! { #tokens });
590                stable.push(tokens);
591            }
592        }
593
594        for peri in self.device.peripherals.iter() {
595            let hal = format_ident!("{}", peri.name);
596            let pac = if peri.is_virtual {
597                format_ident!("virtual")
598            } else {
599                format_ident!("{}", peri.pac_name.as_deref().unwrap_or(peri.name.as_str()))
600            };
601            // Make sure we have a stable order
602            let mut interrupts = peri.interrupts.iter().collect::<Vec<_>>();
603            interrupts.sort_by_key(|(k, _)| k.as_str());
604            let interrupts = interrupts.iter().map(|(k, v)| {
605                let pac_interrupt_name = format_ident!("{v}");
606                let bind = format_ident!("bind_{k}_interrupt");
607                let enable = format_ident!("enable_{k}_interrupt");
608                let disable = format_ident!("disable_{k}_interrupt");
609                quote! { #pac_interrupt_name: { #bind, #enable, #disable } }
610            });
611            let tokens = quote! {
612                #hal <= #pac ( #(#interrupts),* )
613            };
614            if stable_peris
615                .iter()
616                .any(|p| peri.name.eq_ignore_ascii_case(p))
617            {
618                all_peripherals.push(quote! { #tokens });
619                stable.push(tokens);
620            } else {
621                all_peripherals.push(quote! { #tokens (unstable) });
622                unstable.push(tokens);
623            }
624        }
625
626        generate_for_each_macro("peripheral", &[("all", &all_peripherals)])
627    }
628
629    pub fn active_cfgs(&self) -> Vec<String> {
630        let mut cfgs = vec![];
631
632        // Define all necessary configuration symbols for the configured device:
633        for symbol in self.all() {
634            cfgs.push(symbol.replace('.', "_"));
635        }
636
637        cfgs
638    }
639
640    /// For each symbol generates a cargo directive that activates it.
641    pub fn list_of_cfgs(&self) -> Vec<String> {
642        self.active_cfgs()
643            .iter()
644            .map(|cfg| format!("cargo:rustc-cfg={cfg}"))
645            .collect()
646    }
647}
648
649type Branch<'a> = (&'a str, &'a [TokenStream]);
650
651fn generate_for_each_macro(name: &str, branches: &[Branch<'_>]) -> TokenStream {
652    let macro_name = format_ident!("for_each_{name}");
653
654    let flat_branches = branches.iter().flat_map(|b| b.1.iter());
655    let repeat_names = branches.iter().map(|b| TokenStream::from_str(b.0).unwrap());
656    let repeat_branches = branches.iter().map(|b| b.1);
657
658    quote! {
659        // This macro is called in esp-hal to implement a driver's
660        // Instance trait for available peripherals. It works by defining, then calling an inner
661        // macro that substitutes the properties into the template provided by the call in esp-hal.
662        #[macro_export]
663        #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
664        macro_rules! #macro_name {
665            (
666                $($pattern:tt => $code:tt;)*
667            ) => {
668                macro_rules! _for_each_inner {
669                    $(($pattern) => $code;)*
670                    ($other:tt) => {}
671                }
672
673                // Generate single macro calls with each branch
674                // Usage:
675                // ```
676                // for_each_x! {
677                //     ( $pattern ) => {
678                //         $code
679                //     }
680                // }
681                // ```
682                #(_for_each_inner!(( #flat_branches ));)*
683
684                // Generate a single macro call with all branches.
685                // Usage:
686                // ```
687                // for_each_x! {
688                //     (all $( ($pattern) ),*) => {
689                //         $( $code )*
690                //     }
691                // }
692                // ```
693                #( _for_each_inner!( (#repeat_names #( (#repeat_branches) ),*) ); )*
694            };
695        }
696    }
697}
698
699pub fn generate_build_script_utils() -> TokenStream {
700    let check_cfgs = Chip::list_of_check_cfgs();
701
702    let chip = Chip::iter()
703        .map(|c| format_ident!("{}", c.name()))
704        .collect::<Vec<_>>();
705    let feature_env = Chip::iter().map(|c| format!("CARGO_FEATURE_{}", c.as_ref().to_uppercase()));
706    let name = Chip::iter()
707        .map(|c| c.as_ref().to_string())
708        .collect::<Vec<_>>();
709    let all_chip_features = name.join(", ");
710    let config = Chip::iter().map(|chip| {
711        let config = Config::for_chip(&chip);
712        let symbols = config.active_cfgs();
713        let arch = config.device.arch.to_string();
714        let target = config.device.target.as_str();
715        let cfgs = config.list_of_cfgs();
716        quote! {
717            Config {
718                architecture: #arch,
719                target: #target,
720                symbols: &[
721                    #(#symbols,)*
722                ],
723                cfgs: &[
724                    #(#cfgs,)*
725                ],
726            }
727        }
728    });
729
730    let bail_message = format!(
731        "Expected exactly one of the following features to be enabled: {all_chip_features}"
732    );
733
734    quote! {
735        // make it possible to build documentation without `std`.
736        #[cfg(docsrs)]
737        macro_rules! println {
738            ($($any:tt)*) => {};
739        }
740
741        #[derive(Clone, Copy, PartialEq, Eq, Hash)]
742        #[cfg_attr(docsrs, doc(cfg(feature = "build-script")))]
743        pub enum Chip {
744            #(#chip),*
745        }
746
747        impl core::str::FromStr for Chip {
748            type Err = ();
749
750            fn from_str(s: &str) -> Result<Self, Self::Err> {
751                match s {
752                    #( #name => Ok(Self::#chip),)*
753                    _ => Err(()),
754                }
755            }
756        }
757
758        impl Chip {
759            /// Tries to extract the active chip from the active cargo features.
760            ///
761            /// Exactly one device feature must be enabled for this function to succeed.
762            pub fn from_cargo_feature() -> Result<Self, &'static str> {
763                let all_chips = [
764                    #(( #feature_env, Self::#chip )),*
765                ];
766
767                let mut chip = None;
768                for (env, c) in all_chips {
769                    if std::env::var(env).is_ok() {
770                        if chip.is_some() {
771                            return Err(#bail_message);
772                        }
773                        chip = Some(c);
774                    }
775                }
776
777                match chip {
778                    Some(chip) => Ok(chip),
779                    None => Err(#bail_message)
780                }
781            }
782
783            /// Returns whether the current chip uses the Tensilica Xtensa ISA.
784            pub fn is_xtensa(self) -> bool {
785                self.config().architecture == "xtensa"
786            }
787
788            /// The target triple of the current chip.
789            pub fn target(self) -> &'static str {
790                self.config().target
791            }
792
793            /// The simple name of the current chip.
794            ///
795            /// ## Example
796            ///
797            /// ```rust
798            /// assert_eq!(Chip::Esp32s3.name(), "esp32s3");
799            /// ```
800            pub fn name(self) -> &'static str {
801                match self {
802                    #( Self::#chip => #name ),*
803                }
804            }
805
806            /// Returns whether the chip configuration contains the given symbol.
807            ///
808            /// This function is a short-hand for `self.all_symbols().contains(&symbol)`.
809            ///
810            /// ## Example
811            ///
812            /// ```rust
813            /// assert!(Chip::Esp32s3.contains("soc_has_pcnt"));
814            /// ```
815            pub fn contains(self, symbol: &str) -> bool {
816                self.all_symbols().contains(&symbol)
817            }
818
819            /// Calling this function will define all cfg symbols for the firmware crate to use.
820            pub fn define_cfgs(self) {
821                self.config().define_cfgs()
822            }
823
824            /// Returns all symbols as a big slice.
825            ///
826            /// ## Example
827            ///
828            /// ```rust
829            /// assert!(Chip::Esp32s3.all_symbols().contains("soc_has_pcnt"));
830            /// ```
831            pub fn all_symbols(&self) -> &'static [&'static str] {
832                self.config().symbols
833            }
834
835            /// Returns an iterator over all chips.
836            ///
837            /// ## Example
838            ///
839            /// ```rust
840            /// assert!(Chip::iter().any(|c| c == Chip::Esp32));
841            /// ```
842            pub fn iter() -> impl Iterator<Item = Chip> {
843                [
844                    #( Self::#chip ),*
845                ].into_iter()
846            }
847
848            fn config(self) -> Config {
849                match self {
850                    #(Self::#chip => #config),*
851                }
852            }
853        }
854
855        struct Config {
856            architecture: &'static str,
857            target: &'static str,
858            symbols: &'static [&'static str],
859            cfgs: &'static [&'static str],
860        }
861
862        impl Config {
863            fn define_cfgs(&self) {
864                emit_check_cfg_directives();
865                for cfg in self.cfgs {
866                    println!("{cfg}");
867                }
868            }
869        }
870
871        /// Prints `cargo:rustc-check-cfg` lines.
872        pub fn emit_check_cfg_directives() {
873            #(println!(#check_cfgs);)*
874        }
875    }
876}
877
878pub fn generate_lib_rs() -> TokenStream {
879    let chips = Chip::iter().map(|c| {
880        let feature = format!("{c}");
881        let file = format!("_generated_{c}.rs");
882        quote! {
883            #[cfg(feature = #feature)]
884            include!(#file);
885        }
886    });
887
888    quote! {
889        //! # (Generated) metadata for Espressif MCUs.
890        //!
891        //! This crate provides properties that are specific to various Espressif microcontrollers,
892        //! and provides macros to work with peripherals, pins, and various other parts of the chips.
893        //!
894        //! This crate can be used both in firmware, as well as in build scripts, but the usage is different.
895        //!
896        //! ## Usage in build scripts
897        //!
898        //! To use the `Chip` enum, add the crate to your `Cargo.toml` build
899        //! dependencies, with the `build-script` feature:
900        //!
901        //! ```toml
902        //! [build-dependencies]
903        //! esp-metadata-generated = { version = "...", features = ["build-script"] }
904        //! ```
905        //!
906        //! ## Usage in firmware
907        //!
908        //! To use the various macros, add the crate to your `Cargo.toml` dependencies.
909        //! A device-specific feature needs to be enabled in order to use the crate, usually
910        //! picked by the user:
911        //!
912        //! ```toml
913        //! [dependencies]
914        //! esp-metadata-generated = { version = "..." }
915        //! # ...
916        //!
917        //! [features]
918        //! esp32 = ["esp-metadata-generated/esp32"]
919        //! esp32c2 = ["esp-metadata-generated/esp32c2"]
920        //! # ...
921        //! ```
922        //!
923        //! ## `for_each` macros
924        //!
925        //! The basic syntax of this macro looks like a macro definition with two distinct syntax options:
926        //!
927        //! ```rust, no_run
928        //! for_each_peripherals! {
929        //!     // Individual matcher, invoked separately for each peripheral instance
930        //!     ( <individual match syntax> ) => { /* some code */ };
931        //!
932        //!     // Repeated matcher, invoked once with all peripheral instances
933        //!     ( all $( (<individual match syntax>) ),* ) => { /* some code */ };
934        //! }
935        //! ```
936        //!
937        //! You can specify any number of matchers in the same invocation.
938        //!
939        //! > The way code is generated, you will need to use the full `return` syntax to return any
940        //! > values from code generated with these macros.
941        //!
942        //! ### Using the individual matcher
943        //!
944        //! In this use case, each item's data is individually passed through the macro. This can be used to
945        //! generate code for each item separately, allowing specializing the implementation where needed.
946        //!
947        //! ```rust,no_run
948        //! for_each_gpio! {
949        //!   // Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] [Output]))`
950        //!   ($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 */ };
951        //!
952        //!   // You can create matchers with data filled in. This example will specifically match GPIO2
953        //!   ($n:literal, GPIO2 $input_af:tt $output_af:tt $attributes:tt) => { /* Additional case only for GPIO2 */ };
954        //! }
955        //! ```
956        //!
957        //! Different macros can have multiple different syntax options for their individual matchers, usually
958        //! to provide more detailed information, while preserving simpler syntax for more basic use cases.
959        //! Consult each macro's documentation for available options.
960        //!
961        //! ### Repeated matcher
962        //!
963        //! With this option, all data is passed through the macro all at once. This form can be used to,
964        //! for example, generate struct fields. If the macro has multiple individual matcher options,
965        //! there are separate repeated matchers for each of the options.
966        //!
967        //! To use this option, start the match pattern with the name of the individual matcher option. When
968        //! there is only a single individual matcher option, its repeated matcher is named `all` unless
969        //! otherwise specified by the macro.
970        //!
971        //! ```rust,no_run
972        //! // Example usage to create a struct containing all GPIOs:
973        //! for_each_gpio! {
974        //!     (all $( ($n:literal, $gpio:ident $_af_ins:tt $_af_outs:tt $_attrs:tt) ),*) => {
975        //!         struct Gpios {
976        //!             $(
977        //!                 #[doc = concat!(" The ", stringify!($n), "th GPIO pin")]
978        //!                 pub $gpio: Gpio<$n>,
979        //!             )*
980        //!         }
981        //!     };
982        //! }
983        //! ```
984        #![cfg_attr(docsrs, feature(doc_cfg))]
985        #![cfg_attr(not(feature = "build-script"), no_std)]
986
987        #(#chips)*
988
989        #[cfg(any(feature = "build-script", docsrs))]
990        include!( "_build_script_utils.rs");
991    }
992}
993
994pub fn generate_chip_support_status(output: &mut impl Write) -> std::fmt::Result {
995    let nothing = "";
996
997    // Calculate the width of the first column.
998    let driver_col_width = std::iter::once("Driver")
999        .chain(
1000            PeriConfig::drivers()
1001                .iter()
1002                .filter(|i| !i.hide_from_peri_table)
1003                .map(|i| i.name),
1004        )
1005        .map(|c| c.len())
1006        .max()
1007        .unwrap();
1008
1009    // Header
1010    write!(output, "| {:driver_col_width$} |", "Driver")?;
1011    for chip in Chip::iter() {
1012        write!(output, " {} |", chip.pretty_name())?;
1013    }
1014    writeln!(output)?;
1015
1016    // Header separator
1017    write!(output, "| {nothing:-<driver_col_width$} |")?;
1018    for chip in Chip::iter() {
1019        write!(
1020            output,
1021            ":{nothing:-<width$}:|",
1022            width = chip.pretty_name().len()
1023        )?;
1024    }
1025    writeln!(output)?;
1026
1027    // Driver support status
1028    for SupportItem {
1029        name,
1030        config_group,
1031        hide_from_peri_table,
1032    } in PeriConfig::drivers()
1033    {
1034        if *hide_from_peri_table {
1035            continue;
1036        }
1037        write!(output, "| {name:driver_col_width$} |")?;
1038        for chip in Chip::iter() {
1039            let config = Config::for_chip(&chip);
1040
1041            let status = config.device.peri_config.support_status(config_group);
1042            let status_icon = match status {
1043                None => " ",
1044                Some(status) => status.icon(),
1045            };
1046            // VSCode displays emojis just a bit wider than 2 characters, making this
1047            // approximation a bit too wide but good enough.
1048            let support_cell_width = chip.pretty_name().len() - status.is_some() as usize;
1049            write!(output, " {status_icon:support_cell_width$} |")?;
1050        }
1051        writeln!(output)?;
1052    }
1053
1054    writeln!(output)?;
1055
1056    // Print legend
1057    writeln!(output, " * Empty cell: not available")?;
1058    for s in [
1059        SupportStatus::NotSupported,
1060        SupportStatus::Partial,
1061        SupportStatus::Supported,
1062    ] {
1063        writeln!(output, " * {}: {}", s.icon(), s.status())?;
1064    }
1065
1066    Ok(())
1067}