DEV_semaphore/.platformio/packages/tool-esptoolpy/esptool/cmds.py

1199 lines
44 KiB
Python

# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import hashlib
import io
import os
import struct
import sys
import time
import zlib
from .bin_image import ELFFile, ImageSegment, LoadFirmwareImage
from .bin_image import (
ESP8266ROMFirmwareImage,
ESP8266V2FirmwareImage,
ESP8266V3FirmwareImage,
)
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 .util import (
FatalError,
NotImplementedInROMError,
NotSupportedError,
UnsupportedCommandError,
)
from .util import (
div_roundup,
flash_size_bytes,
hexify,
pad_to,
print_overwrite,
)
DETECTED_FLASH_SIZES = {
0x12: "256KB",
0x13: "512KB",
0x14: "1MB",
0x15: "2MB",
0x16: "4MB",
0x17: "8MB",
0x18: "16MB",
0x19: "32MB",
0x1A: "64MB",
0x1B: "128MB",
0x1C: "256MB",
0x20: "64MB",
0x21: "128MB",
0x22: "256MB",
0x32: "256KB",
0x33: "512KB",
0x34: "1MB",
0x35: "2MB",
0x36: "4MB",
0x37: "8MB",
0x38: "16MB",
0x39: "32MB",
0x3A: "64MB",
}
FLASH_MODES = {"qio": 0, "qout": 1, "dio": 2, "dout": 3}
def detect_chip(
port=ESPLoader.DEFAULT_PORT,
baud=ESPLoader.ESP_ROM_BAUD,
connect_mode="default_reset",
trace_enabled=False,
connect_attempts=DEFAULT_CONNECT_ATTEMPTS,
):
"""Use serial access to detect the chip type.
First, get_security_info command is sent to detect the ID of the chip
(supported only by ESP32-C3 and later, works even in the Secure Download Mode).
If this fails, we reconnect and fall-back to reading the magic number.
It's mapped at a specific ROM address and has a different value on each chip model.
This way we use one memory read and compare it to the magic number for each chip.
This routine automatically performs ESPLoader.connect() (passing
connect_mode parameter) as part of querying the chip.
"""
inst = None
detect_port = ESPLoader(port, baud, trace_enabled=trace_enabled)
if detect_port.serial_port.startswith("rfc2217:"):
detect_port.USES_RFC2217 = True
detect_port.connect(connect_mode, connect_attempts, detecting=True)
try:
print("Detecting chip type...", end="")
chip_id = detect_port.get_chip_id()
for cls in [
n for n in ROM_LIST if n.CHIP_NAME not in ("ESP8266", "ESP32", "ESP32S2")
]:
# cmd not supported on ESP8266 and ESP32 + ESP32-S2 doesn't return chip_id
if chip_id == cls.IMAGE_CHIP_ID:
inst = cls(detect_port._port, baud, trace_enabled=trace_enabled)
try:
inst.read_reg(
ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR
) # Dummy read to check Secure Download mode
except UnsupportedCommandError:
inst.secure_download_mode = True
inst._post_connect()
break
else:
err_msg = f"Unexpected chip ID value {chip_id}."
except (UnsupportedCommandError, struct.error, FatalError) as e:
# UnsupportedCommmanddError: ESP8266/ESP32 ROM
# struct.error: ESP32-S2
# FatalError: ESP8266/ESP32 STUB
print(" Unsupported detection protocol, switching and trying again...")
try:
# ESP32/ESP8266 are reset after an unsupported command, need to reconnect
# (not needed on ESP32-S2)
if not isinstance(e, struct.error):
detect_port.connect(
connect_mode, connect_attempts, detecting=True, warnings=False
)
print("Detecting chip type...", end="")
sys.stdout.flush()
chip_magic_value = detect_port.read_reg(
ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR
)
for cls in ROM_LIST:
if chip_magic_value in cls.CHIP_DETECT_MAGIC_VALUE:
inst = cls(detect_port._port, baud, trace_enabled=trace_enabled)
inst._post_connect()
inst.check_chip_id()
break
else:
err_msg = f"Unexpected chip magic value {chip_magic_value:#010x}."
except UnsupportedCommandError:
raise FatalError(
"Unsupported Command Error received. "
"Probably this means Secure Download Mode is enabled, "
"autodetection will not work. Need to manually specify the chip."
)
finally:
if inst is not None:
print(" %s" % inst.CHIP_NAME, end="")
if detect_port.sync_stub_detected:
inst = inst.STUB_CLASS(inst)
inst.sync_stub_detected = True
print("") # end line
return inst
raise FatalError(
f"{err_msg} Failed to autodetect chip type."
"\nProbably it is unsupported by this version of esptool."
)
# "Operation" commands, executable at command line. One function each
#
# Each function takes either two args (<ESPLoader instance>, <args>) or a single <args>
# argument.
def load_ram(esp, args):
image = LoadFirmwareImage(esp.CHIP_NAME, args.filename)
print("RAM boot...")
for seg in image.segments:
size = len(seg.data)
print("Downloading %d bytes at %08x..." % (size, seg.addr), end=" ")
sys.stdout.flush()
esp.mem_begin(
size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, seg.addr
)
seq = 0
while len(seg.data) > 0:
esp.mem_block(seg.data[0 : esp.ESP_RAM_BLOCK], seq)
seg.data = seg.data[esp.ESP_RAM_BLOCK :]
seq += 1
print("done!")
print("All segments done, executing at %08x" % image.entrypoint)
esp.mem_finish(image.entrypoint)
def read_mem(esp, args):
print("0x%08x = 0x%08x" % (args.address, esp.read_reg(args.address)))
def write_mem(esp, args):
esp.write_reg(args.address, args.value, args.mask, 0)
print("Wrote %08x, mask %08x to %08x" % (args.value, args.mask, args.address))
def dump_mem(esp, args):
with open(args.filename, "wb") as f:
for i in range(args.size // 4):
d = esp.read_reg(args.address + (i * 4))
f.write(struct.pack(b"<I", d))
if f.tell() % 1024 == 0:
print_overwrite(
"%d bytes read... (%d %%)" % (f.tell(), f.tell() * 100 // args.size)
)
sys.stdout.flush()
print_overwrite("Read %d bytes" % f.tell(), last_line=True)
print("Done!")
def detect_flash_size(esp, args):
if args.flash_size == "detect":
if esp.secure_download_mode:
raise FatalError(
"Detecting flash size is not supported in secure download mode. "
"Need to manually specify flash size."
)
flash_id = esp.flash_id()
size_id = flash_id >> 16
args.flash_size = DETECTED_FLASH_SIZES.get(size_id)
if args.flash_size is None:
print(
"Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x),"
" defaulting to 4MB" % (flash_id, size_id)
)
args.flash_size = "4MB"
else:
print("Auto-detected Flash size:", args.flash_size)
def _update_image_flash_params(esp, address, args, image):
"""
Modify the flash mode & size bytes if this looks like an executable bootloader image
"""
if len(image) < 8:
return image # not long enough to be a bootloader image
# unpack the (potential) image header
magic, _, flash_mode, flash_size_freq = struct.unpack("BBBB", image[:4])
if address != esp.BOOTLOADER_FLASH_OFFSET:
return image # not flashing bootloader offset, so don't modify this
if (args.flash_mode, args.flash_freq, args.flash_size) == ("keep",) * 3:
return image # all settings are 'keep', not modifying anything
# easy check if this is an image: does it start with a magic byte?
if magic != esp.ESP_IMAGE_MAGIC:
print(
"Warning: Image file at 0x%x doesn't look like an image file, "
"so not changing any flash settings." % address
)
return image
# make sure this really is an image, and not just data that
# starts with esp.ESP_IMAGE_MAGIC (mostly a problem for encrypted
# images that happen to start with a magic byte
try:
test_image = esp.BOOTLOADER_IMAGE(io.BytesIO(image))
test_image.verify()
except Exception:
print(
"Warning: Image file at 0x%x is not a valid %s image, "
"so not changing any flash settings." % (address, esp.CHIP_NAME)
)
return 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
)
)
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_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_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_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:]
return image
def write_flash(esp, args):
# set args.compress based on default behaviour:
# -> if either --compress or --no-compress is set, honour that
# -> otherwise, set --compress unless --no-stub is set
if args.compress is None and not args.no_compress:
args.compress = not args.no_stub
if not args.force and esp.CHIP_NAME != "ESP8266" and not esp.secure_download_mode:
# Check if secure boot is active
if esp.get_secure_boot_enabled():
for address, _ in args.addr_filename:
if address < 0x8000:
raise FatalError(
"Secure Boot detected, writing to flash regions < 0x8000 "
"is disabled to protect the bootloader. "
"Use --force to override, "
"please use with caution, otherwise it may brick your device!"
)
# Check if chip_id and min_rev in image are valid for the target in use
for _, argfile in args.addr_filename:
try:
image = LoadFirmwareImage(esp.CHIP_NAME, argfile)
except (FatalError, struct.error, RuntimeError):
continue
finally:
argfile.seek(0) # LoadFirmwareImage changes the file handle position
if image.chip_id != esp.IMAGE_CHIP_ID:
raise FatalError(
f"{argfile.name} is not an {esp.CHIP_NAME} image. "
"Use --force to flash anyway."
)
# this logic below decides which min_rev to use, min_rev or min/max_rev_full
if image.max_rev_full == 0: # image does not have max/min_rev_full fields
use_rev_full_fields = False
elif image.max_rev_full == 65535: # image has default value of max_rev_full
use_rev_full_fields = True
if (
image.min_rev_full == 0 and image.min_rev != 0
): # min_rev_full is not set, min_rev is used
use_rev_full_fields = False
else: # max_rev_full set to a version
use_rev_full_fields = True
if use_rev_full_fields:
rev = esp.get_chip_revision()
if rev < image.min_rev_full or rev > image.max_rev_full:
error_str = f"{argfile.name} requires chip revision in range "
error_str += (
f"[v{image.min_rev_full // 100}.{image.min_rev_full % 100} - "
)
if image.max_rev_full == 65535:
error_str += "max rev not set] "
else:
error_str += (
f"v{image.max_rev_full // 100}.{image.max_rev_full % 100}] "
)
error_str += f"(this chip is revision v{rev // 100}.{rev % 100})"
raise FatalError(f"{error_str}. Use --force to flash anyway.")
else:
# In IDF, image.min_rev is set based on Kconfig option.
# For C3 chip, image.min_rev is the Minor revision
# while for the rest chips it is the Major revision.
if esp.CHIP_NAME == "ESP32-C3":
rev = esp.get_minor_chip_version()
else:
rev = esp.get_major_chip_version()
if rev < image.min_rev:
raise FatalError(
f"{argfile.name} requires chip revision "
f"{image.min_rev} or higher (this chip is revision {rev}). "
"Use --force to flash anyway."
)
# In case we have encrypted files to write,
# we first do few sanity checks before actual flash
if args.encrypt or args.encrypt_files is not None:
do_write = True
if not esp.secure_download_mode:
if esp.get_encrypted_download_disabled():
raise FatalError(
"This chip has encrypt functionality "
"in UART download mode disabled. "
"This is the Flash Encryption configuration for Production mode "
"instead of Development mode."
)
crypt_cfg_efuse = esp.get_flash_crypt_config()
if crypt_cfg_efuse is not None and crypt_cfg_efuse != 0xF:
print("Unexpected FLASH_CRYPT_CONFIG value: 0x%x" % (crypt_cfg_efuse))
do_write = False
enc_key_valid = esp.is_flash_encryption_key_valid()
if not enc_key_valid:
print("Flash encryption key is not programmed")
do_write = False
# Determine which files list contain the ones to encrypt
files_to_encrypt = args.addr_filename if args.encrypt else args.encrypt_files
for address, argfile in files_to_encrypt:
if address % esp.FLASH_ENCRYPTED_WRITE_ALIGN:
print(
"File %s address 0x%x is not %d byte aligned, can't flash encrypted"
% (argfile.name, address, esp.FLASH_ENCRYPTED_WRITE_ALIGN)
)
do_write = False
if not do_write and not args.ignore_flash_encryption_efuse_setting:
raise FatalError(
"Can't perform encrypted flash write, "
"consult Flash Encryption documentation for more information"
)
else:
if not args.force and esp.CHIP_NAME != "ESP8266":
# ESP32 does not support `get_security_info()` and `secure_download_mode`
if (
esp.CHIP_NAME != "ESP32"
and esp.secure_download_mode
and bin(esp.get_security_info()["flash_crypt_cnt"]).count("1") & 1 != 0
):
raise FatalError(
"WARNING: Detected flash encryption and "
"secure download mode enabled.\n"
"Flashing plaintext binary may brick your device! "
"Use --force to override the warning."
)
if (
not esp.secure_download_mode
and esp.get_encrypted_download_disabled()
and esp.get_flash_encryption_enabled()
):
raise FatalError(
"WARNING: Detected flash encryption enabled and "
"download manual encrypt disabled.\n"
"Flashing plaintext binary may brick your device! "
"Use --force to override the warning."
)
# verify file sizes fit in flash
if args.flash_size != "keep": # TODO: check this even with 'keep'
flash_end = flash_size_bytes(args.flash_size)
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)
)
argfile.seek(0)
if args.erase_all:
erase_flash(esp, args)
else:
for address, argfile in args.addr_filename:
argfile.seek(0, os.SEEK_END)
write_end = address + argfile.tell()
argfile.seek(0)
bytes_over = address % esp.FLASH_SECTOR_SIZE
if bytes_over != 0:
print(
"WARNING: Flash address {:#010x} is not aligned "
"to a {:#x} byte flash sector. "
"{:#x} bytes before this address will be erased.".format(
address, esp.FLASH_SECTOR_SIZE, bytes_over
)
)
# Print the address range of to-be-erased flash memory region
print(
"Flash will be erased from {:#010x} to {:#010x}...".format(
address - bytes_over,
div_roundup(write_end, esp.FLASH_SECTOR_SIZE)
* esp.FLASH_SECTOR_SIZE
- 1,
)
)
""" Create a list describing all the files we have to flash.
Each entry holds an "encrypt" flag marking whether the file needs encryption or not.
This list needs to be sorted.
First, append to each entry of our addr_filename list the flag args.encrypt
E.g., if addr_filename is [(0x1000, "partition.bin"), (0x8000, "bootloader")],
all_files will be [
(0x1000, "partition.bin", args.encrypt),
(0x8000, "bootloader", args.encrypt)
],
where, of course, args.encrypt is either True or False
"""
all_files = [
(offs, filename, args.encrypt) for (offs, filename) in args.addr_filename
]
"""
Now do the same with encrypt_files list, if defined.
In this case, the flag is True
"""
if args.encrypt_files is not None:
encrypted_files_flag = [
(offs, filename, True) for (offs, filename) in args.encrypt_files
]
# Concatenate both lists and sort them.
# As both list are already sorted, we could simply do a merge instead,
# but for the sake of simplicity and because the lists are very small,
# let's use sorted.
all_files = sorted(all_files + encrypted_files_flag, key=lambda x: x[0])
for address, argfile, encrypted in all_files:
compress = args.compress
# Check whether we can compress the current file before flashing
if compress and encrypted:
print("\nWARNING: - compress and encrypt options are mutually exclusive ")
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
)
if len(image) == 0:
print("WARNING: File %s is empty" % argfile.name)
continue
image = _update_image_flash_params(esp, address, args, image)
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)
else:
esp.flash_block(block, seq)
bytes_written += len(block)
bytes_sent += len(block)
image = image[esp.FLASH_WRITE_SIZE :]
seq += 1
if esp.IS_STUB:
# Stub only writes each block to flash after 'ack'ing the receive,
# so do a final dummy operation which will not be 'ack'ed
# until the last block has actually been written out to flash
esp.read_reg(ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR, timeout=timeout)
t = time.time() - t
speed_msg = ""
if compress:
if t > 0.0:
speed_msg = " (effective %.1f kbit/s)" % (uncsize / t * 8 / 1000)
print_overwrite(
"Wrote %d bytes (%d compressed) at 0x%08x in %.1f seconds%s..."
% (uncsize, bytes_sent, address, t, speed_msg),
last_line=True,
)
else:
if t > 0.0:
speed_msg = " (%.1f kbit/s)" % (bytes_written / t * 8 / 1000)
print_overwrite(
"Wrote %d bytes at 0x%08x in %.1f seconds%s..."
% (bytes_written, address, t, speed_msg),
last_line=True,
)
if not encrypted and not esp.secure_download_mode:
try:
res = esp.flash_md5sum(address, uncsize)
if res != calcmd5:
print("File md5: %s" % calcmd5)
print("Flash md5: %s" % res)
print(
"MD5 of 0xFF is %s"
% (hashlib.md5(b"\xFF" * uncsize).hexdigest())
)
raise FatalError("MD5 of file does not match data in flash!")
else:
print("Hash of data verified.")
except NotImplementedInROMError:
pass
print("\nLeaving...")
if esp.IS_STUB:
# skip sending flash_finish to ROM loader here,
# as it causes the loader to exit and run user code
esp.flash_begin(0, 0)
# Get the "encrypted" flag for the last file flashed
# Note: all_files list contains triplets like:
# (address: Integer, filename: String, encrypted: Boolean)
last_file_encrypted = all_files[-1][2]
# Check whether the last file flashed was compressed or not
if args.compress and not last_file_encrypted:
esp.flash_defl_finish(False)
else:
esp.flash_finish(False)
if args.verify:
print("Verifying just-written flash...")
print(
"(This option is deprecated, "
"flash contents are now always read back after flashing.)"
)
# If some encrypted files have been flashed,
# print a warning saying that we won't check them
if args.encrypt or args.encrypt_files is not None:
print("WARNING: - cannot verify encrypted files, they will be ignored")
# Call verify_flash function only if there is at least
# one non-encrypted file flashed
if not args.encrypt:
verify_flash(esp, args)
def image_info(args):
def v2():
def get_key_from_value(dict, val):
"""Get key from value in dictionary"""
for key, value in dict.items():
if value == val:
return key
return None
print()
title = "{} image header".format(args.chip.upper())
print(title)
print("=" * len(title))
print("Image version: {}".format(image.version))
print(
"Entry point: {:#8x}".format(image.entrypoint)
if image.entrypoint != 0
else "Entry point not set"
)
print("Segments: {}".format(len(image.segments)))
# Flash size
flash_s_bits = image.flash_size_freq & 0xF0 # high four bits
flash_s = get_key_from_value(image.ROM_LOADER.FLASH_SIZES, flash_s_bits)
print(
"Flash size: {}".format(flash_s)
if flash_s is not None
else "WARNING: Invalid flash size ({:#02x})".format(flash_s_bits)
)
# Flash frequency
flash_fr_bits = image.flash_size_freq & 0x0F # low four bits
flash_fr = get_key_from_value(image.ROM_LOADER.FLASH_FREQUENCY, flash_fr_bits)
print(
"Flash freq: {}".format(flash_fr)
if flash_fr is not None
else "WARNING: Invalid flash frequency ({:#02x})".format(flash_fr_bits)
)
# Flash mode
flash_mode = get_key_from_value(FLASH_MODES, image.flash_mode)
print(
"Flash mode: {}".format(flash_mode.upper())
if flash_mode is not None
else "WARNING: Invalid flash mode ({})".format(image.flash_mode)
)
# Extended header (ESP32 and later only)
if args.chip != "esp8266":
print()
title = "{} extended image header".format(args.chip.upper())
print(title)
print("=" * len(title))
print("WP pin: {:#02x}".format(image.wp_pin))
print(
"Flash pins drive settings: "
"clk_drv: {:#02x}, q_drv: {:#02x}, d_drv: {:#02x}, "
"cs0_drv: {:#02x}, hd_drv: {:#02x}, wp_drv: {:#02x}".format(
image.clk_drv,
image.q_drv,
image.d_drv,
image.cs_drv,
image.hd_drv,
image.wp_drv,
)
)
print("Chip ID: {}".format(image.chip_id))
print(
"Minimal chip revision: "
f"v{image.min_rev_full // 100}.{image.min_rev_full % 100}, "
f"(legacy min_rev = {image.min_rev})"
)
print(
"Maximal chip revision: "
f"v{image.max_rev_full // 100}.{image.max_rev_full % 100}"
)
print()
# Segments overview
title = "Segments information"
print(title)
print("=" * len(title))
headers_str = "{:>7} {:>7} {:>10} {:>10} {:10}"
print(
headers_str.format(
"Segment", "Length", "Load addr", "File offs", "Memory types"
)
)
print(
"{} {} {} {} {}".format("-" * 7, "-" * 7, "-" * 10, "-" * 10, "-" * 12)
)
format_str = "{:7} {:#07x} {:#010x} {:#010x} {}"
app_desc = None
for idx, seg in enumerate(image.segments, start=1):
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
app_desc = seg.data[:256]
print(
format_str.format(idx, len(seg.data), seg.addr, seg.file_offs, seg_name)
)
print()
# Footer
title = f"{args.chip.upper()} image footer"
print(title)
print("=" * len(title))
calc_checksum = image.calculate_checksum()
print(
"Checksum: {:#02x} ({})".format(
image.checksum,
"valid"
if image.checksum == calc_checksum
else "invalid - calculated {:02x}".format(calc_checksum),
)
)
try:
digest_msg = "Not appended"
if image.append_digest:
is_valid = image.stored_digest == image.calc_digest
digest_msg = "{} ({})".format(
hexify(image.calc_digest, uppercase=False),
"valid" if is_valid else "invalid",
)
print("Validation hash: {}".format(digest_msg))
except AttributeError:
pass # ESP8266 image has no append_digest field
if app_desc:
APP_DESC_STRUCT_FMT = "<II" + "8s" + "32s32s16s16s32s32s" + "80s"
(
magic_word,
secure_version,
reserv1,
version,
project_name,
time,
date,
idf_ver,
app_elf_sha256,
reserv2,
) = struct.unpack(APP_DESC_STRUCT_FMT, app_desc)
if magic_word == 0xABCD5432:
print()
title = "Application information"
print(title)
print("=" * len(title))
print(f'Project name: {project_name.decode("utf-8")}')
print(f'App version: {version.decode("utf-8")}')
print(f'Compile time: {date.decode("utf-8")} {time.decode("utf-8")}')
print(f"ELF file SHA256: {hexify(app_elf_sha256, uppercase=False)}")
print(f'ESP-IDF: {idf_ver.decode("utf-8")}')
print(f"Secure version: {secure_version}")
with open(args.filename, "rb") as f:
# magic number
try:
common_header = f.read(8)
magic = common_header[0]
except IndexError:
raise FatalError("File is empty")
if magic not in [
ESPLoader.ESP_IMAGE_MAGIC,
ESP8266V2FirmwareImage.IMAGE_V2_MAGIC,
]:
raise FatalError(
"This is not a valid image "
"(invalid magic number: {:#x})".format(magic)
)
if args.chip == "auto":
try:
extended_header = f.read(16)
# reserved fields, should all be zero
if int.from_bytes(extended_header[7:-1], "little") != 0:
raise FatalError("Reserved fields not all zero")
# append_digest, either 0 or 1
if extended_header[-1] not in [0, 1]:
raise FatalError("Append digest field not 0 or 1")
chip_id = int.from_bytes(extended_header[4:5], "little")
for rom in [n for n in ROM_LIST if n.CHIP_NAME != "ESP8266"]:
if chip_id == rom.IMAGE_CHIP_ID:
args.chip = rom.CHIP_NAME
break
else:
raise FatalError(f"Unknown image chip ID ({chip_id})")
except FatalError:
args.chip = "esp8266"
print(f"Detected image type: {args.chip.upper()}")
image = LoadFirmwareImage(args.chip, args.filename)
if args.version == "2":
v2()
return
print("Image version: {}".format(image.version))
print(
"Entry point: {:8x}".format(image.entrypoint)
if image.entrypoint != 0
else "Entry point not set"
)
print("{} segments".format(len(image.segments)))
print()
idx = 0
for seg in image.segments:
idx += 1
segs = seg.get_memory_type(image)
seg_name = ",".join(segs)
print("Segment {}: {} [{}]".format(idx, seg, seg_name))
calc_checksum = image.calculate_checksum()
print(
"Checksum: {:02x} ({})".format(
image.checksum,
"valid"
if image.checksum == calc_checksum
else "invalid - calculated {:02x}".format(calc_checksum),
)
)
try:
digest_msg = "Not appended"
if image.append_digest:
is_valid = image.stored_digest == image.calc_digest
digest_msg = "{} ({})".format(
hexify(image.calc_digest, uppercase=False),
"valid" if is_valid else "invalid",
)
print("Validation Hash: {}".format(digest_msg))
except AttributeError:
pass # ESP8266 image has no append_digest field
def make_image(args):
print("Creating {} image...".format(args.chip))
image = ESP8266ROMFirmwareImage()
if len(args.segfile) == 0:
raise FatalError("No segments specified")
if len(args.segfile) != len(args.segaddr):
raise FatalError(
"Number of specified files does not match number of specified addresses"
)
for seg, addr in zip(args.segfile, args.segaddr):
with open(seg, "rb") as f:
data = f.read()
image.segments.append(ImageSegment(addr, data))
image.entrypoint = args.entrypoint
image.save(args.output)
print("Successfully created {} image.".format(args.chip))
def elf2image(args):
e = ELFFile(args.input)
if args.chip == "auto": # Default to ESP8266 for backwards compatibility
args.chip = "esp8266"
print("Creating {} image...".format(args.chip))
if args.chip != "esp8266":
image = CHIP_DEFS[args.chip].BOOTLOADER_IMAGE()
if args.chip == "esp32" and args.secure_pad:
image.secure_pad = "1"
if args.secure_pad_v2:
image.secure_pad = "2"
image.min_rev = args.min_rev
image.min_rev_full = args.min_rev_full
image.max_rev_full = args.max_rev_full
image.append_digest = args.append_digest
elif args.version == "1": # ESP8266
image = ESP8266ROMFirmwareImage()
elif args.version == "2":
image = ESP8266V2FirmwareImage()
else:
image = ESP8266V3FirmwareImage()
image.entrypoint = e.entrypoint
image.flash_mode = FLASH_MODES[args.flash_mode]
if args.flash_mmu_page_size:
image.set_mmu_page_size(flash_size_bytes(args.flash_mmu_page_size))
# ELFSection is a subclass of ImageSegment, so can use interchangeably
image.segments = e.segments if args.use_segments else e.sections
if args.pad_to_size:
image.pad_to_size = flash_size_bytes(args.pad_to_size)
image.flash_size_freq = image.ROM_LOADER.parse_flash_size_arg(args.flash_size)
image.flash_size_freq += image.ROM_LOADER.parse_flash_freq_arg(args.flash_freq)
if args.elf_sha256_offset:
image.elf_sha256 = e.sha256()
image.elf_sha256_offset = args.elf_sha256_offset
before = len(image.segments)
image.merge_adjacent_segments()
if len(image.segments) != before:
delta = before - len(image.segments)
print("Merged %d ELF section%s" % (delta, "s" if delta > 1 else ""))
image.verify()
if args.output is None:
args.output = image.default_output_name(args.input)
image.save(args.output)
print("Successfully created {} image.".format(args.chip))
def read_mac(esp, args):
mac = esp.read_mac()
def print_mac(label, mac):
print("%s: %s" % (label, ":".join(map(lambda x: "%02x" % x, mac))))
print_mac("MAC", mac)
def chip_id(esp, args):
try:
chipid = esp.chip_id()
print("Chip ID: 0x%08x" % chipid)
except NotSupportedError:
print("Warning: %s has no Chip ID. Reading MAC instead." % esp.CHIP_NAME)
read_mac(esp, args)
def erase_flash(esp, args):
if not args.force and esp.CHIP_NAME != "ESP8266" and not esp.secure_download_mode:
if esp.get_flash_encryption_enabled() or esp.get_secure_boot_enabled():
raise FatalError(
"Active security features detected, "
"erasing flash is disabled as a safety measure. "
"Use --force to override, "
"please use with caution, otherwise it may brick your device!"
)
print("Erasing flash (this may take a while)...")
t = time.time()
esp.erase_flash()
print("Chip erase completed successfully in %.1fs" % (time.time() - t))
def erase_region(esp, args):
if not args.force and esp.CHIP_NAME != "ESP8266" and not esp.secure_download_mode:
if esp.get_flash_encryption_enabled() or esp.get_secure_boot_enabled():
raise FatalError(
"Active security features detected, "
"erasing flash is disabled as a safety measure. "
"Use --force to override, "
"please use with caution, otherwise it may brick your device!"
)
print("Erasing region (may be slow depending on size)...")
t = time.time()
esp.erase_region(args.address, args.size)
print("Erase completed successfully in %.1f seconds." % (time.time() - t))
def run(esp, args):
esp.run()
def flash_id(esp, args):
flash_id = esp.flash_id()
print("Manufacturer: %02x" % (flash_id & 0xFF))
flid_lowbyte = (flash_id >> 16) & 0xFF
print("Device: %02x%02x" % ((flash_id >> 8) & 0xFF, flid_lowbyte))
print(
"Detected flash size: %s" % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown"))
)
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}")
def read_flash(esp, args):
if args.no_progress:
flash_progress = None
else:
def flash_progress(progress, length):
msg = "%d (%d %%)" % (progress, progress * 100.0 / length)
padding = "\b" * len(msg)
if progress == length:
padding = "\n"
sys.stdout.write(msg + padding)
sys.stdout.flush()
t = time.time()
data = esp.read_flash(args.address, args.size, flash_progress)
t = time.time() - t
speed_msg = " ({:.1f} kbit/s)".format(len(data) / t * 8 / 1000) if t > 0.0 else ""
print_overwrite(
"Read {:d} bytes at {:#010x} in {:.1f} seconds{}...".format(
len(data), args.address, t, speed_msg
),
last_line=True,
)
with open(args.filename, "wb") as f:
f.write(data)
def verify_flash(esp, args):
differences = False
for address, argfile in args.addr_filename:
image = pad_to(argfile.read(), 4)
argfile.seek(0) # rewind in case we need it again
image = _update_image_flash_params(esp, address, args, image)
image_size = len(image)
print(
"Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s..."
% (image_size, image_size, address, argfile.name)
)
# Try digest first, only read if there are differences.
digest = esp.flash_md5sum(address, image_size)
expected_digest = hashlib.md5(image).hexdigest()
if digest == expected_digest:
print("-- verify OK (digest matched)")
continue
else:
differences = True
if getattr(args, "diff", "no") != "yes":
print("-- verify FAILED (digest mismatch)")
continue
flash = esp.read_flash(address, image_size)
assert flash != image
diff = [i for i in range(image_size) if flash[i] != image[i]]
print(
"-- verify FAILED: %d differences, first @ 0x%08x"
% (len(diff), address + diff[0])
)
for d in diff:
flash_byte = flash[d]
image_byte = image[d]
print(" %08x %02x %02x" % (address + d, flash_byte, image_byte))
if differences:
raise FatalError("Verify failed.")
def read_flash_status(esp, args):
print("Status value: 0x%04x" % esp.read_status(args.bytes))
def write_flash_status(esp, args):
fmt = "0x%%0%dx" % (args.bytes * 2)
args.value = args.value & ((1 << (args.bytes * 8)) - 1)
print(("Initial flash status: " + fmt) % esp.read_status(args.bytes))
print(("Setting flash status: " + fmt) % args.value)
esp.write_status(args.value, args.bytes, args.non_volatile)
print(("After flash status: " + fmt) % esp.read_status(args.bytes))
def get_security_info(esp, args):
si = esp.get_security_info()
# TODO: better display
print("Flags: {:#010x} ({})".format(si["flags"], bin(si["flags"])))
print("Flash_Crypt_Cnt: {:#x}".format(si["flash_crypt_cnt"]))
print("Key_Purposes: {}".format(si["key_purposes"]))
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"]))
def merge_bin(args):
try:
chip_class = CHIP_DEFS[args.chip]
except KeyError:
msg = (
"Please specify the chip argument"
if args.chip == "auto"
else "Invalid chip choice: '{}'".format(args.chip)
)
msg = msg + " (choose from {})".format(", ".join(CHIP_LIST))
raise FatalError(msg)
# sort the files by offset.
# The AddrFilenamePairAction has already checked for overlap
input_files = sorted(args.addr_filename, key=lambda x: x[0])
if not input_files:
raise FatalError("No input files specified")
first_addr = input_files[0][0]
if first_addr < args.target_offset:
raise FatalError(
"Output file target offset is 0x%x. Input file offset 0x%x is before this."
% (args.target_offset, first_addr)
)
if args.format != "raw":
raise FatalError(
"This version of esptool only supports the 'raw' output format"
)
with open(args.output, "wb") as of:
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()))
for addr, argfile in input_files:
pad_to(addr)
image = argfile.read()
image = _update_image_flash_params(chip_class, addr, args, image)
of.write(image)
if args.fill_flash_size:
pad_to(flash_size_bytes(args.fill_flash_size))
print(
"Wrote 0x%x bytes to file %s, ready to flash to offset 0x%x"
% (of.tell(), args.output, args.target_offset)
)
def version(args):
from . import __version__
print(__version__)