Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add watchdog reset experiment #779

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add an environment variable to set monitoring baudrate (`MONITOR_BAUD`) (#737)
- Add list-ports command to list available serial ports. (#761)
- [cargo-espflash]: Add `write-bin` subcommand (#789)
- Add `watchdog-reset` strategy to `--after` subcommand (#779)

### Changed

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion cargo-espflash/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> {
.or(config.partition_table.as_deref());

let mut flasher = connect(&args.connect_args, config, false, false)?;
let chip = flasher.chip();
let partition_table = match partition_table {
Some(path) => Some(parse_partition_table(path)?),
None => None,
Expand All @@ -274,7 +275,7 @@ pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> {
erase_partitions(&mut flasher, partition_table, Some(args.erase_parts), None)?;
flasher
.connection()
.reset_after(!args.connect_args.no_stub)?;
.reset_after(!args.connect_args.no_stub, chip)?;

Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions espflash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ required-features = ["cli", "serialport"]
[dependencies]
addr2line = { version = "0.24.2", optional = true }
base64 = "0.22.1"
bitflags = "2.4"
bytemuck = { version = "1.21.0", features = ["derive"] }
clap = { version = "4.5.24", features = ["derive", "env", "wrap_help"], optional = true }
clap_complete = { version = "4.5.41", optional = true }
Expand Down
3 changes: 2 additions & 1 deletion espflash/src/bin/espflash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> {
}

let mut flasher = connect(&args.connect_args, config, false, false)?;
let chip = flasher.chip();
let partition_table = match args.partition_table {
Some(path) => Some(parse_partition_table(&path)?),
None => None,
Expand All @@ -193,7 +194,7 @@ pub fn erase_parts(args: ErasePartsArgs, config: &Config) -> Result<()> {
erase_partitions(&mut flasher, partition_table, Some(args.erase_parts), None)?;
flasher
.connection()
.reset_after(!args.connect_args.no_stub)?;
.reset_after(!args.connect_args.no_stub, chip)?;

info!("Specified partitions successfully erased!");

Expand Down
7 changes: 5 additions & 2 deletions espflash/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,10 +718,12 @@ pub fn erase_flash(args: EraseFlashArgs, config: &Config) -> Result<()> {
let mut flasher = connect(&args.connect_args, config, true, true)?;
info!("Erasing Flash...");

let chip = flasher.chip();

flasher.erase_flash()?;
flasher
.connection()
.reset_after(!args.connect_args.no_stub)?;
.reset_after(!args.connect_args.no_stub, chip)?;

info!("Flash has been erased!");

Expand All @@ -742,6 +744,7 @@ pub fn erase_region(args: EraseRegionArgs, config: &Config) -> Result<()> {
}

let mut flasher = connect(&args.connect_args, config, true, true)?;
let chip = flasher.chip();

info!(
"Erasing region at 0x{:08x} ({} bytes)",
Expand All @@ -751,7 +754,7 @@ pub fn erase_region(args: EraseRegionArgs, config: &Config) -> Result<()> {
flasher.erase_region(args.address, args.size)?;
flasher
.connection()
.reset_after(!args.connect_args.no_stub)?;
.reset_after(!args.connect_args.no_stub, chip)?;

Ok(())
}
Expand Down
67 changes: 65 additions & 2 deletions espflash/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::{

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

Expand All @@ -33,7 +34,10 @@ use self::{
UsbJtagSerialReset,
},
};
use crate::error::{ConnectionError, Error, ResultExt, RomError, RomErrorKind};
use crate::{
error::{ConnectionError, Error, ResultExt, RomError, RomErrorKind},
targets::{esp32p4, esp32s2, esp32s3, Chip},
};

pub(crate) mod command;
pub(crate) mod reset;
Expand Down Expand Up @@ -274,7 +278,7 @@ impl Connection {
}

// Reset the device taking into account the reset after argument
pub fn reset_after(&mut self, is_stub: bool) -> Result<(), Error> {
pub fn reset_after(&mut self, is_stub: bool, chip: Chip) -> Result<(), Error> {
let pid = self.usb_pid();

match self.after_operation {
Expand All @@ -289,6 +293,52 @@ impl Connection {
info!("Staying in flasher stub");
Ok(())
}
ResetAfterOperation::WatchdogReset => {
info!("Resetting device with watchdog");

match chip {
Chip::Esp32c3 => {
if pid == USB_SERIAL_JTAG_PID {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#795 introduced a is_using_usb_serial_jtag method for this

wdt_reset(chip, self)?;
}
}
Chip::Esp32p4 => {
// Check if the connection is USB OTG
if self.is_using_usb_otg(chip)? {
wdt_reset(chip, self)?;
}
}
Chip::Esp32s2 => {
let esp32s2 = esp32s2::Esp32s2;
// Check if the connection is USB OTG
if self.is_using_usb_otg(chip)? {
// Check the strapping register to see if we can perform RTC WDT
// reset
if esp32s2.can_wtd_reset(self)? {
wdt_reset(chip, self)?;
}
}
}
Chip::Esp32s3 => {
let esp32s3 = esp32s3::Esp32s3;
if pid == USB_SERIAL_JTAG_PID || self.is_using_usb_otg(chip)? {
// Check the strapping register to see if we can perform RTC WDT
// reset
if esp32s3.can_wtd_reset(self)? {
wdt_reset(chip, self)?;
}
}
}
_ => {
return Err(Error::UnsupportedFeature {
chip,
feature: "watchdog reset".into(),
})
}
}

Ok(())
}
}
}

Expand Down Expand Up @@ -522,6 +572,19 @@ impl Connection {
pub(crate) fn is_using_usb_serial_jtag(&self) -> bool {
self.port_info.pid == USB_SERIAL_JTAG_PID
}

#[cfg(feature = "serialport")]
/// Check if the connection is USB OTG
pub(crate) fn is_using_usb_otg(&mut self, chip: Chip) -> Result<bool, Error> {
let (buf_no, no_usb_otg) = match chip {
Chip::Esp32p4 => (esp32p4::UARTDEV_BUF_NO, esp32p4::UARTDEV_BUF_NO_USB_OTG),
Chip::Esp32s2 => (esp32s2::UARTDEV_BUF_NO, esp32s2::UARTDEV_BUF_NO_USB_OTG),
Chip::Esp32s3 => (esp32s3::UARTDEV_BUF_NO, esp32s3::UARTDEV_BUF_NO_USB_OTG),
_ => unreachable!(),
};

Ok(self.read_reg(buf_no)? == no_usb_otg)
}
}

mod encoder {
Expand Down
50 changes: 49 additions & 1 deletion espflash/src/connection/reset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use std::{io, os::fd::AsRawFd};
use std::{thread::sleep, time::Duration};

use bitflags::bitflags;
#[cfg(unix)]
use libc::ioctl;
use log::debug;
Expand All @@ -17,7 +18,7 @@ use super::{
Port,
USB_SERIAL_JTAG_PID,
};
use crate::{flasher::FLASH_WRITE_SIZE, Error};
use crate::{flasher::FLASH_WRITE_SIZE, targets::Chip, Error};

/// Default time to wait before releasing the boot pin after a reset
const DEFAULT_RESET_DELAY: u64 = 50; // ms
Expand Down Expand Up @@ -199,6 +200,14 @@ impl ResetStrategy for UsbJtagSerialReset {
}
}

#[cfg(feature = "serialport")]
pub(crate) trait RtcWdtReset {
fn wdt_wprotect(&self) -> u32;
fn wdt_wkey(&self) -> u32;
fn wdt_config0(&self) -> u32;
fn wdt_config1(&self) -> u32;
}

/// Reset the target device
pub fn reset_after_flash(serial: &mut Port, pid: u16) -> Result<(), serialport::Error> {
sleep(Duration::from_millis(100));
Expand Down Expand Up @@ -294,6 +303,43 @@ pub fn soft_reset(
Ok(())
}

bitflags! {
struct WdtConfig0Flags: u32 {
const EN = 1 << 31;
const STAGE0 = 5 << 28; // 5 (binary: 101) in bits 28-30
const CHIP_RESET_EN = 1 << 8; // 8th bit
const CHIP_RESET_WIDTH = 1 << 2; // 1st bit
}
}

/// Perform Watchdog reset
pub fn wdt_reset(chip: Chip, connection: &mut Connection) -> Result<(), Error> {
debug!("Resetting with RTC WDT");

let chip: Box<dyn RtcWdtReset> = match chip {
Chip::Esp32c3 => Box::new(crate::targets::esp32c3::Esp32c3),
Chip::Esp32s2 => Box::new(crate::targets::esp32s2::Esp32s2),
Chip::Esp32s3 => Box::new(crate::targets::esp32s3::Esp32s3),
_ => unreachable!(),
};

connection.write_reg(chip.wdt_wprotect(), chip.wdt_wkey(), None)?;
connection.write_reg(chip.wdt_config1(), 2000, None)?;
connection.write_reg(
chip.wdt_config0(),
(WdtConfig0Flags::EN.bits()) // enable RTC watchdog
| (WdtConfig0Flags::STAGE0.bits()) // enable at the interrupt/system and RTC stage
| (WdtConfig0Flags::CHIP_RESET_EN.bits()) // enable chip reset
| WdtConfig0Flags::CHIP_RESET_WIDTH.bits(), // set chip reset width
None,
)?;
connection.write_reg(chip.wdt_wprotect(), 0, None)?;

sleep(Duration::from_millis(50));

Ok(())
}

/// Construct a sequence of reset strategies based on the OS and chip.
///
/// Returns a [Vec] containing one or more reset strategies to be attempted
Expand Down Expand Up @@ -362,4 +408,6 @@ pub enum ResetAfterOperation {
NoReset,
/// Leaves the chip in the stub bootloader, no reset is performed.
NoResetNoStub,
/// Hard-resets the chip by triggering an internal watchdog reset.
WatchdogReset,
}
18 changes: 17 additions & 1 deletion espflash/src/targets/esp32c3.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::ops::Range;

#[cfg(feature = "serialport")]
use crate::connection::Connection;
use crate::connection::{reset::RtcWdtReset, Connection};
use crate::{
elf::FirmwareImage,
flasher::{FlashData, FlashFrequency},
Expand Down Expand Up @@ -123,3 +123,19 @@ impl Target for Esp32c3 {
]
}
}

#[cfg(feature = "serialport")]
impl RtcWdtReset for Esp32c3 {
fn wdt_wprotect(&self) -> u32 {
0x6000_8000 + 0x00A8
}
fn wdt_wkey(&self) -> u32 {
0x50D8_3AA1
}
fn wdt_config0(&self) -> u32 {
0x6000_8000 + 0x0090
}
fn wdt_config1(&self) -> u32 {
0x6000_8000 + 0x0094
}
}
24 changes: 23 additions & 1 deletion espflash/src/targets/esp32p4.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::ops::Range;

#[cfg(feature = "serialport")]
use crate::connection::Connection;
use crate::connection::{reset::RtcWdtReset, Connection};
use crate::{
elf::FirmwareImage,
flasher::{FlashData, FlashFrequency},
Expand All @@ -26,10 +26,16 @@ const PARAMS: Esp32Params = Esp32Params::new(
include_bytes!("../../resources/bootloaders/esp32p4-bootloader.bin"),
);

#[cfg(feature = "serialport")]
pub(crate) const UARTDEV_BUF_NO: u32 = 0x4FF3_FEC8; // Address which indicates OTG in use
#[cfg(feature = "serialport")]
pub(crate) const UARTDEV_BUF_NO_USB_OTG: u32 = 5; // Value of UARTDEV_BUF_NO when OTG is in use

/// ESP32-P4 Target
pub struct Esp32p4;

impl Esp32p4 {
/// Check if the magic value contains the specified value
pub fn has_magic_value(value: u32) -> bool {
CHIP_DETECT_MAGIC_VALUES.contains(&value)
}
Expand Down Expand Up @@ -110,3 +116,19 @@ impl Target for Esp32p4 {
&["riscv32imafc-esp-espidf", "riscv32imafc-unknown-none-elf"]
}
}

#[cfg(feature = "serialport")]
impl RtcWdtReset for crate::targets::esp32p4::Esp32p4 {
fn wdt_wprotect(&self) -> u32 {
0x5011_6000 + 0x0018
}
fn wdt_wkey(&self) -> u32 {
0x50D8_3AA1
}
fn wdt_config0(&self) -> u32 {
0x5011_6000 // no offset here
}
fn wdt_config1(&self) -> u32 {
0x5011_6000 + 0x0004
}
}
Loading
Loading