1mod 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#[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 RiscV,
50 Xtensa,
52}
53
54#[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 #[serde(rename = "single_core")]
73 #[strum(serialize = "single_core")]
74 Single,
75 #[serde(rename = "multi_core")]
77 #[strum(serialize = "multi_core")]
78 Multi,
79}
80
81#[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,
104 Esp32c2,
106 Esp32c3,
108 Esp32c6,
110 Esp32h2,
112 Esp32s2,
114 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 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 let symbol_name = symbol_name.replace('.', "_");
211 let entry = cfg_values.entry(symbol_name).or_default();
212 if !entry.contains(&symbol_value.to_string()) {
214 entry.push(symbol_value.to_string());
215 }
216 } else {
217 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 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 name: String,
250 #[serde(default, rename = "pac")]
252 pac_name: Option<String>,
253 #[serde(default, rename = "virtual")]
255 is_virtual: bool,
256 #[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 #[serde(flatten)]
281 peri_config: PeriConfig,
282}
283
284fn number(n: impl std::fmt::Display) -> TokenStream {
287 TokenStream::from_str(&format!("{n}")).unwrap()
288}
289
290#[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 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 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 pub fn name(&self) -> String {
348 self.device.name.clone()
349 }
350
351 pub fn arch(&self) -> Arch {
353 self.device.arch
354 }
355
356 pub fn cores(&self) -> Cores {
358 if self.device.cores > 1 {
359 Cores::Multi
360 } else {
361 Cores::Single
362 }
363 }
364
365 pub fn peripherals(&self) -> &[PeripheralDef] {
367 &self.device.peripherals
368 }
369
370 pub fn symbols(&self) -> &[String] {
372 &self.device.symbols
373 }
374
375 pub fn memory(&self) -> &[MemoryRegion] {
380 &self.device.memory
381 }
382
383 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 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 tokens.extend(quote! {
465 #[doc = concat!("assert_eq!(chip_name, ", chip!(), ")")]
473 #[macro_export]
475 #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
476 macro_rules! chip {
477 () => { #chip_name };
478 }
479 });
480
481 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); 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 tokens.extend(quote! {
527 #[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_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 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 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 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 for symbol in self.all() {
667 cfgs.push(symbol.replace('.', "_"));
668 }
669
670 for memory in self.memory() {
672 cfgs.push(format!("has_{}_region", memory.name.to_lowercase()));
673 }
674
675 cfgs
676 }
677
678 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 #[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 #(_for_each_inner!(( #flat_branches ));)*
721
722 #( _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 #[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 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 pub fn is_xtensa(self) -> bool {
823 self.config().architecture == "xtensa"
824 }
825
826 pub fn target(self) -> &'static str {
828 self.config().target
829 }
830
831 pub fn name(self) -> &'static str {
839 match self {
840 #( Self::#chip => #name ),*
841 }
842 }
843
844 pub fn contains(self, symbol: &str) -> bool {
854 self.all_symbols().contains(&symbol)
855 }
856
857 pub fn define_cfgs(self) {
859 self.config().define_cfgs()
860 }
861
862 pub fn all_symbols(&self) -> &'static [&'static str] {
870 self.config().symbols
871 }
872
873 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 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 #![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 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 write!(output, "| {:driver_col_width$} |", "Driver")?;
1049 for chip in Chip::iter() {
1050 write!(output, " {} |", chip.pretty_name())?;
1051 }
1052 writeln!(output)?;
1053
1054 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 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 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 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}