# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import base64
import hashlib
import itertools
import json
import os
import re
import string
import struct
import sys
import time
from .config import load_config_file
from .reset import (
ClassicReset,
CustomReset,
DEFAULT_RESET_DELAY,
HardReset,
USBJTAGSerialReset,
UnixTightReset,
)
from .util import FatalError, NotImplementedInROMError, UnsupportedCommandError
from .util import byte, hexify, mask_to_shift, pad_to, strip_chip_name
try:
import serial
except ImportError:
print(
"Pyserial is not installed for %s. "
"Check the README for installation instructions." % (sys.executable)
)
raise
# check 'serial' is 'pyserial' and not 'serial'
# ref. https://github.com/espressif/esptool/issues/269
try:
if "serialization" in serial.__doc__ and "deserialization" in serial.__doc__:
raise ImportError(
"esptool.py depends on pyserial, but there is a conflict with a currently "
"installed package named 'serial'.\n"
"You may work around this by 'pip uninstall serial; pip install pyserial' "
"but this may break other installed Python software "
"that depends on 'serial'.\n"
"There is no good fix for this right now, "
"apart from configuring virtualenvs. "
"See https://github.com/espressif/esptool/issues/269#issuecomment-385298196"
" for discussion of the underlying issue(s)."
)
except TypeError:
pass # __doc__ returns None for pyserial
try:
import serial.tools.list_ports as list_ports
except ImportError:
print(
"The installed version (%s) of pyserial appears to be too old for esptool.py "
"(Python interpreter %s). Check the README for installation instructions."
% (sys.VERSION, sys.executable)
)
raise
except Exception:
if sys.platform == "darwin":
# swallow the exception, this is a known issue in pyserial+macOS Big Sur preview
# ref https://github.com/espressif/esptool/issues/540
list_ports = None
else:
raise
cfg, _ = load_config_file()
cfg = cfg["esptool"]
# Timeout for most flash operations
DEFAULT_TIMEOUT = cfg.getfloat("timeout", 3)
# Timeout for full chip erase
CHIP_ERASE_TIMEOUT = cfg.getfloat("chip_erase_timeout", 120)
# Longest any command can run
MAX_TIMEOUT = cfg.getfloat("max_timeout", CHIP_ERASE_TIMEOUT * 2)
# Timeout for syncing with bootloader
SYNC_TIMEOUT = cfg.getfloat("sync_timeout", 0.1)
# Timeout (per megabyte) for calculating md5sum
MD5_TIMEOUT_PER_MB = cfg.getfloat("md5_timeout_per_mb", 8)
# Timeout (per megabyte) for erasing a region
ERASE_REGION_TIMEOUT_PER_MB = cfg.getfloat("erase_region_timeout_per_mb", 30)
# Timeout (per megabyte) for erasing and writing data
ERASE_WRITE_TIMEOUT_PER_MB = cfg.getfloat("erase_write_timeout_per_mb", 40)
# Short timeout for ESP_MEM_END, as it may never respond
MEM_END_ROM_TIMEOUT = cfg.getfloat("mem_end_rom_timeout", 0.2)
# Timeout for serial port write
DEFAULT_SERIAL_WRITE_TIMEOUT = cfg.getfloat("serial_write_timeout", 10)
# Default number of times to try connection
DEFAULT_CONNECT_ATTEMPTS = cfg.getint("connect_attempts", 7)
# Number of times to try writing a data block
WRITE_BLOCK_ATTEMPTS = cfg.getint("write_block_attempts", 3)
STUBS_DIR = os.path.join(os.path.dirname(__file__), "./targets/stub_flasher/")
def get_stub_json_path(chip_name):
chip_name = strip_chip_name(chip_name)
chip_name = chip_name.replace("esp", "")
return STUBS_DIR + "stub_flasher_" + chip_name + ".json"
def timeout_per_mb(seconds_per_mb, size_bytes):
"""Scales timeouts which are size-specific"""
result = seconds_per_mb * (size_bytes / 1e6)
if result < DEFAULT_TIMEOUT:
return DEFAULT_TIMEOUT
return result
def check_supported_function(func, check_func):
"""
Decorator implementation that wraps a check around an ESPLoader
bootloader function to check if it's supported.
This is used to capture the multidimensional differences in
functionality between the ESP8266 & ESP32 (and later chips) ROM loaders, and the
software stub that runs on these. Not possible to do this cleanly
via inheritance alone.
"""
def inner(*args, **kwargs):
obj = args[0]
if check_func(obj):
return func(*args, **kwargs)
else:
raise NotImplementedInROMError(obj, func)
return inner
def stub_function_only(func):
"""Attribute for a function only supported in the software stub loader"""
return check_supported_function(func, lambda o: o.IS_STUB)
def stub_and_esp32_function_only(func):
"""Attribute for a function only supported by stubs or ESP32 and later chips ROM"""
return check_supported_function(
func, lambda o: o.IS_STUB or o.CHIP_NAME not in ["ESP8266"]
)
def esp32s3_or_newer_function_only(func):
"""Attribute for a function only supported by ESP32S3 and later chips ROM"""
return check_supported_function(
func, lambda o: o.CHIP_NAME not in ["ESP8266", "ESP32", "ESP32-S2"]
)
class StubFlasher:
def __init__(self, json_path):
with open(json_path) as json_file:
stub = json.load(json_file)
self.text = base64.b64decode(stub["text"])
self.text_start = stub["text_start"]
self.entry = stub["entry"]
try:
self.data = base64.b64decode(stub["data"])
self.data_start = stub["data_start"]
except KeyError:
self.data = None
self.data_start = None
class ESPLoader(object):
"""Base class providing access to ESP ROM & software stub bootloaders.
Subclasses provide ESP8266 & ESP32 Family specific functionality.
Don't instantiate this base class directly, either instantiate a subclass or
call cmds.detect_chip() which will interrogate the chip and return the
appropriate subclass instance.
"""
CHIP_NAME = "Espressif device"
IS_STUB = False
FPGA_SLOW_BOOT = False
DEFAULT_PORT = "/dev/ttyUSB0"
USES_RFC2217 = False
# Commands supported by ESP8266 ROM bootloader
ESP_FLASH_BEGIN = 0x02
ESP_FLASH_DATA = 0x03
ESP_FLASH_END = 0x04
ESP_MEM_BEGIN = 0x05
ESP_MEM_END = 0x06
ESP_MEM_DATA = 0x07
ESP_SYNC = 0x08
ESP_WRITE_REG = 0x09
ESP_READ_REG = 0x0A
# Some comands supported by ESP32 and later chips ROM bootloader (or -8266 w/ stub)
ESP_SPI_SET_PARAMS = 0x0B
ESP_SPI_ATTACH = 0x0D
ESP_READ_FLASH_SLOW = 0x0E # ROM only, much slower than the stub flash read
ESP_CHANGE_BAUDRATE = 0x0F
ESP_FLASH_DEFL_BEGIN = 0x10
ESP_FLASH_DEFL_DATA = 0x11
ESP_FLASH_DEFL_END = 0x12
ESP_SPI_FLASH_MD5 = 0x13
# Commands supported by ESP32-S2 and later chips ROM bootloader only
ESP_GET_SECURITY_INFO = 0x14
# Some commands supported by stub only
ESP_ERASE_FLASH = 0xD0
ESP_ERASE_REGION = 0xD1
ESP_READ_FLASH = 0xD2
ESP_RUN_USER_CODE = 0xD3
# Flash encryption encrypted data command
ESP_FLASH_ENCRYPT_DATA = 0xD4
# Response code(s) sent by ROM
ROM_INVALID_RECV_MSG = 0x05 # response if an invalid message is received
# Maximum block sized for RAM and Flash writes, respectively.
ESP_RAM_BLOCK = 0x1800
FLASH_WRITE_SIZE = 0x400
# Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want.
ESP_ROM_BAUD = 115200
# First byte of the application image
ESP_IMAGE_MAGIC = 0xE9
# Initial state for the checksum routine
ESP_CHECKSUM_MAGIC = 0xEF
# Flash sector size, minimum unit of erase.
FLASH_SECTOR_SIZE = 0x1000
UART_DATE_REG_ADDR = 0x60000078
# This ROM address has a different value on each chip model
CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000