feat(core): 更新py-esptool (version: 4.8.1)

This commit is contained in:
王立帮
2025-04-07 23:51:29 +08:00
parent 6b4ca0a883
commit 937ecf44f4
120 changed files with 1871 additions and 8899 deletions

View File

@@ -10,31 +10,33 @@ import struct
import sys
import time
import zlib
import itertools
from intelhex import IntelHex
from serial import SerialException
from bin_image import ELFFile, ImageSegment, LoadFirmwareImage
from bin_image import (
from .bin_image import ELFFile, ImageSegment, LoadFirmwareImage
from .bin_image import (
ESP8266ROMFirmwareImage,
ESP8266V2FirmwareImage,
ESP8266V3FirmwareImage,
)
from loader import (
from .loader import (
DEFAULT_CONNECT_ATTEMPTS,
DEFAULT_TIMEOUT,
ERASE_WRITE_TIMEOUT_PER_MB,
ESPLoader,
timeout_per_mb,
)
from targets import CHIP_DEFS, CHIP_LIST, ROM_LIST
from uf2_writer import UF2Writer
from util import (
from .targets import CHIP_DEFS, CHIP_LIST, ROM_LIST
from .uf2_writer import UF2Writer
from .util import (
FatalError,
NotImplementedInROMError,
NotSupportedError,
UnsupportedCommandError,
)
from util import (
from .util import (
div_roundup,
flash_size_bytes,
get_file_size,
@@ -115,7 +117,7 @@ def detect_chip(
else:
err_msg = f"Unexpected chip ID value {chip_id}."
except (UnsupportedCommandError, struct.error, FatalError) as e:
# UnsupportedCommmanddError: ESP8266/ESP32 ROM
# UnsupportedCommandError: ESP8266/ESP32 ROM
# struct.error: ESP32-S2
# FatalError: ESP8266/ESP32 STUB
print(" Unsupported detection protocol, switching and trying again...")
@@ -229,7 +231,7 @@ def detect_flash_size(esp, args=None):
if flash_size is None:
flash_size = "4MB"
print(
"Warning: Could not auto-detect Flash size "
"WARNING: Could not auto-detect Flash size "
f"(FlashID={flash_id:#x}, SizeID={size_id:#x}), defaulting to 4MB"
)
else:
@@ -276,49 +278,60 @@ def _update_image_flash_params(esp, address, args, image):
# After the 8-byte header comes the extended header for chips others than ESP8266.
# The 15th byte of the extended header indicates if the image is protected by
# a SHA256 checksum. In that case we should not modify the header because
# the checksum check would fail.
sha_implies_keep = args.chip != "esp8266" and image[8 + 15] == 1
def print_keep_warning(arg_to_keep, arg_used):
print(
"Warning: Image file at {addr} is protected with a hash checksum, "
"so not changing the flash {arg} setting. "
"Use the --flash_{arg}=keep option instead of --flash_{arg}={arg_orig} "
"in order to remove this warning, or use the --dont-append-digest option "
"for the elf2image command in order to generate an image file "
"without a hash checksum".format(
addr=hex(address), arg=arg_to_keep, arg_orig=arg_used
)
)
# a SHA256 checksum. In that case we recalculate the SHA digest after modifying the header.
sha_appended = args.chip != "esp8266" and image[8 + 15] == 1
if args.flash_mode != "keep":
new_flash_mode = FLASH_MODES[args.flash_mode]
if flash_mode != new_flash_mode and sha_implies_keep:
print_keep_warning("mode", args.flash_mode)
else:
flash_mode = new_flash_mode
flash_mode = FLASH_MODES[args.flash_mode]
flash_freq = flash_size_freq & 0x0F
if args.flash_freq != "keep":
new_flash_freq = esp.parse_flash_freq_arg(args.flash_freq)
if flash_freq != new_flash_freq and sha_implies_keep:
print_keep_warning("frequency", args.flash_freq)
else:
flash_freq = new_flash_freq
flash_freq = esp.parse_flash_freq_arg(args.flash_freq)
flash_size = flash_size_freq & 0xF0
if args.flash_size != "keep":
new_flash_size = esp.parse_flash_size_arg(args.flash_size)
if flash_size != new_flash_size and sha_implies_keep:
print_keep_warning("size", args.flash_size)
else:
flash_size = new_flash_size
flash_size = esp.parse_flash_size_arg(args.flash_size)
flash_params = struct.pack(b"BB", flash_mode, flash_size + flash_freq)
if flash_params != image[2:4]:
print("Flash params set to 0x%04x" % struct.unpack(">H", flash_params))
image = image[0:2] + flash_params + image[4:]
# recalculate the SHA digest if it was appended
if sha_appended:
# Since the changes are only made for images located in the bootloader offset,
# we can assume that the image is always a bootloader image.
# For merged binaries, we check the bootloader SHA when parameters are changed.
image_object = esp.BOOTLOADER_IMAGE(io.BytesIO(image))
# get the image header, extended header (if present) and data
image_data_before_sha = image[: image_object.data_length]
# get the image data after the SHA digest (primary for merged binaries)
image_data_after_sha = image[
(image_object.data_length + image_object.SHA256_DIGEST_LEN) :
]
sha_digest_calculated = hashlib.sha256(image_data_before_sha).digest()
image = bytes(
itertools.chain(
image_data_before_sha, sha_digest_calculated, image_data_after_sha
)
)
# get the SHA digest newly stored in the image and compare it to the calculated one
image_stored_sha = image[
image_object.data_length : image_object.data_length
+ image_object.SHA256_DIGEST_LEN
]
if hexify(sha_digest_calculated) == hexify(image_stored_sha):
print("SHA digest in image updated")
else:
print(
"WARNING: SHA recalculation for binary failed!\n"
f"\tExpected calculated SHA: {hexify(sha_digest_calculated)}\n"
f"\tSHA stored in binary: {hexify(image_stored_sha)}"
)
return image
@@ -465,19 +478,32 @@ def write_flash(esp, args):
"Use --force to override the warning."
)
# verify file sizes fit in flash
flash_end = flash_size_bytes(
detect_flash_size(esp) if args.flash_size == "keep" else args.flash_size
set_flash_size = (
flash_size_bytes(args.flash_size)
if args.flash_size not in ["detect", "keep"]
else None
)
if flash_end is not None: # Not in secure download mode
if esp.secure_download_mode:
flash_end = set_flash_size
else: # Check against real flash chip size if not in SDM
flash_end_str = detect_flash_size(esp)
flash_end = flash_size_bytes(flash_end_str)
if set_flash_size and set_flash_size > flash_end:
print(
f"WARNING: Set --flash_size {args.flash_size} "
f"is larger than the available flash size of {flash_end_str}."
)
# Verify file sizes fit in the set --flash_size, or real flash size if smaller
flash_end = min(set_flash_size, flash_end) if set_flash_size else flash_end
if flash_end is not None:
for address, argfile in args.addr_filename:
argfile.seek(0, os.SEEK_END)
if address + argfile.tell() > flash_end:
raise FatalError(
"File %s (length %d) at offset %d "
"will not fit in %d bytes of flash. "
"Use --flash_size argument, or change flashing address."
% (argfile.name, argfile.tell(), address, flash_end)
f"File {argfile.name} (length {argfile.tell()}) at offset "
f"{address} will not fit in {flash_end} bytes of flash. "
"Change the --flash_size argument, or flashing address."
)
argfile.seek(0)
@@ -547,70 +573,118 @@ def write_flash(esp, args):
print("Will flash %s uncompressed" % argfile.name)
compress = False
if args.no_stub:
print("Erasing flash...")
image = pad_to(
argfile.read(), esp.FLASH_ENCRYPTED_WRITE_ALIGN if encrypted else 4
)
image = argfile.read()
if len(image) == 0:
print("WARNING: File %s is empty" % argfile.name)
continue
image = _update_image_flash_params(esp, address, args, image)
image = pad_to(image, esp.FLASH_ENCRYPTED_WRITE_ALIGN if encrypted else 4)
if args.no_stub:
print("Erasing flash...")
# It is not possible to write to not aligned addresses without stub,
# so there are added 0xFF (erase) bytes at the beginning of the image
# to align it.
bytes_over = address % esp.FLASH_SECTOR_SIZE
address -= bytes_over
image = b"\xFF" * bytes_over + image
if not esp.secure_download_mode and not esp.get_secure_boot_enabled():
image = _update_image_flash_params(esp, address, args, image)
else:
print(
"WARNING: Security features enabled, so not changing any flash settings."
)
calcmd5 = hashlib.md5(image).hexdigest()
uncsize = len(image)
if compress:
uncimage = image
image = zlib.compress(uncimage, 9)
# Decompress the compressed binary a block at a time,
# to dynamically calculate the timeout based on the real write size
decompress = zlib.decompressobj()
blocks = esp.flash_defl_begin(uncsize, len(image), address)
else:
blocks = esp.flash_begin(uncsize, address, begin_rom_encrypted=encrypted)
argfile.seek(0) # in case we need it again
seq = 0
bytes_sent = 0 # bytes sent on wire
bytes_written = 0 # bytes written to flash
t = time.time()
timeout = DEFAULT_TIMEOUT
while len(image) > 0:
print_overwrite(
"Writing at 0x%08x... (%d %%)"
% (address + bytes_written, 100 * (seq + 1) // blocks)
)
sys.stdout.flush()
block = image[0 : esp.FLASH_WRITE_SIZE]
if compress:
# feeding each compressed block into the decompressor lets us
# see block-by-block how much will be written
block_uncompressed = len(decompress.decompress(block))
bytes_written += block_uncompressed
block_timeout = max(
DEFAULT_TIMEOUT,
timeout_per_mb(ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed),
)
if not esp.IS_STUB:
timeout = (
block_timeout # ROM code writes block to flash before ACKing
)
esp.flash_defl_block(block, seq, timeout=timeout)
if esp.IS_STUB:
# Stub ACKs when block is received,
# then writes to flash while receiving the block after it
timeout = block_timeout
else:
# Pad the last block
block = block + b"\xff" * (esp.FLASH_WRITE_SIZE - len(block))
if encrypted:
esp.flash_encrypt_block(block, seq)
original_image = image # Save the whole image in case retry is needed
# Try again if reconnect was successful
for attempt in range(1, esp.WRITE_FLASH_ATTEMPTS + 1):
try:
if compress:
# Decompress the compressed binary a block at a time,
# to dynamically calculate the timeout based on the real write size
decompress = zlib.decompressobj()
blocks = esp.flash_defl_begin(uncsize, len(image), address)
else:
esp.flash_block(block, seq)
bytes_written += len(block)
bytes_sent += len(block)
image = image[esp.FLASH_WRITE_SIZE :]
seq += 1
blocks = esp.flash_begin(
uncsize, address, begin_rom_encrypted=encrypted
)
argfile.seek(0) # in case we need it again
seq = 0
bytes_sent = 0 # bytes sent on wire
bytes_written = 0 # bytes written to flash
t = time.time()
timeout = DEFAULT_TIMEOUT
while len(image) > 0:
print_overwrite(
"Writing at 0x%08x... (%d %%)"
% (address + bytes_written, 100 * (seq + 1) // blocks)
)
sys.stdout.flush()
block = image[0 : esp.FLASH_WRITE_SIZE]
if compress:
# feeding each compressed block into the decompressor lets us
# see block-by-block how much will be written
block_uncompressed = len(decompress.decompress(block))
bytes_written += block_uncompressed
block_timeout = max(
DEFAULT_TIMEOUT,
timeout_per_mb(
ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed
),
)
if not esp.IS_STUB:
timeout = block_timeout # ROM code writes block to flash before ACKing
esp.flash_defl_block(block, seq, timeout=timeout)
if esp.IS_STUB:
# Stub ACKs when block is received,
# then writes to flash while receiving the block after it
timeout = block_timeout
else:
# Pad the last block
block = block + b"\xff" * (esp.FLASH_WRITE_SIZE - len(block))
if encrypted:
esp.flash_encrypt_block(block, seq)
else:
esp.flash_block(block, seq)
bytes_written += len(block)
bytes_sent += len(block)
image = image[esp.FLASH_WRITE_SIZE :]
seq += 1
break
except SerialException:
if attempt == esp.WRITE_FLASH_ATTEMPTS or encrypted:
# Already retried once or encrypted mode is disabled because of security reasons
raise
print("\nLost connection, retrying...")
esp._port.close()
print("Waiting for the chip to reconnect", end="")
for _ in range(DEFAULT_CONNECT_ATTEMPTS):
try:
time.sleep(1)
esp._port.open()
print() # Print new line which was suppressed by print(".")
esp.connect()
if esp.IS_STUB:
# Hack to bypass the stub overwrite check
esp.IS_STUB = False
# Reflash stub because chip was reset
esp = esp.run_stub()
image = original_image
break
except SerialException:
print(".", end="")
sys.stdout.flush()
else:
raise # Reconnect limit reached
if esp.IS_STUB:
# Stub only writes each block to flash after 'ack'ing the receive,
@@ -645,7 +719,7 @@ def write_flash(esp, args):
print("Flash md5: %s" % res)
print(
"MD5 of 0xFF is %s"
% (hashlib.md5(b"\xFF" * uncsize).hexdigest())
% (hashlib.md5(b"\xff" * uncsize).hexdigest())
)
raise FatalError("MD5 of file does not match data in flash!")
else:
@@ -793,7 +867,7 @@ def image_info(args):
format_str = "{:7} {:#07x} {:#010x} {:#010x} {}"
app_desc = None
bootloader_desc = None
for idx, seg in enumerate(image.segments, start=1):
for idx, seg in enumerate(image.segments):
segs = seg.get_memory_type(image)
seg_name = ", ".join(segs)
if "DROM" in segs: # The DROM segment starts with the esp_app_desc_t struct
@@ -815,9 +889,11 @@ def image_info(args):
print(
"Checksum: {:#02x} ({})".format(
image.checksum,
"valid"
if image.checksum == calc_checksum
else "invalid - calculated {:02x}".format(calc_checksum),
(
"valid"
if image.checksum == calc_checksum
else "invalid - calculated {:02x}".format(calc_checksum)
),
)
)
try:
@@ -892,8 +968,7 @@ def image_info(args):
ESP8266V2FirmwareImage.IMAGE_V2_MAGIC,
]:
raise FatalError(
"This is not a valid image "
"(invalid magic number: {:#x})".format(magic)
f"This is not a valid image (invalid magic number: {magic:#x})"
)
if args.chip == "auto":
@@ -940,9 +1015,11 @@ def image_info(args):
print(
"Checksum: {:02x} ({})".format(
image.checksum,
"valid"
if image.checksum == calc_checksum
else "invalid - calculated {:02x}".format(calc_checksum),
(
"valid"
if image.checksum == calc_checksum
else "invalid - calculated {:02x}".format(calc_checksum)
),
)
)
try:
@@ -982,8 +1059,6 @@ def elf2image(args):
args.chip = "esp8266"
print("Creating {} image...".format(args.chip))
if args.ram_only_header:
print("ROM segments hidden - only RAM segments are visible to the ROM loader!")
if args.chip != "esp8266":
image = CHIP_DEFS[args.chip].BOOTLOADER_IMAGE()
@@ -995,7 +1070,10 @@ def elf2image(args):
image.min_rev_full = args.min_rev_full
image.max_rev_full = args.max_rev_full
image.ram_only_header = args.ram_only_header
image.append_digest = args.append_digest
if image.ram_only_header:
image.append_digest = False
else:
image.append_digest = args.append_digest
elif args.version == "1": # ESP8266
image = ESP8266ROMFirmwareImage()
elif args.version == "2":
@@ -1019,6 +1097,13 @@ def elf2image(args):
image.elf_sha256 = e.sha256()
image.elf_sha256_offset = args.elf_sha256_offset
if args.ram_only_header:
print(
"Image has only RAM segments visible. "
"ROM segments are hidden and SHA256 digest is not appended."
)
image.sort_segments()
before = len(image.segments)
image.merge_adjacent_segments()
if len(image.segments) != before:
@@ -1090,7 +1175,7 @@ def run(esp, args):
esp.run()
def flash_id(esp, args):
def detect_flash_id(esp):
flash_id = esp.flash_id()
print("Manufacturer: %02x" % (flash_id & 0xFF))
flid_lowbyte = (flash_id >> 16) & 0xFF
@@ -1098,11 +1183,27 @@ def flash_id(esp, args):
print(
"Detected flash size: %s" % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown"))
)
def flash_id(esp, args):
detect_flash_id(esp)
flash_type = esp.flash_type()
flash_type_dict = {0: "quad (4 data lines)", 1: "octal (8 data lines)"}
flash_type_str = flash_type_dict.get(flash_type)
if flash_type_str:
print(f"Flash type set in eFuse: {flash_type_str}")
esp.get_flash_voltage()
def read_flash_sfdp(esp, args):
detect_flash_id(esp)
sfdp = esp.read_spiflash_sfdp(args.addr, args.bytes * 8)
print(f"SFDP[{args.addr}..{args.addr+args.bytes-1}]: ", end="")
for i in range(args.bytes):
print(f"{sfdp&0xff:02X} ", end="")
sfdp = sfdp >> 8
print()
def read_flash(esp, args):
@@ -1218,7 +1319,16 @@ def get_security_info(esp, args):
print(title)
print("=" * len(title))
print("Flags: {:#010x} ({})".format(si["flags"], bin(si["flags"])))
print("Key Purposes: {}".format(si["key_purposes"]))
if esp.KEY_PURPOSES:
print(f"Key Purposes: {si['key_purposes']}")
desc = "\n ".join(
[
f"BLOCK_KEY{key_num} - {esp.KEY_PURPOSES.get(purpose, 'UNKNOWN')}"
for key_num, purpose in enumerate(si["key_purposes"])
if key_num <= esp.EFUSE_MAX_KEY
]
)
print(f" {desc}")
if si["chip_id"] is not None and si["api_version"] is not None:
print("Chip ID: {}".format(si["chip_id"]))
print("API Version: {}".format(si["api_version"]))
@@ -1270,7 +1380,7 @@ def get_security_info(esp, args):
hard_dis_jtag = get_security_flag_status("HARD_DIS_JTAG", flags)
soft_dis_jtag = get_security_flag_status("SOFT_DIS_JTAG", flags)
if hard_dis_jtag:
print("JTAG: Permenantly Disabled")
print("JTAG: Permanently Disabled")
elif soft_dis_jtag:
print("JTAG: Software Access Disabled")
if get_security_flag_status("DIS_USB", flags):
@@ -1323,7 +1433,7 @@ def merge_bin(args):
def pad_to(flash_offs):
# account for output file offset if there is any
of.write(b"\xFF" * (flash_offs - args.target_offset - of.tell()))
of.write(b"\xff" * (flash_offs - args.target_offset - of.tell()))
for addr, argfile in input_files:
pad_to(addr)
@@ -1338,6 +1448,13 @@ def merge_bin(args):
)
elif args.format == "hex":
out = IntelHex()
if len(input_files) == 1:
print(
"WARNING: Only one input file specified, output may include "
"additional padding if input file was previously merged. "
"Please refer to the documentation for more information: "
"https://docs.espressif.com/projects/esptool/en/latest/esptool/basic-commands.html#hex-output-format" # noqa E501
)
for addr, argfile in input_files:
ihex = IntelHex()
image = argfile.read()