esp_metadata/
lib.rs

1//! Metadata for Espressif devices, primarily intended for use in build scripts.
2
3use std::sync::OnceLock;
4
5use anyhow::{bail, Result};
6use strum::IntoEnumIterator;
7
8macro_rules! include_toml {
9    ($type:ty, $file:expr) => {{
10        static LOADED_TOML: OnceLock<$type> = OnceLock::new();
11        LOADED_TOML.get_or_init(|| basic_toml::from_str(include_str!($file)).unwrap())
12    }};
13}
14
15/// Supported device architectures.
16#[derive(
17    Debug,
18    Clone,
19    Copy,
20    PartialEq,
21    Eq,
22    PartialOrd,
23    Ord,
24    serde::Deserialize,
25    serde::Serialize,
26    strum::Display,
27    strum::EnumIter,
28    strum::EnumString,
29    strum::AsRefStr,
30)]
31#[serde(rename_all = "lowercase")]
32#[strum(serialize_all = "lowercase")]
33pub enum Arch {
34    /// RISC-V architecture
35    RiscV,
36    /// Xtensa architecture
37    Xtensa,
38}
39
40/// Device core count.
41#[derive(
42    Debug,
43    Clone,
44    Copy,
45    PartialEq,
46    Eq,
47    PartialOrd,
48    Ord,
49    serde::Deserialize,
50    serde::Serialize,
51    strum::Display,
52    strum::EnumIter,
53    strum::EnumString,
54    strum::AsRefStr,
55)]
56pub enum Cores {
57    /// Single CPU core
58    #[serde(rename = "single_core")]
59    #[strum(serialize = "single_core")]
60    Single,
61    /// Two or more CPU cores
62    #[serde(rename = "multi_core")]
63    #[strum(serialize = "multi_core")]
64    Multi,
65}
66
67/// Supported devices.
68#[derive(
69    Debug,
70    Clone,
71    Copy,
72    PartialEq,
73    Eq,
74    PartialOrd,
75    Ord,
76    Hash,
77    serde::Deserialize,
78    serde::Serialize,
79    strum::Display,
80    strum::EnumIter,
81    strum::EnumString,
82    strum::AsRefStr,
83)]
84#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
85#[serde(rename_all = "kebab-case")]
86#[strum(serialize_all = "kebab-case")]
87pub enum Chip {
88    /// ESP32
89    Esp32,
90    /// ESP32-C2, ESP8684
91    Esp32c2,
92    /// ESP32-C3, ESP8685
93    Esp32c3,
94    /// ESP32-C6
95    Esp32c6,
96    /// ESP32-H2
97    Esp32h2,
98    /// ESP32-S2
99    Esp32s2,
100    /// ESP32-S3
101    Esp32s3,
102}
103
104impl Chip {
105    pub fn target(&self) -> &str {
106        use Chip::*;
107
108        match self {
109            Esp32 => "xtensa-esp32-none-elf",
110            Esp32c2 | Esp32c3 => "riscv32imc-unknown-none-elf",
111            Esp32c6 | Esp32h2 => "riscv32imac-unknown-none-elf",
112            Esp32s2 => "xtensa-esp32s2-none-elf",
113            Esp32s3 => "xtensa-esp32s3-none-elf",
114        }
115    }
116
117    pub fn has_lp_core(&self) -> bool {
118        use Chip::*;
119
120        matches!(self, Esp32c6 | Esp32s2 | Esp32s3)
121    }
122
123    pub fn lp_target(&self) -> Result<&str> {
124        use Chip::*;
125
126        match self {
127            Esp32c6 => Ok("riscv32imac-unknown-none-elf"),
128            Esp32s2 | Esp32s3 => Ok("riscv32imc-unknown-none-elf"),
129            _ => bail!("Chip does not contain an LP core: '{}'", self),
130        }
131    }
132
133    pub fn pretty_name(&self) -> &str {
134        match self {
135            Chip::Esp32 => "ESP32",
136            Chip::Esp32c2 => "ESP32-C2",
137            Chip::Esp32c3 => "ESP32-C3",
138            Chip::Esp32c6 => "ESP32-C6",
139            Chip::Esp32h2 => "ESP32-H2",
140            Chip::Esp32s2 => "ESP32-S2",
141            Chip::Esp32s3 => "ESP32-S3",
142        }
143    }
144
145    pub fn is_xtensa(&self) -> bool {
146        matches!(self, Chip::Esp32 | Chip::Esp32s2 | Chip::Esp32s3)
147    }
148
149    pub fn is_riscv(&self) -> bool {
150        !self.is_xtensa()
151    }
152}
153
154#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
155struct Device {
156    pub name: String,
157    pub arch: Arch,
158    pub cores: Cores,
159    pub peripherals: Vec<String>,
160    pub symbols: Vec<String>,
161}
162
163/// Device configuration file format.
164#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
165pub struct Config {
166    device: Device,
167}
168
169impl Config {
170    /// The configuration for the specified chip.
171    pub fn for_chip(chip: &Chip) -> &Self {
172        match chip {
173            Chip::Esp32 => include_toml!(Config, "../devices/esp32.toml"),
174            Chip::Esp32c2 => include_toml!(Config, "../devices/esp32c2.toml"),
175            Chip::Esp32c3 => include_toml!(Config, "../devices/esp32c3.toml"),
176            Chip::Esp32c6 => include_toml!(Config, "../devices/esp32c6.toml"),
177            Chip::Esp32h2 => include_toml!(Config, "../devices/esp32h2.toml"),
178            Chip::Esp32s2 => include_toml!(Config, "../devices/esp32s2.toml"),
179            Chip::Esp32s3 => include_toml!(Config, "../devices/esp32s3.toml"),
180        }
181    }
182
183    /// The name of the device.
184    pub fn name(&self) -> String {
185        self.device.name.clone()
186    }
187
188    /// The CPU architecture of the device.
189    pub fn arch(&self) -> Arch {
190        self.device.arch
191    }
192
193    /// The core count of the device.
194    pub fn cores(&self) -> Cores {
195        self.device.cores
196    }
197
198    /// The peripherals of the device.
199    pub fn peripherals(&self) -> &[String] {
200        &self.device.peripherals
201    }
202
203    /// User-defined symbols for the device.
204    pub fn symbols(&self) -> &[String] {
205        &self.device.symbols
206    }
207
208    /// All configuration values for the device.
209    pub fn all(&self) -> impl Iterator<Item = &str> + '_ {
210        [
211            self.device.name.as_str(),
212            self.device.arch.as_ref(),
213            self.device.cores.as_ref(),
214        ]
215        .into_iter()
216        .chain(self.device.peripherals.iter().map(|s| s.as_str()))
217        .chain(self.device.symbols.iter().map(|s| s.as_str()))
218    }
219
220    /// Does the configuration contain `item`?
221    pub fn contains(&self, item: &str) -> bool {
222        self.all().any(|i| i == item)
223    }
224
225    /// Define all symbols for a given configuration.
226    pub fn define_symbols(&self) {
227        define_all_possible_symbols();
228        // Define all necessary configuration symbols for the configured device:
229        for symbol in self.all() {
230            println!("cargo:rustc-cfg={symbol}");
231        }
232    }
233}
234
235/// Defines all possible symbols that _could_ be output from this crate
236/// regardless of the chosen configuration.
237///
238/// This is required to avoid triggering the unexpected-cfgs lint.
239fn define_all_possible_symbols() {
240    // Used by our documentation builds to prevent the huge red warning banner.
241    println!("cargo:rustc-check-cfg=cfg(not_really_docsrs)");
242
243    for chip in Chip::iter() {
244        let config = Config::for_chip(&chip);
245        for symbol in config.all() {
246            // https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-check-cfg
247            println!("cargo:rustc-check-cfg=cfg({})", symbol);
248        }
249    }
250}