Reverse engineering of the ESP8266 watchdog timer

ESP8266 contains a hardware watchdog timer (WDT) module. As many parts of ESP8266, it is undocumented. While working on Mongoose OS ESP8266 port, we reverse-engineered most of the module's functionality and ROM functions. This article describes the process and the results.

ESP8266 WDT API reverse engineering

The linker script shows a number of ets_wdt_* functions provided by ROM.
Here they are, with reconstructed prototypes:

void ets_wdt_init(void);  
void ets_wdt_enable(uint32_t mode, uint32_t arg1, uint32_t arg2);  
void ets_wdt_disable(void);  
void ets_wdt_restore(uint32_t mode);  
uint32_t ets_wdt_get_mode(void);  

From disassembling the functions, we learn that ETS allows 4 different modes for watchdog (the mode argument to ets_wdt_enable()):

  1. Timer auto-feed mode. WDT is configured in single stage mode, with reload value from arg2 (a choice of 3 intervals). A software timer is setup to auto-feed the WDT at the interval passed as arg1 (in microseconds).
  2. Interrupt auto-feed mode. WDT is configured in 2-stage mode, reload value from arg1. WDT is fed automatically when stage 0 expires and generates an interrupt.
  3. Single stage mode, no auto feed. WDT is configured in single stage mode, reload value from arg2.
  4. Two-stage mode, no auto-feed. WDT is configured for 2-stage operation, both intervals from arg2.

Interval takes 3 possible values:

  • 3 - roughly 1.67 sec per stage
  • 6 - about 3.35 sec per stage
  • 12 - about 6.71 sec per stage

In interrupt mode, WDT asserts interrupt number 8, it is possible to install custom handler with ets_isr_attach().

ROM does not provide a function to feed WDT, but invoking ets_wdt_restore(desired_mode) will re-configure WDT with previously saved interval and restart it.

ESP8266 WDT registers

Note that ets_* functions cannot be used on RTOS SDK. Partly because of that, partly because of curiosity, we continued to study WDT registers. Our starting point is this short note on the ESP8266 RE wiki: it shows the base address and a few register names, gathered from one of the headers released by Espressif at one point with their MP3 decoder project.

From disassembling the ets_wdt functions, we learn the following:

  • Bit 0 of the control register is obviously the enable/disable bit. All changes are made with WDT_CNTL.0 = 0 and all the code paths of ets_wdt_enable() end with this bit set to 1.
  • Modes 1 and 3 write value 0x3c to the control register, modes 2 and 4 write 0x38. This suggests that bit2 selects 1 or 2 stage mode operation.
  • Interval values 3, 6, 12 are translated into 11, 12 and 13 and written to REG1 and, for modes 2 and 4, REG2 as well.
  • ets_wdt_init sets the ISR handler function and enables WDT edge interrupt in DPort register 4.
  • Feeding is performed by writing 0x73 to 0x60000914 (and 0x60000918 in mode 2)

With some experimentation we can add more information about the registers, giving them better names:

  • WDT_CNTL (0x60000900) - the control register.

    • Bit 0 - WDT enable/disable. Setting this bit to 1 starts the WDT. Stage is reset to 0 and counter is reloaded from stage 0 reload value.
    • Bit 1 - this one is tricky. In single stage mode, it has no effect, but in two-stage mode, this is what happens when this bit is set to 1:

      • During the first stage 0 -> stage 1 transition reload value is taken from stage 0 register but WDT_STAGE still advances to 1.
      • No reset happens at the end of stage 1. Instead, a reload from stage 0 reload register is performed and the count resumes. No interrupt is generated. Thus, setting bit 1 turns WDT into more or less normal timer counter (no reset), except that there are no interrupts after the first one. Weird.
    • Bit 2 - chooses between 1 and 2 stage operation. 0 = single stage, 1 = 2 stages. In single stage mode, reset is performed at the end of stage 0, in 2-stage mode reaching the end of stage 0 generates an interrupt and reloads counter sccording to the value specified in the stage 1 reload register.
    • Bits 3, 4, 5 - function is still unknown. ROM code sets them all to 1 but there is no apparent effect on operation. When any combination of them is set to 0.
  • WDT_STAGE0_RELOAD (0x60000904) - stage 0 reload configuration. Specifies reload value for stage 0. The value is specified as a 4-bit value as follows: 15 -> 0x80000000
    14 -> 0x40000000
    … and so on, each step reducing reload value by factor of 2 10 -> 0x04000000
  • WDT_STAGE1_RELOAD (0x60000908) - stage 1 reload value, specified the same way as stage 0.
  • WDT_COUNTER (0x6000090c) - current value of the counter. The counter runs at CPU_CLK (80 MHz).
  • WDT_STAGE (0x60000910) - indicates current stage the WDT is in, either 0 or 1
  • WDT_RESET (0x60000914) - Writing the magic value of 0x73 to this register resets the WDT to the beginning of stage 0.
  • WDT_RESET_STAGE (0x60000918) - Writing the same magic value (0x73) to this register resets only the stage register. Writing to this register does not reset the counter and when it reaches 0, stage 0 to stage 1 transition is performed as normal.

Header and source files

And here is the result:

Download and install Mongoose OS from here.

Have a question? Ask on our developer forum or send us a message.