Skip to content

Commit

Permalink
Support to reboot into UART download mode
Browse files Browse the repository at this point in the history
without any external wiring.

This patch introduces the new method
Esp.rebootIntoUartDownloadMode()

When the user calls this method the ESP8266 reboots into the UART
download mode. In this mode the user can use esptool.py to flash a new
firmware file. The following command was used to test it:
$ esptool.py --before no_reset --after soft_reset --chip esp8266 \
    --port /dev/ttyUSB0 --baud 460800 write_flash 0x0 firmware.bin

The implementation is based on the original implementation in the
boot ROM. Some parts of the original implementation can be found in
[1]. This patch is a squashed and simplified version of [2]. The non
squashed version might be helpful in case of debugging issues.

[1] https://github.com/twischer/xtensa-subjects/blob/master/reversed/bootrom.c
[2] https://github.com/twischer/Arduino/tree/reboot_uart_download_full

Signed-off-by: Timo Wischer <[email protected]>
  • Loading branch information
twischer committed Feb 16, 2021
1 parent bc3daef commit c7efc35
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 0 deletions.
10 changes: 10 additions & 0 deletions cores/esp8266/Esp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "umm_malloc/umm_malloc.h"
// #include "core_esp8266_vm.h"
#include <pgmspace.h>
#include "reboot_uart_dwnld.h"

extern "C" {
#include "user_interface.h"
Expand Down Expand Up @@ -203,6 +204,15 @@ void EspClass::restart(void)
esp_yield();
}

[[noreturn]] void EspClass::rebootIntoUartDownloadMode()
{
wdtDisable();
/* disable hardware watchdog */
CLEAR_PERI_REG_MASK(PERIPHS_HW_WDT, 0x1);

esp8266RebootIntoUartDownloadMode();
}

