mirror of
https://github.com/esp-rs/espflash.git
synced 2026-03-14 01:47:47 +00:00
Add checksum-md5 command (#536)
* Add `checksum-md5` command * fmt * clippy * CHANGELOG.md entry * `checksum-md5` for cargo-espflash, add timeout
This commit is contained in:
parent
77fd5b4417
commit
51239117ae
@ -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
|
||||
|
||||
|
||||
31
Cargo.lock
generated
31
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<u32, ParseIntError> {
|
||||
parse_int::parse(input)
|
||||
}
|
||||
|
||||
/// Select a serial port and establish a connection with a target device
|
||||
pub fn connect(args: &ConnectArgs, config: &Config) -> Result<Flasher> {
|
||||
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());
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
@ -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<u32> for CommandResponseValue {
|
||||
type Error = crate::error::Error;
|
||||
|
||||
fn try_into(self) -> Result<u32, Self::Error> {
|
||||
match self {
|
||||
CommandResponseValue::ValueU32(value) => Ok(value),
|
||||
CommandResponseValue::ValueU128(_) => Err(crate::error::Error::InternalError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<u128> for CommandResponseValue {
|
||||
type Error = crate::error::Error;
|
||||
|
||||
fn try_into(self) -> Result<u128, Self::Error> {
|
||||
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<u32, Error> {
|
||||
pub fn command(&mut self, command: Command) -> Result<CommandResponseValue, Error> {
|
||||
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
|
||||
|
||||
@ -179,6 +179,9 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
#[diagnostic(transparent)]
|
||||
Defmt(#[from] DefmtError),
|
||||
|
||||
#[error("Internal Error")]
|
||||
InternalError,
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
@ -187,12 +190,6 @@ impl From<io::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<binrw::Error> for Error {
|
||||
fn from(err: binrw::Error) -> Self {
|
||||
Self::Connection(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serialport::Error> for Error {
|
||||
fn from(err: serialport::Error) -> Self {
|
||||
Self::Connection(err.into())
|
||||
@ -261,15 +258,6 @@ impl From<io::Error> for ConnectionError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<binrw::Error> for ConnectionError {
|
||||
fn from(err: binrw::Error) -> Self {
|
||||
match err {
|
||||
binrw::Error::Io(e) => ConnectionError::from(e),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serialport::Error> for ConnectionError {
|
||||
fn from(err: serialport::Error) -> Self {
|
||||
use serialport::ErrorKind;
|
||||
|
||||
@ -888,6 +888,19 @@ impl Flasher {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get MD5 of region
|
||||
pub fn checksum_md5(&mut self, addr: u32, length: u32) -> Result<u128, Error> {
|
||||
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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user