use anyhow::{Context, Result, anyhow};
use clap::Parser;
use sftool_lib::{SifliToolBase, create_sifli_tool};
mod cli;
mod config;
mod config_exec;
mod progress;
mod serial;
mod stub_config_spec;
mod stub_ops;
use cli::{Cli, CommandSource, Commands, StubAction, get_command_source, merge_config};
use config::SfToolConfig;
use config_exec::execute_config_command;
use progress::create_progress_sink;
use serial::{check_port_available, normalize_port_name};
use stub_ops::{
chip_key, execute_stub_clear, execute_stub_config_command, execute_stub_read,
execute_stub_write, load_stub_config_spec, prepare_stub_path,
};
fn main() -> Result<()> {
let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("off"));
tracing_subscriber::fmt().with_env_filter(env_filter).init();
let args = Cli::parse();
let config = match &args.command {
Some(Commands::Config(params)) => {
let cfg = SfToolConfig::from_file(¶ms.path)
.map_err(|e| anyhow!("Failed to load config file '{}': {}", params.path, e))?;
cfg.validate().map_err(|e| {
anyhow!(
"Configuration validation failed for '{}': {}",
params.path,
e
)
})?;
Some(cfg)
}
_ => None,
};
let command_source = get_command_source(&args, config.clone())?;
match &command_source {
CommandSource::Cli(Commands::Stub(stub)) => {
match &stub.action {
StubAction::Write(params) => {
let stub_spec = load_stub_config_spec(¶ms.stub_config)?;
execute_stub_write(¶ms.files, &stub_spec)?;
}
StubAction::Clear(params) => {
execute_stub_clear(¶ms.files)?;
}
StubAction::Read(params) => {
execute_stub_read(¶ms.files, params.output.as_deref())?;
}
}
return Ok(());
}
CommandSource::Config(cfg) => {
if let Some(stub) = &cfg.stub
&& (stub.write.is_some() || stub.clear.is_some() || stub.read.is_some())
{
execute_stub_config_command(cfg)?;
return Ok(());
}
}
_ => {}
}
let (
chip_type,
memory_type,
port,
baud,
before,
after,
connect_attempts,
compat,
quiet,
stub_path,
) = merge_config(&args, config.clone()).context("Configuration error")?;
let (stub_path, _stub_temp) = prepare_stub_path(
args.stub_config_json.as_deref(),
&chip_type,
&memory_type,
stub_path,
)
.with_context(|| {
format!(
"Failed to prepare stub with config for chip {}",
chip_key(&chip_type)
)
})?;
let port = normalize_port_name(&port);
check_port_available(&port)?;
let mut siflitool = create_sifli_tool(
chip_type,
SifliToolBase::new_with_external_stub(
port.clone(),
before,
memory_type.to_lowercase(),
baud,
connect_attempts,
compat,
if quiet {
sftool_lib::progress::no_op_progress_sink()
} else {
create_progress_sink()
},
stub_path,
),
);
if baud != 1000000 {
siflitool
.set_speed(baud)
.with_context(|| format!("Failed to set baud rate to {}", baud))?;
}
match command_source {
CommandSource::Cli(command) => match command {
Commands::Stub(_) | Commands::Config(_) => {
}
Commands::WriteFlash(params) => {
let mut files = Vec::new();
for file_str in params.files.iter() {
let mut parsed_files = sftool_lib::utils::Utils::parse_file_info(file_str)
.with_context(|| format!("Failed to parse file {}", file_str))?;
files.append(&mut parsed_files);
}
let write_params = sftool_lib::WriteFlashParams {
files,
verify: params.verify,
no_compress: params.no_compress,
erase_all: params.erase_all,
};
siflitool
.write_flash(&write_params)
.context("Failed to execute write_flash command")?;
}
Commands::ReadFlash(params) => {
let mut files = Vec::new();
for file_str in params.files.iter() {
let parsed_file = sftool_lib::utils::Utils::parse_read_file_info(file_str)
.with_context(|| format!("Failed to parse read file {}", file_str))?;
files.push(parsed_file);
}
let read_params = sftool_lib::ReadFlashParams { files };
siflitool
.read_flash(&read_params)
.context("Failed to execute read_flash command")?;
}
Commands::EraseFlash(params) => {
let address = sftool_lib::utils::Utils::parse_erase_address(¶ms.address)
.with_context(|| format!("Failed to parse erase address {}", params.address))?;
let erase_params = sftool_lib::EraseFlashParams { address };
siflitool
.erase_flash(&erase_params)
.context("Failed to execute erase_flash command")?;
}
Commands::EraseRegion(params) => {
let mut regions = Vec::new();
for region_str in params.region.iter() {
let parsed_region = sftool_lib::utils::Utils::parse_erase_region(region_str)
.with_context(|| format!("Failed to parse erase region {}", region_str))?;
regions.push(parsed_region);
}
let erase_region_params = sftool_lib::EraseRegionParams { regions };
siflitool
.erase_region(&erase_region_params)
.context("Failed to execute erase_region command")?;
}
},
CommandSource::Config(config) => {
execute_config_command(&config, &mut siflitool)?;
}
}
if after.requires_soft_reset() {
siflitool
.soft_reset()
.context("Failed to perform post-operation soft reset")?;
}
Ok(())
}