From 8298cdcc68a8063df1ca932d42e21555e15269b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radim=20Karni=C5=A1?= Date: Fri, 11 Oct 2024 13:26:35 +0200 Subject: [PATCH] fix(usb_resets): Fix resetting in USB-OTG and USB-Serial/JTAG modes Closes https://github.com/espressif/esptool/issues/970 --- .gitlab-ci.yml | 4 ++ docs/en/esptool/advanced-options.rst | 4 +- esptool/__init__.py | 1 + esptool/targets/esp32c2.py | 11 +++- esptool/targets/esp32c3.py | 18 ++++++- esptool/targets/esp32c6.py | 3 +- esptool/targets/esp32h2.py | 7 ++- esptool/targets/esp32p4.py | 72 ++++++++++++++++++++++++-- esptool/targets/esp32s2.py | 50 +++++++++--------- esptool/targets/esp32s3.py | 50 ++++++++---------- test/test_esptool.py | 76 ++++++++++++++++++++++++---- 11 files changed, 222 insertions(+), 74 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c322fa31f8..2ba1d8b794 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -297,6 +297,8 @@ target_esp32s2_usbcdc: extends: .target_esptool_test tags: - esptool_esp32s2_cdc_target + variables: + ESPTOOL_TEST_USB_OTG: "1" script: - coverage run --parallel-mode -m pytest ${CI_PROJECT_DIR}/test/test_esptool.py --port /dev/serial_ports/ESP32S2_USBCDC --chip esp32s2 --baud 115200 @@ -347,6 +349,8 @@ target_esp32s3_usbcdc: extends: .target_esptool_test tags: - esptool_esp32s3_cdc_target + variables: + ESPTOOL_TEST_USB_OTG: "1" script: - coverage run --parallel-mode -m pytest ${CI_PROJECT_DIR}/test/test_esptool.py --port /dev/serial_ports/ESP32S3_USBCDC --chip esp32s3 --baud 115200 diff --git a/docs/en/esptool/advanced-options.rst b/docs/en/esptool/advanced-options.rst index a5e00c7956..43ef41d4a5 100644 --- a/docs/en/esptool/advanced-options.rst +++ b/docs/en/esptool/advanced-options.rst @@ -31,8 +31,8 @@ The ``--after`` argument allows you to specify whether the chip should be reset .. list:: - * ``--after hard_reset`` is the default. The DTR serial control line is used to reset the chip into a normal boot sequence. - :esp8266:* ``--after soft_reset`` This runs the user firmware, but any subsequent reset will return to the serial bootloader. This was the reset behaviour in esptool v1.x. + * ``--after hard_reset`` is the default. The RTS serial control line is used to reset the chip into a normal boot sequence. + :esp8266: * ``--after soft_reset`` runs the user firmware, but any subsequent reset will return to the serial bootloader. This was the reset behaviour in esptool v1.x. * ``--after no_reset`` leaves the chip in the serial bootloader, no reset is performed. * ``--after no_reset_stub`` leaves the chip in the stub bootloader, no reset is performed. diff --git a/esptool/__init__.py b/esptool/__init__.py index 7f919bc3f4..2d94e9d967 100644 --- a/esptool/__init__.py +++ b/esptool/__init__.py @@ -18,6 +18,7 @@ "merge_bin", "read_flash", "read_flash_status", + "read_flash_sfdp", "read_mac", "read_mem", "run", diff --git a/esptool/targets/esp32c2.py b/esptool/targets/esp32c2.py index 100e55e395..45e6dd5afd 100644 --- a/esptool/targets/esp32c2.py +++ b/esptool/targets/esp32c2.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# SPDX-FileCopyrightText: 2014-2024 Fredrik Ahlberg, Angus Gratton, # Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -63,6 +63,12 @@ class ESP32C2ROM(ESP32C3ROM): [0x4037C000, 0x403C0000, "IRAM"], ] + RTCCNTL_BASE_REG = 0x60008000 + RTC_CNTL_WDTCONFIG0_REG = RTCCNTL_BASE_REG + 0x0084 + RTC_CNTL_WDTCONFIG1_REG = RTCCNTL_BASE_REG + 0x0088 + RTC_CNTL_WDTWPROTECT_REG = RTCCNTL_BASE_REG + 0x009C + RTC_CNTL_WDT_WKEY = 0x50D83AA1 + UF2_FAMILY_ID = 0x2B88D29C KEY_PURPOSES: Dict[int, str] = {} @@ -130,6 +136,9 @@ def _post_connect(self): self.stub_is_disabled = True self.IS_STUB = False + def hard_reset(self): + ESPLoader.hard_reset(self) + """ Try to read (encryption key) and check if it is valid """ def is_flash_encryption_key_valid(self): diff --git a/esptool/targets/esp32c3.py b/esptool/targets/esp32c3.py index beb88656cb..8abdae8668 100644 --- a/esptool/targets/esp32c3.py +++ b/esptool/targets/esp32c3.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# SPDX-FileCopyrightText: 2014-2024 Fredrik Ahlberg, Angus Gratton, # Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -83,6 +83,7 @@ class ESP32C3ROM(ESP32ROM): RTC_CNTL_SWD_WKEY = 0x8F1D312A RTC_CNTL_WDTCONFIG0_REG = RTCCNTL_BASE_REG + 0x0090 + RTC_CNTL_WDTCONFIG1_REG = RTCCNTL_BASE_REG + 0x0094 RTC_CNTL_WDTWPROTECT_REG = RTCCNTL_BASE_REG + 0x00A8 RTC_CNTL_WDT_WKEY = 0x50D83AA1 @@ -252,6 +253,21 @@ def _post_connect(self): if not self.sync_stub_detected: # Don't run if stub is reused self.disable_watchdogs() + def hard_reset(self): + if self.uses_usb_jtag_serial(): + self.rtc_wdt_reset() + else: + ESPLoader.hard_reset(self) + + def rtc_wdt_reset(self): + print("Hard resetting with RTC WDT...") + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) # unlock + self.write_reg(self.RTC_CNTL_WDTCONFIG1_REG, 5000) # set WDT timeout + self.write_reg( + self.RTC_CNTL_WDTCONFIG0_REG, (1 << 31) | (5 << 28) | (1 << 8) | 2 + ) # enable WDT + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) # lock + def check_spi_connection(self, spi_connection): if not set(spi_connection).issubset(set(range(0, 22))): raise FatalError("SPI Pin numbers must be in the range 0-21.") diff --git a/esptool/targets/esp32c6.py b/esptool/targets/esp32c6.py index c78e1f72a4..b4cb5d829a 100644 --- a/esptool/targets/esp32c6.py +++ b/esptool/targets/esp32c6.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Fredrik Ahlberg, Angus Gratton, +# SPDX-FileCopyrightText: 2024 Fredrik Ahlberg, Angus Gratton, # Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -72,6 +72,7 @@ class ESP32C6ROM(ESP32C3ROM): DR_REG_LP_WDT_BASE = 0x600B1C00 RTC_CNTL_WDTCONFIG0_REG = DR_REG_LP_WDT_BASE + 0x0 # LP_WDT_RWDT_CONFIG0_REG + RTC_CNTL_WDTCONFIG1_REG = DR_REG_LP_WDT_BASE + 0x0004 # LP_WDT_RWDT_CONFIG1_REG RTC_CNTL_WDTWPROTECT_REG = DR_REG_LP_WDT_BASE + 0x0018 # LP_WDT_RWDT_WPROTECT_REG RTC_CNTL_SWD_CONF_REG = DR_REG_LP_WDT_BASE + 0x001C # LP_WDT_SWD_CONFIG_REG diff --git a/esptool/targets/esp32h2.py b/esptool/targets/esp32h2.py index da7d34a589..59604f2d62 100644 --- a/esptool/targets/esp32h2.py +++ b/esptool/targets/esp32h2.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Fredrik Ahlberg, Angus Gratton, +# SPDX-FileCopyrightText: 2024 Fredrik Ahlberg, Angus Gratton, # Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -6,6 +6,7 @@ from typing import Dict from .esp32c6 import ESP32C6ROM +from ..loader import ESPLoader from ..util import FatalError @@ -18,6 +19,7 @@ class ESP32H2ROM(ESP32C6ROM): DR_REG_LP_WDT_BASE = 0x600B1C00 RTC_CNTL_WDTCONFIG0_REG = DR_REG_LP_WDT_BASE + 0x0 # LP_WDT_RWDT_CONFIG0_REG + RTC_CNTL_WDTCONFIG1_REG = DR_REG_LP_WDT_BASE + 0x0004 # LP_WDT_RWDT_CONFIG1_REG RTC_CNTL_WDTWPROTECT_REG = DR_REG_LP_WDT_BASE + 0x001C # LP_WDT_RWDT_WPROTECT_REG RTC_CNTL_SWD_CONF_REG = DR_REG_LP_WDT_BASE + 0x0020 # LP_WDT_SWD_CONFIG_REG @@ -77,6 +79,9 @@ def get_crystal_freq(self): # ESP32H2 XTAL is fixed to 32MHz return 32 + def hard_reset(self): + ESPLoader.hard_reset(self) + def check_spi_connection(self, spi_connection): if not set(spi_connection).issubset(set(range(0, 28))): raise FatalError("SPI Pin numbers must be in the range 0-27.") diff --git a/esptool/targets/esp32p4.py b/esptool/targets/esp32p4.py index 2b0a06498b..d61108c721 100644 --- a/esptool/targets/esp32p4.py +++ b/esptool/targets/esp32p4.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023 Fredrik Ahlberg, Angus Gratton, +# SPDX-FileCopyrightText: 2024 Fredrik Ahlberg, Angus Gratton, # Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -72,6 +72,10 @@ class ESP32P4ROM(ESP32ROM): FLASH_ENCRYPTED_WRITE_ALIGN = 16 + UARTDEV_BUF_NO = 0x4FF3FEC8 # Variable in ROM .bss which indicates the port in use + UARTDEV_BUF_NO_USB_OTG = 5 # The above var when USB-OTG is used + UARTDEV_BUF_NO_USB_JTAG_SERIAL = 6 # The above var when USB-JTAG/Serial is used + MEMORY_MAP = [ [0x00000000, 0x00010000, "PADDING"], [0x40000000, 0x4C000000, "DROM"], @@ -105,6 +109,17 @@ class ESP32P4ROM(ESP32ROM): 12: "KM_INIT_KEY", } + DR_REG_LP_WDT_BASE = 0x50116000 + RTC_CNTL_WDTCONFIG0_REG = DR_REG_LP_WDT_BASE + 0x0 # LP_WDT_CONFIG0_REG + RTC_CNTL_WDTCONFIG1_REG = DR_REG_LP_WDT_BASE + 0x0004 # LP_WDT_CONFIG1_REG + RTC_CNTL_WDTWPROTECT_REG = DR_REG_LP_WDT_BASE + 0x0018 # LP_WDT_WPROTECT_REG + RTC_CNTL_WDT_WKEY = 0x50D83AA1 + + RTC_CNTL_SWD_CONF_REG = DR_REG_LP_WDT_BASE + 0x001C # RTC_WDT_SWD_CONFIG_REG + RTC_CNTL_SWD_AUTO_FEED_EN = 1 << 18 + RTC_CNTL_SWD_WPROTECT_REG = DR_REG_LP_WDT_BASE + 0x0020 # RTC_WDT_SWD_WPROTECT_REG + RTC_CNTL_SWD_WKEY = 0x50D83AA1 # RTC_WDT_SWD_WKEY, same as WDT key in this case + def get_pkg_version(self): num_word = 2 return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 20) & 0x07 @@ -191,10 +206,42 @@ def change_baud(self, baud): ESPLoader.change_baud(self, baud) def _post_connect(self): - pass - # TODO: Disable watchdogs when USB modes are supported in the stub - # if not self.sync_stub_detected: # Don't run if stub is reused - # self.disable_watchdogs() + if not self.sync_stub_detected: # Don't run if stub is reused + self.disable_watchdogs() + + def uses_usb_otg(self): + """ + Check the UARTDEV_BUF_NO register to see if USB-OTG console is being used + """ + if self.secure_download_mode: + return False # can't detect native USB in secure download mode + return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_OTG + + def uses_usb_jtag_serial(self): + """ + Check the UARTDEV_BUF_NO register to see if USB-JTAG/Serial is being used + """ + if self.secure_download_mode: + return False # can't detect USB-JTAG/Serial in secure download mode + return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_JTAG_SERIAL + + def disable_watchdogs(self): + # When USB-JTAG/Serial is used, the RTC WDT and SWD watchdog are not reset + # and can then reset the board during flashing. Disable them. + if self.uses_usb_jtag_serial(): + # Disable RTC WDT + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_SWD_WKEY) + self.write_reg(self.RTC_CNTL_WDTCONFIG0_REG, 0) + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) + + # Automatically feed SWD + self.write_reg(self.RTC_CNTL_SWD_WPROTECT_REG, self.RTC_CNTL_SWD_WKEY) + self.write_reg( + self.RTC_CNTL_SWD_CONF_REG, + self.read_reg(self.RTC_CNTL_SWD_CONF_REG) + | self.RTC_CNTL_SWD_AUTO_FEED_EN, + ) + self.write_reg(self.RTC_CNTL_SWD_WPROTECT_REG, 0) def check_spi_connection(self, spi_connection): if not set(spi_connection).issubset(set(range(0, 55))): @@ -205,6 +252,21 @@ def check_spi_connection(self, spi_connection): "consider using other pins for SPI flash connection." ) + def rtc_wdt_reset(self): + print("Hard resetting with RTC WDT...") + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) # unlock + self.write_reg(self.RTC_CNTL_WDTCONFIG1_REG, 5000) # set WDT timeout + self.write_reg( + self.RTC_CNTL_WDTCONFIG0_REG, (1 << 31) | (5 << 28) | (1 << 8) | 2 + ) # enable WDT + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) # lock + + def hard_reset(self): + if self.uses_usb_jtag_serial(): + self.rtc_wdt_reset() + else: + ESPLoader.hard_reset(self) + class ESP32P4StubLoader(ESP32P4ROM): """Access class for ESP32P4 stub loader, runs on top of ROM. diff --git a/esptool/targets/esp32s2.py b/esptool/targets/esp32s2.py index a4509b52f4..9a83791149 100644 --- a/esptool/targets/esp32s2.py +++ b/esptool/targets/esp32s2.py @@ -1,9 +1,8 @@ -# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton, +# SPDX-FileCopyrightText: 2014-2024 Fredrik Ahlberg, Angus Gratton, # Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later -import os import struct from typing import Dict @@ -82,11 +81,17 @@ class ESP32S2ROM(ESP32ROM): USB_RAM_BLOCK = 0x800 # Max block size USB-OTG is used GPIO_STRAP_REG = 0x3F404038 - GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode + GPIO_STRAP_SPI_BOOT_MASK = 1 << 3 # Not download mode GPIO_STRAP_VDDSPI_MASK = 1 << 4 RTC_CNTL_OPTION1_REG = 0x3F408128 RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB? + RTCCNTL_BASE_REG = 0x3F408000 + RTC_CNTL_WDTCONFIG0_REG = RTCCNTL_BASE_REG + 0x0094 + RTC_CNTL_WDTCONFIG1_REG = RTCCNTL_BASE_REG + 0x0098 + RTC_CNTL_WDTWPROTECT_REG = RTCCNTL_BASE_REG + 0x00AC + RTC_CNTL_WDT_WKEY = 0x50D83AA1 + MEMORY_MAP = [ [0x00000000, 0x00010000, "PADDING"], [0x3F000000, 0x3FF80000, "DROM"], @@ -282,32 +287,27 @@ def _post_connect(self): if self.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK - def _check_if_can_reset(self): - """ - Check the strapping register to see if we can reset out of download mode. - """ - if os.getenv("ESPTOOL_TESTING") is not None: - print("ESPTOOL_TESTING is set, ignoring strapping mode check") - # Esptool tests over USB-OTG run with GPIO0 strapped low, - # don't complain in this case. - return - strap_reg = self.read_reg(self.GPIO_STRAP_REG) - force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG) - if ( - strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 - and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0 - ): - raise SystemExit( - f"Error: {self.get_chip_description()} chip was placed into download " - "mode using GPIO0.\nesptool.py can not exit the download mode over " - "USB. To run the app, reset the chip manually.\n" - "To suppress this note, set --after option to 'no_reset'." - ) + def rtc_wdt_reset(self): + print("Hard resetting with RTC WDT...") + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) # unlock + self.write_reg(self.RTC_CNTL_WDTCONFIG1_REG, 5000) # set WDT timeout + self.write_reg( + self.RTC_CNTL_WDTCONFIG0_REG, (1 << 31) | (5 << 28) | (1 << 8) | 2 + ) # enable WDT + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) # lock def hard_reset(self): uses_usb_otg = self.uses_usb_otg() if uses_usb_otg: - self._check_if_can_reset() + # Check the strapping register to see if we can perform RTC WDT reset + strap_reg = self.read_reg(self.GPIO_STRAP_REG) + force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG) + if ( + strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 # GPIO0 low + and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0 + ): + self.rtc_wdt_reset() + return ESPLoader.hard_reset(self, uses_usb_otg) diff --git a/esptool/targets/esp32s3.py b/esptool/targets/esp32s3.py index 581128df7c..90cd63707f 100644 --- a/esptool/targets/esp32s3.py +++ b/esptool/targets/esp32s3.py @@ -1,9 +1,8 @@ -# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton, +# SPDX-FileCopyrightText: 2014-2024 Fredrik Ahlberg, Angus Gratton, # Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later -import os import struct from typing import Dict @@ -90,13 +89,14 @@ class ESP32S3ROM(ESP32ROM): RTC_CNTL_SWD_WKEY = 0x8F1D312A RTC_CNTL_WDTCONFIG0_REG = RTCCNTL_BASE_REG + 0x0098 + RTC_CNTL_WDTCONFIG1_REG = RTCCNTL_BASE_REG + 0x009C RTC_CNTL_WDTWPROTECT_REG = RTCCNTL_BASE_REG + 0x00B0 RTC_CNTL_WDT_WKEY = 0x50D83AA1 USB_RAM_BLOCK = 0x800 # Max block size USB-OTG is used GPIO_STRAP_REG = 0x60004038 - GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode + GPIO_STRAP_SPI_BOOT_MASK = 1 << 3 # Not download mode GPIO_STRAP_VDDSPI_MASK = 1 << 4 RTC_CNTL_OPTION1_REG = 0x6000812C RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB? @@ -344,33 +344,16 @@ def _post_connect(self): if not self.sync_stub_detected: # Don't run if stub is reused self.disable_watchdogs() - def _check_if_can_reset(self): - """ - Check the strapping register to see if we can reset out of download mode. - """ - if os.getenv("ESPTOOL_TESTING") is not None: - print("ESPTOOL_TESTING is set, ignoring strapping mode check") - # Esptool tests over USB-OTG run with GPIO0 strapped low, - # don't complain in this case. - return - strap_reg = self.read_reg(self.GPIO_STRAP_REG) - force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG) - if ( - strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 - and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0 - ): - raise SystemExit( - f"Error: {self.get_chip_description()} chip was placed into download " - "mode using GPIO0.\nesptool.py can not exit the download mode over " - "USB. To run the app, reset the chip manually.\n" - "To suppress this note, set --after option to 'no_reset'." - ) + def rtc_wdt_reset(self): + print("Hard resetting with RTC WDT...") + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) # unlock + self.write_reg(self.RTC_CNTL_WDTCONFIG1_REG, 5000) # set WDT timeout + self.write_reg( + self.RTC_CNTL_WDTCONFIG0_REG, (1 << 31) | (5 << 28) | (1 << 8) | 2 + ) # enable WDT + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) # lock def hard_reset(self): - uses_usb_otg = self.uses_usb_otg() - if uses_usb_otg: - self._check_if_can_reset() - try: # Clear force download boot mode to avoid the chip being stuck in download mode after reset # workaround for issue: https://github.com/espressif/arduino-esp32/issues/6762 @@ -380,6 +363,17 @@ def hard_reset(self): except Exception: # Skip if response was not valid and proceed to reset; e.g. when monitoring while resetting pass + uses_usb_otg = self.uses_usb_otg() + if uses_usb_otg or self.uses_usb_jtag_serial(): + # Check the strapping register to see if we can perform RTC WDT reset + strap_reg = self.read_reg(self.GPIO_STRAP_REG) + force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG) + if ( + strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 # GPIO0 low + and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0 + ): + self.rtc_wdt_reset() + return ESPLoader.hard_reset(self, uses_usb_otg) diff --git a/test/test_esptool.py b/test/test_esptool.py index 5fc1fb58b9..240c178e1d 100755 --- a/test/test_esptool.py +++ b/test/test_esptool.py @@ -12,6 +12,13 @@ # - --chip - ESP chip name # - --baud - baud rate # - --with-trace - trace all interactions (True or False) +# +# To run the tests in USB-OTG mode, ground the boot mode straping pin +# and set ESPTOOL_TEST_USB_OTG environment variable to any value. +# +# To run the tests in USB-Serial/JTAG mode, set both --port and --preload-port +# options. The --preload-port needs to be connected to a USB-to-UART bridge, +# while --port needs to be connected to the USB-Serial/JTAG peripheral. import os import os.path @@ -21,7 +28,6 @@ import subprocess import sys import tempfile -import time from socket import AF_INET, SOCK_STREAM, socket from time import sleep from typing import List @@ -51,9 +57,6 @@ TEST_DIR = os.path.abspath(os.path.dirname(__file__)) -# esptool.py skips strapping mode check in USB-CDC case if this is set -os.environ["ESPTOOL_TESTING"] = "1" - print("Running esptool.py tests...") @@ -177,7 +180,12 @@ def run_esptool_process(cmd): if baud or arg_baud is not None: base_cmd += ["--baud", str(baud or arg_baud)] usb_jtag_serial_reset = ["--before", "usb_reset"] if arg_preload_port else [] - full_cmd = base_cmd + usb_jtag_serial_reset + args.split(" ") + usb_otg_dont_reset = ( + ["--after", "no_reset_stub"] if "ESPTOOL_TEST_USB_OTG" in os.environ else [] + ) + full_cmd = ( + base_cmd + usb_jtag_serial_reset + usb_otg_dont_reset + args.split(" ") + ) # Preload a dummy binary to disable the RTC watchdog, needed in USB-JTAG/Serial if ( @@ -204,12 +212,16 @@ def run_esptool_process(cmd): print("\nPreloading dummy binary to disable RTC watchdog...") run_esptool_process(preload_cmd) print("Dummy binary preloaded successfully.") - time.sleep(0.3) # Wait for the app to run and port to appear + sleep(0.3) # Wait for the app to run and port to appear # Run the command print(f'\nRunning the "{args}" command...') output = run_esptool_process(full_cmd) print(output) # for more complete stdout logs on failure + + if "ESPTOOL_TEST_USB_OTG" in os.environ: + sleep(0.5) # Wait for the port to enumerate between tests + return output def run_esptool_error(self, args, baud=None, chip=None): @@ -284,6 +296,20 @@ def verify_readback( ct = ct[8:] self.diff(rb, ct) + def verify_output(self, expected_out: List[bytes]): + """Verify that at least one element of expected_out is in serial output""" + # Setting rtscts to true enables hardware flow control. + # This removes unwanted RTS logic level changes for some machines + # (and, therefore, chip resets) + # when the port is opened by the following function. + # As a result, if an app loaded to RAM, it has a chance to run and send + # "Hello world" data without unwanted chip reset. + with serial.serial_for_url(arg_port, arg_baud, rtscts=True) as p: + p.timeout = 5 + output = p.read(100) + print(f"Output: {output}") + assert any(item in output for item in expected_out) + @pytest.mark.skipif(arg_chip != "esp32", reason="ESP32 only") class TestFlashEncryption(EsptoolTestCase): @@ -486,7 +512,7 @@ def test_write_larger_area_to_32M_flash(self): def test_correct_offset(self): """Verify writing at an offset actually writes to that offset.""" self.run_esptool("write_flash 0x2000 images/sector.bin") - time.sleep(0.1) + sleep(0.1) three_sectors = self.readback(0, 0x3000) last_sector = three_sectors[0x2000:] with open("images/sector.bin", "rb") as f: @@ -675,7 +701,8 @@ def test_flash_not_aligned_nostub(self): "WARNING: Flash address 0x00000001 is not aligned to a 0x1000 byte flash sector. 0x1 bytes before this address will be erased." in output ) - assert "Hard resetting via RTS pin..." in output + if "ESPTOOL_TEST_USB_OTG" not in os.environ: + assert "Hard resetting" in output @pytest.mark.skipif(arg_preload_port is False, reason="USB-JTAG/Serial only") @pytest.mark.skipif(arg_chip != "esp32c3", reason="ESP32-C3 only") @@ -749,7 +776,7 @@ def test_flash_watchdogs(self): ) assert "Stub is already running. No upload is necessary." in output - time.sleep(10) # Wait if RTC WDT triggers + sleep(10) # Wait if RTC WDT triggers with esptool.cmds.detect_chip( port=arg_port, connect_mode="no_reset" @@ -979,6 +1006,10 @@ def test_short_flash_to_external_ROM(self): self.verify_readback(0, 1024, "images/one_kb.bin", spi_connection=self.conn) +@pytest.mark.skipif( + "ESPTOOL_TEST_USB_OTG" in os.environ, + reason="USB-OTG tests require --after no_reset for stability.", +) class TestStubReuse(EsptoolTestCase): def test_stub_reuse_with_synchronization(self): """Keep the flasher stub running and reuse it the next time.""" @@ -1257,7 +1288,7 @@ def test_deep_sleep_flash(self): # (so GPIO16, etc, config is not important for this test) self.run_esptool("write_flash 0x0 images/esp8266_deepsleep.bin", baud=230400) - time.sleep(0.25) # give ESP8266 time to enter deep sleep + sleep(0.25) # give ESP8266 time to enter deep sleep self.run_esptool("write_flash 0x0 images/fifty_kb.bin", baud=230400) self.verify_readback(0, 50 * 1024, "images/fifty_kb.bin") @@ -1491,6 +1522,31 @@ def test_make_image(self): os.remove("test0x00000.bin") +@pytest.mark.skipif( + arg_chip in ["esp8266", "esp32", "esp32h2"], reason="Not supported on this chip" +) +@pytest.mark.skipif( + "ESPTOOL_TEST_USB_OTG" in os.environ or arg_preload_port is not False, + reason="Boot mode strapping pin pulled constantly low, can't reset out of bootloader", +) +class TestReset(EsptoolTestCase): + def test_rtc_wdt_reset(self): + # Erase the bootloader to get "invalid header" output + test RTC WDT reset + res = self.run_esptool("--after no_reset erase_region 0x0 0x4000") + assert "Erase completed" in res + try: + esp = esptool.get_default_connected_device( + [arg_port], arg_port, 10, 115200, arg_chip + ) + esp.rtc_wdt_reset() + finally: + esp._port.close() + sleep(0.2) # Give the chip time to reset + # If there is no output, the chip did not reset + # Mangled bytes are for C2 26 MHz when the baudrate doesn't match + self.verify_output([b"invalid header", b"\x02b\xe2n\x9e\xe0p\x12n\x9c\x0cn"]) + + @pytest.mark.skipif(arg_chip != "esp32", reason="Don't need to test multiple times") @pytest.mark.quick_test class TestConfigFile(EsptoolTestCase):