diff --git a/espflash/src/command.rs b/espflash/src/command.rs new file mode 100644 index 00000000..88f7332a --- /dev/null +++ b/espflash/src/command.rs @@ -0,0 +1,398 @@ +use crate::flasher::{checksum, SpiAttachParams, CHECKSUM_INIT}; +use bytemuck::{bytes_of, Pod, Zeroable}; +use std::io::Write; +use std::mem::size_of; +use std::time::Duration; +use strum_macros::Display; + +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3); +const ERASE_REGION_TIMEOUT_PER_MB: Duration = Duration::from_secs(30); +const ERASE_WRITE_TIMEOUT_PER_MB: Duration = Duration::from_secs(40); +const MEM_END_TIMEOUT: Duration = Duration::from_millis(50); +const SYNC_TIMEOUT: Duration = Duration::from_millis(100); + +#[derive(Copy, Clone, Debug, Display)] +#[allow(dead_code)] +#[repr(u8)] +#[non_exhaustive] +pub enum CommandType { + Unknown = 0, + FlashBegin = 0x02, + FlashData = 0x03, + FlashEnd = 0x04, + MemBegin = 0x05, + MemEnd = 0x06, + MemData = 0x07, + Sync = 0x08, + WriteReg = 0x09, + ReadReg = 0x0a, + SpiSetParams = 0x0B, + SpiAttach = 0x0D, + ChangeBaud = 0x0F, + FlashDeflateBegin = 0x10, + FlashDeflateData = 0x11, + FlashDeflateEnd = 0x12, + FlashMd5 = 0x13, + FlashDetect = 0x9f, +} + +impl CommandType { + pub fn timeout(&self) -> Duration { + match self { + CommandType::MemEnd => MEM_END_TIMEOUT, + CommandType::Sync => SYNC_TIMEOUT, + _ => DEFAULT_TIMEOUT, + } + } + + pub fn timeout_for_size(&self, size: u32) -> Duration { + fn calc_timeout(timeout_per_mb: Duration, size: u32) -> Duration { + let mb = size as f64 / 1_000_000.0; + std::cmp::max( + DEFAULT_TIMEOUT, + Duration::from_millis((timeout_per_mb.as_millis() as f64 * mb) as u64), + ) + } + match self { + CommandType::FlashBegin | CommandType::FlashDeflateBegin => { + calc_timeout(ERASE_REGION_TIMEOUT_PER_MB, size) + } + CommandType::FlashData | CommandType::FlashDeflateData => { + calc_timeout(ERASE_WRITE_TIMEOUT_PER_MB, size) + } + _ => self.timeout(), + } + } +} + +#[derive(Copy, Clone, Debug)] +pub enum Command<'a> { + FlashBegin { + size: u32, + blocks: u32, + block_size: u32, + offset: u32, + supports_encryption: bool, + }, + FlashData { + data: &'a [u8], + pad_to: usize, + pad_byte: u8, + sequence: u32, + }, + FlashEnd { + reboot: bool, + }, + MemBegin { + size: u32, + blocks: u32, + block_size: u32, + offset: u32, + supports_encryption: bool, + }, + MemData { + data: &'a [u8], + pad_to: usize, + pad_byte: u8, + sequence: u32, + }, + MemEnd { + no_entry: bool, + entry: u32, + }, + Sync, + WriteReg { + address: u32, + value: u32, + mask: Option, + }, + ReadReg { + address: u32, + }, + SpiAttach { + spi_params: SpiAttachParams, + }, + ChangeBaud { + speed: u32, + }, + FlashDeflateBegin { + size: u32, + blocks: u32, + block_size: u32, + offset: u32, + supports_encryption: bool, + }, + FlashDeflateData { + data: &'a [u8], + pad_to: usize, + pad_byte: u8, + sequence: u32, + }, + FlashDeflateEnd { + reboot: bool, + }, + FlashDetect, +} + +impl<'a> Command<'a> { + pub fn command_type(&self) -> CommandType { + match self { + Command::FlashBegin { .. } => CommandType::FlashBegin, + Command::FlashData { .. } => CommandType::FlashData, + Command::FlashEnd { .. } => CommandType::FlashEnd, + Command::MemBegin { .. } => CommandType::MemBegin, + Command::MemData { .. } => CommandType::MemData, + Command::MemEnd { .. } => CommandType::MemEnd, + Command::Sync => CommandType::Sync, + Command::WriteReg { .. } => CommandType::WriteReg, + Command::ReadReg { .. } => CommandType::ReadReg, + Command::SpiAttach { .. } => CommandType::SpiAttach, + Command::ChangeBaud { .. } => CommandType::ChangeBaud, + Command::FlashDeflateBegin { .. } => CommandType::FlashDeflateBegin, + Command::FlashDeflateData { .. } => CommandType::FlashDeflateData, + Command::FlashDeflateEnd { .. } => CommandType::FlashDeflateEnd, + Command::FlashDetect => CommandType::FlashDetect, + } + } + + pub fn timeout_for_size(&self, size: u32) -> Duration { + self.command_type().timeout_for_size(size) + } + + pub fn write(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&[0, self.command_type() as u8])?; + match *self { + Command::FlashBegin { + size, + blocks, + block_size, + offset, + supports_encryption, + } => { + begin_command( + writer, + size, + blocks, + block_size, + offset, + supports_encryption, + )?; + } + Command::FlashData { + pad_to, + pad_byte, + data, + sequence, + } => { + data_command(writer, data, pad_to, pad_byte, sequence)?; + } + Command::FlashEnd { reboot } => { + write_basic(writer, &[if reboot { 0 } else { 1 }], 0)?; + } + Command::MemBegin { + size, + blocks, + block_size, + offset, + supports_encryption, + } => { + begin_command( + writer, + size, + blocks, + block_size, + offset, + supports_encryption, + )?; + } + Command::MemData { + pad_to, + pad_byte, + data, + sequence, + } => { + data_command(writer, data, pad_to, pad_byte, sequence)?; + } + Command::MemEnd { + no_entry: reboot, + entry, + } => { + #[derive(Zeroable, Pod, Copy, Clone)] + #[repr(C)] + struct EntryParams { + no_entry: u32, + entry: u32, + } + let params = EntryParams { + no_entry: if reboot { 1 } else { 0 }, + entry, + }; + 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, + )?; + } + Command::WriteReg { + address, + value, + mask, + } => { + #[derive(Zeroable, Pod, Copy, Clone, Debug)] + #[repr(C)] + struct WriteRegParams { + addr: u32, + value: u32, + mask: u32, + delay_us: u32, + } + let params = WriteRegParams { + addr: address, + value, + mask: mask.unwrap_or(0xFFFFFFFF), + delay_us: 0, + }; + write_basic(writer, bytes_of(¶ms), 0)?; + } + Command::ReadReg { address } => { + write_basic(writer, &address.to_le_bytes(), 0)?; + } + Command::SpiAttach { spi_params } => { + write_basic(writer, &spi_params.encode(), 0)?; + } + Command::ChangeBaud { speed } => { + // length + writer.write_all(&(8u16.to_le_bytes()))?; + // checksum + writer.write_all(&(0u32.to_le_bytes()))?; + // data + writer.write_all(&speed.to_le_bytes())?; + writer.write_all(&0u32.to_le_bytes())?; + } + Command::FlashDeflateBegin { + size, + blocks, + block_size, + offset, + supports_encryption, + } => { + begin_command( + writer, + size, + blocks, + block_size, + offset, + supports_encryption, + )?; + } + Command::FlashDeflateData { + pad_to, + pad_byte, + data, + sequence, + } => { + data_command(writer, data, pad_to, pad_byte, sequence)?; + } + Command::FlashDeflateEnd { reboot } => { + write_basic(writer, &[if reboot { 0 } else { 1 }], 0)?; + } + Command::FlashDetect => { + write_basic(writer, &[], 0)?; + } + }; + Ok(()) + } +} + +fn write_basic(mut writer: W, data: &[u8], checksum: u32) -> std::io::Result<()> { + writer.write_all(&((data.len() as u16).to_le_bytes()))?; + writer.write_all(&(checksum.to_le_bytes()))?; + writer.write_all(data)?; + Ok(()) +} + +fn begin_command( + writer: W, + size: u32, + blocks: u32, + block_size: u32, + offset: u32, + supports_encryption: bool, +) -> std::io::Result<()> { + #[derive(Zeroable, Pod, Copy, Clone, Debug)] + #[repr(C)] + struct BeginParams { + size: u32, + blocks: u32, + block_size: u32, + offset: u32, + encrypted: u32, + } + let params = BeginParams { + size, + blocks, + block_size, + offset, + encrypted: 0, + }; + + let bytes = bytes_of(¶ms); + let data = if !supports_encryption { + // The ESP32 and ESP8266 do not take the `encrypted` field, so truncate the last + // 4 bytes of the slice where it resides. + let end = bytes.len() - 4; + &bytes[0..end] + } else { + bytes + }; + write_basic(writer, data, 0) +} + +fn data_command( + mut writer: W, + block_data: &[u8], + pad_to: usize, + pad_byte: u8, + sequence: u32, +) -> std::io::Result<()> { + #[derive(Zeroable, Pod, Copy, Clone, Debug)] + #[repr(C)] + struct BlockParams { + size: u32, + sequence: u32, + dummy1: u32, + dummy2: u32, + } + + let pad_length = pad_to.saturating_sub(block_data.len()); + + let params = BlockParams { + size: (block_data.len() + pad_length) as u32, + sequence, + dummy1: 0, + dummy2: 0, + }; + + let mut check = checksum(block_data, CHECKSUM_INIT); + + for _ in 0..pad_length { + check = checksum(&[pad_byte], check); + } + + let total_length = size_of::() + block_data.len() + pad_length; + writer.write_all(&((total_length as u16).to_le_bytes()))?; + writer.write_all(&((check as u32).to_le_bytes()))?; + writer.write_all(bytes_of(¶ms))?; + writer.write_all(block_data)?; + for _ in 0..pad_length { + writer.write_all(&[pad_byte])?; + } + Ok(()) +} diff --git a/espflash/src/connection.rs b/espflash/src/connection.rs index b57fdd61..e5a46d05 100644 --- a/espflash/src/connection.rs +++ b/espflash/src/connection.rs @@ -1,14 +1,14 @@ use std::{io::Write, thread::sleep, time::Duration}; use binread::{io::Cursor, BinRead, BinReaderExt}; -use bytemuck::{bytes_of, Pod, Zeroable}; +use bytemuck::{Pod, Zeroable}; use serial::{BaudRate, SerialPort, SerialPortSettings, SystemPort}; use slip_codec::Decoder; use crate::{ + command::{Command, CommandType}, encoder::SlipEncoder, error::{ConnectionError, Error, ResultExt, RomError, RomErrorKind}, - flasher::Command, }; #[derive(Debug, Copy, Clone, BinRead)] @@ -115,38 +115,24 @@ impl Connection { Ok(Some(header)) } - pub fn write_command( - &mut self, - command: u8, - data: impl LazyBytes, - check: u32, - ) -> Result<(), Error> { + pub fn write_command(&mut self, command: Command) -> Result<(), Error> { let mut encoder = SlipEncoder::new(&mut self.serial)?; - encoder.write(&[0])?; - encoder.write(&[command])?; - encoder.write(&(data.length().to_le_bytes()))?; - encoder.write(&(check.to_le_bytes()))?; - data.write(&mut encoder)?; + command.write(&mut encoder)?; encoder.finish()?; Ok(()) } - pub fn command>( - &mut self, - command: Command, - data: Data, - check: u32, - ) -> Result { - self.write_command(command as u8, data, check) - .for_command(command)?; + pub fn command(&mut self, command: Command) -> Result { + let ty = command.command_type(); + self.write_command(command).for_command(ty)?; for _ in 0..100 { - match self.read_response().for_command(command)? { - Some(response) if response.return_op == command as u8 => { + match self.read_response().for_command(ty)? { + Some(response) if response.return_op == ty as u8 => { return if response.status == 1 { let _error = self.flush(); Err(Error::RomError(RomError::new( - command, + command.command_type(), RomErrorKind::from(response.error), ))) } else { @@ -162,20 +148,18 @@ impl Connection { } pub fn read_reg(&mut self, reg: u32) -> Result { - self.with_timeout(Command::ReadReg.timeout(), |connection| { - connection.command(Command::ReadReg, ®.to_le_bytes()[..], 0) + self.with_timeout(CommandType::ReadReg.timeout(), |connection| { + connection.command(Command::ReadReg { address: reg }) }) } pub fn write_reg(&mut self, addr: u32, value: u32, mask: Option) -> Result<(), Error> { - let params = WriteRegParams { - addr, - value, - mask: mask.unwrap_or(0xFFFFFFFF), - delay_us: 0, - }; - self.with_timeout(Command::WriteReg.timeout(), |connection| { - connection.command(Command::WriteReg, bytes_of(¶ms), 0) + self.with_timeout(CommandType::WriteReg.timeout(), |connection| { + connection.command(Command::WriteReg { + address: addr, + value, + mask, + }) })?; Ok(()) @@ -196,30 +180,3 @@ impl Connection { self.serial } } - -pub trait LazyBytes { - fn write(self, encoder: &mut SlipEncoder) -> Result<(), Error>; - - fn length(&self) -> u16; -} - -impl LazyBytes for &[u8] { - fn write(self, encoder: &mut SlipEncoder) -> Result<(), Error> { - encoder.write(self)?; - Ok(()) - } - - fn length(&self) -> u16 { - self.len() as u16 - } -} - -impl) -> Result<(), Error>> LazyBytes for (u16, F) { - fn write(self, encoder: &mut SlipEncoder) -> Result<(), Error> { - self.1(encoder) - } - - fn length(&self) -> u16 { - self.0 - } -} diff --git a/espflash/src/encoder.rs b/espflash/src/encoder.rs index 075e7c35..c6edf199 100644 --- a/espflash/src/encoder.rs +++ b/espflash/src/encoder.rs @@ -17,7 +17,14 @@ impl<'a, W: Write> SlipEncoder<'a, W> { Ok(Self { writer, len }) } - pub fn write(&mut self, buf: &[u8]) -> std::io::Result<()> { + pub fn finish(mut self) -> std::io::Result { + self.len += self.writer.write(&[END])?; + Ok(self.len) + } +} + +impl<'a, W: Write> Write for SlipEncoder<'a, W> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { for value in buf.iter() { match *value { END => { @@ -32,11 +39,10 @@ impl<'a, W: Write> SlipEncoder<'a, W> { } } - Ok(()) + Ok(buf.len()) } - pub fn finish(mut self) -> std::io::Result { - self.len += self.writer.write(&[END])?; - Ok(self.len) + fn flush(&mut self) -> std::io::Result<()> { + self.writer.flush() } } diff --git a/espflash/src/error.rs b/espflash/src/error.rs index 2bfa5562..d1d74984 100644 --- a/espflash/src/error.rs +++ b/espflash/src/error.rs @@ -1,4 +1,4 @@ -use crate::flasher::Command; +use crate::command::CommandType; use crate::image_format::ImageFormatId; use crate::partition_table::{SubType, Type}; use crate::Chip; @@ -90,11 +90,11 @@ pub enum ConnectionError { #[derive(Debug, Default, Clone)] pub struct TimedOutCommand { - command: Option, + command: Option, } -impl From for TimedOutCommand { - fn from(c: Command) -> Self { +impl From for TimedOutCommand { + fn from(c: CommandType) -> Self { TimedOutCommand { command: Some(c) } } } @@ -227,13 +227,13 @@ impl From for RomErrorKind { #[non_exhaustive] #[error("Error while running {command} command")] pub struct RomError { - command: Command, + command: CommandType, #[source] kind: RomErrorKind, } impl RomError { - pub fn new(command: Command, kind: RomErrorKind) -> RomError { + pub fn new(command: CommandType, kind: RomErrorKind) -> RomError { RomError { command, kind } } } @@ -242,7 +242,7 @@ pub(crate) trait ResultExt { /// mark an error as having occurred during the flashing stage fn flashing(self) -> Self; /// mark the command from which this error originates - fn for_command(self, command: Command) -> Self; + fn for_command(self, command: CommandType) -> Self; } impl ResultExt for Result { @@ -253,7 +253,7 @@ impl ResultExt for Result { } } - fn for_command(self, command: Command) -> Self { + fn for_command(self, command: CommandType) -> Self { match self { Err(Error::Connection(ConnectionError::Timeout(_))) => { Err(Error::Connection(ConnectionError::Timeout(command.into()))) diff --git a/espflash/src/flash_target/esp32.rs b/espflash/src/flash_target/esp32.rs index 11010371..454942a9 100644 --- a/espflash/src/flash_target/esp32.rs +++ b/espflash/src/flash_target/esp32.rs @@ -1,8 +1,9 @@ +use crate::command::{Command, CommandType}; use crate::connection::Connection; use crate::elf::{FirmwareImage, RomSegment}; use crate::error::Error; -use crate::flash_target::{begin_command, block_command_with_timeout, FlashTarget}; -use crate::flasher::{Command, SpiAttachParams, FLASH_SECTOR_SIZE, FLASH_WRITE_SIZE}; +use crate::flash_target::FlashTarget; +use crate::flasher::{SpiAttachParams, FLASH_SECTOR_SIZE, FLASH_WRITE_SIZE}; use crate::Chip; use flate2::write::{ZlibDecoder, ZlibEncoder}; use flate2::Compression; @@ -25,9 +26,10 @@ impl Esp32Target { impl FlashTarget for Esp32Target { fn begin(&mut self, connection: &mut Connection, _image: &FirmwareImage) -> Result<(), Error> { - let spi_params = self.spi_attach_params.encode(); - connection.with_timeout(Command::SpiAttach.timeout(), |connection| { - connection.command(Command::SpiAttach, spi_params.as_slice(), 0) + connection.with_timeout(CommandType::SpiAttach.timeout(), |connection| { + connection.command(Command::SpiAttach { + spi_params: self.spi_attach_params, + }) })?; Ok(()) } @@ -47,14 +49,18 @@ impl FlashTarget for Esp32Target { // round up to sector size let erase_size = (erase_count * FLASH_SECTOR_SIZE) as u32; - begin_command( - connection, - Command::FlashDeflateBegin, - erase_size, - block_count as u32, - FLASH_WRITE_SIZE as u32, - addr, - self.chip != Chip::Esp32, + connection.with_timeout( + CommandType::FlashDeflateBegin.timeout_for_size(erase_size), + |connection| { + connection.command(Command::FlashDeflateBegin { + size: erase_size, + blocks: block_count as u32, + block_size: FLASH_WRITE_SIZE as u32, + offset: addr, + supports_encryption: self.chip != Chip::Esp32, + })?; + Ok(()) + }, )?; let chunks = compressed.chunks(FLASH_WRITE_SIZE); @@ -79,14 +85,17 @@ impl FlashTarget for Esp32Target { decoded_size = decoder.get_ref().len(); pb_chunk.set_message(format!("segment 0x{:X} writing chunks", addr)); - block_command_with_timeout( - connection, - Command::FlashDeflateData, - block, - 0, - 0xff, - i as u32, - Command::FlashDeflateData.timeout_for_size(size as u32), + connection.with_timeout( + CommandType::FlashDeflateData.timeout_for_size(size as u32), + |connection| { + connection.command(Command::FlashDeflateData { + sequence: i as u32, + pad_to: 0, + pad_byte: 0xff, + data: block, + })?; + Ok(()) + }, )?; pb_chunk.inc(1); } @@ -97,8 +106,8 @@ impl FlashTarget for Esp32Target { } fn finish(&mut self, connection: &mut Connection, reboot: bool) -> Result<(), Error> { - connection.with_timeout(Command::FlashDeflateEnd.timeout(), |connection| { - connection.write_command(Command::FlashDeflateEnd as u8, &[1][..], 0) + connection.with_timeout(CommandType::FlashDeflateEnd.timeout(), |connection| { + connection.write_command(Command::FlashDeflateEnd { reboot: false }) })?; if reboot { connection.reset() diff --git a/espflash/src/flash_target/esp8266.rs b/espflash/src/flash_target/esp8266.rs index da81c69d..45c8dd97 100644 --- a/espflash/src/flash_target/esp8266.rs +++ b/espflash/src/flash_target/esp8266.rs @@ -1,8 +1,9 @@ +use crate::command::{Command, CommandType}; use crate::connection::Connection; use crate::elf::{FirmwareImage, RomSegment}; use crate::error::Error; -use crate::flash_target::{begin_command, block_command, FlashTarget}; -use crate::flasher::{get_erase_size, Command, FLASH_WRITE_SIZE}; +use crate::flash_target::FlashTarget; +use crate::flasher::{get_erase_size, FLASH_WRITE_SIZE}; use indicatif::{ProgressBar, ProgressStyle}; pub struct Esp8266Target; @@ -15,15 +16,14 @@ impl Esp8266Target { impl FlashTarget for Esp8266Target { fn begin(&mut self, connection: &mut Connection, _image: &FirmwareImage) -> Result<(), Error> { - begin_command( - connection, - Command::FlashBegin, - 0, - 0, - FLASH_WRITE_SIZE as u32, - 0, - false, - ) + connection.command(Command::FlashBegin { + size: 0, + blocks: 0, + block_size: FLASH_WRITE_SIZE as u32, + offset: 0, + supports_encryption: false, + })?; + Ok(()) } fn write_segment( @@ -36,14 +36,17 @@ impl FlashTarget for Esp8266Target { let erase_size = get_erase_size(addr as usize, segment.data.len()) as u32; - begin_command( - connection, - Command::FlashBegin, - erase_size, - block_count as u32, - FLASH_WRITE_SIZE as u32, - addr, - false, + connection.with_timeout( + CommandType::FlashBegin.timeout_for_size(erase_size), + |connection| { + connection.command(Command::FlashBegin { + size: erase_size, + blocks: block_count as u32, + block_size: FLASH_WRITE_SIZE as u32, + offset: addr, + supports_encryption: false, + }) + }, )?; let chunks = segment.data.chunks(FLASH_WRITE_SIZE); @@ -59,15 +62,12 @@ impl FlashTarget for Esp8266Target { for (i, block) in chunks.enumerate() { pb_chunk.set_message(format!("segment 0x{:X} writing chunks", addr)); - let block_padding = FLASH_WRITE_SIZE - block.len(); - block_command( - connection, - Command::FlashData, - block, - block_padding, - 0xff, - i as u32, - )?; + connection.command(Command::FlashData { + sequence: i as u32, + pad_to: FLASH_WRITE_SIZE, + pad_byte: 0xff, + data: block, + })?; pb_chunk.inc(1); } @@ -77,8 +77,8 @@ impl FlashTarget for Esp8266Target { } fn finish(&mut self, connection: &mut Connection, reboot: bool) -> Result<(), Error> { - connection.with_timeout(Command::FlashEnd.timeout(), |connection| { - connection.write_command(Command::FlashEnd as u8, &[1][..], 0) + connection.with_timeout(CommandType::FlashEnd.timeout(), |connection| { + connection.write_command(Command::FlashEnd { reboot: false }) })?; if reboot { connection.reset() diff --git a/espflash/src/flash_target/mod.rs b/espflash/src/flash_target/mod.rs index 896b416a..c1097029 100644 --- a/espflash/src/flash_target/mod.rs +++ b/espflash/src/flash_target/mod.rs @@ -5,13 +5,11 @@ mod ram; use crate::connection::Connection; use crate::elf::{FirmwareImage, RomSegment}; use crate::error::Error; -use crate::flasher::{checksum, Command, Encoder, CHECKSUM_INIT, FLASH_WRITE_SIZE}; -use bytemuck::{bytes_of, Pod, Zeroable}; + +use bytemuck::{Pod, Zeroable}; pub use esp32::Esp32Target; pub use esp8266::Esp8266Target; pub use ram::RamTarget; -use std::mem::size_of; -use std::time::Duration; pub trait FlashTarget { fn begin(&mut self, connection: &mut Connection, image: &FirmwareImage) -> Result<(), Error>; @@ -32,104 +30,3 @@ struct BeginParams { offset: u32, encrypted: u32, } - -fn begin_command( - connection: &mut Connection, - command: Command, - size: u32, - blocks: u32, - block_size: u32, - offset: u32, - supports_encrypted: bool, -) -> Result<(), Error> { - let params = BeginParams { - size, - blocks, - block_size, - offset, - encrypted: 0, - }; - - let bytes = bytes_of(¶ms); - let data = if !supports_encrypted { - // The ESP32 and ESP8266 do not take the `encrypted` field, so truncate the last - // 4 bytes of the slice where it resides. - let end = bytes.len() - 4; - &bytes[0..end] - } else { - bytes - }; - - connection.with_timeout(command.timeout_for_size(size), |connection| { - connection.command(command, data, 0)?; - Ok(()) - }) -} - -#[derive(Zeroable, Pod, Copy, Clone, Debug)] -#[repr(C)] -struct BlockParams { - size: u32, - sequence: u32, - dummy1: u32, - dummy2: u32, -} - -fn block_command( - connection: &mut Connection, - command: Command, - data: &[u8], - padding: usize, - padding_byte: u8, - sequence: u32, -) -> Result<(), Error> { - block_command_with_timeout( - connection, - command, - data, - padding, - padding_byte, - sequence, - command.timeout_for_size(data.len() as u32), - ) -} - -fn block_command_with_timeout( - connection: &mut Connection, - command: Command, - data: &[u8], - padding: usize, - padding_byte: u8, - sequence: u32, - timout: Duration, -) -> Result<(), Error> { - let params = BlockParams { - size: (data.len() + padding) as u32, - sequence, - dummy1: 0, - dummy2: 0, - }; - - let length = size_of::() + data.len() + padding; - - let mut check = checksum(data, CHECKSUM_INIT); - - for _ in 0..padding { - check = checksum(&[padding_byte], check); - } - - connection.with_timeout(timout, |connection| { - connection.command( - command, - (length as u16, |encoder: &mut Encoder| { - encoder.write(bytes_of(¶ms))?; - encoder.write(data)?; - let padding = &[padding_byte; FLASH_WRITE_SIZE][0..padding]; - encoder.write(padding)?; - Ok(()) - }), - check as u32, - )?; - Ok(()) - }) -} diff --git a/espflash/src/flash_target/ram.rs b/espflash/src/flash_target/ram.rs index 7e46932f..d72652b9 100644 --- a/espflash/src/flash_target/ram.rs +++ b/espflash/src/flash_target/ram.rs @@ -1,9 +1,9 @@ +use crate::command::{Command, CommandType}; use crate::connection::Connection; use crate::elf::{FirmwareImage, RomSegment}; use crate::error::Error; -use crate::flash_target::{begin_command, block_command, FlashTarget}; -use crate::flasher::Command; -use bytemuck::{bytes_of, Pod, Zeroable}; +use crate::flash_target::FlashTarget; +use bytemuck::{Pod, Zeroable}; #[derive(Zeroable, Pod, Copy, Clone)] #[repr(C)] @@ -39,41 +39,33 @@ impl FlashTarget for RamTarget { let block_count = (segment.data.len() + padding + MAX_RAM_BLOCK_SIZE - 1) / MAX_RAM_BLOCK_SIZE; - begin_command( - connection, - Command::MemBegin, - segment.data.len() as u32, - block_count as u32, - MAX_RAM_BLOCK_SIZE as u32, - segment.addr, - false, - )?; + connection.command(Command::MemBegin { + size: segment.data.len() as u32, + blocks: block_count as u32, + block_size: MAX_RAM_BLOCK_SIZE as u32, + offset: segment.addr, + supports_encryption: false, + })?; for (i, block) in segment.data.chunks(MAX_RAM_BLOCK_SIZE).enumerate() { - let block_padding = if i == block_count - 1 { padding } else { 0 }; - block_command( - connection, - Command::MemData, - block, - block_padding, - 0, - i as u32, - )?; + connection.command(Command::MemData { + sequence: i as u32, + pad_to: 4, + pad_byte: 0, + data: block, + })?; } Ok(()) } fn finish(&mut self, connection: &mut Connection, reboot: bool) -> Result<(), Error> { if reboot { - let params = match self.entry { - Some(entry) if entry > 0 => EntryParams { no_entry: 0, entry }, - _ => EntryParams { - no_entry: 1, - entry: 0, - }, - }; - connection.with_timeout(Command::MemEnd.timeout(), |connection| { - connection.write_command(Command::MemEnd as u8, bytes_of(¶ms), 0) + let entry = self.entry.unwrap_or_default(); + connection.with_timeout(CommandType::MemEnd.timeout(), |connection| { + connection.write_command(Command::MemEnd { + no_entry: entry == 0, + entry, + }) }) } else { Ok(()) diff --git a/espflash/src/flasher.rs b/espflash/src/flasher.rs index b0307102..527172aa 100644 --- a/espflash/src/flasher.rs +++ b/espflash/src/flasher.rs @@ -1,20 +1,19 @@ use std::{borrow::Cow, thread::sleep}; -use bytemuck::{__core::time::Duration, bytes_of, Pod, Zeroable}; +use bytemuck::{Pod, Zeroable, __core::time::Duration}; use serial::{BaudRate, SystemPort}; use strum_macros::Display; use crate::{ chip::Chip, + command::{Command, CommandType}, connection::Connection, elf::{FirmwareImage, RomSegment}, - encoder::SlipEncoder, error::{ConnectionError, FlashDetectError, ResultExt, RomError, RomErrorKind}, Error, PartitionTable, }; -pub(crate) type Encoder<'a> = SlipEncoder<'a, SystemPort>; - +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3); pub(crate) const FLASH_SECTOR_SIZE: usize = 0x1000; const FLASH_BLOCK_SIZE: usize = 0x100; const FLASH_SECTORS_PER_BLOCK: usize = FLASH_SECTOR_SIZE / FLASH_BLOCK_SIZE; @@ -23,66 +22,6 @@ pub(crate) const FLASH_WRITE_SIZE: usize = 0x400; // register used for chip detect const CHIP_DETECT_MAGIC_REG_ADDR: u32 = 0x40001000; -const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3); -const ERASE_REGION_TIMEOUT_PER_MB: Duration = Duration::from_secs(30); -const ERASE_WRITE_TIMEOUT_PER_MB: Duration = Duration::from_secs(40); -const MEM_END_TIMEOUT: Duration = Duration::from_millis(50); -const SYNC_TIMEOUT: Duration = Duration::from_millis(100); - -#[derive(Copy, Clone, Debug, Display)] -#[allow(dead_code)] -#[repr(u8)] -#[non_exhaustive] -pub enum Command { - Unknown = 0, - FlashBegin = 0x02, - FlashData = 0x03, - FlashEnd = 0x04, - MemBegin = 0x05, - MemEnd = 0x06, - MemData = 0x07, - Sync = 0x08, - WriteReg = 0x09, - ReadReg = 0x0a, - SpiSetParams = 0x0B, - SpiAttach = 0x0D, - ChangeBaud = 0x0F, - FlashDeflateBegin = 0x10, - FlashDeflateData = 0x11, - FlashDeflateEnd = 0x12, - FlashMd5 = 0x13, - FlashDetect = 0x9f, -} - -impl Command { - pub fn timeout(&self) -> Duration { - match self { - Command::MemEnd => MEM_END_TIMEOUT, - Command::Sync => SYNC_TIMEOUT, - _ => DEFAULT_TIMEOUT, - } - } - - pub fn timeout_for_size(&self, size: u32) -> Duration { - fn calc_timeout(timeout_per_mb: Duration, size: u32) -> Duration { - let mb = size as f64 / 1_000_000.0; - std::cmp::max( - DEFAULT_TIMEOUT, - Duration::from_millis((timeout_per_mb.as_millis() as f64 * mb) as u64), - ) - } - match self { - Command::FlashBegin | Command::FlashDeflateBegin => { - calc_timeout(ERASE_REGION_TIMEOUT_PER_MB, size) - } - Command::FlashData | Command::FlashDeflateData => { - calc_timeout(ERASE_WRITE_TIMEOUT_PER_MB, size) - } - _ => self.timeout(), - } - } -} - #[derive(Clone, Copy, Debug, Eq, PartialEq, Display)] #[allow(dead_code)] #[repr(u8)] @@ -126,7 +65,7 @@ impl FlashSize { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] #[repr(C)] pub struct SpiAttachParams { clk: u8, @@ -261,7 +200,7 @@ impl Flasher { } fn flash_detect(&mut self) -> Result { - let flash_id = self.spi_command(Command::FlashDetect, &[], 24)?; + let flash_id = self.spi_command(CommandType::FlashDetect, &[], 24)?; let size_id = flash_id >> 16; self.flash_size = match FlashSize::from(size_id as u8) { @@ -281,22 +220,16 @@ impl Flasher { fn sync(&mut self) -> Result<(), Error> { self.connection - .with_timeout(Command::Sync.timeout(), |connection| { - let data = &[ - 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, - ][..]; - - connection.write_command(Command::Sync as u8, data, 0)?; + .with_timeout(CommandType::Sync.timeout(), |connection| { + connection.write_command(Command::Sync)?; for _ in 0..100 { match connection.read_response()? { - Some(response) if response.return_op == Command::Sync as u8 => { + Some(response) if response.return_op == CommandType::Sync as u8 => { if response.status == 1 { let _error = connection.flush(); return Err(Error::RomError(RomError::new( - Command::Sync, + CommandType::Sync, RomErrorKind::from(response.error), ))); } else { @@ -329,56 +262,33 @@ impl Flasher { Err(Error::Connection(ConnectionError::ConnectionFailed)) } - fn begin_command( - &mut self, - command: Command, - size: u32, - blocks: u32, - block_size: u32, - offset: u32, - ) -> Result<(), Error> { - let params = BeginParams { - size, - blocks, - block_size, - offset, - encrypted: 0, - }; - - let bytes = bytes_of(¶ms); - let data = if self.chip == Chip::Esp32 || self.chip == Chip::Esp8266 { - // The ESP32 and ESP8266 do not take the `encrypted` field, so truncate the last - // 4 bytes of the slice where it resides. - let end = bytes.len() - 4; - &bytes[0..end] - } else { - bytes - }; - - self.connection - .with_timeout(command.timeout_for_size(size), |connection| { - connection.command(command, data, 0)?; - Ok(()) - }) - } - - fn enable_flash(&mut self, spi_attach_params: SpiAttachParams) -> Result<(), Error> { + fn enable_flash(&mut self, spi_params: SpiAttachParams) -> Result<(), Error> { match self.chip { Chip::Esp8266 => { - self.begin_command(Command::FlashBegin, 0, 0, FLASH_WRITE_SIZE as u32, 0)?; + self.connection.command(Command::FlashBegin { + supports_encryption: false, + offset: 0, + block_size: FLASH_WRITE_SIZE as u32, + size: 0, + blocks: 0, + })?; } _ => { - let spi_params = spi_attach_params.encode(); self.connection - .with_timeout(Command::SpiAttach.timeout(), |connection| { - connection.command(Command::SpiAttach, spi_params.as_slice(), 0) + .with_timeout(CommandType::SpiAttach.timeout(), |connection| { + connection.command(Command::SpiAttach { spi_params }) })?; } } Ok(()) } - fn spi_command(&mut self, command: Command, data: &[u8], read_bits: u32) -> Result { + fn spi_command( + &mut self, + command: CommandType, + data: &[u8], + read_bits: u32, + ) -> Result { assert!(read_bits < 32); assert!(data.len() < 64); @@ -556,12 +466,11 @@ impl Flasher { } pub fn change_baud(&mut self, speed: BaudRate) -> Result<(), Error> { - let new_speed = (speed.speed() as u32).to_le_bytes(); - let old_speed = 0u32.to_le_bytes(); - self.connection - .with_timeout(Command::ChangeBaud.timeout(), |connection| { - connection.command(Command::ChangeBaud, &[new_speed, old_speed].concat()[..], 0) + .with_timeout(CommandType::ChangeBaud.timeout(), |connection| { + connection.command(Command::ChangeBaud { + speed: speed.speed() as u32, + }) })?; self.connection.set_baud(speed)?; std::thread::sleep(Duration::from_secs_f32(0.05)); diff --git a/espflash/src/lib.rs b/espflash/src/lib.rs index 5d5c1a33..97054794 100644 --- a/espflash/src/lib.rs +++ b/espflash/src/lib.rs @@ -1,4 +1,5 @@ mod chip; +mod command; mod config; mod connection; mod elf;