Skip to main content

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
14mod support_status;
15
16use crate::{
17    cfg::{SupportItem, Value},
18    support_status::SupportStatusLevel,
19};
20
21macro_rules! include_toml {
22    (Config, $file:expr) => {{
23        static LOADED_TOML: OnceLock<Config> = OnceLock::new();
24        LOADED_TOML.get_or_init(|| {
25            let config: Config = basic_toml::from_str(include_str!($file))
26                .with_context(|| format!("Failed to load device configuration: {}", $file))
27                .unwrap();
28
29            config
30                .validate()
31                .with_context(|| format!("Failed to validate device configuration: {}", $file))
32                .unwrap();
33
34            config
35        })
36    }};
37}
38
39/// Supported device architectures.
40#[derive(
41    Debug,
42    Clone,
43    Copy,
44    PartialEq,
45    Eq,
46    PartialOrd,
47    Ord,
48    serde::Deserialize,
49    serde::Serialize,
50    strum::Display,
51    strum::EnumIter,
52    strum::EnumString,
53    strum::AsRefStr,
54)]
55#[serde(rename_all = "lowercase")]
56#[strum(serialize_all = "lowercase")]
57pub enum Arch {
58    /// RISC-V architecture
59    RiscV,
60    /// Xtensa architecture
61    Xtensa,
62}
63
64/// Device core count.
65#[derive(
66    Debug,
67    Clone,
68    Copy,
69    PartialEq,
70    Eq,
71    PartialOrd,
72    Ord,
73    serde::Deserialize,
74    serde::Serialize,
75    strum::Display,
76    strum::EnumIter,
77    strum::EnumString,
78    strum::AsRefStr,
79)]
80pub enum Cores {
81    /// Single CPU core
82    #[serde(rename = "single_core")]
83    #[strum(serialize = "single_core")]
84    Single,
85    /// Two or more CPU cores
86    #[serde(rename = "multi_core")]
87    #[strum(serialize = "multi_core")]
88    Multi,
89}
90
91/// Supported devices.
92#[derive(
93    Debug,
94    Clone,
95    Copy,
96    PartialEq,
97    Eq,
98    PartialOrd,
99    Ord,
100    Hash,
101    serde::Deserialize,
102    serde::Serialize,
103    strum::Display,
104    strum::EnumIter,
105    strum::EnumString,
106    strum::AsRefStr,
107)]
108#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
109#[serde(rename_all = "kebab-case")]
110#[strum(serialize_all = "kebab-case")]
111pub enum Chip {
112    /// ESP32
113    Esp32,
114    /// ESP32-C2, ESP8684
115    Esp32c2,
116    /// ESP32-C3, ESP8685
117    Esp32c3,
118    /// ESP32-C5
119    Esp32c5,
120    /// ESP32-C6
121    Esp32c6,
122    /// ESP32-C61
123    Esp32c61,
124    /// ESP32-H2
125    Esp32h2,
126    /// ESP32-S2
127    Esp32s2,
128    /// ESP32-S3
129    Esp32s3,
130}
131
132impl Chip {
133    pub fn from_cargo_feature() -> Result<Self> {
134        let all_chips = Chip::iter().map(|c| c.to_string()).collect::<Vec<_>>();
135
136        let mut chip = None;
137        for c in all_chips.iter() {
138            if std::env::var(format!("CARGO_FEATURE_{}", c.to_uppercase())).is_ok() {
139                if chip.is_some() {
140                    bail!(
141                        "Expected exactly one of the following features to be enabled: {}",
142                        all_chips.join(", ")
143                    );
144                }
145                chip = Some(c);
146            }
147        }
148
149        let Some(chip) = chip else {
150            bail!(
151                "Expected exactly one of the following features to be enabled: {}",
152                all_chips.join(", ")
153            );
154        };
155
156        Ok(Self::from_str(chip.as_str()).unwrap())
157    }
158
159    pub fn target(&self) -> String {
160        Config::for_chip(self).device.target.clone()
161    }
162
163    pub fn has_lp_core(&self) -> bool {
164        use Chip::*;
165        // TODO this should be checking for lp_core_driver_supported
166        matches!(self, Esp32c6 | Esp32s2 | Esp32s3)
167    }
168
169    pub fn lp_target(&self) -> Result<&'static str> {
170        match self {
171            Chip::Esp32c5 | Chip::Esp32c6 => Ok("riscv32imac-unknown-none-elf"),
172            Chip::Esp32s2 | Chip::Esp32s3 => Ok("riscv32imc-unknown-none-elf"),
173            _ => bail!("Chip does not contain an LP core: '{self}'"),
174        }
175    }
176
177    pub fn name(&self) -> &str {
178        match self {
179            Chip::Esp32 => "Esp32",
180            Chip::Esp32c2 => "Esp32c2",
181            Chip::Esp32c3 => "Esp32c3",
182            Chip::Esp32c5 => "Esp32c5",
183            Chip::Esp32c6 => "Esp32c6",
184            Chip::Esp32c61 => "Esp32c61",
185            Chip::Esp32h2 => "Esp32h2",
186            Chip::Esp32s2 => "Esp32s2",
187            Chip::Esp32s3 => "Esp32s3",
188        }
189    }
190
191    pub fn pretty_name(&self) -> &str {
192        match self {
193            Chip::Esp32 => "ESP32",
194            Chip::Esp32c2 => "ESP32-C2",
195            Chip::Esp32c3 => "ESP32-C3",
196            Chip::Esp32c5 => "ESP32-C5",
197            Chip::Esp32c6 => "ESP32-C6",
198            Chip::Esp32c61 => "ESP32-C61",
199            Chip::Esp32h2 => "ESP32-H2",
200            Chip::Esp32s2 => "ESP32-S2",
201            Chip::Esp32s3 => "ESP32-S3",
202        }
203    }
204
205    pub fn is_xtensa(&self) -> bool {
206        matches!(self, Chip::Esp32 | Chip::Esp32s2 | Chip::Esp32s3)
207    }
208
209    pub fn is_riscv(&self) -> bool {
210        !self.is_xtensa()
211    }
212
213    pub fn list_of_possible_symbols() -> &'static IndexMap<String, Option<Vec<String>>> {
214        type SymbolMap = IndexMap<String, Option<Vec<String>>>;
215        static CACHED_SYMBOLS: OnceLock<SymbolMap> = OnceLock::new();
216        CACHED_SYMBOLS.get_or_init(|| {
217            let mut cfgs: SymbolMap = SymbolMap::new();
218
219            for chip in Chip::iter() {
220                let config = Config::for_chip(&chip);
221                for symbol in config.all() {
222                    if let Some((symbol_name, symbol_value)) = symbol.split_once('=') {
223                        let symbol_name = symbol_name.replace('.', "_");
224                        let entry = cfgs.entry(symbol_name).or_default();
225                        let vec = entry.get_or_insert_with(Vec::new);
226
227                        // Avoid duplicates in the same cfg.
228                        if !vec.contains(&symbol_value.to_string()) {
229                            vec.push(symbol_value.to_string());
230                        }
231                    } else {
232                        // https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-check-cfg
233                        let cfg = symbol.replace('.', "_");
234
235                        if !cfgs.contains_key(&cfg) {
236                            cfgs.insert(cfg, None);
237                        }
238                    }
239                }
240            }
241
242            cfgs
243        })
244    }
245
246    pub fn list_of_check_cfgs() -> Vec<String> {
247        let mut cfgs = vec![];
248
249        // Used by our documentation builds to prevent the huge red warning banner.
250        cfgs.push(String::from("cargo:rustc-check-cfg=cfg(not_really_docsrs)"));
251        cfgs.push(String::from("cargo:rustc-check-cfg=cfg(semver_checks)"));
252
253        let possible_symbols = Self::list_of_possible_symbols();
254        for (sym, values) in possible_symbols.iter() {
255            if values.is_none() {
256                cfgs.push(format!("cargo:rustc-check-cfg=cfg({})", sym));
257            }
258        }
259
260        for (sym, values) in possible_symbols.iter() {
261            if let Some(values) = values {
262                cfgs.push(format!(
263                    "cargo:rustc-check-cfg=cfg({sym}, values({}))",
264                    values.join(",")
265                ));
266            }
267        }
268
269        cfgs
270    }
271}
272
273#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
274pub struct MemoryRegion {
275    name: String,
276    start: u32,
277    end: u32,
278}
279
280#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
281pub struct PeripheralDef {
282    /// The name of the esp-hal peripheral singleton
283    name: String,
284    /// When omitted, same as `name`
285    #[serde(default, rename = "pac")]
286    pac_name: Option<String>,
287    /// Whether or not the peripheral has a PAC counterpart
288    #[serde(default, rename = "virtual")]
289    is_virtual: bool,
290    /// List of related interrupt signals
291    #[serde(default)]
292    interrupts: IndexMap<String, String>,
293    /// If the peripheral is DMA eligible, this defines the peripheral selector value.
294    #[serde(default)]
295    dma_peripheral: Option<u32>,
296    /// Set to true to hide a peripheral from the Peripherals struct.
297    #[serde(default)]
298    hidden: bool,
299    /// If the peripheral isn't specifically named by a driver, this field can be used to mark it
300    /// as stable.
301    #[serde(default)]
302    stable: bool,
303
304    /// Instantiates a clock group for the peripheral.
305    #[serde(default)]
306    clock_group: Option<String>,
307}
308
309impl PeripheralDef {
310    fn symbol_name(&self) -> String {
311        format!("soc_has_{}", self.name.to_lowercase())
312    }
313}
314
315#[derive(Debug, Clone, serde::Deserialize)]
316struct Device {
317    name: String,
318    arch: Arch,
319    target: String,
320    cores: usize,
321    trm: String,
322
323    symbols: Vec<String>,
324
325    // Peripheral driver configuration:
326    #[serde(flatten)]
327    peri_config: PeriConfig,
328}
329
330// Output a Display-able value as a TokenStream, intended to generate numbers
331// without the type suffix.
332fn number(n: impl std::fmt::Display) -> TokenStream {
333    TokenStream::from_str(&format!("{n}")).unwrap()
334}
335fn number_hex(n: impl std::fmt::Display + std::fmt::UpperHex) -> TokenStream {
336    TokenStream::from_str(&format!("{n:#X}")).unwrap()
337}
338
339/// Device configuration file format.
340#[derive(Debug, Clone, serde::Deserialize)]
341pub struct Config {
342    device: Device,
343    #[serde(skip)]
344    all_symbols: OnceLock<Vec<String>>,
345}
346
347impl Config {
348    /// The configuration for the specified chip.
349    pub fn for_chip(chip: &Chip) -> &Self {
350        match chip {
351            Chip::Esp32 => include_toml!(Config, "../devices/esp32.toml"),
352            Chip::Esp32c2 => include_toml!(Config, "../devices/esp32c2.toml"),
353            Chip::Esp32c3 => include_toml!(Config, "../devices/esp32c3.toml"),
354            Chip::Esp32c5 => include_toml!(Config, "../devices/esp32c5.toml"),
355            Chip::Esp32c6 => include_toml!(Config, "../devices/esp32c6.toml"),
356            Chip::Esp32c61 => include_toml!(Config, "../devices/esp32c61.toml"),
357            Chip::Esp32h2 => include_toml!(Config, "../devices/esp32h2.toml"),
358            Chip::Esp32s2 => include_toml!(Config, "../devices/esp32s2.toml"),
359            Chip::Esp32s3 => include_toml!(Config, "../devices/esp32s3.toml"),
360        }
361    }
362
363    /// Create an empty configuration
364    pub fn empty() -> Self {
365        Self {
366            device: Device {
367                name: String::new(),
368                arch: Arch::RiscV,
369                target: String::new(),
370                cores: 1,
371                trm: String::new(),
372                symbols: Vec::new(),
373                peri_config: PeriConfig::default(),
374            },
375            all_symbols: OnceLock::new(),
376        }
377    }
378
379    fn validate(&self) -> Result<()> {
380        for instance in self.device.peri_config.driver_instances() {
381            let (driver, peri) = instance.split_once('.').unwrap();
382            ensure!(
383                self.peripherals()
384                    .iter()
385                    .any(|p| p.name.eq_ignore_ascii_case(peri)),
386                "Driver {driver} marks an implementation for '{peri}' but this peripheral is not defined for '{}'",
387                self.device.name
388            );
389        }
390
391        Ok(())
392    }
393
394    /// The name of the device.
395    pub fn name(&self) -> String {
396        self.device.name.clone()
397    }
398
399    /// The CPU architecture of the device.
400    pub fn arch(&self) -> Arch {
401        self.device.arch
402    }
403
404    /// The core count of the device.
405    pub fn cores(&self) -> Cores {
406        if self.device.cores > 1 {
407            Cores::Multi
408        } else {
409            Cores::Single
410        }
411    }
412
413    /// The peripherals of the device.
414    pub fn peripherals(&self) -> &[PeripheralDef] {
415        self.device
416            .peri_config
417            .soc
418            .as_ref()
419            .map(|props| props.config.peripherals.as_slice())
420            .unwrap_or(&[])
421    }
422
423    /// User-defined symbols for the device.
424    pub fn symbols(&self) -> &[String] {
425        &self.device.symbols
426    }
427
428    /// All configuration values for the device.
429    pub fn all(&self) -> &[String] {
430        self.all_symbols.get_or_init(|| {
431            let mut all = vec![
432                self.device.name.clone(),
433                self.device.arch.to_string(),
434                match self.cores() {
435                    Cores::Single => String::from("single_core"),
436                    Cores::Multi => String::from("multi_core"),
437                },
438            ];
439            all.extend(self.peripherals().iter().map(|p| p.symbol_name()));
440            all.extend_from_slice(&self.device.symbols);
441            all.extend(
442                self.device
443                    .peri_config
444                    .driver_names()
445                    .map(|name| format!("{name}_driver_supported")),
446            );
447            all.extend(self.device.peri_config.driver_instances());
448
449            all.extend(
450                self.device
451                    .peri_config
452                    .properties()
453                    .filter_map(|(name, optional, value)| {
454                        let is_unset = matches!(value, Value::Unset);
455                        let mut syms = match value {
456                            Value::Boolean(true) => Some(vec![name.to_string()]),
457                            Value::NumberList(_) => None,
458                            Value::String(value) => Some(vec![format!("{name}=\"{value}\"")]),
459                            Value::Generic(v) => v.cfgs(),
460                            Value::StringList(values) => Some(
461                                values
462                                    .iter()
463                                    .map(|val| {
464                                        format!(
465                                            "{name}_{}",
466                                            val.to_lowercase().replace("-", "_").replace("/", "_")
467                                        )
468                                    })
469                                    .collect(),
470                            ),
471                            Value::Number(value) => Some(vec![format!("{name}=\"{value}\"")]),
472                            _ => None,
473                        };
474
475                        if optional && !is_unset {
476                            syms.get_or_insert_default().push(format!("{name}_is_set"));
477                        }
478
479                        syms
480                    })
481                    .flatten(),
482            );
483            all
484        })
485    }
486
487    /// Does the configuration contain `item`?
488    pub fn contains(&self, item: &str) -> bool {
489        self.all().iter().any(|i| i == item)
490    }
491
492    pub fn generate_metadata(&self) -> TokenStream {
493        let properties = self.generate_properties();
494        let peris = self.generate_peripherals();
495        let gpios = self.generate_gpios();
496
497        quote! {
498            #properties
499            #peris
500            #gpios
501        }
502    }
503
504    fn generate_properties(&self) -> TokenStream {
505        let chip_name = self.name();
506        let chip_pretty_name = Chip::from_str(&chip_name)
507            .expect("Valid chip name")
508            .pretty_name()
509            .to_string();
510
511        // Translate the chip properties into a macro that can be used in esp-hal:
512        let arch = self.device.arch.as_ref();
513        let cores = number(self.device.cores);
514        let trm = &self.device.trm;
515
516        let mut macros = vec![];
517
518        let peripheral_properties =
519            self.device
520                .peri_config
521                .properties()
522                .flat_map(|(name, _optional, value)| match value {
523                    Value::Number(value) => {
524                        let value = number(value); // ensure no numeric suffix is added
525                        quote! {
526                            (#name) => { #value };
527                            (#name, str) => { stringify!(#value) };
528                        }
529                    }
530                    Value::Boolean(value) => quote! {
531                        (#name) => { #value };
532                    },
533                    Value::String(value) => quote! {
534                        (#name) => { #value };
535                    },
536                    Value::NumberList(numbers) => {
537                        let numbers = numbers.into_iter().map(number).collect::<Vec<_>>();
538                        macros.push(generate_for_each_macro(
539                            &name.replace(".", "_"),
540                            &[("all", &numbers)],
541                        ));
542                        quote! {}
543                    }
544                    Value::Generic(v) => {
545                        if let Some(for_each) = v.macros() {
546                            macros.push(for_each);
547                        }
548                        v.property_macro_branches()
549                    }
550                    Value::Unset | Value::StringList(_) => {
551                        quote! {}
552                    }
553                });
554
555        quote! {
556            /// The name of the chip as `&str`
557            ///
558            /// # Example
559            ///
560            /// ```rust, no_run
561            /// use esp_hal::chip;
562            /// let chip_name = chip!();
563            #[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")]
564            /// ```
565            #[macro_export]
566            #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
567            macro_rules! chip {
568                () => { #chip_name };
569            }
570
571            /// The pretty name of the chip as `&str`
572            ///
573            /// # Example
574            ///
575            /// ```rust, no_run
576            /// use esp_hal::chip;
577            /// let chip_name = chip_pretty!();
578            #[doc = concat!("assert_eq!(chip_name, ", chip_pretty!(), ")")]
579            /// ```
580            #[macro_export]
581            #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
582            macro_rules! chip_pretty {
583                () => { #chip_pretty_name };
584            }
585
586            /// The properties of this chip and its drivers.
587            #[macro_export]
588            #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
589            macro_rules! property {
590                ("chip") => { #chip_name };
591                ("arch") => { #arch };
592                ("cores") => { #cores };
593                ("cores", str) => { stringify!(#cores) };
594                ("trm") => { #trm };
595                #(#peripheral_properties)*
596            }
597
598            #(#macros)*
599        }
600    }
601
602    fn generate_gpios(&self) -> TokenStream {
603        let Some(gpio) = self.device.peri_config.gpio.as_ref() else {
604            // No GPIOs defined, nothing to do.
605            return quote! {};
606        };
607
608        cfg::generate_gpios(gpio)
609    }
610
611    fn generate_peripherals(&self) -> TokenStream {
612        let mut tokens = TokenStream::new();
613
614        // TODO: repeat for all drivers that have Instance traits
615        if let Some(peri) = self.device.peri_config.i2c_master.as_ref() {
616            tokens.extend(cfg::generate_i2c_master_peripherals(peri));
617        };
618        if let Some(peri) = self.device.peri_config.uart.as_ref() {
619            tokens.extend(cfg::generate_uart_peripherals(peri));
620        }
621        if let Some(peri) = self.device.peri_config.spi_master.as_ref() {
622            tokens.extend(cfg::generate_spi_master_peripherals(peri));
623        };
624        if let Some(peri) = self.device.peri_config.spi_slave.as_ref() {
625            tokens.extend(cfg::generate_spi_slave_peripherals(peri));
626        };
627
628        tokens.extend(self.generate_peripherals_macro());
629
630        tokens
631    }
632
633    fn generate_peripherals_macro(&self) -> TokenStream {
634        let mut all_peripherals = vec![];
635        let mut singleton_peripherals = vec![];
636        let mut dma_peripherals = vec![];
637
638        let mut stable_peris = vec![];
639
640        for p in self.peripherals().iter() {
641            if p.stable && !stable_peris.contains(&p.name.as_str()) {
642                stable_peris.push(p.name.as_str());
643            }
644        }
645
646        if let Some(gpio) = self.device.peri_config.gpio.as_ref() {
647            for gpio in gpio.pins_and_signals.pins.iter() {
648                let pin = format_ident!("GPIO{}", gpio.pin);
649                let mut docs = format!("GPIO{} peripheral singleton", gpio.pin);
650
651                let limitations = gpio.limitations();
652
653                if !limitations.is_empty() {
654                    // Append a marker and an explanation to the short description
655                    let limitations = limitations
656                        .iter()
657                        .map(|limitation| format!("<li>{}</li>", limitation))
658                        .collect::<Vec<_>>()
659                        .join("\n");
660                    write!(
661                        &mut docs,
662                        r#" (Limitations exist)
663
664<section class="warning">
665This pin may be available with certain limitations. Check your hardware to make sure whether you can use it.
666<ul>
667{limitations}
668</ul>
669</section>"#,
670                    )
671                    .unwrap();
672                }
673                let docs = docs.lines();
674                let tokens = quote! {
675                    #(#[doc = #docs])* #pin <= virtual ()
676                };
677                all_peripherals.push(quote! { @peri_type #tokens });
678                singleton_peripherals.push(quote! { #pin });
679            }
680        }
681
682        for peri in self.peripherals().iter() {
683            let hal = format_ident!("{}", peri.name);
684            let pac = if peri.is_virtual {
685                format_ident!("virtual")
686            } else {
687                format_ident!("{}", peri.pac_name.as_deref().unwrap_or(peri.name.as_str()))
688            };
689            // Make sure we have a stable order
690            let mut interrupts = peri.interrupts.iter().collect::<Vec<_>>();
691            interrupts.sort_by_key(|(k, _)| k.as_str());
692            let interrupts = interrupts.iter().map(|(k, v)| {
693                let pac_interrupt_name = format_ident!("{v}");
694                let bind = format_ident!("bind_{k}_interrupt");
695                let enable = format_ident!("enable_{k}_interrupt");
696                let disable = format_ident!("disable_{k}_interrupt");
697                quote! { #pac_interrupt_name: { #bind, #enable, #disable } }
698            });
699            let singleton_doc = format!("{} peripheral singleton", peri.name);
700            let tokens = quote! {
701                #[doc = #singleton_doc] #hal <= #pac ( #(#interrupts),* )
702            };
703            if stable_peris
704                .iter()
705                .any(|p| peri.name.eq_ignore_ascii_case(p))
706            {
707                all_peripherals.push(quote! { @peri_type #tokens });
708                if !peri.hidden {
709                    singleton_peripherals.push(quote! { #hal });
710                }
711            } else {
712                all_peripherals.push(quote! { @peri_type #tokens (unstable) });
713                if !peri.hidden {
714                    singleton_peripherals.push(quote! { #hal (unstable) });
715                }
716            }
717
718            if let Some(dma_peripheral) = peri.dma_peripheral {
719                dma_peripherals.push((peri.name.as_str(), dma_peripheral));
720            }
721        }
722
723        dma_peripherals.sort_by_key(|(_, dma_peripheral)| *dma_peripheral);
724
725        let dma_peripherals = dma_peripherals
726            .into_iter()
727            .map(|(name, dma_peripheral)| {
728                use convert_case::{Boundary, Case, Casing, pattern};
729
730                let peri = format_ident!("{}", name);
731                let dma_peripheral = number(dma_peripheral);
732                let variant_name = format_ident!(
733                    "{}",
734                    name.from_case(Case::Custom {
735                        boundaries: &[Boundary::LOWER_UPPER, Boundary::UNDERSCORE],
736                        pattern: pattern::capital,
737                        delim: "",
738                    })
739                    .to_case(Case::Pascal)
740                );
741                quote! { #peri, #variant_name, #dma_peripheral }
742            })
743            .collect::<Vec<_>>();
744
745        generate_for_each_macro(
746            "peripheral",
747            &[
748                ("all", &all_peripherals),
749                ("singletons", &singleton_peripherals),
750                ("dma_eligible", &dma_peripherals),
751            ],
752        )
753    }
754
755    pub fn active_cfgs(&self) -> Vec<String> {
756        let mut cfgs = vec![];
757
758        // Define all necessary configuration symbols for the configured device:
759        for symbol in self.all() {
760            cfgs.push(symbol.replace('.', "_"));
761        }
762
763        cfgs
764    }
765
766    /// For each symbol generates a cargo directive that activates it.
767    pub fn list_of_cfgs(&self) -> Vec<String> {
768        self.active_cfgs()
769            .iter()
770            .map(|cfg| format!("cargo:rustc-cfg={cfg}"))
771            .collect()
772    }
773}
774
775type Branch<'a> = (&'a str, &'a [TokenStream]);
776
777fn generate_for_each_macro(name: &str, branches: &[Branch<'_>]) -> TokenStream {
778    let macro_name = format_ident!("for_each_{name}");
779
780    let flat_branches = branches.iter().flat_map(|b| b.1.iter());
781    let repeat_names = branches.iter().map(|b| TokenStream::from_str(b.0).unwrap());
782    let repeat_branches = branches.iter().map(|b| b.1);
783    let inner = format_ident!("_for_each_inner_{name}");
784
785    quote! {
786        // This macro is called in esp-hal to implement a driver's
787        // Instance trait for available peripherals. It works by defining, then calling an inner
788        // macro that substitutes the properties into the template provided by the call in esp-hal.
789        #[macro_export]
790        #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
791        macro_rules! #macro_name {
792            (
793                $($pattern:tt => $code:tt;)*
794            ) => {
795                macro_rules! #inner {
796                    $(($pattern) => $code;)*
797                    ($other:tt) => {}
798                }
799
800                // Generate single macro calls with each branch
801                // Usage:
802                // ```
803                // for_each_x! {
804                //     ( $pattern ) => {
805                //         $code
806                //     }
807                // }
808                // ```
809                #( #inner!(( #flat_branches ));)*
810
811                // Generate a single macro call with all branches.
812                // Usage:
813                // ```
814                // for_each_x! {
815                //     (all $( ($pattern) ),*) => {
816                //         $( $code )*
817                //     }
818                // }
819                // ```
820                #(  #inner!( (#repeat_names #( (#repeat_branches) ),*) ); )*
821            };
822        }
823    }
824}
825
826pub fn generate_build_script_utils() -> TokenStream {
827    let check_cfgs = Chip::list_of_check_cfgs();
828
829    let chip = Chip::iter()
830        .map(|c| format_ident!("{}", c.name()))
831        .collect::<Vec<_>>();
832    let feature_env = Chip::iter().map(|c| format!("CARGO_FEATURE_{}", c.as_ref().to_uppercase()));
833    let name = Chip::iter()
834        .map(|c| c.as_ref().to_string())
835        .collect::<Vec<_>>();
836    let all_chip_features = name.join(", ");
837    let config = Chip::iter().map(|chip| {
838        let config = Config::for_chip(&chip);
839        let symbols = config.active_cfgs();
840        let arch = config.device.arch.to_string();
841        let target = config.device.target.as_str();
842        let cfgs = config.list_of_cfgs();
843        let soc_config = config.device.peri_config.soc.as_ref().unwrap();
844        let memory_regions = soc_config.config.memory_map.ranges.iter().map(|r| {
845            let name = r.name.as_str();
846            let start = number_hex(r.range.start);
847            let end = number_hex(r.range.end);
848            quote! {
849                (#name, MemoryRegion {
850                    address_range: #start .. #end,
851                })
852            }
853        });
854        let pins = config
855            .device
856            .peri_config
857            .gpio
858            .as_ref()
859            .map(|gpio| {
860                gpio.pins_and_signals
861                    .pins
862                    .iter()
863                    .map(|pin| {
864                        let num = number(pin.pin);
865                        let limitations = pin.limitations().into_iter().map(|limitation| {
866                            TokenStream::from_str(
867                                &basic_toml::to_string(&limitation)
868                                    .expect("Serializing limitations should be infallible"),
869                            )
870                            .expect("Valid TOML string can be re-parsed as Rust strings")
871                        });
872                        quote! {
873                            PinInfo {
874                                pin: #num,
875                                limitations: &[#(#limitations,)*],
876                            }
877                        }
878                    })
879                    .collect::<Vec<_>>()
880            })
881            .unwrap_or_default();
882        quote! {
883            Config {
884                architecture: #arch,
885                target: #target,
886                symbols: &[
887                    #(#symbols,)*
888                ],
889                cfgs: &[
890                    #(#cfgs,)*
891                ],
892                memory_layout: &MemoryLayout {
893                    regions: &[
894                        #(#memory_regions,)*
895                    ],
896                },
897                pins: &[
898                    #(#pins,)*
899                ]
900            }
901        }
902    });
903
904    let bail_message = format!(
905        "Expected exactly one of the following features to be enabled: {all_chip_features}"
906    );
907
908    let from_str_err = format!("Unknown chip {{s}}. Possible options: {all_chip_features}");
909
910    quote! {
911        use core::ops::Range;
912
913        extern crate alloc;
914
915        // make it possible to build documentation without `std`.
916        #[cfg(docsrs)]
917        macro_rules! println {
918            ($($any:tt)*) => {};
919        }
920
921        #[doc(hidden)]
922        #[macro_export]
923        macro_rules! __assert_features_logic {
924            ($op:tt, $limit:expr, $msg:literal, $($feature:literal),+ $(,)?) => {{
925                let enabled: Vec<&str> = [
926                    $( if cfg!(feature = $feature) { Some($feature) } else { None }, )+
927                ]
928                .into_iter()
929                .flatten()
930                .collect();
931
932                assert!(
933                    enabled.len() $op $limit,
934                    concat!($msg, ": {}.\nCurrently enabled: {}. This might be caused by enabled default features.\n"),
935                    [$($feature),+].join(", "),
936                    if enabled.is_empty() {
937                        "none".to_string()
938                    } else {
939                        enabled.join(", ")
940                    }
941                );
942            }};
943        }
944
945        #[macro_export]
946        macro_rules! assert_unique_features {
947            ($($f:literal),+ $(,)?) => {
948                $crate::__assert_features_logic!(
949                    <=,
950                    1,
951                    "\nAt most one of the following features must be enabled",
952                    $($f),+
953                );
954            };
955        }
956
957        #[macro_export]
958        macro_rules! assert_unique_used_features {
959            ($($f:literal),+ $(,)?) => {
960                $crate::__assert_features_logic!(
961                    ==,
962                    1,
963                    "\nExactly one of the following features must be enabled",
964                    $($f),+
965                );
966            };
967        }
968
969        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
970        #[cfg_attr(docsrs, doc(cfg(feature = "build-script")))]
971        pub enum Chip {
972            #(#chip),*
973        }
974
975        impl core::str::FromStr for Chip {
976            type Err = alloc::string::String;
977
978            fn from_str(s: &str) -> Result<Self, Self::Err> {
979                match s {
980                    #( #name => Ok(Self::#chip), )*
981                    _ => Err(alloc::format!(#from_str_err)),
982                }
983            }
984        }
985
986        impl Chip {
987            /// Tries to extract the active chip from the active cargo features.
988            ///
989            /// Exactly one device feature must be enabled for this function to succeed.
990            pub fn from_cargo_feature() -> Result<Self, &'static str> {
991                let all_chips = [
992                    #((#feature_env, Self::#chip)),*
993                ];
994
995                let mut chip = None;
996
997                for (env, c) in all_chips {
998                    if std::env::var(env).is_ok() {
999                        if chip.is_some() {
1000                            return Err(#bail_message);
1001                        }
1002                        chip = Some(c);
1003                    }
1004                }
1005
1006                match chip {
1007                    Some(chip) => Ok(chip),
1008                    None => Err(#bail_message),
1009                }
1010            }
1011
1012            /// Returns whether the current chip uses the Tensilica Xtensa ISA.
1013            pub fn is_xtensa(self) -> bool {
1014                self.config().architecture == "xtensa"
1015            }
1016
1017            /// The target triple of the current chip.
1018            pub fn target(self) -> &'static str {
1019                self.config().target
1020            }
1021
1022            /// The simple name of the current chip.
1023            ///
1024            /// ## Example
1025            ///
1026            /// ```rust,no_run
1027            /// assert_eq!(Chip::Esp32s3.name(), "esp32s3");
1028            /// ```
1029            pub fn name(self) -> &'static str {
1030                match self {
1031                    #( Self::#chip => #name ),*
1032                }
1033            }
1034
1035            /// Returns whether the chip configuration contains the given symbol.
1036            ///
1037            /// This function is a short-hand for `self.all_symbols().contains(&symbol)`.
1038            ///
1039            /// ## Example
1040            ///
1041            /// ```rust,no_run
1042            /// assert!(Chip::Esp32s3.contains("soc_has_pcnt"));
1043            /// ```
1044            pub fn contains(self, symbol: &str) -> bool {
1045                self.all_symbols().contains(&symbol)
1046            }
1047
1048            /// Calling this function will define all cfg symbols for the firmware crate to use.
1049            pub fn define_cfgs(self) {
1050                self.config().define_cfgs()
1051            }
1052
1053            /// Returns all symbols as a big slice.
1054            ///
1055            /// ## Example
1056            ///
1057            /// ```rust,no_run
1058            /// assert!(Chip::Esp32s3.all_symbols().contains("soc_has_pcnt"));
1059            /// ```
1060            pub fn all_symbols(&self) -> &'static [&'static str] {
1061                self.config().symbols
1062            }
1063
1064            /// Returns memory layout information.
1065            pub fn memory_layout(&self) -> &'static MemoryLayout {
1066                self.config().memory_layout
1067            }
1068
1069            /// Returns information about all pins.
1070            pub fn pins(&self) -> &'static [PinInfo] {
1071                self.config().pins
1072            }
1073
1074            /// Returns an iterator over all chips.
1075            ///
1076            /// ## Example
1077            ///
1078            /// ```rust,no_run
1079            /// assert!(Chip::iter().any(|c| c == Chip::Esp32));
1080            /// ```
1081            pub fn iter() -> impl Iterator<Item = Chip> {
1082                [
1083                    #( Self::#chip ),*
1084                ]
1085                .into_iter()
1086            }
1087
1088            fn config(self) -> Config {
1089                match self {
1090                    #( Self::#chip => #config ),*
1091                }
1092            }
1093        }
1094
1095        /// Information about a memory region.
1096        pub struct MemoryRegion {
1097            address_range: Range<u32>,
1098        }
1099
1100        impl MemoryRegion {
1101            /// Returns the address range of the memory region.
1102            pub fn range(&self) -> Range<u32> {
1103                self.address_range.clone()
1104            }
1105
1106            /// Returns the size of the memory region in bytes.
1107            pub fn size(&self) -> u32 {
1108                self.address_range.end - self.address_range.start
1109            }
1110        }
1111
1112        /// Information about the memory layout of a chip.
1113        pub struct MemoryLayout {
1114            regions: &'static [(&'static str, MemoryRegion)],
1115        }
1116
1117        impl MemoryLayout {
1118            /// Returns the memory region with the given name.
1119            pub fn region(&self, name: &str) -> Option<&'static MemoryRegion> {
1120                self.regions
1121                    .iter()
1122                    .find_map(|(n, r)| if *n == name { Some(r) } else { None })
1123            }
1124        }
1125
1126        /// Information about a specific pin.
1127        #[non_exhaustive]
1128        pub struct PinInfo {
1129            /// The pin number.
1130            pub pin: usize,
1131
1132            /// The list of possible restriction categories for this pin.
1133            ///
1134            /// This can include "strapping", "spi_psram", etc.
1135            pub limitations: &'static [&'static str],
1136        }
1137
1138        struct Config {
1139            architecture: &'static str,
1140            target: &'static str,
1141            symbols: &'static [&'static str],
1142            cfgs: &'static [&'static str],
1143            memory_layout: &'static MemoryLayout,
1144            pins: &'static [PinInfo],
1145        }
1146
1147        impl Config {
1148            fn define_cfgs(&self) {
1149                emit_check_cfg_directives();
1150                for cfg in self.cfgs {
1151                    println!("{cfg}");
1152                }
1153            }
1154        }
1155
1156        /// Prints `cargo:rustc-check-cfg` lines.
1157        pub fn emit_check_cfg_directives() {
1158            #( println!(#check_cfgs); )*
1159        }
1160    }
1161}
1162
1163pub fn generate_lib_rs() -> TokenStream {
1164    let chips = Chip::iter().map(|c| {
1165        let feature = format!("{c}");
1166        let file = format!("_generated_{c}.rs");
1167        quote! {
1168            #[cfg(feature = #feature)]
1169            include!(#file);
1170        }
1171    });
1172
1173    quote! {
1174        //! # (Generated) metadata for Espressif MCUs.
1175        //!
1176        //! This crate provides properties that are specific to various Espressif microcontrollers,
1177        //! and provides macros to work with peripherals, pins, and various other parts of the chips.
1178        //!
1179        //! This crate can be used both in firmware, as well as in build scripts, but the usage is different.
1180        //!
1181        //! ## Usage in build scripts
1182        //!
1183        //! To use the `Chip` enum, add the crate to your `Cargo.toml` build
1184        //! dependencies, with the `build-script` feature:
1185        //!
1186        //! ```toml
1187        //! [build-dependencies]
1188        //! esp-metadata-generated = { version = "...", features = ["build-script"] }
1189        //! ```
1190        //!
1191        //! ## Usage in firmware
1192        //!
1193        //! To use the various macros, add the crate to your `Cargo.toml` dependencies.
1194        //! A device-specific feature needs to be enabled in order to use the crate, usually
1195        //! picked by the user:
1196        //!
1197        //! ```toml
1198        //! [dependencies]
1199        //! esp-metadata-generated = { version = "..." }
1200        //! # ...
1201        //!
1202        //! [features]
1203        //! esp32 = ["esp-metadata-generated/esp32"]
1204        //! esp32c2 = ["esp-metadata-generated/esp32c2"]
1205        //! # ...
1206        //! ```
1207        //!
1208        //! ## `for_each` macros
1209        //!
1210        //! The basic syntax of this macro looks like a macro definition with two distinct syntax options:
1211        //!
1212        //! ```rust, no_run
1213        //! for_each_peripherals! {
1214        //!     // Individual matcher, invoked separately for each peripheral instance
1215        //!     ( <individual match syntax> ) => { /* some code */ };
1216        //!
1217        //!     // Repeated matcher, invoked once with all peripheral instances
1218        //!     ( all $( (<individual match syntax>) ),* ) => { /* some code */ };
1219        //! }
1220        //! ```
1221        //!
1222        //! You can specify any number of matchers in the same invocation.
1223        //!
1224        //! > The way code is generated, you will need to use the full `return` syntax to return any
1225        //! > values from code generated with these macros.
1226        //!
1227        //! ### Using the individual matcher
1228        //!
1229        //! In this use case, each item's data is individually passed through the macro. This can be used to
1230        //! generate code for each item separately, allowing specializing the implementation where needed.
1231        //!
1232        //! ```rust,no_run
1233        //! for_each_gpio! {
1234        //!   // Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] [Output]))`
1235        //!   ($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 */ };
1236        //!
1237        //!   // You can create matchers with data filled in. This example will specifically match GPIO2
1238        //!   ($n:literal, GPIO2 $input_af:tt $output_af:tt $attributes:tt) => { /* Additional case only for GPIO2 */ };
1239        //! }
1240        //! ```
1241        //!
1242        //! Different macros can have multiple different syntax options for their individual matchers, usually
1243        //! to provide more detailed information, while preserving simpler syntax for more basic use cases.
1244        //! Consult each macro's documentation for available options.
1245        //!
1246        //! ### Repeated matcher
1247        //!
1248        //! With this option, all data is passed through the macro all at once. This form can be used to,
1249        //! for example, generate struct fields. If the macro has multiple individual matcher options,
1250        //! there are separate repeated matchers for each of the options.
1251        //!
1252        //! To use this option, start the match pattern with the name of the individual matcher option. When
1253        //! there is only a single individual matcher option, its repeated matcher is named `all` unless
1254        //! otherwise specified by the macro.
1255        //!
1256        //! ```rust,no_run
1257        //! // Example usage to create a struct containing all GPIOs:
1258        //! for_each_gpio! {
1259        //!     (all $( ($n:literal, $gpio:ident $_af_ins:tt $_af_outs:tt $_attrs:tt) ),*) => {
1260        //!         struct Gpios {
1261        //!             $(
1262        //!                 #[doc = concat!(" The ", stringify!($n), "th GPIO pin")]
1263        //!                 pub $gpio: Gpio<$n>,
1264        //!             )*
1265        //!         }
1266        //!     };
1267        //! }
1268        //! ```
1269        #![cfg_attr(docsrs, feature(doc_cfg))]
1270        #![cfg_attr(not(feature = "build-script"), no_std)]
1271
1272        #(#chips)*
1273
1274        #[cfg(any(feature = "build-script", docsrs))]
1275        include!( "_build_script_utils.rs");
1276    }
1277}
1278
1279pub fn generate_chip_support_status(output: &mut impl Write) -> std::fmt::Result {
1280    let nothing = "";
1281
1282    // Calculate the width of the first column.
1283    let driver_col_width = std::iter::once("Driver")
1284        .chain(
1285            PeriConfig::drivers()
1286                .iter()
1287                .filter(|i| !i.hide_from_peri_table)
1288                .map(|i| i.name),
1289        )
1290        .map(|c| c.len())
1291        .max()
1292        .unwrap();
1293
1294    // Header
1295    write!(output, "| {:driver_col_width$} |", "Driver")?;
1296    for chip in Chip::iter() {
1297        write!(output, " {} |", chip.pretty_name())?;
1298    }
1299    writeln!(output)?;
1300
1301    // Header separator
1302    write!(output, "| {nothing:-<driver_col_width$} |")?;
1303    for chip in Chip::iter() {
1304        write!(
1305            output,
1306            ":{nothing:-<width$}:|",
1307            width = chip.pretty_name().len()
1308        )?;
1309    }
1310    writeln!(output)?;
1311
1312    // Driver support status
1313    let mut issues = Vec::new();
1314    for SupportItem {
1315        name,
1316        config_group,
1317        hide_from_peri_table,
1318    } in PeriConfig::drivers()
1319    {
1320        if *hide_from_peri_table {
1321            continue;
1322        }
1323        write!(output, "| {name:driver_col_width$} |")?;
1324        for chip in Chip::iter() {
1325            let config = Config::for_chip(&chip);
1326
1327            let status = config.device.peri_config.support_status(config_group);
1328            // VSCode displays emojis just a bit wider than 2 characters, making this
1329            // approximation a bit too wide but good enough.
1330            let support_cell_width =
1331                chip.pretty_name().len() - !status.status.icon().is_empty() as usize;
1332            if let Some(issue) = status.issue {
1333                write!(output, " [{}][{issue}] [^1] |", status.status.icon())?;
1334                issues.push(issue);
1335            } else {
1336                write!(output, " {:support_cell_width$} |", status.status.icon())?;
1337            }
1338        }
1339        writeln!(output)?;
1340    }
1341
1342    writeln!(output)?;
1343    SupportStatusLevel::write_legend(output)?;
1344    writeln!(output)?;
1345
1346    // Print issue link definitions
1347    issues.sort();
1348    issues.dedup();
1349
1350    if !issues.is_empty() {
1351        writeln!(
1352            output,
1353            "[^1]: This cell is clickable and will open the peripheral's issue on GitHub"
1354        )?;
1355        writeln!(output)?;
1356    }
1357    for issue in issues {
1358        writeln!(
1359            output,
1360            "[{issue}]: https://github.com/esp-rs/esp-hal/issues/{issue}"
1361        )?;
1362    }
1363
1364    Ok(())
1365}