use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;
/// Tegra X1 (T210) hardware specifications
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TegraX1Config {
/// SoC variant
pub soc_variant: String,
/// Process node
pub process: String,
/// Big cores (Cortex-A57)
pub big_cores: CoreConfig,
/// Little cores (Cortex-A53)
pub little_cores: CoreConfig,
/// GPU config
pub gpu: GpuConfig,
/// Memory config
pub memory: MemoryConfig,
/// Bootloader config
pub bootloader: BootloaderConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoreConfig {
pub architecture: String,
pub count: u32,
pub frequency_mhz: u32,
/// ARM extensions supported
pub extensions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GpuConfig {
pub model: String,
pub arch: String,
pub cores: u32,
pub frequency_mhz: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryConfig {
pub type_name: String,
pub size_gb: u32,
pub bus_width: u32,
pub bandwidth_gbps: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BootloaderConfig {
/// Hekate version
pub hekate_version: String,
/// Boot method (payload, hekate, direct)
pub boot_method: String,
/// Use BDK (Boot Development Kit) for payload
pub use_bdk: bool,
/// Custom payload path (if any)
pub custom_payload: Option<String>,
}
impl TegraX1Config {
/// ODNX02-A2 variant configuration (Nintendo Switch HAC-001)
pub fn odnx02_a2() -> Self {
Self {
soc_variant: "ODNX02-A2".to_string(),
process: "TSMC 20nm".to_string(),
big_cores: CoreConfig {
architecture: "armv8-a".to_string(),
count: 4,
frequency_mhz: 2200,
extensions: vec![
"lse".to_string(), // Large System Extensions
"fp16".to_string(), // Half-precision FP
"crc32".to_string(), // CRC32 instruction
],
},
little_cores: CoreConfig {
architecture: "armv8-a".to_string(),
count: 4,
frequency_mhz: 1300,
extensions: vec![
// A53 has fewer extensions; target base for compatibility
],
},
gpu: GpuConfig {
model: "GM20B".to_string(),
arch: "Maxwell".to_string(),
cores: 256,
frequency_mhz: 1000,
},
memory: MemoryConfig {
type_name: "LPDDR4".to_string(),
size_gb: 8,
bus_width: 64,
bandwidth_gbps: 25.6,
},
bootloader: BootloaderConfig {
hekate_version: "6.5.4".to_string(),
boot_method: "payload".to_string(),
use_bdk: true,
custom_payload: None,
},
}
}
}
/// Build configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildConfig {
pub tegra: TegraX1Config,
/// Image / partition layout settings
#[serde(default)]
pub image: ImageConfig,
pub cross_compiler: String,
pub kernel_version: String,
pub target_arch: String,
/// Compiler flags optimized for Cortex-A57 (big cores)
pub cflags: String,
/// Rust target triple
pub rust_target: String,
/// Output directory for hekate payload
pub payload_output: String,
}
/// Partition layout for the bootable SD-card image.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageConfig {
/// Filesystem label used by the L4T initramfs to locate the rootfs.
/// Must match `id=` in the hekate L4T entry.
pub label: String,
/// Size of the FAT32 boot partition (holds `bootloader/` + `switchroot/`).
pub boot_size_mib: u32,
/// Default total image size in GiB.
pub size_gb: u32,
}
impl Default for ImageConfig {
fn default() -> Self {
Self {
label: "ALARM".to_string(),
boot_size_mib: 512,
size_gb: 8,
}
}
}
impl BuildConfig {
pub fn default_for_odnx02_a2() -> Self {
let tegra = TegraX1Config::odnx02_a2();
// Cortex-A57 compiler flags:
// -march=armv8-a+lse+fp16+crc: Use extensions available on A57
// -mtune=cortex-a57: Optimize scheduling for A57 pipeline
// -mcpu=cortex-a57: Full CPU feature set
let cflags = [
"-march=armv8-a+lse+fp16+crc",
"-mtune=cortex-a57",
"-mcpu=cortex-a57",
"-O2",
"-pipe",
"-fstack-protector-strong",
"-fno-plt",
"-fPIC",
].join(" ");
Self {
tegra,
image: ImageConfig::default(),
cross_compiler: "aarch64-linux-gnu".to_string(),
kernel_version: "7.2".to_string(),
target_arch: "arm64".to_string(),
cflags,
rust_target: "aarch64-unknown-linux-gnu".to_string(),
payload_output: "./output".to_string(),
}
}
}
/// Initialize build environment
pub fn init_build_env(build_dir: &Path) -> Result<()> {
let config_dir = build_dir.join("config");
std::fs::create_dir_all(&config_dir)
.context("Failed to create config directory")?;
// Write default config
let config = BuildConfig::default_for_odnx02_a2();
let config_path = config_dir.join("build.toml");
let config_str = toml::to_string_pretty(&config)
.context("Failed to serialize config")?;
std::fs::write(&config_path, config_str)
.context("Failed to write config")?;
// Create directory structure
let dirs = [
"kernel", "uboot", "rootfs", "packages",
"device-tree", "payload", "scripts", "tmp",
];
for dir in &dirs {
std::fs::create_dir_all(build_dir.join(dir))
.context(format!("Failed to create {} directory", dir))?;
}
println!("Build environment initialized at: {}", build_dir.display());
println!("Config: {}", config_path.display());
Ok(())
}
/// Load configuration from build directory
pub fn load_config(build_dir: &Path) -> Result<BuildConfig> {
let config_path = build_dir.join("config/build.toml");
if !config_path.exists() {
anyhow::bail!(
"Config not found at {}. Run 'init' first.",
config_path.display()
);
}
let config_str = std::fs::read_to_string(&config_path)
.context("Failed to read config")?;
let config: BuildConfig = toml::from_str(&config_str)
.context("Failed to parse config")?;
Ok(config)
}
/// Display current configuration
pub fn show_config() -> Result<()> {
let config = BuildConfig::default_for_odnx02_a2();
println!("\n{}", "=== Tegra X1 (ODNX02-A2) Build Configuration ===".cyan().bold());
println!("\n{}", "Hardware:".yellow().bold());
println!(" SoC Variant: {}", config.tegra.soc_variant);
println!(" Process: {}", config.tegra.process);
println!(" Big Cores: {}x {} @ {} MHz",
config.tegra.big_cores.count,
config.tegra.big_cores.architecture,
config.tegra.big_cores.frequency_mhz);
println!(" Little Cores: {}x {} @ {} MHz",
config.tegra.little_cores.count,
config.tegra.little_cores.architecture,
config.tegra.little_cores.frequency_mhz);
println!(" GPU: {} ({}) {} cores @ {} MHz",
config.tegra.gpu.model,
config.tegra.gpu.arch,
config.tegra.gpu.cores,
config.tegra.gpu.frequency_mhz);
println!(" Memory: {} GB {} ({}-bit, {} GB/s)",
config.tegra.memory.size_gb,
config.tegra.memory.type_name,
config.tegra.memory.bus_width,
config.tegra.memory.bandwidth_gbps);
println!("\n{}", "Bootloader:".yellow().bold());
println!(" Hekate: v{}", config.tegra.bootloader.hekate_version);
println!(" Boot Method: {}", config.tegra.bootloader.boot_method);
println!(" Use BDK: {}", config.tegra.bootloader.use_bdk);
println!("\n{}", "Build Settings:".yellow().bold());
println!(" Cross Compiler: {}", config.cross_compiler);
println!(" Target Arch: {}", config.target_arch);
println!(" Rust Target: {}", config.rust_target);
println!(" Kernel Version: {}", config.kernel_version);
println!(" Payload Output: {}", config.payload_output);
println!("\n{}", "Image Layout:".yellow().bold());
println!(" Root FS label: {}", config.image.label);
println!(" Boot partition: {} MiB (FAT32)", config.image.boot_size_mib);
println!(" Default size: {} GiB", config.image.size_gb);
println!("\n{}", "Compiler Flags (for Cortex-A57):".yellow().bold());
println!(" {}", config.cflags);
println!("\n{}", "ARM Extensions (A57):".yellow().bold());
for ext in &config.tegra.big_cores.extensions {
println!(" +{}", ext);
}
println!("\n{}", "Note:".yellow().bold());
println!(" Default boot uses the switchroot L4T boot chain (known-good display).");
println!(" Use '--kernel built' to package the locally built kernel.");
println!(" Cortex-A53 (LITTLE) supports base ARMv8-A only.");
Ok(())
}
use colored::Colorize;