Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `serialport` feature. (#535)
- 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)

### Fixed

Expand Down
5 changes: 4 additions & 1 deletion cargo-espflash/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,11 @@ pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> {
};

info!("Erasing the following partitions: {:?}", args.erase_parts);
let chip: Chip = flash.chip();
erase_partitions(&mut flash, partition_table, Some(args.erase_parts), None)?;
flash.connection().reset()?;
flash
.connection()
.reset_after(!args.connect_args.no_stub, chip)?;

Ok(())
}
Expand Down
5 changes: 4 additions & 1 deletion espflash/src/bin/espflash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,11 @@ pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> {
};

info!("Erasing the following partitions: {:?}", args.erase_parts);
let chip = flash.chip();
erase_partitions(&mut flash, partition_table, Some(args.erase_parts), None)?;
flash.connection().reset()?;
flash
.connection()
.reset_after(!args.connect_args.no_stub, chip)?;

Ok(())
}
Expand Down
33 changes: 28 additions & 5 deletions espflash/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use clap_complete::Shell;
use comfy_table::{modifiers, presets::UTF8_FULL, Attribute, Cell, Color, Table};
use esp_idf_part::{DataType, Partition, PartitionTable};
use indicatif::{style::ProgressStyle, HumanCount, ProgressBar};
use log::{debug, info};
use log::{debug, info, warn};
use miette::{IntoDiagnostic, Result, WrapErr};
use serialport::{SerialPortType, UsbPortInfo};

Expand All @@ -28,6 +28,7 @@ use self::{
serial::get_serial_port_info,
};
use crate::{
connection::reset::{ResetAfterOperation, ResetBeforeOperation},
elf::ElfFirmwareImage,
error::{Error, MissingPartition, MissingPartitionTable},
flasher::{
Expand All @@ -48,9 +49,15 @@ mod serial;
#[derive(Debug, Args)]
#[non_exhaustive]
pub struct ConnectArgs {
/// Reset operation to perform after connecting to the target
#[arg(short = 'a', long, default_value = "hard-reset")]
pub after: ResetAfterOperation,
/// Baud rate at which to communicate with target device
#[arg(short = 'b', long, env = "ESPFLASH_BAUD")]
#[arg(short = 'B', long, env = "ESPFLASH_BAUD")]
pub baud: Option<u32>,
/// Reset operation to perform before connecting to the target
#[arg(short = 'b', long, default_value = "default-reset")]
pub before: ResetBeforeOperation,
/// Target device
#[arg(short = 'c', long)]
pub chip: Option<Chip>,
Expand Down Expand Up @@ -263,6 +270,15 @@ pub fn connect(
no_verify: bool,
no_skip: bool,
) -> Result<Flasher> {
if args.before == ResetBeforeOperation::NoReset
|| args.before == ResetBeforeOperation::NoResetNoSync
{
warn!(
"Pre-connection option '{:#?}' was selected. Connection may fail if the chip is not in bootloader or flasher stub mode.",
args.before
);
}

let port_info = get_serial_port_info(args, config)?;

// Attempt to open the serial port and set its initial baud rate.
Expand Down Expand Up @@ -305,6 +321,8 @@ pub fn connect(
!no_verify,
!no_skip,
args.chip,
args.after,
args.before,
)?)
}

Expand Down Expand Up @@ -547,8 +565,10 @@ pub fn erase_flash(args: EraseFlashArgs, config: &Config) -> Result<()> {

info!("Erasing Flash...");
flash.erase_flash()?;

flash.connection().reset()?;
let chip = flash.chip();
flash
.connection()
.reset_after(!args.connect_args.no_stub, chip)?;

Ok(())
}
Expand All @@ -565,7 +585,10 @@ pub fn erase_region(args: EraseRegionArgs, config: &Config) -> Result<()> {
args.addr, args.size
);
flash.erase_region(args.addr, args.size)?;
flash.connection().reset()?;
let chip = flash.chip();
flash
.connection()
.reset_after(!args.connect_args.no_stub, chip)?;

Ok(())
}
Expand Down
6 changes: 6 additions & 0 deletions espflash/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub enum CommandType {
// Some commands supported by stub only
EraseFlash = 0xd0,
EraseRegion = 0xd1,
RunUserCode = 0xd3,
}

impl CommandType {
Expand Down Expand Up @@ -165,6 +166,7 @@ pub enum Command<'a> {
offset: u32,
size: u32,
},
RunUserCode,
}

impl<'a> Command<'a> {
Expand All @@ -191,6 +193,7 @@ impl<'a> Command<'a> {
Command::EraseFlash { .. } => CommandType::EraseFlash,
Command::EraseRegion { .. } => CommandType::EraseRegion,
Command::FlashMd5 { .. } => CommandType::FlashMd5,
Command::RunUserCode { .. } => CommandType::RunUserCode,
}
}

Expand Down Expand Up @@ -379,6 +382,9 @@ impl<'a> Command<'a> {
writer.write_all(&(0u32.to_le_bytes()))?;
writer.write_all(&(0u32.to_le_bytes()))?;
}
Command::RunUserCode => {
write_basic(writer, &[], 0)?;
}
};
Ok(())
}
Expand Down
103 changes: 97 additions & 6 deletions espflash/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,29 @@ use std::{
time::Duration,
};

