diff --git a/CHANGELOG.md b/CHANGELOG.md index b49991ac..6bd85df6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add --chip argument for flash and write-bin commands (#514) - Add --partition-table-offset argument for specifying the partition table offset (#516) - Add `Serialize` and `Deserialize` to `FlashFrequency`, `FlashMode` and `FlashSize`. (#528) +- Add `checksum-md5` command (#536) ### Fixed diff --git a/Cargo.lock b/Cargo.lock index c7bf16fa..9464c66e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,12 +125,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" -[[package]] -name = "array-init" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" - [[package]] name = "arrayvec" version = "0.5.2" @@ -194,30 +188,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "binrw" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8318fda24dc135cdd838f57a2b5ccb6e8f04ff6b6c65528c4bd9b5fcdc5cf6" -dependencies = [ - "array-init", - "binrw_derive", - "bytemuck", -] - -[[package]] -name = "binrw_derive" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0832bed83248115532dfb25af54fae1c83d67a2e4e3e2f591c13062e372e7e" -dependencies = [ - "either", - "owo-colors", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -1099,7 +1069,6 @@ version = "3.0.0-dev" dependencies = [ "addr2line", "base64", - "binrw", "bytemuck", "clap", "clap_complete", diff --git a/cargo-espflash/src/main.rs b/cargo-espflash/src/main.rs index 67664caa..8de23b3c 100644 --- a/cargo-espflash/src/main.rs +++ b/cargo-espflash/src/main.rs @@ -8,11 +8,11 @@ use cargo_metadata::Message; use clap::{Args, CommandFactory, Parser, Subcommand}; use espflash::{ cli::{ - self, board_info, completions, config::Config, connect, erase_flash, erase_partitions, - erase_region, flash_elf_image, monitor::monitor, parse_partition_table, partition_table, - print_board_info, save_elf_as_image, serial_monitor, CompletionsArgs, ConnectArgs, - EraseFlashArgs, EraseRegionArgs, EspflashProgress, FlashConfigArgs, MonitorArgs, - PartitionTableArgs, + self, board_info, checksum_md5, completions, config::Config, connect, erase_flash, + erase_partitions, erase_region, flash_elf_image, monitor::monitor, parse_partition_table, + partition_table, print_board_info, save_elf_as_image, serial_monitor, ChecksumMd5Args, + CompletionsArgs, ConnectArgs, EraseFlashArgs, EraseRegionArgs, EspflashProgress, + FlashConfigArgs, MonitorArgs, PartitionTableArgs, }, error::Error as EspflashError, image_format::ImageFormatKind, @@ -106,6 +106,8 @@ enum Commands { /// Otherwise, each segment will be saved as individual binaries, prefixed /// with their intended addresses in flash. SaveImage(SaveImageArgs), + /// Calculate the MD5 checksum of the given region + ChecksumMd5(ChecksumMd5Args), } #[derive(Debug, Args)] @@ -217,6 +219,7 @@ fn main() -> Result<()> { Commands::Monitor(args) => serial_monitor(args, &config), Commands::PartitionTable(args) => partition_table(args), Commands::SaveImage(args) => save_image(args), + Commands::ChecksumMd5(args) => checksum_md5(&args, &config), } } diff --git a/espflash/Cargo.toml b/espflash/Cargo.toml index bd8b66e8..84d80cd5 100644 --- a/espflash/Cargo.toml +++ b/espflash/Cargo.toml @@ -25,7 +25,6 @@ required-features = ["cli"] [dependencies] addr2line = { version = "0.21.0", optional = true } base64 = "0.21.4" -binrw = "0.12.0" bytemuck = { version = "1.14.0", features = ["derive"] } clap = { version = "4.4.6", features = ["derive", "env", "wrap_help"], optional = true } clap_complete = { version = "4.4.3", optional = true } diff --git a/espflash/src/bin/espflash.rs b/espflash/src/bin/espflash.rs index 436bf48a..66208237 100644 --- a/espflash/src/bin/espflash.rs +++ b/espflash/src/bin/espflash.rs @@ -7,11 +7,11 @@ use std::{ use clap::{Args, CommandFactory, Parser, Subcommand}; use espflash::{ cli::{ - 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, + self, board_info, checksum_md5, 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, + ChecksumMd5Args, CompletionsArgs, ConnectArgs, EraseFlashArgs, EraseRegionArgs, + EspflashProgress, FlashConfigArgs, MonitorArgs, PartitionTableArgs, }, error::Error, image_format::ImageFormatKind, @@ -83,6 +83,8 @@ enum Commands { SaveImage(SaveImageArgs), /// Write a binary file to a specific address in a target device's flash WriteBin(WriteBinArgs), + /// Calculate the MD5 checksum of the given region + ChecksumMd5(ChecksumMd5Args), } /// Erase named partitions based on provided partition table @@ -176,6 +178,7 @@ fn main() -> Result<()> { Commands::PartitionTable(args) => partition_table(args), Commands::SaveImage(args) => save_image(args), Commands::WriteBin(args) => write_bin(args, &config), + Commands::ChecksumMd5(args) => checksum_md5(&args, &config), } } diff --git a/espflash/src/cli/mod.rs b/espflash/src/cli/mod.rs index 4c3d959e..5d1c9a21 100644 --- a/espflash/src/cli/mod.rs +++ b/espflash/src/cli/mod.rs @@ -231,6 +231,24 @@ pub struct MonitorArgs { pub log_format: LogFormat, } +#[derive(Debug, Args)] +#[non_exhaustive] +pub struct ChecksumMd5Args { + /// Start address + #[clap(short, long, value_parser=parse_u32)] + address: u32, + /// Length + #[clap(short, long, value_parser=parse_u32)] + length: u32, + /// Connection configuration + #[clap(flatten)] + connect_args: ConnectArgs, +} + +pub fn parse_u32(input: &str) -> Result { + parse_int::parse(input) +} + /// Select a serial port and establish a connection with a target device pub fn connect(args: &ConnectArgs, config: &Config) -> Result { let port_info = get_serial_port_info(args, config)?; @@ -284,6 +302,16 @@ pub fn board_info(args: &ConnectArgs, config: &Config) -> Result<()> { Ok(()) } +/// Connect to a target device and calculate the checksum of the given region +pub fn checksum_md5(args: &ChecksumMd5Args, config: &Config) -> Result<()> { + let mut flasher = connect(&args.connect_args, config)?; + + let checksum = flasher.checksum_md5(args.address, args.length)?; + println!("0x{:x}", checksum); + + Ok(()) +} + /// Generate shell completions for the given shell pub fn completions(args: &CompletionsArgs, app: &mut clap::Command, bin_name: &str) -> Result<()> { clap_complete::generate(args.shell, app, bin_name, &mut std::io::stdout()); diff --git a/espflash/src/command.rs b/espflash/src/command.rs index d71ce10f..1bc4f76e 100644 --- a/espflash/src/command.rs +++ b/espflash/src/command.rs @@ -14,6 +14,7 @@ const ERASE_CHIP_TIMEOUT: Duration = Duration::from_secs(120); const MEM_END_TIMEOUT: Duration = Duration::from_millis(50); const SYNC_TIMEOUT: Duration = Duration::from_millis(100); const FLASH_DEFLATE_END_TIMEOUT: Duration = Duration::from_secs(10); +const FLASH_MD5_TIMEOUT: Duration = Duration::from_secs(8); /// Types of commands that can be sent to a target device #[derive(Copy, Clone, Debug, Display)] @@ -51,6 +52,7 @@ impl CommandType { CommandType::Sync => SYNC_TIMEOUT, CommandType::EraseFlash => ERASE_CHIP_TIMEOUT, CommandType::FlashDeflateEnd => FLASH_DEFLATE_END_TIMEOUT, + CommandType::FlashMd5 => FLASH_MD5_TIMEOUT, _ => DEFAULT_TIMEOUT, } } @@ -159,6 +161,10 @@ pub enum Command<'a> { offset: u32, size: u32, }, + FlashMd5 { + offset: u32, + size: u32, + }, } impl<'a> Command<'a> { @@ -184,6 +190,7 @@ impl<'a> Command<'a> { Command::FlashDetect => CommandType::FlashDetect, Command::EraseFlash { .. } => CommandType::EraseFlash, Command::EraseRegion { .. } => CommandType::EraseRegion, + Command::FlashMd5 { .. } => CommandType::FlashMd5, } } @@ -361,6 +368,17 @@ impl<'a> Command<'a> { writer.write_all(&offset.to_le_bytes())?; writer.write_all(&size.to_le_bytes())?; } + Command::FlashMd5 { offset, size } => { + // length + writer.write_all(&(16u16.to_le_bytes()))?; + // checksum + writer.write_all(&(0u32.to_le_bytes()))?; + // data + writer.write_all(&offset.to_le_bytes())?; + writer.write_all(&size.to_le_bytes())?; + writer.write_all(&(0u32.to_le_bytes()))?; + writer.write_all(&(0u32.to_le_bytes()))?; + } }; Ok(()) } diff --git a/espflash/src/connection/mod.rs b/espflash/src/connection/mod.rs index ab885fa4..ca76a9ba 100644 --- a/espflash/src/connection/mod.rs +++ b/espflash/src/connection/mod.rs @@ -11,7 +11,6 @@ use std::{ time::Duration, }; -use binrw::{io::Cursor, BinRead, BinReaderExt}; use log::debug; use serialport::UsbPortInfo; use slip_codec::SlipDecoder; @@ -34,13 +33,41 @@ const MAX_CONNECT_ATTEMPTS: usize = 7; const MAX_SYNC_ATTEMPTS: usize = 5; pub(crate) const USB_SERIAL_JTAG_PID: u16 = 0x1001; +#[derive(Debug, Copy, Clone)] +pub enum CommandResponseValue { + ValueU32(u32), + ValueU128(u128), +} + +impl TryInto for CommandResponseValue { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + match self { + CommandResponseValue::ValueU32(value) => Ok(value), + CommandResponseValue::ValueU128(_) => Err(crate::error::Error::InternalError), + } + } +} + +impl TryInto for CommandResponseValue { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + match self { + CommandResponseValue::ValueU32(_) => Err(crate::error::Error::InternalError), + CommandResponseValue::ValueU128(value) => Ok(value), + } + } +} + /// A response from a target device following a command -#[derive(Debug, Copy, Clone, BinRead)] +#[derive(Debug, Copy, Clone)] pub struct CommandResponse { pub resp: u8, pub return_op: u8, pub return_length: u16, - pub value: u32, + pub value: CommandResponseValue, pub error: u8, pub status: u8, } @@ -195,8 +222,56 @@ impl Connection { match self.read(10)? { None => Ok(None), Some(response) => { - let mut cursor = Cursor::new(response); - let header = cursor.read_le()?; + // here is what esptool does: https://github.com/espressif/esptool/blob/master/esptool/loader.py#L458 + // from esptool: things are a bit weird here, bear with us + + // we rely on the known and expected response sizes which should be fine for now - if that changes we need to pass the command type + // we are parsing the response for + // for most commands the response length is 10 (for the stub) or 12 (for ROM code) + // the MD5 command response is 44 for ROM loader, 26 for the stub + // see https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html?highlight=md5#response-packet + // see https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html?highlight=md5#status-bytes + // see https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html?highlight=md5#verifying-uploaded-data + let status_len = if response.len() == 10 || response.len() == 26 { + 2 + } else { + 4 + }; + + let value = match response.len() { + 10 | 12 => CommandResponseValue::ValueU32(u32::from_le_bytes( + response[4..][..4].try_into().unwrap(), + )), + 44 => { + // MD5 is in ASCII + CommandResponseValue::ValueU128( + u128::from_str_radix( + std::str::from_utf8(&response[8..][..32]).unwrap(), + 16, + ) + .unwrap(), + ) + } + 26 => { + // MD5 is BE bytes + CommandResponseValue::ValueU128(u128::from_be_bytes( + response[8..][..16].try_into().unwrap(), + )) + } + _ => { + return Err(Error::InternalError); + } + }; + + let header = CommandResponse { + resp: response[0], + return_op: response[1], + return_length: u16::from_le_bytes(response[2..][..2].try_into().unwrap()), + value, + error: response[response.len() - status_len], + status: response[response.len() - status_len + 1], + }; + Ok(Some(header)) } } @@ -217,7 +292,7 @@ impl Connection { } /// Write a command and reads the response - pub fn command(&mut self, command: Command) -> Result { + pub fn command(&mut self, command: Command) -> Result { let ty = command.command_type(); self.write_command(command).for_command(ty)?; @@ -247,6 +322,7 @@ impl Connection { self.with_timeout(CommandType::ReadReg.timeout(), |connection| { connection.command(Command::ReadReg { address: reg }) }) + .map(|v| v.try_into().unwrap()) } /// Write a register command with a timeout diff --git a/espflash/src/error.rs b/espflash/src/error.rs index 178e63fe..4cdf6771 100644 --- a/espflash/src/error.rs +++ b/espflash/src/error.rs @@ -179,6 +179,9 @@ pub enum Error { #[error(transparent)] #[diagnostic(transparent)] Defmt(#[from] DefmtError), + + #[error("Internal Error")] + InternalError, } impl From for Error { @@ -187,12 +190,6 @@ impl From for Error { } } -impl From for Error { - fn from(err: binrw::Error) -> Self { - Self::Connection(err.into()) - } -} - impl From for Error { fn from(err: serialport::Error) -> Self { Self::Connection(err.into()) @@ -261,15 +258,6 @@ impl From for ConnectionError { } } -impl From for ConnectionError { - fn from(err: binrw::Error) -> Self { - match err { - binrw::Error::Io(e) => ConnectionError::from(e), - _ => unreachable!(), - } - } -} - impl From for ConnectionError { fn from(err: serialport::Error) -> Self { use serialport::ErrorKind; diff --git a/espflash/src/flasher/mod.rs b/espflash/src/flasher/mod.rs index acaa6aaf..fe28f38e 100644 --- a/espflash/src/flasher/mod.rs +++ b/espflash/src/flasher/mod.rs @@ -888,6 +888,19 @@ impl Flasher { Ok(()) } + /// Get MD5 of region + pub fn checksum_md5(&mut self, addr: u32, length: u32) -> Result { + self.connection + .with_timeout(CommandType::FlashMd5.timeout(), |connection| { + connection + .command(crate::command::Command::FlashMd5 { + offset: addr, + size: length, + })? + .try_into() + }) + } + /// Load an ELF image to flash and execute it pub fn load_elf_to_flash( &mut self,