mirror of
https://github.com/espressif/esptool.git
synced 2025-10-16 23:06:31 +08:00
feat(flash_attach): Encapsulate logic for flash attaching and configuration
This commit is contained in:
@@ -44,7 +44,7 @@ The following is an example on how to use esptool as a Python module and leverag
|
||||
description = esp.get_chip_description()
|
||||
features = esp.get_chip_features()
|
||||
print(f"Detected ESP on port {PORT}: {description}")
|
||||
print(f"Features: {", ".join(features)}")
|
||||
print("Features:", ", ".join(features))
|
||||
|
||||
esp = esp.run_stub()
|
||||
with open(BIN_FILE, 'rb') as binary:
|
||||
@@ -60,7 +60,7 @@ The following is an example on how to use esptool as a Python module and leverag
|
||||
# Pad the last block
|
||||
block = block + bytes([0xFF]) * (esp.FLASH_WRITE_SIZE - len(block))
|
||||
esp.flash_block(block, i + FLASH_ADDRESS)
|
||||
progress_callback(float(i + len(block)) / total_size * 100)
|
||||
progress_callback(i / total_size * 100)
|
||||
esp.flash_finish()
|
||||
|
||||
# Reset the chip out of bootloader mode
|
||||
|
@@ -3,6 +3,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
__all__ = [
|
||||
"chip_id",
|
||||
"detect_chip",
|
||||
@@ -11,6 +12,8 @@ __all__ = [
|
||||
"erase_flash",
|
||||
"erase_region",
|
||||
"flash_id",
|
||||
"flash_attach",
|
||||
"flash_set_parameters",
|
||||
"get_security_info",
|
||||
"image_info",
|
||||
"load_ram",
|
||||
@@ -41,7 +44,6 @@ import traceback
|
||||
|
||||
from esptool.bin_image import intel_hex_to_bin
|
||||
from esptool.cmds import (
|
||||
DETECTED_FLASH_SIZES,
|
||||
chip_id,
|
||||
detect_chip,
|
||||
detect_flash_size,
|
||||
@@ -49,6 +51,8 @@ from esptool.cmds import (
|
||||
elf2image,
|
||||
erase_flash,
|
||||
erase_region,
|
||||
flash_attach,
|
||||
flash_set_parameters,
|
||||
flash_id,
|
||||
read_flash_sfdp,
|
||||
get_security_info,
|
||||
@@ -876,163 +880,18 @@ def main(argv=None, esp=None):
|
||||
f"Keeping initial baud rate {initial_baud}"
|
||||
)
|
||||
|
||||
def _define_spi_conn(spi_connection):
|
||||
"""Prepare SPI configuration string and value for flash_spi_attach()"""
|
||||
clk, q, d, hd, cs = spi_connection
|
||||
spi_config_txt = f"CLK:{clk}, Q:{q}, D:{d}, HD:{hd}, CS:{cs}"
|
||||
value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk
|
||||
return spi_config_txt, value
|
||||
|
||||
# Override the common SPI flash parameter stuff if configured to do so
|
||||
if hasattr(args, "spi_connection") and args.spi_connection is not None:
|
||||
spi_config = args.spi_connection
|
||||
if args.spi_connection == "SPI":
|
||||
value = 0
|
||||
elif args.spi_connection == "HSPI":
|
||||
value = 1
|
||||
else:
|
||||
esp.check_spi_connection(args.spi_connection)
|
||||
# Encode the pin numbers as a 32-bit integer with packed 6-bit values,
|
||||
# the same way the ESP ROM takes them
|
||||
spi_config, value = _define_spi_conn(args.spi_connection)
|
||||
log.print(f"Configuring SPI flash mode ({spi_config})...")
|
||||
esp.flash_spi_attach(value)
|
||||
elif args.no_stub:
|
||||
if esp.CHIP_NAME != "ESP32" or esp.secure_download_mode:
|
||||
log.print("Enabling default SPI flash mode...")
|
||||
# ROM loader doesn't enable flash unless we explicitly do it
|
||||
esp.flash_spi_attach(0)
|
||||
else:
|
||||
# ROM doesn't attach in-package flash chips
|
||||
spi_chip_pads = esp.get_chip_spi_pads()
|
||||
spi_config_txt, value = _define_spi_conn(spi_chip_pads)
|
||||
if spi_chip_pads != (0, 0, 0, 0, 0):
|
||||
log.print(
|
||||
"Attaching flash from eFuses' SPI pads configuration "
|
||||
f"({spi_config_txt})..."
|
||||
)
|
||||
else:
|
||||
log.print("Enabling default SPI flash mode...")
|
||||
esp.flash_spi_attach(value)
|
||||
|
||||
# XMC chip startup sequence
|
||||
XMC_VENDOR_ID = 0x20
|
||||
|
||||
def is_xmc_chip_strict():
|
||||
id = esp.flash_id()
|
||||
rdid = ((id & 0xFF) << 16) | ((id >> 16) & 0xFF) | (id & 0xFF00)
|
||||
|
||||
vendor_id = (rdid >> 16) & 0xFF
|
||||
mfid = (rdid >> 8) & 0xFF
|
||||
cpid = rdid & 0xFF
|
||||
|
||||
if vendor_id != XMC_VENDOR_ID:
|
||||
return False
|
||||
|
||||
matched = False
|
||||
if mfid == 0x40:
|
||||
if cpid >= 0x13 and cpid <= 0x20:
|
||||
matched = True
|
||||
elif mfid == 0x41:
|
||||
if cpid >= 0x17 and cpid <= 0x20:
|
||||
matched = True
|
||||
elif mfid == 0x50:
|
||||
if cpid >= 0x15 and cpid <= 0x16:
|
||||
matched = True
|
||||
return matched
|
||||
|
||||
def flash_xmc_startup():
|
||||
# If the RDID value is a valid XMC one, may skip the flow
|
||||
fast_check = True
|
||||
if fast_check and is_xmc_chip_strict():
|
||||
return # Successful XMC flash chip boot-up detected by RDID, skipping.
|
||||
|
||||
sfdp_mfid_addr = 0x10
|
||||
mf_id = esp.read_spiflash_sfdp(sfdp_mfid_addr, 8)
|
||||
if mf_id != XMC_VENDOR_ID: # Non-XMC chip detected by SFDP Read, skipping.
|
||||
return
|
||||
|
||||
log.warning(
|
||||
"XMC flash chip boot-up failure detected! "
|
||||
"Running XMC25QHxxC startup flow."
|
||||
)
|
||||
esp.run_spiflash_command(0xB9) # Enter DPD
|
||||
esp.run_spiflash_command(0x79) # Enter UDPD
|
||||
esp.run_spiflash_command(0xFF) # Exit UDPD
|
||||
time.sleep(0.002) # Delay tXUDPD
|
||||
esp.run_spiflash_command(0xAB) # Release Power-Down
|
||||
time.sleep(0.00002)
|
||||
# Check for success
|
||||
if not is_xmc_chip_strict():
|
||||
log.warning("XMC flash boot-up fix failed.")
|
||||
log.print("XMC flash chip boot-up fix successful!")
|
||||
|
||||
# Check flash chip connection
|
||||
if not esp.secure_download_mode:
|
||||
try:
|
||||
flash_id = esp.flash_id()
|
||||
if flash_id in (0xFFFFFF, 0x000000):
|
||||
log.warning(
|
||||
"Failed to communicate with the flash chip, "
|
||||
"read/write operations will fail. "
|
||||
"Try checking the chip connections or removing "
|
||||
"any other hardware connected to IOs."
|
||||
)
|
||||
if (
|
||||
hasattr(args, "spi_connection")
|
||||
and args.spi_connection is not None
|
||||
):
|
||||
log.print(
|
||||
"Some GPIO pins might be used by other peripherals, "
|
||||
"try using another --spi-connection combination."
|
||||
)
|
||||
|
||||
except FatalError as e:
|
||||
raise FatalError(f"Unable to verify flash chip connection ({e}).")
|
||||
|
||||
# Check if XMC SPI flash chip booted-up successfully, fix if not
|
||||
if not esp.secure_download_mode:
|
||||
try:
|
||||
flash_xmc_startup()
|
||||
except FatalError as e:
|
||||
esp.trace(f"Unable to perform XMC flash chip startup sequence ({e}).")
|
||||
|
||||
flash_attach(esp, args.spi_connection if hasattr(args, "spi_connection") else None)
|
||||
if hasattr(args, "flash_size"):
|
||||
log.print("Configuring flash size...")
|
||||
if args.flash_size == "detect":
|
||||
flash_size = detect_flash_size(esp, args)
|
||||
elif args.flash_size == "keep":
|
||||
flash_size = detect_flash_size(esp, args=None)
|
||||
if not esp.IS_STUB:
|
||||
log.warning(
|
||||
"In case of failure, please set a specific --flash_size."
|
||||
)
|
||||
else:
|
||||
flash_size = args.flash_size
|
||||
|
||||
if flash_size is not None: # Secure download mode
|
||||
esp.flash_set_parameters(flash_size_bytes(flash_size))
|
||||
# Check if stub supports chosen flash size
|
||||
if (
|
||||
esp.IS_STUB
|
||||
and esp.CHIP_NAME != "ESP32-S3"
|
||||
and flash_size_bytes(flash_size) > 16 * 1024 * 1024
|
||||
):
|
||||
log.note(
|
||||
"Flasher stub doesn't fully support flash size larger "
|
||||
"than 16MB, in case of failure use --no-stub."
|
||||
)
|
||||
args.flash_size = flash_set_parameters(esp, args.flash_size)
|
||||
|
||||
# Detect flash size if "ALL" is specified
|
||||
if getattr(args, "size", "") == "all":
|
||||
if esp.secure_download_mode:
|
||||
raise FatalError(
|
||||
"Detecting flash size is not supported in secure download mode. "
|
||||
"Set an exact size value."
|
||||
)
|
||||
# detect flash size
|
||||
flash_id = esp.flash_id()
|
||||
size_id = flash_id >> 16
|
||||
size_str = DETECTED_FLASH_SIZES.get(size_id)
|
||||
size_str = detect_flash_size(esp)
|
||||
if size_str is None:
|
||||
raise FatalError(
|
||||
"Detecting flash size failed. Set an exact size value."
|
||||
@@ -1040,7 +899,8 @@ def main(argv=None, esp=None):
|
||||
log.print(f"Detected flash size: {size_str}")
|
||||
args.size = flash_size_bytes(size_str)
|
||||
|
||||
if esp.IS_STUB and hasattr(args, "address") and hasattr(args, "size"):
|
||||
# Check if we are writing past 16MB boundary
|
||||
if esp.IS_STUB and args.operation != "dump_mem" and hasattr(args, "address") and hasattr(args, "size"):
|
||||
if esp.CHIP_NAME != "ESP32-S3" and args.address + args.size > 0x1000000:
|
||||
log.note(
|
||||
"Flasher stub doesn't fully support flash size larger "
|
||||
|
205
esptool/cmds.py
205
esptool/cmds.py
@@ -218,29 +218,15 @@ def dump_mem(esp, args):
|
||||
log.print("Done!")
|
||||
|
||||
|
||||
def detect_flash_size(esp, args=None):
|
||||
# TODO: Remove the dependency on args in the next major release (v5.0)
|
||||
def detect_flash_size(esp):
|
||||
if esp.secure_download_mode:
|
||||
if args is not None and args.flash_size == "detect":
|
||||
raise FatalError(
|
||||
"Detecting flash size is not supported in secure download mode. "
|
||||
"Need to manually specify flash size."
|
||||
)
|
||||
else:
|
||||
return None
|
||||
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
|
||||
flash_size = DETECTED_FLASH_SIZES.get(size_id)
|
||||
if args is not None and args.flash_size == "detect":
|
||||
if flash_size is None:
|
||||
flash_size = "4MB"
|
||||
log.warning(
|
||||
"Could not auto-detect Flash size "
|
||||
f"(FlashID={flash_id:#x}, SizeID={size_id:#x}), defaulting to 4MB"
|
||||
)
|
||||
else:
|
||||
log.print("Auto-detected Flash size:", flash_size)
|
||||
args.flash_size = flash_size
|
||||
return flash_size
|
||||
|
||||
|
||||
@@ -276,7 +262,7 @@ def _update_image_flash_params(esp, address, args, image):
|
||||
test_image.verify()
|
||||
except Exception:
|
||||
log.warning(
|
||||
"Image file at 0x{address:x} is not a valid {esp.CHIP_NAME} image,"
|
||||
f"Image file at 0x{address:x} is not a valid {esp.CHIP_NAME} image,"
|
||||
" so not changing any flash settings."
|
||||
)
|
||||
return image
|
||||
@@ -1181,6 +1167,179 @@ def chip_id(esp, args):
|
||||
read_mac(esp, args)
|
||||
|
||||
|
||||
def flash_attach(esp, spi_connection=None):
|
||||
def _define_spi_conn(spi_connection):
|
||||
"""Prepare SPI configuration string and value for flash_spi_attach()"""
|
||||
clk, q, d, hd, cs = spi_connection
|
||||
spi_config_txt = f"CLK:{clk}, Q:{q}, D:{d}, HD:{hd}, CS:{cs}"
|
||||
value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk
|
||||
return spi_config_txt, value
|
||||
|
||||
# Override the common SPI flash parameter stuff if configured to do so
|
||||
if spi_connection is not None:
|
||||
spi_config = spi_connection
|
||||
if spi_connection == "SPI":
|
||||
value = 0
|
||||
elif spi_connection == "HSPI":
|
||||
value = 1
|
||||
else:
|
||||
esp.check_spi_connection(spi_connection)
|
||||
# Encode the pin numbers as a 32-bit integer with packed 6-bit values,
|
||||
# the same way the ESP ROM takes them
|
||||
spi_config, value = _define_spi_conn(spi_connection)
|
||||
log.print(f"Configuring SPI flash mode ({spi_config})...")
|
||||
esp.flash_spi_attach(value)
|
||||
elif not esp.IS_STUB:
|
||||
if esp.CHIP_NAME != "ESP32" or esp.secure_download_mode:
|
||||
log.print("Enabling default SPI flash mode...")
|
||||
# ROM loader doesn't enable flash unless we explicitly do it
|
||||
esp.flash_spi_attach(0)
|
||||
else:
|
||||
# ROM doesn't attach in-package flash chips
|
||||
spi_chip_pads = esp.get_chip_spi_pads()
|
||||
spi_config_txt, value = _define_spi_conn(spi_chip_pads)
|
||||
if spi_chip_pads != (0, 0, 0, 0, 0):
|
||||
log.print(
|
||||
"Attaching flash from eFuses' SPI pads configuration "
|
||||
f"({spi_config_txt})..."
|
||||
)
|
||||
else:
|
||||
log.print("Enabling default SPI flash mode...")
|
||||
esp.flash_spi_attach(value)
|
||||
|
||||
# XMC chip startup sequence
|
||||
XMC_VENDOR_ID = 0x20
|
||||
|
||||
def is_xmc_chip_strict():
|
||||
# Read ID without cache, because it should be different after the XMC startup
|
||||
id = esp.flash_id(cache=False)
|
||||
rdid = ((id & 0xFF) << 16) | ((id >> 16) & 0xFF) | (id & 0xFF00)
|
||||
|
||||
vendor_id = (rdid >> 16) & 0xFF
|
||||
mfid = (rdid >> 8) & 0xFF
|
||||
cpid = rdid & 0xFF
|
||||
|
||||
if vendor_id != XMC_VENDOR_ID:
|
||||
return False
|
||||
|
||||
matched = False
|
||||
if mfid == 0x40:
|
||||
if cpid >= 0x13 and cpid <= 0x20:
|
||||
matched = True
|
||||
elif mfid == 0x41:
|
||||
if cpid >= 0x17 and cpid <= 0x20:
|
||||
matched = True
|
||||
elif mfid == 0x50:
|
||||
if cpid >= 0x15 and cpid <= 0x16:
|
||||
matched = True
|
||||
return matched
|
||||
|
||||
def flash_xmc_startup():
|
||||
# If the RDID value is a valid XMC one, may skip the flow
|
||||
fast_check = True
|
||||
if fast_check and is_xmc_chip_strict():
|
||||
return # Successful XMC flash chip boot-up detected by RDID, skipping.
|
||||
|
||||
sfdp_mfid_addr = 0x10
|
||||
mf_id = esp.read_spiflash_sfdp(sfdp_mfid_addr, 8)
|
||||
if mf_id != XMC_VENDOR_ID: # Non-XMC chip detected by SFDP Read, skipping.
|
||||
return
|
||||
|
||||
log.warning(
|
||||
"XMC flash chip boot-up failure detected! "
|
||||
"Running XMC25QHxxC startup flow."
|
||||
)
|
||||
esp.run_spiflash_command(0xB9) # Enter DPD
|
||||
esp.run_spiflash_command(0x79) # Enter UDPD
|
||||
esp.run_spiflash_command(0xFF) # Exit UDPD
|
||||
time.sleep(0.002) # Delay tXUDPD
|
||||
esp.run_spiflash_command(0xAB) # Release Power-Down
|
||||
time.sleep(0.00002)
|
||||
# Check for success
|
||||
if not is_xmc_chip_strict():
|
||||
log.warning("XMC flash boot-up fix failed.")
|
||||
log.print("XMC flash chip boot-up fix successful!")
|
||||
|
||||
# Check if XMC SPI flash chip booted-up successfully, fix if not
|
||||
if not esp.secure_download_mode:
|
||||
try:
|
||||
flash_xmc_startup()
|
||||
except FatalError as e:
|
||||
esp.trace(f"Unable to perform XMC flash chip startup sequence ({e}).")
|
||||
|
||||
# Check flash chip connection
|
||||
if not esp.secure_download_mode:
|
||||
try:
|
||||
flash_id = esp.flash_id()
|
||||
if flash_id in (0xFFFFFF, 0x000000):
|
||||
log.warning(
|
||||
"Failed to communicate with the flash chip, "
|
||||
"read/write operations will fail. "
|
||||
"Try checking the chip connections or removing "
|
||||
"any other hardware connected to IOs."
|
||||
)
|
||||
if spi_connection is not None:
|
||||
log.note(
|
||||
"Some GPIO pins might be used by other peripherals, try using "
|
||||
"another combination of pins for SPI flash connection."
|
||||
)
|
||||
|
||||
except FatalError as e:
|
||||
raise FatalError(f"Unable to verify flash chip connection ({e}).")
|
||||
|
||||
|
||||
def flash_set_parameters(esp, flash_size):
|
||||
"""
|
||||
Configures ESP chip flash memory parameters based on the selected flash size.
|
||||
|
||||
Needs to be called after the flash_attach() and before the read_flash()
|
||||
and write_flash() operations.
|
||||
|
||||
flash_size arg handles three operational modes:
|
||||
- "detect": Auto-detects flash size with fallback to 4MB
|
||||
- "keep": Auto-detects flash size, skips setting flash parameters in SDM
|
||||
- Explicit size: Uses directly specified flash size
|
||||
|
||||
Returns the final flash size value used for configuration:
|
||||
- Auto-detected value in "detect" mode (fallback to "4MB")
|
||||
- Original argument value in other modes ("keep" or explicit size)
|
||||
"""
|
||||
|
||||
log.print("Configuring flash size...")
|
||||
keep = flash_size == "keep"
|
||||
|
||||
# Determine flash size
|
||||
if flash_size == "detect":
|
||||
flash_size = detect_flash_size(esp)
|
||||
if flash_size is None:
|
||||
log.warning("Could not auto-detect flash size, defaulting to 4MB.")
|
||||
flash_size = "4MB"
|
||||
else:
|
||||
log.print(f"Auto-detected flash size: {flash_size}")
|
||||
elif flash_size == "keep":
|
||||
# Set flash size will not change in image header,
|
||||
# but the flash chip should be configured with the real size if possible
|
||||
flash_size = None if esp.secure_download_mode else detect_flash_size(esp)
|
||||
if not esp.IS_STUB:
|
||||
log.warning("In case of failure, please set a specific flash size.")
|
||||
|
||||
# Set flash parameters
|
||||
if flash_size is not None: # Not "keep" in secure download mode
|
||||
esp.flash_set_parameters(flash_size_bytes(flash_size))
|
||||
# Check if stub supports chosen flash size
|
||||
if (
|
||||
esp.IS_STUB
|
||||
and esp.CHIP_NAME != "ESP32-S3"
|
||||
and flash_size_bytes(flash_size) > 16 * 1024 * 1024
|
||||
):
|
||||
log.note(
|
||||
"Flasher stub doesn't fully support flash size larger "
|
||||
"than 16MB, disable it in case of failure."
|
||||
)
|
||||
|
||||
return "keep" if keep else flash_size
|
||||
|
||||
|
||||
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():
|
||||
@@ -1234,11 +1393,11 @@ def run(esp, args):
|
||||
|
||||
def detect_flash_id(esp):
|
||||
flash_id = esp.flash_id()
|
||||
log.print("Manufacturer: %02x" % (flash_id & 0xFF))
|
||||
log.print(f"Manufacturer: {flash_id & 0xFF:02x}")
|
||||
flid_lowbyte = (flash_id >> 16) & 0xFF
|
||||
log.print("Device: %02x%02x" % ((flash_id >> 8) & 0xFF, flid_lowbyte))
|
||||
log.print(f"Device: {(flash_id >> 8) & 0xFF:02x}{flid_lowbyte:02x}")
|
||||
log.print(
|
||||
"Detected flash size: %s" % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown"))
|
||||
f"Detected flash size: {DETECTED_FLASH_SIZES.get(flid_lowbyte, 'Unknown')}"
|
||||
)
|
||||
|
||||
|
||||
|
@@ -1017,9 +1017,9 @@ class ESPLoader(object):
|
||||
self.flash_begin(0, 0)
|
||||
self.flash_finish(reboot)
|
||||
|
||||
def flash_id(self):
|
||||
"""Read SPI flash manufacturer and device id"""
|
||||
if self.cache["flash_id"] is None:
|
||||
def flash_id(self, cache=True):
|
||||
"""Read SPI flash manufacturer and device ID"""
|
||||
if not cache or self.cache["flash_id"] is None:
|
||||
SPIFLASH_RDID = 0x9F
|
||||
self.cache["flash_id"] = self.run_spiflash_command(SPIFLASH_RDID, b"", 24)
|
||||
return self.cache["flash_id"]
|
||||
|
Reference in New Issue
Block a user