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