From de24719820eb11b06139ccc66edf0f55ea57486e Mon Sep 17 00:00:00 2001 From: AVee Date: Sun, 16 Apr 2023 20:36:19 +0200 Subject: [PATCH] #349 Add UnixTight reset strategy. Adds a reset approach for *nix called UnixTight reset by Espressif to avoid timing issues with USB-serial connections on Linux and macOS. See https://github.com/espressif/esptool/issues/712 for details. --- Cargo.lock | 1 + espflash/Cargo.toml | 1 + espflash/src/connection.rs | 103 +++++++++++++++++++++++++------------ espflash/src/interface.rs | 50 +++++++++++++++--- 4 files changed, 117 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ba2812b..d43b2a7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -907,6 +907,7 @@ dependencies = [ "hex 0.4.3", "indicatif", "lazy_static", + "libc", "log", "miette", "parse_int", diff --git a/espflash/Cargo.toml b/espflash/Cargo.toml index 2dfcba44..e0b09058 100644 --- a/espflash/Cargo.toml +++ b/espflash/Cargo.toml @@ -54,6 +54,7 @@ thiserror = "1.0.38" toml = "0.7.3" update-informer = { version = "0.6.0", optional = true } xmas-elf = "0.9.0" +libc = "0.2" [features] default = ["cli"] diff --git a/espflash/src/connection.rs b/espflash/src/connection.rs index fde0664f..bdb82565 100644 --- a/espflash/src/connection.rs +++ b/espflash/src/connection.rs @@ -54,11 +54,6 @@ impl Connection { for _ in 0..DEFAULT_CONNECT_ATTEMPTS { if self.connect_attempt(extra_delay).is_err() { extra_delay = !extra_delay; - - info!( - "Unable to connect, retrying with {} delay...", - if extra_delay { "extra" } else { "default" } - ); } else { return Ok(()); } @@ -69,8 +64,19 @@ impl Connection { /// Try to connect to a device fn connect_attempt(&mut self, extra_delay: bool) -> Result<(), Error> { - self.reset_to_flash(extra_delay)?; + if self.port_info.pid == USB_SERIAL_JTAG_PID { + self.usb_jtag_reset() + } else { + #[cfg(unix)] + if self.unix_tight_reset(extra_delay).is_ok() { + return Ok(()) + } + + self.classic_reset(extra_delay) + } + } + fn wait_for_connection(&mut self) -> Result<(), Error> { for _ in 0..5 { self.flush()?; if self.sync().is_ok() { @@ -119,43 +125,76 @@ impl Connection { Ok(reset_after_flash(&mut self.serial, pid)?) } - // 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 { - self.serial.write_data_terminal_ready(false)?; - self.serial.write_request_to_send(false)?; + fn classic_reset(&mut self, extra_delay: bool) -> Result<(), Error> { + info!( + "Attempting Classic reset with {} delay...", + if extra_delay { "extra" } else { "default" } + ); - sleep(Duration::from_millis(100)); + self.serial.write_data_terminal_ready(false)?; + self.serial.write_request_to_send(false)?; - self.serial.write_data_terminal_ready(true)?; - self.serial.write_request_to_send(false)?; + self.serial.write_data_terminal_ready(true)?; + self.serial.write_request_to_send(true)?; - sleep(Duration::from_millis(100)); + self.serial.write_data_terminal_ready(false)?; + self.serial.write_request_to_send(true)?; - self.serial.write_request_to_send(true)?; - self.serial.write_data_terminal_ready(false)?; - self.serial.write_request_to_send(true)?; + sleep(Duration::from_millis(100)); - sleep(Duration::from_millis(100)); + self.serial.write_data_terminal_ready(true)?; + self.serial.write_request_to_send(false)?; - self.serial.write_data_terminal_ready(false)?; - self.serial.write_request_to_send(false)?; - } else { - self.serial.write_data_terminal_ready(false)?; - self.serial.write_request_to_send(true)?; + let millis = if extra_delay { 500 } else { 50 }; + sleep(Duration::from_millis(millis)); - sleep(Duration::from_millis(100)); + self.serial.write_data_terminal_ready(false)?; + self.wait_for_connection() + } - self.serial.write_data_terminal_ready(true)?; - self.serial.write_request_to_send(false)?; + fn unix_tight_reset(&mut self, extra_delay: bool) -> Result<(), Error> { + info!( + "Attempting UnixTight reset with {} delay...", + if extra_delay { "extra" } else { "default" } + ); - let millis = if extra_delay { 500 } else { 50 }; - sleep(Duration::from_millis(millis)); + self.serial.write_dtr_rts(false, false)?; + self.serial.write_dtr_rts(true, true)?; + self.serial.write_dtr_rts(false, true)?; // IO = HIGH, EN = LOW, chip in reset - self.serial.write_data_terminal_ready(false)?; - } + sleep(Duration::from_millis(100)); - Ok(()) + self.serial.write_dtr_rts(true, false)?; // IO0 = LOW, EN = HIGH, chip out of reset + + let millis = if extra_delay { 500 } else { 50 }; + sleep(Duration::from_millis(millis)); + + self.serial.write_dtr_rts(false, false)?; // IO0 = HIGH, done + self.serial.write_data_terminal_ready(false)?; // Needed in some environments to ensure IO0 = HIGH + self.wait_for_connection() + } + + fn usb_jtag_reset(&mut self) -> Result<(), Error> { + info!("Attempting USB-JTAG reset..."); + self.serial.write_data_terminal_ready(false)?; + self.serial.write_request_to_send(false)?; + + sleep(Duration::from_millis(100)); + + self.serial.write_data_terminal_ready(true)?; + self.serial.write_request_to_send(false)?; + + sleep(Duration::from_millis(100)); + + self.serial.write_request_to_send(true)?; + self.serial.write_data_terminal_ready(false)?; + self.serial.write_request_to_send(true)?; + + sleep(Duration::from_millis(100)); + + self.serial.write_data_terminal_ready(false)?; + self.serial.write_request_to_send(false)?; + self.wait_for_connection() } /// Set timeout for the serial port diff --git a/espflash/src/interface.rs b/espflash/src/interface.rs index a999d956..5cbe2026 100644 --- a/espflash/src/interface.rs +++ b/espflash/src/interface.rs @@ -6,6 +6,9 @@ use std::io::Read; +#[cfg(unix)] +use { std::os::unix::io::AsRawFd, std::io, libc }; + use miette::{Context, Result}; #[cfg(feature = "raspberry")] use rppal::gpio::{Gpio, OutputPin}; @@ -13,6 +16,11 @@ use serialport::{FlowControl, SerialPort, SerialPortInfo}; use crate::error::Error; +#[cfg(unix)] +type Port = serialport::TTYPort; +#[cfg(windows)] +type Port = serialport::COMPort; + /// Errors relating to the configuration of a serial port #[derive(thiserror::Error, Debug)] #[non_exhaustive] @@ -29,7 +37,7 @@ pub enum SerialConfigError { /// implemented. pub struct Interface { /// Hardware serial port used for communication - pub serial_port: Box, + pub serial_port: Port, /// Data Transmit Ready pin #[cfg(feature = "raspberry")] pub dtr: Option, @@ -49,10 +57,10 @@ fn write_gpio(gpio: &mut OutputPin, level: bool) { } /// Open a serial port -fn open_port(port_info: &SerialPortInfo) -> Result> { +fn open_port(port_info: &SerialPortInfo) -> Result { serialport::new(&port_info.port_name, 115_200) .flow_control(FlowControl::None) - .open() + .open_native() .map_err(Error::from) .wrap_err_with(|| format!("Failed to open serial port {}", port_info.port_name)) } @@ -112,6 +120,36 @@ impl Interface { self.serial_port.write_data_terminal_ready(pin_state) } + #[cfg(unix)] + pub fn write_dtr_rts(&self, dtr: bool, rts: bool) -> serialport::Result<()> { + let fd = self.serial_port.as_raw_fd(); + unsafe { + let mut status: i32 = 0; + let res = libc::ioctl(fd, libc::TIOCMGET, &status); + if res != 0 { + return Err(io::Error::last_os_error().into()) + } + + if dtr { + status |= libc::TIOCM_DTR + } else { + status &= !libc::TIOCM_DTR + } + + if rts { + status |= libc::TIOCM_RTS + } else { + status &= !libc::TIOCM_RTS + } + + let res = libc::ioctl(fd, libc::TIOCMSET, &status); + if res != 0 { + return Err(io::Error::last_os_error().into()) + } + } + Ok(()) + } + /// Set the level of the RTS pin pub fn write_request_to_send(&mut self, pin_state: bool) -> serialport::Result<()> { #[cfg(feature = "raspberry")] @@ -125,17 +163,17 @@ impl Interface { /// Turn an [Interface] into a [SerialPort] pub fn into_serial(self) -> Box { - self.serial_port + Box::new(self.serial_port) } /// Turn an [Interface] into a `&`[SerialPort] pub fn serial_port(&self) -> &dyn SerialPort { - self.serial_port.as_ref() + &self.serial_port } /// Turn an [Interface] into a `&mut `[SerialPort] pub fn serial_port_mut(&mut self) -> &mut dyn SerialPort { - self.serial_port.as_mut() + &mut self.serial_port } }