use log::debug;
use log::{debug, info};
use regex::Regex;
use serialport::UsbPortInfo;
use slip_codec::SlipDecoder;

#[cfg(unix)]
use self::reset::UnixTightReset;
use self::{
encoder::SlipEncoder,
reset::{construct_reset_strategy_sequence, ClassicReset, ResetStrategy, UsbJtagSerialReset},
reset::{
construct_reset_strategy_sequence, ClassicReset, HardReset, ResetAfterOperation,
ResetBeforeOperation, ResetStrategy, UsbJtagSerialReset,
},
};
use crate::{
command::{Command, CommandType},
connection::reset::soft_reset,
error::{ConnectionError, Error, ResultExt, RomError, RomErrorKind},
interface::Interface,
targets::Chip,
};

mod reset;
pub mod reset;

const MAX_CONNECT_ATTEMPTS: usize = 7;
const MAX_SYNC_ATTEMPTS: usize = 5;
Expand Down Expand Up @@ -77,21 +83,34 @@ pub struct Connection {
serial: Interface,
port_info: UsbPortInfo,
decoder: SlipDecoder,
after_operation: ResetAfterOperation,
before_operation: ResetBeforeOperation,
}

impl Connection {
pub fn new(serial: Interface, port_info: UsbPortInfo) -> Self {
pub fn new(
serial: Interface,
port_info: UsbPortInfo,
after_operation: ResetAfterOperation,
before_operation: ResetBeforeOperation,
) -> Self {
Connection {
serial,
port_info,
decoder: SlipDecoder::new(),
after_operation,
before_operation,
}
}

/// Initialize a connection with a device
pub fn begin(&mut self) -> Result<(), Error> {
let port_name = self.serial.serial_port().name().unwrap_or_default();
let reset_sequence = construct_reset_strategy_sequence(&port_name, self.port_info.pid);
let reset_sequence = construct_reset_strategy_sequence(
&port_name,
self.port_info.pid,
self.before_operation,
);

for (_, reset_strategy) in zip(0..MAX_CONNECT_ATTEMPTS, reset_sequence.iter().cycle()) {
match self.connect_attempt(reset_strategy) {
Expand All @@ -110,7 +129,47 @@ impl Connection {
/// Try to connect to a device
#[allow(clippy::borrowed_box)]
fn connect_attempt(&mut self, reset_strategy: &Box<dyn ResetStrategy>) -> Result<(), Error> {
reset_strategy.reset(&mut self.serial)?;
// If we're doing no_sync, we're likely communicating as a pass through
// with an intermediate device to the ESP32
if self.before_operation == ResetBeforeOperation::NoResetNoSync {
return Ok(());
}
let mut download_mode: bool = false;
let mut boot_mode: &str = "";
let mut boot_log_detected = false;
let mut buff: Vec<u8>;
if self.before_operation != ResetBeforeOperation::NoReset {
// Reset the chip to bootloader (download mode)
reset_strategy.reset(&mut self.serial)?;

let available_bytes = self.serial.serial_port_mut().bytes_to_read()?;
buff = vec![0; available_bytes as usize];
let read_bytes = self.serial.serial_port_mut().read(&mut buff)? as u32;

if read_bytes != available_bytes {
return Err(Error::Connection(ConnectionError::ReadMissmatch(
available_bytes,
read_bytes,
)));
}

let read_slice = std::str::from_utf8(&buff[..read_bytes as usize]).unwrap();

let pattern = Regex::new(r"boot:(0x[0-9a-fA-F]+)(.*waiting for download)?").unwrap();

// Search for the pattern in the read data
if let Some(data) = pattern.captures(read_slice) {
boot_log_detected = true;
// Boot log detected
boot_mode = data.get(1).map(|m| m.as_str()).unwrap_or_default();
download_mode = data.get(2).is_some();

// Further processing or printing the results
debug!("Boot Mode: {}", boot_mode);
debug!("Download Mode: {}", download_mode);
};
}

for _ in 0..MAX_SYNC_ATTEMPTS {
self.flush()?;

Expand All @@ -119,6 +178,16 @@ impl Connection {
}
}

if boot_log_detected {
if download_mode {
return Err(Error::Connection(ConnectionError::NoSyncReply));
} else {
return Err(Error::Connection(ConnectionError::WrongBootMode(
boot_mode.to_string(),
)));
}
}

Err(Error::Connection(ConnectionError::ConnectionFailed))
}

Expand Down Expand Up @@ -163,6 +232,28 @@ impl Connection {
Ok(())
}

// Reset the device taking into account the reset after argument
pub fn reset_after(&mut self, is_stub: bool, chip: Chip) -> Result<(), Error> {
match self.after_operation {
ResetAfterOperation::HardReset => HardReset.reset(&mut self.serial),
ResetAfterOperation::SoftReset => {
info!("Soft resetting");
soft_reset(self, false, is_stub, chip)?;
Ok(())
}
ResetAfterOperation::NoReset => {
info!("Staying in bootloader");
soft_reset(self, true, is_stub, chip)?;

Ok(())
}
ResetAfterOperation::NoResetNoStub => {
info!("Staying in flasher stub");
Ok(())
}
}
}

// Reset the device to flash mode
pub fn reset_to_flash(&mut self, extra_delay: bool) -> Result<(), Error> {
if self.port_info.pid == USB_SERIAL_JTAG_PID {
Expand Down
Loading