diff --git a/README.md b/README.md index 06325a2..96b7e1e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ SFTool是一个专为SiFli系列SoC(系统芯片)设计的开源工具,用 ## 特性 -- 支持SF32LB52芯片 +- 支持SF32LB52、SF32LB56、SF32LB58芯片 - 支持多种存储类型:NOR闪存、NAND闪存和SD卡 - 可配置的串口参数 - 可靠的闪存写入功能,支持验证和压缩 @@ -45,19 +45,34 @@ cargo build --release ```bash sftool [选项] 命令 [命令选项] +sftool [选项] config ``` ### 全局选项 - `-c, --chip `: 目标芯片类型 (目前支持SF32LB52) -- `-m, --memory `: 存储类型 [nor, nand, sd] (默认: nor) +- `-m, --memory `: 存储类型 [nor, nand, sd] (默认: nor,不区分大小写) - `-p, --port `: 串行端口设备路径 - `-b, --baud `: 闪存/读取时使用的串口波特率 (默认: 1000000) - `--before `: 连接芯片前的操作 [default_reset, no_reset, no_reset_no_sync] (默认: default_reset) - `--after `: 工具完成后的操作 [soft_reset, no_reset] (默认: soft_reset) -- `--connect-attempts `: 连接尝试次数,负数或0表示无限次 (默认: 7) +- `--connect-attempts `: 连接尝试次数,负数或0表示无限次 (默认: 3) - `--compat` : 兼容模式,如果经常出现超时错误或下载后校验失败,则应打开此选项。 +### JSON 参数文件(sftool_param.json) + +可以用 JSON 描述一次命令并通过 `config` 子命令执行: + +```bash +sftool config sftool_param.json + +# CLI 参数可以覆盖或补充 JSON 中的字段 +sftool -c SF32LB52 -p /dev/ttyUSB0 config sftool_param.json +``` + +JSON 文件可以不包含所有字段,CLI 参数与默认值会先合并;合并后仍缺少必须参数才会报错。 +schema 位于仓库中的 `sftool_param_schema.json`。 + ### 写入闪存命令 ```bash diff --git a/README_EN.md b/README_EN.md index 523ca03..f9f66bf 100644 --- a/README_EN.md +++ b/README_EN.md @@ -10,7 +10,7 @@ SFTool is an open-source tool specifically designed for SiFli series SoCs (Syste ## Features -- Support for SF32LB52 chip +- Support for SF32LB52, SF32LB56, SF32LB58 chips - Support for multiple storage types: NOR flash, NAND flash, and SD card - Configurable serial port parameters - Reliable flash writing functionality with verification and compression support @@ -47,19 +47,35 @@ cargo build --release ```bash sftool [OPTIONS] COMMAND [COMMAND OPTIONS] +sftool [OPTIONS] config ``` ### Global Options - `-c, --chip `: Target chip type (currently supporting SF32LB52) -- `-m, --memory `: Storage type [nor, nand, sd] (default: nor) +- `-m, --memory `: Storage type [nor, nand, sd] (default: nor, case-insensitive) - `-p, --port `: Serial port device path - `-b, --baud `: Baud rate used for flashing/reading (default: 1000000) - `--before `: Operation before connecting to the chip [default_reset, no_reset, no_reset_no_sync] (default: default_reset) - `--after `: Operation after the tool completes [soft_reset, no_reset] (default: soft_reset) -- `--connect-attempts `: Number of connection attempts, negative or 0 means infinite (default: 7) +- `--connect-attempts `: Number of connection attempts, negative or 0 means infinite (default: 3) - `--compat` : Compatibility mode, should be turned on if timeout errors or verification failures occur frequently after downloading. +### JSON Config (sftool_param.json) + +You can describe a command in a JSON file (for automation) and run it with the `config` subcommand: + +```bash +sftool config sftool_param.json + +# CLI options can override or fill missing fields in the JSON +sftool -c SF32LB52 -p /dev/ttyUSB0 config sftool_param.json +``` + +The JSON file does not need to include every field; CLI options and defaults are merged first, and +validation fails only if required values are still missing. The schema is in +`sftool_param_schema.json` in the repository. + ### Write Flash Command ```bash diff --git a/sftool/README.md b/sftool/README.md index fe02ad6..3097f2d 100644 --- a/sftool/README.md +++ b/sftool/README.md @@ -55,19 +55,35 @@ cargo build --release ```bash sftool [OPTIONS] COMMAND [COMMAND_OPTIONS] +sftool [OPTIONS] config ``` ### Global Options - `-c, --chip `: Target chip type (currently supports SF32LB52, SF32LB56, SF32LB58) -- `-m, --memory `: Memory type [nor, nand, sd] (default: nor) +- `-m, --memory `: Memory type [nor, nand, sd] (default: nor, case-insensitive) - `-p, --port `: Serial port device path - `-b, --baud `: Baud rate for flash/read operations (default: 1000000) - `--before `: Operation before connecting to the chip [default_reset, no_reset, no_reset_no_sync] (default: default_reset) - `--after `: Operation after tool completion [soft_reset, no_reset] (default: soft_reset) -- `--connect-attempts `: Number of connection attempts, negative or 0 for infinite (default: 7) +- `--connect-attempts `: Number of connection attempts, negative or 0 for infinite (default: 3) - `--compat`: Compatibility mode, enable if you frequently encounter timeout errors or checksum failures +### JSON Config (sftool_param.json) + +You can describe a command in a JSON file (for automation) and run it with the `config` subcommand: + +```bash +sftool config sftool_param.json + +# CLI options can override or fill missing fields in the JSON +sftool -c SF32LB52 -p /dev/ttyUSB0 config sftool_param.json +``` + +The JSON file does not need to include every field; CLI options and defaults are merged first, and +validation fails only if required values are still missing. The schema is in +`sftool_param_schema.json` in the repository. + ### Write Flash Command ```bash diff --git a/sftool/sftool_param_schema.json b/sftool/sftool_param_schema.json index 36b4f64..5c7473c 100644 --- a/sftool/sftool_param_schema.json +++ b/sftool/sftool_param_schema.json @@ -11,9 +11,9 @@ }, "memory": { "type": "string", - "enum": [ "nor", "nand", "sd" ], + "pattern": "^(?:[nN][oO][rR]|[nN][aA][nN][dD]|[sS][dD])$", "default": "nor", - "description": "Memory type" + "description": "Memory type (case-insensitive)" }, "port": { "type": "string", @@ -78,24 +78,6 @@ { "required": [ "erase_region" ] }, { "required": [ "stub" ] } ], - "allOf": [ - { - "if": { "required": [ "write_flash" ] }, - "then": { "required": [ "chip", "port" ] } - }, - { - "if": { "required": [ "read_flash" ] }, - "then": { "required": [ "chip", "port" ] } - }, - { - "if": { "required": [ "erase_flash" ] }, - "then": { "required": [ "chip", "port" ] } - }, - { - "if": { "required": [ "erase_region" ] }, - "then": { "required": [ "chip", "port" ] } - } - ], "additionalProperties": false, "definitions": { "hexString": { diff --git a/sftool/src/cli.rs b/sftool/src/cli.rs index d3994ee..d176278 100644 --- a/sftool/src/cli.rs +++ b/sftool/src/cli.rs @@ -31,16 +31,12 @@ pub enum Memory { #[derive(Parser, Debug)] #[command(author, version, about = "sftool CLI", long_about = None)] pub struct Cli { - /// JSON configuration file path - #[arg(long = "config", short = 'f')] - pub config: Option, - /// Target chip type #[arg(short = 'c', long = "chip", value_enum)] pub chip: Option, /// Memory type (default: nor) - #[arg(short = 'm', long = "memory", value_enum)] + #[arg(short = 'm', long = "memory", value_enum, ignore_case = true)] pub memory: Option, /// Serial port device @@ -85,6 +81,10 @@ pub struct Cli { #[derive(Subcommand, Debug, Clone)] pub enum Commands { + /// Execute a command from a JSON configuration file + #[command(name = "config")] + Config(ConfigCommand), + /// Write a binary blob to flash #[command(name = "write_flash")] WriteFlash(WriteFlash), @@ -106,6 +106,14 @@ pub enum Commands { Stub(StubCommand), } +#[derive(Parser, Debug, Clone)] +#[command(about = "Execute a command from a JSON configuration file")] +pub struct ConfigCommand { + /// JSON configuration file path + #[arg(required = true, value_name = "FILE")] + pub path: String, +} + #[derive(Parser, Debug, Clone)] #[command(about = "Write a binary blob to flash")] pub struct WriteFlash { @@ -213,6 +221,17 @@ fn memory_to_string(memory: &Memory) -> String { } } +fn normalize_memory(memory: &str) -> Result { + let normalized = memory.to_ascii_lowercase(); + match normalized.as_str() { + "nor" | "nand" | "sd" => Ok(normalized), + _ => bail!( + "Invalid memory type '{}'. Must be one of: nor, nand, sd", + memory + ), + } +} + /// Merge CLI arguments with configuration file, CLI args take precedence pub fn merge_config(args: &Cli, config: Option) -> Result { // 使用配置文件或默认配置 @@ -225,11 +244,12 @@ pub fn merge_config(args: &Cli, config: Option) -> Result) -> Result { - match (&args.command, &config) { - (Some(cmd), _) => Ok(CommandSource::Cli(cmd.clone())), - (None, Some(cfg)) => Ok(CommandSource::Config(cfg.clone())), - (None, None) => { - bail!("No command specified. Use a subcommand or provide a config file with a command.") - } + match &args.command { + Some(Commands::Config(_)) => config + .map(CommandSource::Config) + .ok_or_else(|| anyhow!("Config command requires a configuration file")), + Some(cmd) => Ok(CommandSource::Cli(cmd.clone())), + None => bail!("No command specified. Use a subcommand or `config `."), } } diff --git a/sftool/src/config.rs b/sftool/src/config.rs index be900c6..d6d62ea 100644 --- a/sftool/src/config.rs +++ b/sftool/src/config.rs @@ -288,21 +288,6 @@ impl SfToolConfig { return Ok(()); } - // 验证芯片类型 - self.parse_chip_type()?; - - // 验证操作类型 - self.parse_before()?; - self.parse_after()?; - - // 验证内存类型 - if !["nor", "nand", "sd"].contains(&self.memory.as_str()) { - return Err(format!( - "Invalid memory type '{}'. Must be one of: nor, nand, sd", - self.memory - )); - } - // 验证文件路径格式中的十六进制字符串 if let Some(ref write_flash) = self.write_flash { for file in &write_flash.files { diff --git a/sftool/src/main.rs b/sftool/src/main.rs index 729d8f4..b48d89f 100644 --- a/sftool/src/main.rs +++ b/sftool/src/main.rs @@ -30,20 +30,21 @@ fn main() -> Result<()> { tracing_subscriber::fmt().with_env_filter(env_filter).init(); let args = Cli::parse(); - // Load config file if specified - let config = if let Some(ref config_path) = args.config { - let cfg = SfToolConfig::from_file(config_path) - .map_err(|e| anyhow!("Failed to load config file '{}': {}", config_path, e))?; - cfg.validate().map_err(|e| { - anyhow!( - "Configuration validation failed for '{}': {}", - config_path, - e - ) - })?; - Some(cfg) - } else { - None + // Load config file when using the config subcommand + 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, }; // Determine which command to execute @@ -135,7 +136,7 @@ fn main() -> Result<()> { match command_source { CommandSource::Cli(command) => match command { - Commands::Stub(_) => { + Commands::Stub(_) | Commands::Config(_) => { // handled earlier } Commands::WriteFlash(params) => {