diff --git a/cargo-espflash/src/error.rs b/cargo-espflash/src/error.rs index df8e2b39..0afa8f92 100644 --- a/cargo-espflash/src/error.rs +++ b/cargo-espflash/src/error.rs @@ -54,7 +54,7 @@ pub enum Error { #[error("No package could be located in the current workspace")] #[diagnostic( code(cargo_espflash::no_package), - help("Ensure that you are executing from a valid package, and that the specified package name\ + help("Ensure that you are executing from a valid package, and that the specified package name \ exists in the current workspace.") )] NoPackage, diff --git a/cargo-espflash/src/main.rs b/cargo-espflash/src/main.rs index a7daba94..e639edd7 100644 --- a/cargo-espflash/src/main.rs +++ b/cargo-espflash/src/main.rs @@ -6,6 +6,7 @@ use std::{ use cargo_metadata::Message; use clap::{Args, CommandFactory, Parser, Subcommand}; +use espflash::cli::{erase_flash, erase_region, EraseFlashArgs, EraseRegionArgs}; use espflash::{ cli::{ self, board_info, completions, config::Config, connect, erase_partitions, flash_elf_image, @@ -13,12 +14,13 @@ use espflash::{ save_elf_as_image, serial_monitor, CompletionsArgs, ConnectArgs, EspflashProgress, FlashConfigArgs, MonitorArgs, PartitionTableArgs, }, + error::Error as EspflashError, image_format::ImageFormatKind, logging::initialize_logger, targets::Chip, update::check_for_update, }; -use log::{debug, LevelFilter}; +use log::{debug, info, LevelFilter}; use miette::{IntoDiagnostic, Result, WrapErr}; use crate::{ @@ -66,6 +68,12 @@ enum Commands { /// depending on which shell is being used; consult your shell's /// documentation to determine the appropriate path. Completions(CompletionsArgs), + /// Erase Flash entirely + EraseFlash(EraseFlashArgs), + /// Erase specified partitions + EraseParts(ErasePartsArgs), + /// Erase specified region + EraseRegion(EraseRegionArgs), /// Flash an application in ELF format to a target device /// /// First convert the ELF file produced by cargo into the appropriate @@ -137,6 +145,26 @@ struct BuildArgs { pub flash_config_args: FlashConfigArgs, } +/// Erase named partitions based on provided partition table +#[derive(Debug, Args)] +pub struct ErasePartsArgs { + /// Connection configuration + #[clap(flatten)] + pub connect_args: ConnectArgs, + + /// Labels of the partitions to be erased + #[arg(value_name = "LABELS", value_delimiter = ',')] + pub erase_parts: Vec, + + /// Input partition table + #[arg(long, value_name = "FILE")] + pub partition_table: Option, + + /// Specify a (binary) package within a workspace which may provide a partition table + #[arg(long)] + pub package: Option, +} + /// Build and flash an application to a target device #[derive(Debug, Args)] struct FlashArgs { @@ -182,6 +210,9 @@ fn main() -> Result<()> { match args { Commands::BoardInfo(args) => board_info(&args, &config), Commands::Completions(args) => completions(&args, &mut Cli::command(), "cargo"), + Commands::EraseFlash(args) => erase_flash(args, &config), + Commands::EraseParts(args) => erase_parts(args, &config), + Commands::EraseRegion(args) => erase_region(args, &config), Commands::Flash(args) => flash(args, &config), Commands::Monitor(args) => serial_monitor(args, &config), Commands::PartitionTable(args) => partition_table(args), @@ -196,6 +227,33 @@ struct BuildContext { pub partition_table_path: Option, } +pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> { + if args.connect_args.no_stub { + return Err(EspflashError::StubRequiredToEraseFlash).into_diagnostic(); + } + + let metadata_partition_table = PackageMetadata::load(&args.package) + .ok() + .and_then(|m| m.partition_table); + + let partition_table = args + .partition_table + .as_deref() + .or(metadata_partition_table.as_deref()); + + let mut flash = connect(&args.connect_args, config)?; + let partition_table = match partition_table { + Some(path) => Some(parse_partition_table(path)?), + None => None, + }; + + info!("Erasing the following partitions: {:?}", args.erase_parts); + erase_partitions(&mut flash, partition_table, Some(args.erase_parts), None)?; + flash.connection().reset()?; + + Ok(()) +} + fn flash(args: FlashArgs, config: &Config) -> Result<()> { let metadata = PackageMetadata::load(&args.build_args.package)?; let cargo_config = CargoConfig::load(&metadata.workspace_root, &metadata.package_root); diff --git a/espflash/src/bin/espflash.rs b/espflash/src/bin/espflash.rs index 429102c1..e95cde63 100644 --- a/espflash/src/bin/espflash.rs +++ b/espflash/src/bin/espflash.rs @@ -1,24 +1,25 @@ use std::{ fs::{self, File}, io::Read, - num::ParseIntError, path::PathBuf, }; use clap::{Args, CommandFactory, Parser, Subcommand}; use espflash::{ cli::{ - self, board_info, completions, config::Config, connect, erase_partitions, flash_elf_image, - monitor::monitor, parse_partition_table, partition_table, print_board_info, - save_elf_as_image, serial_monitor, CompletionsArgs, ConnectArgs, EspflashProgress, - FlashConfigArgs, MonitorArgs, PartitionTableArgs, + self, board_info, completions, config::Config, connect, erase_flash, erase_partitions, + erase_region, flash_elf_image, monitor::monitor, parse_partition_table, parse_uint32, + partition_table, print_board_info, save_elf_as_image, serial_monitor, CompletionsArgs, + ConnectArgs, EraseFlashArgs, EraseRegionArgs, EspflashProgress, FlashConfigArgs, + MonitorArgs, PartitionTableArgs, }, + error::Error, image_format::ImageFormatKind, logging::initialize_logger, targets::Chip, update::check_for_update, }; -use log::{debug, LevelFilter}; +use log::{debug, info, LevelFilter}; use miette::{IntoDiagnostic, Result, WrapErr}; #[derive(Debug, Parser)] @@ -42,6 +43,12 @@ enum Commands { /// depending on which shell is being used; consult your shell's /// documentation to determine the appropriate path. Completions(CompletionsArgs), + /// Erase Flash entirely + EraseFlash(EraseFlashArgs), + /// Erase specified partitions + EraseParts(ErasePartsArgs), + /// Erase specified region + EraseRegion(EraseRegionArgs), /// Flash an application in ELF format to a connected target device /// /// Given a path to an ELF file, first convert it into the appropriate @@ -78,6 +85,22 @@ enum Commands { WriteBin(WriteBinArgs), } +/// Erase named partitions based on provided partition table +#[derive(Debug, Args)] +pub struct ErasePartsArgs { + /// Connection configuration + #[clap(flatten)] + pub connect_args: ConnectArgs, + + /// Labels of the partitions to be erased + #[arg(value_name = "LABELS", value_delimiter = ',')] + pub erase_parts: Vec, + + /// Input partition table + #[arg(long, value_name = "FILE")] + pub partition_table: Option, +} + #[derive(Debug, Args)] struct FlashArgs { /// Connection configuration @@ -121,11 +144,6 @@ struct WriteBinArgs { connect_args: ConnectArgs, } -/// Parses a string as a 32-bit unsigned integer. -fn parse_uint32(input: &str) -> Result { - parse_int::parse(input) -} - fn main() -> Result<()> { miette::set_panic_hook(); initialize_logger(LevelFilter::Info); @@ -148,6 +166,9 @@ fn main() -> Result<()> { match args { Commands::BoardInfo(args) => board_info(&args, &config), Commands::Completions(args) => completions(&args, &mut Cli::command(), "espflash"), + Commands::EraseFlash(args) => erase_flash(args, &config), + Commands::EraseParts(args) => erase_parts(args, &config), + Commands::EraseRegion(args) => erase_region(args, &config), Commands::Flash(args) => flash(args, &config), Commands::Monitor(args) => serial_monitor(args, &config), Commands::PartitionTable(args) => partition_table(args), @@ -156,6 +177,24 @@ fn main() -> Result<()> { } } +pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> { + if args.connect_args.no_stub { + return Err(Error::StubRequiredToEraseFlash).into_diagnostic(); + } + + let mut flash = connect(&args.connect_args, config)?; + let partition_table = match args.partition_table { + Some(path) => Some(parse_partition_table(&path)?), + None => None, + }; + + info!("Erasing the following partitions: {:?}", args.erase_parts); + erase_partitions(&mut flash, partition_table, Some(args.erase_parts), None)?; + flash.connection().reset()?; + + Ok(()) +} + fn flash(args: FlashArgs, config: &Config) -> Result<()> { let mut flasher = connect(&args.connect_args, config)?; diff --git a/espflash/src/cli/mod.rs b/espflash/src/cli/mod.rs index 0354d617..6377d4bd 100644 --- a/espflash/src/cli/mod.rs +++ b/espflash/src/cli/mod.rs @@ -7,6 +7,7 @@ //! [cargo-espflash]: https://crates.io/crates/cargo-espflash //! [espflash]: https://crates.io/crates/espflash +use std::num::ParseIntError; use std::{ collections::HashMap, fs, @@ -26,7 +27,7 @@ use serialport::{SerialPortType, UsbPortInfo}; use self::{config::Config, monitor::monitor, serial::get_serial_port_info}; use crate::{ elf::ElfFirmwareImage, - error::{MissingPartition, MissingPartitionTable}, + error::{Error, MissingPartition, MissingPartitionTable}, flasher::{FlashFrequency, FlashMode, FlashSize, Flasher, ProgressCallbacks}, image_format::ImageFormatKind, interface::Interface, @@ -67,6 +68,30 @@ pub struct CompletionsArgs { pub shell: Shell, } +/// Erase entire flash of target device +#[derive(Debug, Args)] +pub struct EraseFlashArgs { + /// Connection configuration + #[clap(flatten)] + pub connect_args: ConnectArgs, +} + +/// Erase specified region of flash +#[derive(Debug, Args)] +pub struct EraseRegionArgs { + /// Connection configuration + #[clap(flatten)] + pub connect_args: ConnectArgs, + + /// Offset to start erasing from + #[arg(value_name = "OFFSET", value_parser = parse_uint32)] + pub addr: u32, + + /// Size of the region to erase + #[arg(value_name = "SIZE", value_parser = parse_uint32)] + pub size: u32, +} + /// Configure communication with the target device's flash #[derive(Debug, Args)] pub struct FlashConfigArgs { @@ -445,6 +470,38 @@ impl ProgressCallbacks for EspflashProgress { } } +pub fn erase_flash(args: EraseFlashArgs, config: &Config) -> Result<()> { + if args.connect_args.no_stub { + return Err(Error::StubRequiredToEraseFlash).into_diagnostic(); + } + + let mut flash = connect(&args.connect_args, config)?; + + info!("Erasing Flash..."); + flash.erase_flash()?; + + flash.connection().reset()?; + + Ok(()) +} + +pub fn erase_region(args: EraseRegionArgs, config: &Config) -> Result<()> { + if args.connect_args.no_stub { + return Err(Error::StubRequiredToEraseFlash).into_diagnostic(); + } + + let mut flash = connect(&args.connect_args, config)?; + + info!( + "Erasing region at 0x{:08x} ({} bytes)", + args.addr, args.size + ); + flash.erase_region(args.addr, args.size)?; + flash.connection().reset()?; + + Ok(()) +} + /// Write an ELF image to a target device's flash pub fn flash_elf_image( flasher: &mut Flasher, @@ -637,3 +694,8 @@ fn pretty_print(table: PartitionTable) { println!("{pretty}"); } + +/// Parses a string as a 32-bit unsigned integer. +pub fn parse_uint32(input: &str) -> Result { + parse_int::parse(input) +} diff --git a/espflash/src/error.rs b/espflash/src/error.rs index a5169930..133b7e08 100644 --- a/espflash/src/error.rs +++ b/espflash/src/error.rs @@ -83,6 +83,13 @@ pub enum Error { )] NoSerial, + #[error("Erase commands require using the RAM stub")] + #[diagnostic( + code(espflash::stub_required_to_erase_flash), + help("Don't use the `--no-stub` option with erase commands") + )] + StubRequiredToEraseFlash, + #[error("Incorrect serial port configuration")] #[diagnostic( code(espflash::serial_config), diff --git a/espflash/src/flasher/mod.rs b/espflash/src/flasher/mod.rs index acff35f7..7399dd69 100644 --- a/espflash/src/flasher/mod.rs +++ b/espflash/src/flasher/mod.rs @@ -855,12 +855,25 @@ impl Flasher { pub fn erase_region(&mut self, offset: u32, size: u32) -> Result<(), Error> { debug!("Erasing region of 0x{:x}B at 0x{:08x}", size, offset); + self.connection.with_timeout( + CommandType::EraseRegion.timeout_for_size(size), + |connection| connection.command(Command::EraseRegion { offset, size }), + )?; + std::thread::sleep(Duration::from_secs_f32(0.05)); + self.connection.flush()?; + Ok(()) + } + + pub fn erase_flash(&mut self) -> Result<(), Error> { + debug!("Erasing the entire flash"); + self.connection - .with_timeout(CommandType::EraseRegion.timeout(), |connection| { - connection.command(Command::EraseRegion { offset, size }) + .with_timeout(CommandType::EraseFlash.timeout(), |connection| { + connection.command(Command::EraseFlash) })?; - std::thread::sleep(Duration::from_secs_f32(0.05)); + sleep(Duration::from_secs_f32(0.05)); self.connection.flush()?; + Ok(()) }