use std::{
env,
error::Error,
fs::{self, File},
io::{BufRead, Write},
path::{Path, PathBuf},
str::FromStr,
};
use esp_build::assert_unique_used_features;
use esp_config::{generate_config, Value};
use esp_metadata::{Chip, Config};
fn main() -> Result<(), Box<dyn Error>> {
println!("cargo:rustc-check-cfg=cfg(is_debug_build)");
if let Ok(level) = std::env::var("OPT_LEVEL") {
if level == "0" || level == "1" {
println!("cargo:rustc-cfg=is_debug_build");
}
}
assert_unique_used_features!(
"esp32", "esp32c2", "esp32c3", "esp32c6", "esp32h2", "esp32s2", "esp32s3"
);
#[cfg(all(
feature = "flip-link",
not(any(feature = "esp32c6", feature = "esp32h2"))
))]
esp_build::error!("flip-link is only available on ESP32-C6/ESP32-H2");
let device_name = if cfg!(feature = "esp32") {
"esp32"
} else if cfg!(feature = "esp32c2") {
"esp32c2"
} else if cfg!(feature = "esp32c3") {
"esp32c3"
} else if cfg!(feature = "esp32c6") {
"esp32c6"
} else if cfg!(feature = "esp32h2") {
"esp32h2"
} else if cfg!(feature = "esp32s2") {
"esp32s2"
} else if cfg!(feature = "esp32s3") {
"esp32s3"
} else {
unreachable!() };
let chip = Chip::from_str(device_name)?;
let config = Config::for_chip(&chip);
if !config.contains(&String::from("psram")) && cfg!(feature = "quad-psram") {
panic!("The target does not support PSRAM");
}
if !config.contains(&String::from("octal_psram")) && cfg!(feature = "octal-psram") {
panic!("The target does not support Octal PSRAM");
}
config.define_symbols();
let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
println!("cargo:rustc-link-search={}", out.display());
let cfg = generate_config(
"esp_hal",
&[
(
"place-spi-driver-in-ram",
"Places the SPI driver in RAM for better performance",
Value::Bool(false),
None
),
(
"spi-address-workaround",
"(ESP32 only) Enables a workaround for the issue where SPI in half-duplex mode incorrectly transmits the address on a single line if the data buffer is empty.",
Value::Bool(true),
None
),
(
"place-switch-tables-in-ram",
"Places switch-tables, some lookup tables and constants related to interrupt handling into RAM - resulting in better performance but slightly more RAM consumption.",
Value::Bool(true),
None
),
(
"place-anon-in-ram",
"Places anonymous symbols into RAM - resulting in better performance at the cost of significant more RAM consumption. Best to be combined with `place-switch-tables-in-ram`.",
Value::Bool(false),
None
),
],
true,
);
#[allow(unused_mut)]
let mut config_symbols = config.all().collect::<Vec<_>>();
#[cfg(feature = "flip-link")]
config_symbols.push("flip-link");
for (key, value) in &cfg {
if let Value::Bool(true) = value {
config_symbols.push(key);
}
}
if cfg!(feature = "esp32") || cfg!(feature = "esp32s2") || cfg!(feature = "esp32s3") {
#[cfg(any(feature = "esp32", feature = "esp32s2"))]
File::create(out.join("memory_extras.x"))?.write_all(&generate_memory_extras())?;
let (irtc, drtc) = if cfg!(feature = "esp32s3") {
("rtc_fast_seg", "rtc_fast_seg")
} else {
("rtc_fast_iram_seg", "rtc_fast_dram_seg")
};
let alias = format!(
r#"
REGION_ALIAS("ROTEXT", irom_seg);
REGION_ALIAS("RWTEXT", iram_seg);
REGION_ALIAS("RODATA", drom_seg);
REGION_ALIAS("RWDATA", dram_seg);
REGION_ALIAS("RTC_FAST_RWTEXT", {});
REGION_ALIAS("RTC_FAST_RWDATA", {});
"#,
irtc, drtc
);
fs::write(out.join("alias.x"), alias)?;
fs::copy("ld/xtensa/hal-defaults.x", out.join("hal-defaults.x"))?;
} else {
preprocess_file(&config_symbols, "ld/riscv/asserts.x", out.join("asserts.x"))?;
preprocess_file(&config_symbols, "ld/riscv/debug.x", out.join("debug.x"))?;
preprocess_file(
&config_symbols,
"ld/riscv/hal-defaults.x",
out.join("hal-defaults.x"),
)?;
}
copy_dir_all(&config_symbols, "ld/sections", &out)?;
copy_dir_all(&config_symbols, format!("ld/{device_name}"), &out)?;
Ok(())
}
fn copy_dir_all(
config_symbols: &[&str],
src: impl AsRef<Path>,
dst: impl AsRef<Path>,
) -> std::io::Result<()> {
fs::create_dir_all(&dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(
config_symbols,
entry.path(),
dst.as_ref().join(entry.file_name()),
)?;
} else {
preprocess_file(
config_symbols,
entry.path(),
dst.as_ref().join(entry.file_name()),
)?;
}
}
Ok(())
}
fn preprocess_file(
config: &[&str],
src: impl AsRef<Path>,
dst: impl AsRef<Path>,
) -> std::io::Result<()> {
let file = File::open(src)?;
let mut out_file = File::create(dst)?;
let mut take = Vec::new();
take.push(true);
for line in std::io::BufReader::new(file).lines() {
let line = line?;
let trimmed = line.trim();
if let Some(condition) = trimmed.strip_prefix("#IF ") {
let should_take = take.iter().all(|v| *v);
let should_take = should_take && config.contains(&condition);
take.push(should_take);
continue;
} else if trimmed == "#ELSE" {
let taken = take.pop().unwrap();
let should_take = take.iter().all(|v| *v);
let should_take = should_take && !taken;
take.push(should_take);
continue;
} else if trimmed == "#ENDIF" {
take.pop();
continue;
}
if *take.last().unwrap() {
out_file.write_all(line.as_bytes())?;
let _ = out_file.write(b"\n")?;
}
}
Ok(())
}
#[cfg(feature = "esp32")]
fn generate_memory_extras() -> Vec<u8> {
let reserve_dram = if cfg!(feature = "bluetooth") {
"0x10000"
} else {
"0x0"
};
format!(
"
/* reserved at the start of DRAM for e.g. the BT stack */
RESERVE_DRAM = {reserve_dram};
"
)
.as_bytes()
.to_vec()
}
#[cfg(feature = "esp32s2")]
fn generate_memory_extras() -> Vec<u8> {
let reserved_cache = if cfg!(feature = "quad-psram") {
"0x4000"
} else {
"0x2000"
};
format!(
"
/* reserved at the start of DRAM/IRAM */
RESERVE_CACHES = {reserved_cache};
"
)
.as_bytes()
.to_vec()
}