use std::str::FromStr;
use color_eyre::eyre::{Result, eyre};
use target_lexicon::{Architecture, Riscv32Architecture, Triple};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Esp32Arch {
Xtensa,
RiscV,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Esp32Chip {
Esp32S3,
Esp32C3,
Esp32P4,
}
impl Esp32Chip {
#[must_use]
pub const fn id(self) -> &'static str {
match self {
Self::Esp32S3 => "esp32s3",
Self::Esp32C3 => "esp32c3",
Self::Esp32P4 => "esp32p4",
}
}
#[must_use]
pub const fn arch(self) -> Esp32Arch {
match self {
Self::Esp32S3 => Esp32Arch::Xtensa,
Self::Esp32C3 | Self::Esp32P4 => Esp32Arch::RiscV,
}
}
#[must_use]
pub const fn target_triple(self) -> &'static str {
match self {
Self::Esp32S3 => "xtensa-esp32s3-espidf",
Self::Esp32C3 => "riscv32imc-esp-espidf",
Self::Esp32P4 => "riscv32imafc-esp-espidf",
}
}
#[must_use]
pub fn triple(self) -> Triple {
Triple::from_str(self.target_triple())
.unwrap_or_else(|error| panic!("{} target triple must be valid: {error}", self.id()))
}
#[must_use]
pub const fn lexicon_arch(self) -> Architecture {
match self {
Self::Esp32S3 => Architecture::XTensa,
Self::Esp32C3 => Architecture::Riscv32(Riscv32Architecture::Riscv32imc),
Self::Esp32P4 => Architecture::Riscv32(Riscv32Architecture::Riscv32imafc),
}
}
#[must_use]
pub const fn qemu_binary(self) -> &'static str {
match self.arch() {
Esp32Arch::Xtensa => "qemu-system-xtensa",
Esp32Arch::RiscV => "qemu-system-riscv32",
}
}
#[must_use]
pub const fn qemu_machine(self) -> &'static str {
self.id()
}
#[must_use]
pub const fn needs_qemu_efuse_workaround(self) -> bool {
matches!(self.arch(), Esp32Arch::Xtensa)
}
#[must_use]
pub const fn gcc_component(self) -> Esp32GccComponent {
match self.arch() {
Esp32Arch::Xtensa => Esp32GccComponent {
component: "xtensa-esp-elf",
bin_subpath: "xtensa-esp-elf/bin",
what: "Xtensa GCC toolchain",
},
Esp32Arch::RiscV => Esp32GccComponent {
component: "riscv32-esp-elf",
bin_subpath: "riscv32-esp-elf/bin",
what: "RISC-V GCC toolchain",
},
}
}
#[must_use]
pub const fn firmware_params(self) -> Esp32FirmwareParams {
match self {
Self::Esp32S3 => Esp32FirmwareParams {
console_uart_default: false,
flash_size_mb: 8,
main_task_stack_bytes: 163_840,
app_partition_offset: "0x10000",
app_partition_size: "0x600000",
opt_level: "s",
},
Self::Esp32C3 => Esp32FirmwareParams {
console_uart_default: true,
flash_size_mb: 4,
main_task_stack_bytes: 49_152,
app_partition_offset: "0x10000",
app_partition_size: "0x300000",
opt_level: "2",
},
Self::Esp32P4 => Esp32FirmwareParams {
console_uart_default: false,
flash_size_mb: 16,
main_task_stack_bytes: 98_304,
app_partition_offset: "0x10000",
app_partition_size: "0xC00000",
opt_level: "2",
},
}
}
}
impl FromStr for Esp32Chip {
type Err = color_eyre::eyre::Error;
fn from_str(value: &str) -> Result<Self> {
match value {
"esp32s3" => Ok(Self::Esp32S3),
"esp32c3" => Ok(Self::Esp32C3),
"esp32p4" => Ok(Self::Esp32P4),
other => Err(eyre!(
"unsupported ESP32 chip {other:?}. Supported chips: esp32s3, esp32c3, esp32p4."
)),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Esp32GccComponent {
pub component: &'static str,
pub bin_subpath: &'static str,
pub what: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Esp32FirmwareParams {
pub console_uart_default: bool,
pub flash_size_mb: u32,
pub main_task_stack_bytes: u32,
pub app_partition_offset: &'static str,
pub app_partition_size: &'static str,
pub opt_level: &'static str,
}
impl Esp32FirmwareParams {
#[must_use]
pub fn flash_size_arg(&self) -> String {
format!("{}mb", self.flash_size_mb)
}
}
#[cfg(test)]
mod tests {
use super::{Esp32Arch, Esp32Chip};
use std::str::FromStr;
#[test]
fn parses_known_chips_and_rejects_unknown() {
assert_eq!(
Esp32Chip::from_str("esp32s3").expect("esp32s3 parses"),
Esp32Chip::Esp32S3
);
assert_eq!(
Esp32Chip::from_str("esp32c3").expect("esp32c3 parses"),
Esp32Chip::Esp32C3
);
assert_eq!(
Esp32Chip::from_str("esp32p4").expect("esp32p4 parses"),
Esp32Chip::Esp32P4
);
assert!(Esp32Chip::from_str("esp32c6").is_err());
}
#[test]
fn esp32p4_derives_riscv_fpu_build_parameters() {
let p4 = Esp32Chip::Esp32P4;
assert_eq!(p4.arch(), Esp32Arch::RiscV);
assert_eq!(p4.target_triple(), "riscv32imafc-esp-espidf");
assert_eq!(p4.qemu_binary(), "qemu-system-riscv32");
assert_eq!(p4.qemu_machine(), "esp32p4");
assert!(!p4.needs_qemu_efuse_workaround());
assert_eq!(p4.gcc_component().component, "riscv32-esp-elf");
let params = p4.firmware_params();
assert!(!params.console_uart_default);
assert_eq!(params.flash_size_mb, 16);
assert_eq!(params.opt_level, "2");
}
#[test]
fn xtensa_and_riscv_derive_distinct_build_parameters() {
let s3 = Esp32Chip::Esp32S3;
assert_eq!(s3.arch(), Esp32Arch::Xtensa);
assert_eq!(s3.target_triple(), "xtensa-esp32s3-espidf");
assert_eq!(s3.qemu_binary(), "qemu-system-xtensa");
assert_eq!(s3.qemu_machine(), "esp32s3");
assert!(s3.needs_qemu_efuse_workaround());
assert_eq!(s3.gcc_component().component, "xtensa-esp-elf");
assert!(!s3.firmware_params().console_uart_default);
assert_eq!(s3.firmware_params().flash_size_arg(), "8mb");
let c3 = Esp32Chip::Esp32C3;
assert_eq!(c3.arch(), Esp32Arch::RiscV);
assert_eq!(c3.target_triple(), "riscv32imc-esp-espidf");
assert_eq!(c3.qemu_binary(), "qemu-system-riscv32");
assert_eq!(c3.qemu_machine(), "esp32c3");
assert!(!c3.needs_qemu_efuse_workaround());
assert_eq!(c3.gcc_component().component, "riscv32-esp-elf");
assert!(c3.firmware_params().console_uart_default);
assert_eq!(c3.firmware_params().flash_size_arg(), "4mb");
}
#[test]
fn triple_parses_for_every_chip() {
for chip in [Esp32Chip::Esp32S3, Esp32Chip::Esp32C3] {
let triple = chip.triple();
assert_eq!(triple.architecture, chip.lexicon_arch());
}
}
}