esp_metadata/
generate_cfg.rs

1use core::str::FromStr;
2use std::sync::OnceLock;
3
4use anyhow::{Result, bail};
5use strum::IntoEnumIterator;
6
7macro_rules! include_toml {
8    ($type:ty, $file:expr) => {{
9        static LOADED_TOML: OnceLock<$type> = OnceLock::new();
10        LOADED_TOML.get_or_init(|| basic_toml::from_str(include_str!($file)).unwrap())
11    }};
12}
13
14/// Supported device architectures.
15#[derive(
16    Debug,
17    Clone,
18    Copy,
19    PartialEq,
20    Eq,
21    PartialOrd,
22    Ord,
23    serde::Deserialize,
24    serde::Serialize,
25    strum::Display,
26    strum::EnumIter,
27    strum::EnumString,
28    strum::AsRefStr,
29)]
30#[serde(rename_all = "lowercase")]
31#[strum(serialize_all = "lowercase")]
32pub enum Arch {
33    /// RISC-V architecture
34    RiscV,
35    /// Xtensa architecture
36    Xtensa,
37}
38
39/// Device core count.
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)]
55pub enum Cores {
56    /// Single CPU core
57    #[serde(rename = "single_core")]
58    #[strum(serialize = "single_core")]
59    Single,
60    /// Two or more CPU cores
61    #[serde(rename = "multi_core")]
62    #[strum(serialize = "multi_core")]
63    Multi,
64}
65
66/// Supported devices.
67#[derive(
68    Debug,
69    Clone,
70    Copy,
71    PartialEq,
72    Eq,
73    PartialOrd,
74    Ord,
75    Hash,
76    serde::Deserialize,
77    serde::Serialize,
78    strum::Display,
79    strum::EnumIter,
80    strum::EnumString,
81    strum::AsRefStr,
82)]
83#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
84#[serde(rename_all = "kebab-case")]
85#[strum(serialize_all = "kebab-case")]
86pub enum Chip {
87    /// ESP32
88    Esp32,
89    /// ESP32-C2, ESP8684
90    Esp32c2,
91    /// ESP32-C3, ESP8685
92    Esp32c3,
93    /// ESP32-C6
94    Esp32c6,
95    /// ESP32-H2
96    Esp32h2,
97    /// ESP32-S2
98    Esp32s2,
99    /// ESP32-S3
100    Esp32s3,
101}
102
103impl Chip {
104    pub fn from_cargo_feature() -> Result<Self> {
105        let all_chips = Chip::iter().map(|c| c.to_string()).collect::<Vec<_>>();
106
107        let mut chip = None;
108        for c in all_chips.iter() {
109            if std::env::var(format!("CARGO_FEATURE_{}", c.to_uppercase())).is_ok() {
110                if chip.is_some() {
111                    bail!(
112                        "Expected exactly one of the following features to be enabled: {}",
113                        all_chips.join(", ")
114                    );
115                }
116                chip = Some(c);
117            }
118        }
119
120        let Some(chip) = chip else {
121            bail!(
122                "Expected exactly one of the following features to be enabled: {}",
123                all_chips.join(", ")
124            );
125        };
126
127        Ok(Self::from_str(chip.as_str()).unwrap())
128    }
129
130    pub fn target(&self) -> &'static str {
131        use Chip::*;
132
133        match self {
134            Esp32 => "xtensa-esp32-none-elf",
135            Esp32c2 | Esp32c3 => "riscv32imc-unknown-none-elf",
136            Esp32c6 | Esp32h2 => "riscv32imac-unknown-none-elf",
137            Esp32s2 => "xtensa-esp32s2-none-elf",
138            Esp32s3 => "xtensa-esp32s3-none-elf",
139        }
140    }
141
142    pub fn has_lp_core(&self) -> bool {
143        use Chip::*;
144
145        matches!(self, Esp32c6 | Esp32s2 | Esp32s3)
146    }
147
148    pub fn lp_target(&self) -> Result<&'static str> {
149        use Chip::*;
150
151        match self {
152            Esp32c6 => Ok("riscv32imac-unknown-none-elf"),
153            Esp32s2 | Esp32s3 => Ok("riscv32imc-unknown-none-elf"),
154            _ => bail!("Chip does not contain an LP core: '{}'", self),
155        }
156    }
157
158    pub fn pretty_name(&self) -> &str {
159        match self {
160            Chip::Esp32 => "ESP32",
161            Chip::Esp32c2 => "ESP32-C2",
162            Chip::Esp32c3 => "ESP32-C3",
163            Chip::Esp32c6 => "ESP32-C6",
164            Chip::Esp32h2 => "ESP32-H2",
165            Chip::Esp32s2 => "ESP32-S2",
166            Chip::Esp32s3 => "ESP32-S3",
167        }
168    }
169
170    pub fn is_xtensa(&self) -> bool {
171        matches!(self, Chip::Esp32 | Chip::Esp32s2 | Chip::Esp32s3)
172    }
173
174    pub fn is_riscv(&self) -> bool {
175        !self.is_xtensa()
176    }
177}
178
179#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
180pub struct MemoryRegion {
181    name: String,
182    start: u32,
183    end: u32,
184}
185
186#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
187struct Device {
188    pub name: String,
189    pub arch: Arch,
190    pub cores: Cores,
191    pub peripherals: Vec<String>,
192    pub symbols: Vec<String>,
193    pub memory: Vec<MemoryRegion>,
194}
195
196/// Device configuration file format.
197#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
198pub struct Config {
199    device: Device,
200}
201
202impl Config {
203    /// The configuration for the specified chip.
204    pub fn for_chip(chip: &Chip) -> &Self {
205        match chip {
206            Chip::Esp32 => include_toml!(Config, "../devices/esp32.toml"),
207            Chip::Esp32c2 => include_toml!(Config, "../devices/esp32c2.toml"),
208            Chip::Esp32c3 => include_toml!(Config, "../devices/esp32c3.toml"),
209            Chip::Esp32c6 => include_toml!(Config, "../devices/esp32c6.toml"),
210            Chip::Esp32h2 => include_toml!(Config, "../devices/esp32h2.toml"),
211            Chip::Esp32s2 => include_toml!(Config, "../devices/esp32s2.toml"),
212            Chip::Esp32s3 => include_toml!(Config, "../devices/esp32s3.toml"),
213        }
214    }
215
216    /// Create an empty configuration
217    pub fn empty() -> Self {
218        Self {
219            device: Device {
220                name: "".to_owned(),
221                arch: Arch::RiscV,
222                cores: Cores::Single,
223                peripherals: Vec::new(),
224                symbols: Vec::new(),
225                memory: Vec::new(),
226            },
227        }
228    }
229
230    /// The name of the device.
231    pub fn name(&self) -> String {
232        self.device.name.clone()
233    }
234
235    /// The CPU architecture of the device.
236    pub fn arch(&self) -> Arch {
237        self.device.arch
238    }
239
240    /// The core count of the device.
241    pub fn cores(&self) -> Cores {
242        self.device.cores
243    }
244
245    /// The peripherals of the device.
246    pub fn peripherals(&self) -> &[String] {
247        &self.device.peripherals
248    }
249
250    /// User-defined symbols for the device.
251    pub fn symbols(&self) -> &[String] {
252        &self.device.symbols
253    }
254
255    /// Memory regions.
256    ///
257    /// Will be available as env-variables `REGION-<NAME>-START` /
258    /// `REGION-<NAME>-END`
259    pub fn memory(&self) -> &[MemoryRegion] {
260        &self.device.memory
261    }
262
263    /// All configuration values for the device.
264    pub fn all(&self) -> impl Iterator<Item = &str> + '_ {
265        [
266            self.device.name.as_str(),
267            self.device.arch.as_ref(),
268            self.device.cores.as_ref(),
269        ]
270        .into_iter()
271        .chain(self.device.peripherals.iter().map(|s| s.as_str()))
272        .chain(self.device.symbols.iter().map(|s| s.as_str()))
273    }
274
275    /// Does the configuration contain `item`?
276    pub fn contains(&self, item: &str) -> bool {
277        self.all().any(|i| i == item)
278    }
279
280    /// Define all symbols for a given configuration.
281    pub fn define_symbols(&self) {
282        define_all_possible_symbols();
283        // Define all necessary configuration symbols for the configured device:
284        for symbol in self.all() {
285            println!("cargo:rustc-cfg={symbol}");
286        }
287
288        // Define env-vars for all memory regions
289        for memory in self.memory() {
290            println!("cargo:rustc-cfg=has_{}_region", memory.name.to_lowercase());
291
292            println!(
293                "cargo::rustc-env=ESP_METADATA_REGION_{}_START={}",
294                memory.name.to_uppercase(),
295                memory.start
296            );
297            println!(
298                "cargo::rustc-env=ESP_METADATA_REGION_{}_END={}",
299                memory.name.to_uppercase(),
300                memory.end
301            );
302        }
303    }
304}
305
306/// Defines all possible symbols that _could_ be output from this crate
307/// regardless of the chosen configuration.
308///
309/// This is required to avoid triggering the unexpected-cfgs lint.
310fn define_all_possible_symbols() {
311    // Used by our documentation builds to prevent the huge red warning banner.
312    println!("cargo:rustc-check-cfg=cfg(not_really_docsrs)");
313
314    for chip in Chip::iter() {
315        let config = Config::for_chip(&chip);
316        for symbol in config.all() {
317            // https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-check-cfg
318            println!("cargo:rustc-check-cfg=cfg({})", symbol);
319        }
320    }
321}