uint16_t EspClass::getVcc(void)
{
esp8266::InterruptLock lock;
Expand Down
6 changes: 6 additions & 0 deletions cores/esp8266/Esp.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ class EspClass {

void reset();
void restart();
/**
* @brief When calling this method the ESP8266 reboots into the UART download mode without
* the need of any external wiring. This is the same mode which can also be entered by
* pulling GPIO0=low, GPIO2=high, GPIO15=low and resetting the ESP8266.
*/
[[noreturn]] void rebootIntoUartDownloadMode();

uint16_t getVcc();
uint32_t getChipId();
Expand Down
35 changes: 35 additions & 0 deletions cores/esp8266/esp8266_undocumented.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <eagle_soc.h>
#include <spi_flash.h>

#define PERIPHS_DPORT_18 (PERIPHS_DPORT_BASEADDR + 0x018)
#define PERIPHS_DPORT_ICACHE_ENABLE (PERIPHS_DPORT_BASEADDR + 0x024)
/* When enabled 16K IRAM starting at 0x4010C000 is unmapped */
#define ICACHE_ENABLE_FIRST_16K BIT3
/* When enabled 16K IRAM starting at 0x40108000 is unmapped */
#define ICACHE_ENABLE_SECOND_16K BIT4
#define PERIPHS_HW_WDT (0x60000900)
#define PERIPHS_I2C_48 (0x60000a00 + 0x348)


extern void (*user_start_fptr)();

#ifndef XCHAL_EXCCAUSE_NUM
// from tools/xtensa-lx106-elf/include/xtensa/config/core.h:629:#define XCHAL_EXCCAUSE_NUM 64
Expand All @@ -19,6 +34,12 @@ extern int rom_i2c_readReg_Mask(int, int, int, int, int);

extern int uart_baudrate_detect(int, int);

/* SDK/Flash contains also an implementation of this function
* but for reboot into UART download mode the version from ROM
* has to be used because flash is not accessible.
*/
extern void rom_uart_div_modify(uint8 uart_no, uint32 DivLatchValue);

/*
ROM function, uart_buff_switch(), is used to switch printing between UART0 and
UART1. It updates a structure that only controls a select group of print
Expand All @@ -40,6 +61,10 @@ extern void uart_buff_switch(uint8_t);
*/
extern int ets_uart_printf(const char *format, ...) __attribute__ ((format (printf, 1, 2)));

extern void user_uart_wait_tx_fifo_empty(uint32_t ch, uint32_t arg2);
extern void uartAttach();
extern void Uart_Init(uint32_t uart_no);

extern void ets_delay_us(uint32_t us);

#ifndef GDBSTUB_H
Expand Down Expand Up @@ -206,6 +231,16 @@ extern fn_c_exception_handler_t _xtos_c_handler_table[XCHAL_EXCCAUSE_NUM];
extern fn_c_exception_handler_t _xtos_set_exception_handler(int cause, fn_c_exception_handler_t fn);
#endif

extern uint32_t Wait_SPI_Idle(SpiFlashChip *fc);
extern void Cache_Read_Disable();
extern int32_t system_func1(uint32_t);
extern void clockgate_watchdog(uint32_t);
extern void pm_open_rf();
extern void ets_install_uart_printf(uint32_t uart_no);
extern void UartDwnLdProc(uint8_t* ram_addr, uint32_t size, void (**user_start_ptr)());
extern int boot_from_flash();
extern void ets_run() __attribute__((noreturn));

#ifdef __cplusplus
};
#endif
Expand Down
151 changes: 151 additions & 0 deletions cores/esp8266/reboot_uart_dwnld.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
ESP8266-specific implementation of the UART download mode
Copyright (c) 2021 Timo Wischer <[email protected]>
All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
This implementation is based on the original implementation of the ROM.
It was shortend to reduce the memory usage. The complete version and the
development history can be found in:
https://github.com/twischer/Arduino/tree/reboot_uart_download_full
This might be usefull in case of issues.
*/
#include "reboot_uart_dwnld.h"
#include <stdnoreturn.h>
#include <user_interface.h>
#include <esp8266_undocumented.h>


static inline uint32_t __rsil_1() {
uint32_t program_state;
asm volatile("rsil %0, 1" : "=r" (program_state));
return program_state;
}

static inline void __wsr_intenable(uint32_t interupt_enable) {
asm volatile("wsr.intenable %0" :: "r" (interupt_enable));
}

static inline void __wsr_litbase(uint32_t literal_base) {
asm volatile("wsr.litbase %0" :: "r" (literal_base));
}

static inline void __wsr_ps(uint32_t program_state) {
asm volatile("wsr.ps %0" :: "r" (program_state));
}

static inline void __wsr_vecbase(uint32_t vector_base) {
asm volatile("wsr.vecbase %0" :: "r" (vector_base));
}

[[noreturn]] void ICACHE_RAM_ATTR esp8266UartDownloadMode()
{
/* reverse engineered from system_restart_core() */
/* Before disabling instruction cache and restoring instruction RAM to a
* power-on like state, SPI bus must be idle.
*/
Wait_SPI_Idle(flashchip);

Cache_Read_Disable();
/* This will disable the 32kB instruction cache and extend the IRAM by 32kB.
* Therefore the full 64kB of IRAM will be available for boot.
* Cache_Read_Enable() sets those bits but Cache_Read_Disable() does not clear
* them. On hardware reset those bits are cleared. Therefore clear them also
* for this reboot.
*/
CLEAR_PERI_REG_MASK(PERIPHS_DPORT_ICACHE_ENABLE,
ICACHE_ENABLE_FIRST_16K | ICACHE_ENABLE_SECOND_16K);

/* reverse engineered from _ResetHandler() */
/* disable all level 1 interrupts */
__wsr_intenable(0);
/* Clear the literal base to use an offset of 0 for
* Load 32-bit PC-Relative(L32R) instructions
*/
__wsr_litbase(0);
asm volatile("rsync");

/* Set interrupt vector base address to system ROM */
__wsr_vecbase(0x40000000);
/* Set interrupt level to 1. Therefore disable interrupts of level 1.
* Above levels like level 2,... might still be active if available
* on ESP8266.
*/
__rsil_1();

/* reverse engineered from _start() */
/* Set stack pointer to upper end of data RAM */
const uint32_t stack_pointer = 0x40000000;
asm volatile("mov a1, %0" :: "r" (stack_pointer));

/* Set the program state register
* Name Value Description
* Interrupt level disable 0 enable all interrupt levels
* Exception mode 0 normal operation
* User vector mode 1 user vector mode, exceptions need to switch stacks
* Privilege level 0 Set to Ring 0
*/
__wsr_ps(0x20);
asm volatile("rsync");

/* reverse engineered from main() */
const uint32_t uart_no = 0;
uartAttach();
Uart_Init(uart_no);
ets_install_uart_printf(uart_no);

/* reverse engineered from boot_from_something() */
const uint16_t divlatch = uart_baudrate_detect(uart_no, 0);
rom_uart_div_modify(uart_no, divlatch);
UartDwnLdProc((uint8_t*)0x3fffa000, 0x2000, &user_start_fptr);

/* reverse engineered from main() */
if (user_start_fptr == NULL) {
if (boot_from_flash() != 0) {
ets_printf("boot_from_flash() failed\n");
while (true);
}
}

if (user_start_fptr) {
user_start_fptr();
}

ets_printf("user code done\n");
ets_run();
}

[[noreturn]] void esp8266RebootIntoUartDownloadMode()
{
/* reverse engineered from system_restart_local() */
if (system_func1(0x4) == -1) {
clockgate_watchdog(0);
SET_PERI_REG_MASK(PERIPHS_DPORT_18, 0xffff00ff);
pm_open_rf();
}

user_uart_wait_tx_fifo_empty(0, 0x7a120);
user_uart_wait_tx_fifo_empty(1, 0x7a120);
ets_intr_lock();
SET_PERI_REG_MASK(PERIPHS_DPORT_18, 0x7500);
CLEAR_PERI_REG_MASK(PERIPHS_DPORT_18, 0x7500);
SET_PERI_REG_MASK(PERIPHS_I2C_48, 0x2);
CLEAR_PERI_REG_MASK(PERIPHS_I2C_48, 0x2);

esp8266UartDownloadMode();
}

23 changes: 23 additions & 0 deletions cores/esp8266/reboot_uart_dwnld.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
ESP8266-specific implementation of the UART download mode
Copyright (c) 2021 Timo Wischer <[email protected]>
All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdnoreturn.h>

[[noreturn]] void esp8266RebootIntoUartDownloadMode();
3 changes: 3 additions & 0 deletions tools/sdk/ld/eagle.rom.addr.v6.ld
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ PROVIDE ( aes_decrypt_init = 0x40008ea4 );
PROVIDE ( aes_unwrap = 0x40009410 );
PROVIDE ( base64_decode = 0x40009648 );
PROVIDE ( base64_encode = 0x400094fc );
PROVIDE ( boot_from_flash = 0x40001308 );
PROVIDE ( bzero = 0x4000de84 );
PROVIDE ( cmd_parse = 0x40000814 );
PROVIDE ( conv_str_decimal = 0x40000b24 );
Expand Down Expand Up @@ -278,6 +279,7 @@ PROVIDE ( rom_stop_tx_tone = 0x4000698c );
PROVIDE ( rom_tx_mac_disable = 0x40006a98 );
PROVIDE ( rom_tx_mac_enable = 0x40006ad4 );
PROVIDE ( rom_txtone_linear_pwr = 0x40006a1c );
PROVIDE ( rom_uart_div_modify = 0x400039d8 );
PROVIDE ( rom_write_rfpll_sdm = 0x400078dc );
PROVIDE ( roundup2 = 0x400031b4 );
PROVIDE ( rtc_enter_sleep = 0x40002870 );
Expand Down Expand Up @@ -353,5 +355,6 @@ PROVIDE ( Te0 = 0x3fffccf0 );
PROVIDE ( Td0 = 0x3fffd100 );
PROVIDE ( Td4s = 0x3fffd500);
PROVIDE ( rcons = 0x3fffd0f0);
PROVIDE ( user_start_fptr = 0x3fffdcd0 );
PROVIDE ( UartDev = 0x3fffde10 );
PROVIDE ( flashchip = 0x3fffc714);
Binary file modified tools/sdk/lib/NONOSDK221/libmain.a
Binary file not shown.
Binary file modified tools/sdk/lib/NONOSDK22x_190313/libmain.a
Binary file not shown.
Binary file modified tools/sdk/lib/NONOSDK22x_190703/libmain.a
Binary file not shown.
Binary file modified tools/sdk/lib/NONOSDK22x_191024/libmain.a
Binary file not shown.
Binary file modified tools/sdk/lib/NONOSDK22x_191105/libmain.a
Binary file not shown.
Binary file modified tools/sdk/lib/NONOSDK22x_191122/libmain.a
Binary file not shown.
Binary file modified tools/sdk/lib/NONOSDK3V0/libmain.a
Binary file not shown.
18 changes: 18 additions & 0 deletions tools/sdk/lib/fix_sdk_libs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
set -e

export PATH=../../xtensa-lx106-elf/bin:$PATH
VERSION=$(basename ${PWD})

addSymbol_system_func1() {
ADDRESS=$1
xtensa-lx106-elf-objcopy --add-symbol system_func1=.irom0.text:${ADDRESS},function,global user_interface.o
}


# Remove mem_manager.o from libmain.a to use custom heap implementation,
# and time.o to fix redefinition of time-related functions:
Expand All @@ -14,5 +21,16 @@ xtensa-lx106-elf-objcopy --redefine-sym hostname=wifi_station_hostname user_inte
xtensa-lx106-elf-objcopy --redefine-sym hostname=wifi_station_hostname eagle_lwip_if.o
xtensa-lx106-elf-objcopy --redefine-sym default_hostname=wifi_station_default_hostname user_interface.o
xtensa-lx106-elf-objcopy --redefine-sym default_hostname=wifi_station_default_hostname eagle_lwip_if.o

if [[ ${VERSION} == "NONOSDK221" ]]; then
addSymbol_system_func1 "0x60"
elif [[ ${VERSION} == "NONOSDK22x"* ]]; then
addSymbol_system_func1 "0x54"
elif [[ ${VERSION} == "NONOSDK3"* ]]; then
addSymbol_system_func1 "0x60"
else
echo "WARN: Unknown address for system_func1() called by system_restart_local()"
fi

xtensa-lx106-elf-ar r libmain.a eagle_lwip_if.o user_interface.o
rm -f eagle_lwip_if.o user_interface.o

0 comments on commit c7efc35

Please sign in to comment.