diff --git a/CHANGELOG.md b/CHANGELOG.md index c7e1705a..1716ac3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add support for 26 MHz bootloader for ESP32 and ESP32-C2 (#553) - Add CI check to verify that CHANGELOG is updated (#560) - Add `--before` and `--after` reset arguments (#561) +- Add `read-flash` command (#558) ### Fixed diff --git a/cargo-espflash/src/main.rs b/cargo-espflash/src/main.rs index 5b65fbc3..ab30c8c1 100644 --- a/cargo-espflash/src/main.rs +++ b/cargo-espflash/src/main.rs @@ -10,9 +10,9 @@ use espflash::{ cli::{ self, board_info, checksum_md5, completions, config::Config, connect, erase_flash, erase_partitions, erase_region, flash_elf_image, monitor::monitor, partition_table, - print_board_info, save_elf_as_image, serial_monitor, ChecksumMd5Args, CompletionsArgs, - ConnectArgs, EraseFlashArgs, EraseRegionArgs, EspflashProgress, FlashConfigArgs, - MonitorArgs, PartitionTableArgs, + print_board_info, read_flash, save_elf_as_image, serial_monitor, ChecksumMd5Args, + CompletionsArgs, ConnectArgs, EraseFlashArgs, EraseRegionArgs, EspflashProgress, + FlashConfigArgs, MonitorArgs, PartitionTableArgs, ReadFlashArgs, }, error::Error as EspflashError, flasher::{parse_partition_table, FlashData, FlashSettings}, @@ -100,6 +100,8 @@ enum Commands { /// '--to-binary' options, plus the ability to print a partition table /// in tabular format. PartitionTable(PartitionTableArgs), + /// Read SPI flash content + ReadFlash(ReadFlashArgs), /// Generate a binary application image and save it to a local disk /// /// If the '--merge' option is used, then the bootloader, partition table, @@ -219,6 +221,7 @@ fn main() -> Result<()> { Commands::Flash(args) => flash(args, &config), Commands::Monitor(args) => serial_monitor(args, &config), Commands::PartitionTable(args) => partition_table(args), + Commands::ReadFlash(args) => read_flash(args, &config), Commands::SaveImage(args) => save_image(args), Commands::ChecksumMd5(args) => checksum_md5(&args, &config), } @@ -233,7 +236,7 @@ struct BuildContext { pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> { if args.connect_args.no_stub { - return Err(EspflashError::StubRequiredToEraseFlash).into_diagnostic(); + return Err(EspflashError::StubRequired).into_diagnostic(); } let metadata_partition_table = PackageMetadata::load(&args.package) diff --git a/espflash/src/bin/espflash.rs b/espflash/src/bin/espflash.rs index aa307e2e..bf42833a 100644 --- a/espflash/src/bin/espflash.rs +++ b/espflash/src/bin/espflash.rs @@ -9,9 +9,9 @@ use espflash::{ cli::{ self, board_info, checksum_md5, completions, config::Config, connect, erase_flash, erase_partitions, erase_region, flash_elf_image, monitor::monitor, parse_uint32, - partition_table, print_board_info, save_elf_as_image, serial_monitor, ChecksumMd5Args, - CompletionsArgs, ConnectArgs, EraseFlashArgs, EraseRegionArgs, EspflashProgress, - FlashConfigArgs, MonitorArgs, PartitionTableArgs, + partition_table, print_board_info, read_flash, save_elf_as_image, serial_monitor, + ChecksumMd5Args, CompletionsArgs, ConnectArgs, EraseFlashArgs, EraseRegionArgs, + EspflashProgress, FlashConfigArgs, MonitorArgs, PartitionTableArgs, ReadFlashArgs, }, error::Error, flasher::{parse_partition_table, FlashData, FlashSettings}, @@ -75,6 +75,8 @@ enum Commands { /// '--to-binary' options, plus the ability to print a partition table /// in tabular format. PartitionTable(PartitionTableArgs), + /// Read SPI flash content + ReadFlash(ReadFlashArgs), /// Generate a binary application image and save it to a local disk /// /// If the '--merge' option is used, then the bootloader, partition table, @@ -178,6 +180,7 @@ fn main() -> Result<()> { Commands::Monitor(args) => serial_monitor(args, &config), Commands::PartitionTable(args) => partition_table(args), Commands::SaveImage(args) => save_image(args), + Commands::ReadFlash(args) => read_flash(args, &config), Commands::WriteBin(args) => write_bin(args, &config), Commands::ChecksumMd5(args) => checksum_md5(&args, &config), } @@ -185,7 +188,7 @@ fn main() -> Result<()> { pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> { if args.connect_args.no_stub { - return Err(Error::StubRequiredToEraseFlash).into_diagnostic(); + return Err(Error::StubRequired.into()); } let mut flash = connect(&args.connect_args, config, false, false)?; diff --git a/espflash/src/cli/mod.rs b/espflash/src/cli/mod.rs index bd1425ab..b650a19b 100644 --- a/espflash/src/cli/mod.rs +++ b/espflash/src/cli/mod.rs @@ -194,6 +194,32 @@ pub struct PartitionTableArgs { to_csv: bool, } +/// Reads the content of flash memory and saves it to a file +#[derive(Debug, Args)] +#[non_exhaustive] +pub struct ReadFlashArgs { + /// Offset to start reading from + #[arg(value_name = "OFFSET", value_parser = parse_uint32)] + pub addr: u32, + /// Size of each individual packet of data + /// + /// Defaults to 0x1000 (FLASH_SECTOR_SIZE) + #[arg(long, default_value = "0x1000", value_parser = parse_uint32)] + pub block_size: u32, + /// Connection configuration + #[clap(flatten)] + connect_args: ConnectArgs, + /// Size of the region to erase + #[arg(value_name = "SIZE", value_parser = parse_uint32)] + pub size: u32, + /// Name of binary dump + #[arg(value_name = "FILE")] + pub file: PathBuf, + /// Maximum number of un-acked packets + #[arg(long, default_value = "64", value_parser = parse_uint32)] + pub max_in_flight: u32, +} + /// Save the image to disk instead of flashing to device #[derive(Debug, Args)] #[non_exhaustive] @@ -558,7 +584,7 @@ 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(); + return Err(Error::StubRequired.into()); } let mut flash = connect(&args.connect_args, config, true, true)?; @@ -575,7 +601,7 @@ pub fn erase_flash(args: EraseFlashArgs, config: &Config) -> Result<()> { pub fn erase_region(args: EraseRegionArgs, config: &Config) -> Result<()> { if args.connect_args.no_stub { - return Err(Error::StubRequiredToEraseFlash).into_diagnostic(); + return Err(Error::StubRequired).into_diagnostic(); } let mut flash = connect(&args.connect_args, config, true, true)?; @@ -677,6 +703,25 @@ fn erase_partition(flasher: &mut Flasher, part: &Partition) -> Result<()> { flasher.erase_region(offset, size).into_diagnostic() } +/// Read flash content and write it to a file +pub fn read_flash(args: ReadFlashArgs, config: &Config) -> Result<()> { + if args.connect_args.no_stub { + return Err(Error::StubRequired.into()); + } + + let mut flasher = connect(&args.connect_args, config, false, false)?; + print_board_info(&mut flasher)?; + flasher.read_flash( + args.addr, + args.size, + args.block_size, + args.max_in_flight, + args.file, + )?; + + Ok(()) +} + /// Convert and display CSV and binary partition tables pub fn partition_table(args: PartitionTableArgs) -> Result<()> { if args.to_binary { diff --git a/espflash/src/command.rs b/espflash/src/command.rs index 4bfca7c8..0ae2f923 100644 --- a/espflash/src/command.rs +++ b/espflash/src/command.rs @@ -16,12 +16,22 @@ 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); +/// Input data for SYNC command (36 bytes: 0x07 0x07 0x12 0x20, followed by 32 x 0x55) +const SYNC_FRAME: [u8; 36] = [ + 0x07, 0x07, 0x12, 0x20, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, +]; + /// Types of commands that can be sent to a target device +/// +/// https://docs.espressif.com/projects/esptool/en/latest/esp32c3/advanced-topics/serial-protocol.html#supported-by-stub-loader-and-rom-loader #[derive(Copy, Clone, Debug, Display)] #[non_exhaustive] #[repr(u8)] pub enum CommandType { Unknown = 0, + // Commands supported by the ESP8266 & ESP32s bootloaders FlashBegin = 0x02, FlashData = 0x03, FlashEnd = 0x04, @@ -30,19 +40,26 @@ pub enum CommandType { MemData = 0x07, Sync = 0x08, WriteReg = 0x09, - ReadReg = 0x0a, + ReadReg = 0x0A, + // Commands supported by the ESP32s bootloaders SpiSetParams = 0x0B, SpiAttach = 0x0D, - ChangeBaud = 0x0F, - FlashDeflateBegin = 0x10, - FlashDeflateData = 0x11, - FlashDeflateEnd = 0x12, + ChangeBaudrate = 0x0F, + FlashDeflBegin = 0x10, + FlashDeflData = 0x11, + FlashDeflEnd = 0x12, FlashMd5 = 0x13, - FlashDetect = 0x9f, - // Some commands supported by stub only - EraseFlash = 0xd0, - EraseRegion = 0xd1, - RunUserCode = 0xd3, + // Not supported on ESP32 + GetSecurityInfo = 0x14, + // Stub-only commands + EraseFlash = 0xD0, + EraseRegion = 0xD1, + ReadFlash = 0xD2, + RunUserCode = 0xD3, + // Flash encryption debug mode supported command + FlashEncryptedData = 0xD4, + // Read SPI flash manufacturer and device id - Not part of the protocol + FlashDetect = 0x9F, } impl CommandType { @@ -52,7 +69,7 @@ impl CommandType { CommandType::MemEnd => MEM_END_TIMEOUT, CommandType::Sync => SYNC_TIMEOUT, CommandType::EraseFlash => ERASE_CHIP_TIMEOUT, - CommandType::FlashDeflateEnd => FLASH_DEFLATE_END_TIMEOUT, + CommandType::FlashDeflEnd => FLASH_DEFLATE_END_TIMEOUT, CommandType::FlashMd5 => FLASH_MD5_TIMEOUT, _ => DEFAULT_TIMEOUT, } @@ -68,10 +85,10 @@ impl CommandType { ) } match self { - CommandType::FlashBegin | CommandType::FlashDeflateBegin | CommandType::EraseRegion => { + CommandType::FlashBegin | CommandType::FlashDeflBegin | CommandType::EraseRegion => { calc_timeout(ERASE_REGION_TIMEOUT_PER_MB, size) } - CommandType::FlashData | CommandType::FlashDeflateData => { + CommandType::FlashData | CommandType::FlashDeflData => { calc_timeout(ERASE_WRITE_TIMEOUT_PER_MB, size) } _ => self.timeout(), @@ -106,16 +123,16 @@ pub enum Command<'a> { offset: u32, supports_encryption: bool, }, + MemEnd { + no_entry: bool, + entry: u32, + }, MemData { data: &'a [u8], pad_to: usize, pad_byte: u8, sequence: u32, }, - MemEnd { - no_entry: bool, - entry: u32, - }, Sync, WriteReg { address: u32, @@ -134,39 +151,45 @@ pub enum Command<'a> { SpiAttachStub { spi_params: SpiAttachParams, }, - ChangeBaud { + ChangeBaudrate { /// New baud rate new_baud: u32, /// Prior baud rate ('0' for ROM flasher) prior_baud: u32, }, - FlashDeflateBegin { + FlashDeflBegin { size: u32, blocks: u32, block_size: u32, offset: u32, supports_encryption: bool, }, - FlashDeflateData { + FlashDeflData { data: &'a [u8], pad_to: usize, pad_byte: u8, sequence: u32, }, - FlashDeflateEnd { + FlashDeflEnd { reboot: bool, }, - FlashDetect, + FlashMd5 { + offset: u32, + size: u32, + }, EraseFlash, EraseRegion { offset: u32, size: u32, }, - FlashMd5 { + ReadFlash { offset: u32, size: u32, + block_size: u32, + max_in_flight: u32, }, RunUserCode, + FlashDetect, } impl<'a> Command<'a> { @@ -185,15 +208,16 @@ impl<'a> Command<'a> { Command::SpiSetParams { .. } => CommandType::SpiSetParams, Command::SpiAttach { .. } => CommandType::SpiAttach, Command::SpiAttachStub { .. } => CommandType::SpiAttach, - Command::ChangeBaud { .. } => CommandType::ChangeBaud, - Command::FlashDeflateBegin { .. } => CommandType::FlashDeflateBegin, - Command::FlashDeflateData { .. } => CommandType::FlashDeflateData, - Command::FlashDeflateEnd { .. } => CommandType::FlashDeflateEnd, - Command::FlashDetect => CommandType::FlashDetect, + Command::ChangeBaudrate { .. } => CommandType::ChangeBaudrate, + Command::FlashDeflBegin { .. } => CommandType::FlashDeflBegin, + Command::FlashDeflData { .. } => CommandType::FlashDeflData, + Command::FlashDeflEnd { .. } => CommandType::FlashDeflEnd, + Command::FlashMd5 { .. } => CommandType::FlashMd5, Command::EraseFlash { .. } => CommandType::EraseFlash, Command::EraseRegion { .. } => CommandType::EraseRegion, - Command::FlashMd5 { .. } => CommandType::FlashMd5, + Command::ReadFlash { .. } => CommandType::ReadFlash, Command::RunUserCode { .. } => CommandType::RunUserCode, + Command::FlashDetect => CommandType::FlashDetect, } } @@ -204,6 +228,7 @@ impl<'a> Command<'a> { /// Write a command pub fn write(&self, mut writer: W) -> std::io::Result<()> { + // Write the Direction and Command Indentifier writer.write_all(&[0, self.command_type() as u8])?; match *self { Command::FlashBegin { @@ -274,15 +299,7 @@ impl<'a> Command<'a> { write_basic(writer, bytes_of(¶ms), 0)?; } Command::Sync => { - write_basic( - writer, - &[ - 0x07, 0x07, 0x12, 0x20, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, - 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, - 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, - ], - 0, - )?; + write_basic(writer, &SYNC_FRAME, 0)?; } Command::WriteReg { address, @@ -317,7 +334,7 @@ impl<'a> Command<'a> { Command::SpiAttachStub { spi_params } => { write_basic(writer, &spi_params.encode(true), 0)?; } - Command::ChangeBaud { + Command::ChangeBaudrate { new_baud, prior_baud, } => { @@ -329,7 +346,7 @@ impl<'a> Command<'a> { writer.write_all(&new_baud.to_le_bytes())?; writer.write_all(&prior_baud.to_le_bytes())?; } - Command::FlashDeflateBegin { + Command::FlashDeflBegin { size, blocks, block_size, @@ -345,7 +362,7 @@ impl<'a> Command<'a> { supports_encryption, )?; } - Command::FlashDeflateData { + Command::FlashDeflData { pad_to, pad_byte, data, @@ -353,11 +370,21 @@ impl<'a> Command<'a> { } => { data_command(writer, data, pad_to, pad_byte, sequence)?; } - Command::FlashDeflateEnd { reboot } => { + Command::FlashDeflEnd { reboot } => { + // As per the logic here: https://github.com/espressif/esptool/blob/0a9caaf04cfde6fd97c785d4811f3fde09b1b71f/flasher_stub/stub_flasher.c#L402 + // 0 means reboot, 1 means do nothing write_basic(writer, &[u8::from(!reboot)], 0)?; } - Command::FlashDetect => { - write_basic(writer, &[], 0)?; + 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()))?; } Command::EraseFlash => { write_basic(writer, &[], 0)?; @@ -371,7 +398,12 @@ impl<'a> Command<'a> { writer.write_all(&offset.to_le_bytes())?; writer.write_all(&size.to_le_bytes())?; } - Command::FlashMd5 { offset, size } => { + Command::ReadFlash { + offset, + size, + block_size, + max_in_flight, + } => { // length writer.write_all(&(16u16.to_le_bytes()))?; // checksum @@ -379,12 +411,15 @@ impl<'a> Command<'a> { // 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()))?; + writer.write_all(&block_size.to_le_bytes())?; + writer.write_all(&(max_in_flight.to_le_bytes()))?; } Command::RunUserCode => { write_basic(writer, &[], 0)?; } + Command::FlashDetect => { + write_basic(writer, &[], 0)?; + } }; Ok(()) } diff --git a/espflash/src/connection/mod.rs b/espflash/src/connection/mod.rs index cd9f9650..9dcd2193 100644 --- a/espflash/src/connection/mod.rs +++ b/espflash/src/connection/mod.rs @@ -39,10 +39,11 @@ 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)] +#[derive(Debug, Clone)] pub enum CommandResponseValue { ValueU32(u32), ValueU128(u128), + Vector(Vec), } impl TryInto for CommandResponseValue { @@ -52,6 +53,7 @@ impl TryInto for CommandResponseValue { match self { CommandResponseValue::ValueU32(value) => Ok(value), CommandResponseValue::ValueU128(_) => Err(crate::error::Error::InternalError), + CommandResponseValue::Vector(_) => Err(crate::error::Error::InternalError), } } } @@ -63,12 +65,25 @@ impl TryInto for CommandResponseValue { match self { CommandResponseValue::ValueU32(_) => Err(crate::error::Error::InternalError), CommandResponseValue::ValueU128(value) => Ok(value), + CommandResponseValue::Vector(_) => Err(crate::error::Error::InternalError), + } + } +} + +impl TryInto> for CommandResponseValue { + type Error = crate::error::Error; + + fn try_into(self) -> Result, Self::Error> { + match self { + CommandResponseValue::ValueU32(_) => Err(crate::error::Error::InternalError), + CommandResponseValue::ValueU128(_) => Err(crate::error::Error::InternalError), + CommandResponseValue::Vector(value) => Ok(value), } } } /// A response from a target device following a command -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct CommandResponse { pub resp: u8, pub return_op: u8, @@ -349,9 +364,7 @@ impl Connection { response[8..][..16].try_into().unwrap(), )) } - _ => { - return Err(Error::InternalError); - } + _ => CommandResponseValue::Vector(response.clone()), }; let header = CommandResponse { @@ -368,6 +381,19 @@ impl Connection { } } + /// Write raw data to the serial port + pub fn write_raw(&mut self, data: u32) -> Result<(), Error> { + let serial = self.serial.serial_port_mut(); + + serial.clear(serialport::ClearBuffer::Input)?; + let mut writer = BufWriter::new(serial); + let mut encoder = SlipEncoder::new(&mut writer)?; + encoder.write_all(&data.to_le_bytes())?; + encoder.finish()?; + writer.flush()?; + Ok(()) + } + /// Write a command to the serial port pub fn write_command(&mut self, command: Command) -> Result<(), Error> { debug!("Writing command: {:?}", command); @@ -510,6 +536,9 @@ mod encoder { } impl<'a, W: Write> Write for SlipEncoder<'a, W> { + /// Writes the given buffer replacing the END and ESC bytes + /// + /// See https://docs.espressif.com/projects/esptool/en/latest/esp32c3/advanced-topics/serial-protocol.html#low-level-protocol fn write(&mut self, buf: &[u8]) -> std::io::Result { for value in buf.iter() { match *value { diff --git a/espflash/src/error.rs b/espflash/src/error.rs index 25c46a11..936ef27d 100644 --- a/espflash/src/error.rs +++ b/espflash/src/error.rs @@ -56,6 +56,14 @@ pub enum Error { )] ChipNotProvided, + #[error("Corrupt data, expected {0:2x?} bytes but receved {1:2x?} bytes")] + #[diagnostic(code(espflash::read_flash::corrupt_data))] + CorruptData(usize, usize), + + #[error("MD5 digest missmatch: expected {0:2x?}, received: {1:2x?}")] + #[diagnostic(code(espflash::read_flash::digest_missmatch))] + DigestMissmatch(Vec, Vec), + #[error("Supplied ELF image can not be run from RAM, as it includes segments mapped to ROM addresses")] #[diagnostic( code(espflash::not_ram_loadable), @@ -77,6 +85,14 @@ pub enum Error { #[diagnostic(code(espflash::flash_connect))] FlashConnect, + #[error("Expected MD5 digest (16 bytes), received: {0:#x} bytes")] + #[diagnostic(code(espflash::read_flash::incorrect_digest_length))] + IncorrectDigestLength(usize), + + #[error("Incorrect response from the sutb/ROM loader")] + #[diagnostic(code(espflash::read_flash::incorrect_response))] + IncorrectReposnse, + #[error("The provided bootloader binary is invalid")] InvalidBootloader, @@ -108,12 +124,16 @@ pub enum Error { )] NoSerial, - #[error("Erase commands require using the RAM stub")] + #[error("Read more bytes than expected")] + #[diagnostic(code(espflash::read_flash::read_more_than_expected))] + ReadMoreThanExpected, + + #[error("This command requires using the RAM stub")] #[diagnostic( code(espflash::stub_required_to_erase_flash), - help("Don't use the `--no-stub` option with erase commands") + help("Don't use the `--no-stub` option with the command") )] - StubRequiredToEraseFlash, + StubRequired, #[cfg(feature = "serialport")] #[error("Incorrect serial port configuration")] diff --git a/espflash/src/flasher/mod.rs b/espflash/src/flasher/mod.rs index 92aea4a7..a6dc82b6 100644 --- a/espflash/src/flasher/mod.rs +++ b/espflash/src/flasher/mod.rs @@ -4,11 +4,19 @@ //! application to a target device. It additionally provides some operations to //! read information from the target device. -use std::{borrow::Cow, fs, path::Path, str::FromStr, thread::sleep}; +use std::{ + borrow::Cow, + fs, + io::Write, + path::{Path, PathBuf}, + str::FromStr, + thread::sleep, +}; use bytemuck::{Pod, Zeroable, __core::time::Duration}; use esp_idf_part::PartitionTable; use log::{debug, info, warn}; +use md5::{Digest, Md5}; use miette::{Context, IntoDiagnostic, Result}; use serde::{Deserialize, Serialize}; #[cfg(feature = "serialport")] @@ -430,14 +438,22 @@ impl FlashData { } /// Parameters of the attached SPI flash chip (sizes, etc). +/// +/// See https://github.com/espressif/esptool/blob/da31d9d7a1bb496995f8e30a6be259689948e43e/esptool.py#L655 #[derive(Copy, Clone, Debug)] #[repr(C)] pub struct SpiSetParams { + /// Flash chip ID fl_id: u32, + /// Total size in bytes total_size: u32, + /// Block size block_size: u32, + /// Sector size sector_size: u32, + /// Page size page_size: u32, + /// Status mask status_mask: u32, } @@ -1112,8 +1128,8 @@ impl Flasher { } self.connection - .with_timeout(CommandType::ChangeBaud.timeout(), |connection| { - connection.command(Command::ChangeBaud { + .with_timeout(CommandType::ChangeBaudrate.timeout(), |connection| { + connection.command(Command::ChangeBaudrate { new_baud, prior_baud, }) @@ -1154,6 +1170,82 @@ impl Flasher { Ok(()) } + pub fn read_flash( + &mut self, + offset: u32, + size: u32, + block_size: u32, + max_in_flight: u32, + file_path: PathBuf, + ) -> Result<(), Error> { + debug!("Reading 0x{:x}B from 0x{:08x}", size, offset); + + let mut data = Vec::new(); + + let mut file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(file_path)?; + + self.connection + .with_timeout(CommandType::ReadFlash.timeout(), |connection| { + connection.command(Command::ReadFlash { + offset, + size, + block_size, + max_in_flight, + }) + })?; + + while data.len() < size as usize { + let response = self.connection.read_response()?; + let chunk: Vec = if let Some(response) = response { + response.value.try_into().unwrap() + } else { + return Err(Error::IncorrectReposnse); + }; + + data.extend_from_slice(&chunk); + + if data.len() < size as usize && chunk.len() < block_size as usize { + return Err(Error::CorruptData(block_size as usize, chunk.len())); + } + + self.connection.write_raw(data.len() as u32)?; + } + + if data.len() > size as usize { + return Err(Error::ReadMoreThanExpected); + } + + let response = self.connection.read_response()?; + let digest: Vec = if let Some(response) = response { + response.value.try_into().unwrap() + } else { + return Err(Error::IncorrectReposnse); + }; + + if digest.len() != 16 { + return Err(Error::IncorrectDigestLength(digest.len())); + } + + let mut md5_hasher = Md5::new(); + md5_hasher.update(&data); + let checksum_md5 = md5_hasher.finalize(); + + if digest != checksum_md5.as_slice() { + return Err(Error::DigestMissmatch( + digest, + checksum_md5.as_slice().to_vec(), + )); + } + + file.write_all(&data)?; + + Ok(()) + } + pub fn verify_minimum_revision(&mut self, minimum: u16) -> Result<(), Error> { let (major, minor) = self.chip.into_target().chip_revision(self.connection())?; let revision = (major * 100 + minor) as u16; diff --git a/espflash/src/targets/flash_target/esp32.rs b/espflash/src/targets/flash_target/esp32.rs index 5b7ee074..fba6d9fc 100644 --- a/espflash/src/targets/flash_target/esp32.rs +++ b/espflash/src/targets/flash_target/esp32.rs @@ -172,9 +172,9 @@ impl FlashTarget for Esp32Target { let erase_size = (erase_count * FLASH_SECTOR_SIZE) as u32; connection.with_timeout( - CommandType::FlashDeflateBegin.timeout_for_size(erase_size), + CommandType::FlashDeflBegin.timeout_for_size(erase_size), |connection| { - connection.command(Command::FlashDeflateBegin { + connection.command(Command::FlashDeflBegin { size: segment.data.len() as u32, blocks: block_count as u32, block_size: flash_write_size as u32, @@ -204,9 +204,9 @@ impl FlashTarget for Esp32Target { decoded_size = decoder.get_ref().len(); connection.with_timeout( - CommandType::FlashDeflateData.timeout_for_size(size as u32), + CommandType::FlashDeflData.timeout_for_size(size as u32), |connection| { - connection.command(Command::FlashDeflateData { + connection.command(Command::FlashDeflData { sequence: i as u32, pad_to: 0, pad_byte: 0xff, @@ -246,8 +246,8 @@ impl FlashTarget for Esp32Target { fn finish(&mut self, connection: &mut Connection, reboot: bool) -> Result<(), Error> { if self.need_deflate_end { - connection.with_timeout(CommandType::FlashDeflateEnd.timeout(), |connection| { - connection.command(Command::FlashDeflateEnd { reboot: false }) + connection.with_timeout(CommandType::FlashDeflEnd.timeout(), |connection| { + connection.command(Command::FlashDeflEnd { reboot: false }) })?; }