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#[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 RiscV,
35 Xtensa,
37}
38
39#[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 #[serde(rename = "single_core")]
58 #[strum(serialize = "single_core")]
59 Single,
60 #[serde(rename = "multi_core")]
62 #[strum(serialize = "multi_core")]
63 Multi,
64}
65
66#[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,
89 Esp32c2,
91 Esp32c3,
93 Esp32c6,
95 Esp32h2,
97 Esp32s2,
99 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#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
198pub struct Config {
199 device: Device,
200}
201
202impl Config {
203 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 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 pub fn name(&self) -> String {
232 self.device.name.clone()
233 }
234
235 pub fn arch(&self) -> Arch {
237 self.device.arch
238 }
239
240 pub fn cores(&self) -> Cores {
242 self.device.cores
243 }
244
245 pub fn peripherals(&self) -> &[String] {
247 &self.device.peripherals
248 }
249
250 pub fn symbols(&self) -> &[String] {
252 &self.device.symbols
253 }
254
255 pub fn memory(&self) -> &[MemoryRegion] {
260 &self.device.memory
261 }
262
263 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 pub fn contains(&self, item: &str) -> bool {
277 self.all().any(|i| i == item)
278 }
279
280 pub fn define_symbols(&self) {
282 define_all_possible_symbols();
283 for symbol in self.all() {
285 println!("cargo:rustc-cfg={symbol}");
286 }
287
288 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
306fn define_all_possible_symbols() {
311 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 println!("cargo:rustc-check-cfg=cfg({})", symbol);
319 }
320 }
321}