Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add erase-flash, erase-region, and erase-parts subcommands #462

Merged
merged 10 commits into from
Sep 29, 2023
2 changes: 1 addition & 1 deletion cargo-espflash/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
60 changes: 59 additions & 1 deletion cargo-espflash/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ 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,
monitor::monitor, parse_partition_table, partition_table, print_board_info,
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::{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<String>,

/// Input partition table
#[arg(long, value_name = "FILE")]
pub partition_table: Option<PathBuf>,

/// Specify a (binary) package within a workspace which may provide a partition table
#[arg(long)]
pub package: Option<String>,
}

/// Build and flash an application to a target device
#[derive(Debug, Args)]
struct FlashArgs {
Expand Down Expand Up @@ -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),
Expand All @@ -196,6 +227,33 @@ struct BuildContext {
pub partition_table_path: Option<PathBuf>,
}

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);
Expand Down
61 changes: 50 additions & 11 deletions espflash/src/bin/espflash.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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
Expand Down Expand Up @@ -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<String>,

/// Input partition table
#[arg(long, value_name = "FILE")]
pub partition_table: Option<PathBuf>,
}

#[derive(Debug, Args)]
struct FlashArgs {
/// Connection configuration
Expand Down Expand Up @@ -121,11 +144,6 @@ struct WriteBinArgs {
connect_args: ConnectArgs,
}

/// Parses a string as a 32-bit unsigned integer.
fn parse_uint32(input: &str) -> Result<u32, ParseIntError> {
parse_int::parse(input)
}

fn main() -> Result<()> {
miette::set_panic_hook();
initialize_logger(LevelFilter::Info);
Expand All @@ -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),
Expand All @@ -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)?;

Expand Down
64 changes: 63 additions & 1 deletion espflash/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<u32, ParseIntError> {
parse_int::parse(input)
}
7 changes: 7 additions & 0 deletions espflash/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
19 changes: 16 additions & 3 deletions espflash/src/flasher/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down