#[cfg(unix)]
use std::{io, os::fd::AsRawFd};
use std::{thread::sleep, time::Duration};
#[cfg(unix)]
use libc::ioctl;
use log::debug;
use serde::{Deserialize, Serialize};
use serialport::SerialPort;
use strum::{Display, EnumIter, EnumString, VariantNames};
use super::{Connection, Port, USB_SERIAL_JTAG_PID};
use crate::{
Error,
command::{Command, CommandType},
flasher::FLASH_WRITE_SIZE,
};
const DEFAULT_RESET_DELAY: u64 = 50; const EXTRA_RESET_DELAY: u64 = 500;
pub trait ResetStrategy {
fn reset(&self, serial_port: &mut Port) -> Result<(), Error>;
fn set_dtr(&self, serial_port: &mut Port, level: bool) -> Result<(), Error> {
serial_port.write_data_terminal_ready(level)?;
Ok(())
}
fn set_rts(&self, serial_port: &mut Port, level: bool) -> Result<(), Error> {
serial_port.write_request_to_send(level)?;
Ok(())
}
#[cfg(unix)]
fn set_dtr_rts(
&self,
serial_port: &mut Port,
dtr_level: bool,
rts_level: bool,
) -> Result<(), Error> {
let fd = serial_port.as_raw_fd();
let mut status: i32 = 0;
match unsafe { ioctl(fd, libc::TIOCMGET, &status) } {
0 => (),
_ => return Err(io::Error::last_os_error().into()),
}
if dtr_level {
status |= libc::TIOCM_DTR
} else {
status &= !libc::TIOCM_DTR
}
if rts_level {
status |= libc::TIOCM_RTS
} else {
status &= !libc::TIOCM_RTS
}
match unsafe { ioctl(fd, libc::TIOCMSET, &status) } {
0 => (),
_ => return Err(io::Error::last_os_error().into()),
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, Serialize, Hash, Deserialize)]
pub struct ClassicReset {
delay: u64,
}
impl ClassicReset {
pub fn new(extra_delay: bool) -> Self {
let delay = if extra_delay {
EXTRA_RESET_DELAY
} else {
DEFAULT_RESET_DELAY
};
Self { delay }
}
}
impl ResetStrategy for ClassicReset {
fn reset(&self, serial_port: &mut Port) -> Result<(), Error> {
debug!(
"Using Classic reset strategy with delay of {}ms",
self.delay
);
self.set_dtr(serial_port, false)?; self.set_rts(serial_port, true)?; self.set_dtr(serial_port, false)?;
sleep(Duration::from_millis(100));
self.set_dtr(serial_port, true)?; self.set_rts(serial_port, false)?; self.set_dtr(serial_port, true)?;
sleep(Duration::from_millis(self.delay));
self.set_dtr(serial_port, false)?;
Ok(())
}
}
#[cfg(unix)]
#[derive(Debug, Clone, Copy, Serialize, Hash, Deserialize)]
pub struct UnixTightReset {
delay: u64,
}
#[cfg(unix)]
impl UnixTightReset {
pub fn new(extra_delay: bool) -> Self {
let delay = if extra_delay {
EXTRA_RESET_DELAY
} else {
DEFAULT_RESET_DELAY
};
Self { delay }
}
}
#[cfg(unix)]
impl ResetStrategy for UnixTightReset {
fn reset(&self, serial_port: &mut Port) -> Result<(), Error> {
debug!(
"Using UnixTight reset strategy with delay of {}ms",
self.delay
);
self.set_dtr_rts(serial_port, false, false)?;
self.set_dtr_rts(serial_port, true, true)?;
self.set_dtr_rts(serial_port, false, true)?;
sleep(Duration::from_millis(100));
self.set_dtr_rts(serial_port, true, false)?;
sleep(Duration::from_millis(self.delay));
self.set_dtr_rts(serial_port, false, false)?; self.set_dtr(serial_port, false)?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, Serialize, Hash, Deserialize)]
pub struct UsbJtagSerialReset;
impl ResetStrategy for UsbJtagSerialReset {
fn reset(&self, serial_port: &mut Port) -> Result<(), Error> {
debug!("Using UsbJtagSerial reset strategy");
self.set_rts(serial_port, false)?;
self.set_dtr(serial_port, false)?;
sleep(Duration::from_millis(100));
self.set_dtr(serial_port, true)?; self.set_rts(serial_port, false)?;
sleep(Duration::from_millis(100));
self.set_rts(serial_port, true)?; self.set_dtr(serial_port, false)?;
self.set_rts(serial_port, true)?;
sleep(Duration::from_millis(100));
self.set_dtr(serial_port, false)?;
self.set_rts(serial_port, false)?;
Ok(())
}
}
pub fn reset_after_flash(serial: &mut Port, pid: u16) -> Result<(), serialport::Error> {
sleep(Duration::from_millis(100));
if pid == USB_SERIAL_JTAG_PID {
serial.write_data_terminal_ready(false)?;
sleep(Duration::from_millis(100));
serial.write_request_to_send(true)?;
serial.write_data_terminal_ready(false)?;
serial.write_request_to_send(true)?;
sleep(Duration::from_millis(100));
serial.write_request_to_send(false)?;
} else {
serial.write_request_to_send(true)?;
sleep(Duration::from_millis(100));
serial.write_request_to_send(false)?;
}
Ok(())
}
pub fn hard_reset(serial_port: &mut Port, pid: u16) -> Result<(), Error> {
debug!("Using HardReset reset strategy");
reset_after_flash(serial_port, pid)?;
Ok(())
}
pub fn soft_reset(
connection: &mut Connection,
stay_in_bootloader: bool,
is_stub: bool,
) -> Result<(), Error> {
debug!("Using SoftReset reset strategy");
if !is_stub {
if stay_in_bootloader {
return Ok(());
} else {
connection.with_timeout(CommandType::FlashBegin.timeout(), |connection| {
let size: u32 = 0;
let offset: u32 = 0;
let blocks: u32 = size.div_ceil(FLASH_WRITE_SIZE as u32);
connection.command(Command::FlashBegin {
size,
blocks,
block_size: FLASH_WRITE_SIZE.try_into().unwrap(),
offset,
supports_encryption: false,
})
})?;
connection.with_timeout(CommandType::FlashEnd.timeout(), |connection| {
connection.write_command(Command::FlashEnd { reboot: false })
})?;
}
} else if stay_in_bootloader {
connection.with_timeout(CommandType::FlashBegin.timeout(), |connection| {
let size: u32 = 0;
let offset: u32 = 0;
let blocks: u32 = size.div_ceil(FLASH_WRITE_SIZE as u32);
connection.command(Command::FlashBegin {
size,
blocks,
block_size: FLASH_WRITE_SIZE.try_into().unwrap(),
offset,
supports_encryption: false,
})
})?;
connection.with_timeout(CommandType::FlashEnd.timeout(), |connection| {
connection.write_command(Command::FlashEnd { reboot: true })
})?;
} else {
connection.with_timeout(CommandType::RunUserCode.timeout(), |connection| {
connection.command(Command::RunUserCode)
})?;
}
Ok(())
}
#[allow(unused_variables)]
pub fn construct_reset_strategy_sequence(
port_name: &str,
pid: u16,
mode: ResetBeforeOperation,
) -> Vec<Box<dyn ResetStrategy>> {
if pid == USB_SERIAL_JTAG_PID || mode == ResetBeforeOperation::UsbReset {
return vec![Box::new(UsbJtagSerialReset)];
}
#[cfg(unix)]
if cfg!(unix) && !port_name.starts_with("rfc2217:") {
return vec![
Box::new(UnixTightReset::new(false)),
Box::new(UnixTightReset::new(true)),
Box::new(ClassicReset::new(false)),
Box::new(ClassicReset::new(true)),
];
}
vec![
Box::new(ClassicReset::new(false)),
Box::new(ClassicReset::new(true)),
]
}
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
#[derive(
Debug,
Default,
Clone,
Copy,
PartialEq,
Eq,
Display,
EnumIter,
EnumString,
VariantNames,
Hash,
Serialize,
Deserialize,
)]
#[non_exhaustive]
#[strum(serialize_all = "lowercase")]
pub enum ResetBeforeOperation {
#[default]
DefaultReset,
NoReset,
NoResetNoSync,
UsbReset,
}
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
#[derive(
Debug,
Default,
Clone,
Copy,
PartialEq,
Eq,
Display,
EnumIter,
EnumString,
VariantNames,
Hash,
Serialize,
Deserialize,
)]
#[non_exhaustive]
pub enum ResetAfterOperation {
#[default]
HardReset,
NoReset,
NoResetNoStub,
WatchdogReset,
}