fix: enable ESP32-P4 ECO5 chip detection

Register was read in order to detect SDM, this cannot be done with new
ESP32-P4 ECO5, because it has different address space and crashes
when trying to read current magic address. This also improves detection
for ESP32-S2 as is is only chip that has SDM support and is detected
by reading magic address.
This commit is contained in:
Jaroslav Burian
2025-06-20 16:29:41 +02:00
committed by Radim Karniš
parent 9cb4e7de81
commit 0b3460fa12
3 changed files with 98 additions and 73 deletions

View File

@@ -150,21 +150,16 @@ def detect_chip(
continue continue
if chip_id == cls.IMAGE_CHIP_ID: if chip_id == cls.IMAGE_CHIP_ID:
inst = cls(detect_port._port, baud, trace_enabled=trace_enabled) inst = cls(detect_port._port, baud, trace_enabled=trace_enabled)
try: si = inst.get_security_info()
inst.read_reg( inst.secure_download_mode = si["parsed_flags"]["SECURE_DOWNLOAD_ENABLE"]
ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR
) # Dummy read to check Secure Download mode
except UnsupportedCommandError:
inst.secure_download_mode = True
inst = check_if_stub(inst) inst = check_if_stub(inst)
inst._post_connect() inst._post_connect()
break break
else: else:
err_msg = f"Unexpected chip ID value {chip_id}." err_msg = f"Unexpected chip ID value {chip_id}."
except (UnsupportedCommandError, struct.error, FatalError): except (UnsupportedCommandError, FatalError):
# UnsupportedCommandError: ESP8266/ESP32 ROM # UnsupportedCommandError: ESP8266/ESP32 ROM
# struct.error: ESP32-S2 # FatalError: ESP8266/ESP32 STUB or ESP32-S2
# FatalError: ESP8266/ESP32 STUB
try: try:
chip_magic_value = detect_port.read_reg( chip_magic_value = detect_port.read_reg(
ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR
@@ -1495,30 +1490,9 @@ def get_security_info(esp: ESPLoader) -> None:
Args: Args:
esp: Initiated esp object connected to a real device. esp: Initiated esp object connected to a real device.
""" """
# The following mapping was taken from the ROM code
# This mapping is same across all targets in the ROM
SECURITY_INFO_FLAG_MAP = {
"SECURE_BOOT_EN": (1 << 0),
"SECURE_BOOT_AGGRESSIVE_REVOKE": (1 << 1),
"SECURE_DOWNLOAD_ENABLE": (1 << 2),
"SECURE_BOOT_KEY_REVOKE0": (1 << 3),
"SECURE_BOOT_KEY_REVOKE1": (1 << 4),
"SECURE_BOOT_KEY_REVOKE2": (1 << 5),
"SOFT_DIS_JTAG": (1 << 6),
"HARD_DIS_JTAG": (1 << 7),
"DIS_USB": (1 << 8),
"DIS_DOWNLOAD_DCACHE": (1 << 9),
"DIS_DOWNLOAD_ICACHE": (1 << 10),
}
# Get the status of respective security flag
def get_security_flag_status(flag_name, flags_value):
try:
return (flags_value & SECURITY_INFO_FLAG_MAP[flag_name]) != 0
except KeyError:
raise ValueError(f"Invalid flag name: {flag_name}")
si = esp.get_security_info() si = esp.get_security_info()
parsed_flags = si["parsed_flags"]
title = "Security Information:" title = "Security Information:"
log.print(title) log.print(title)
log.print("=" * len(title)) log.print("=" * len(title))
@@ -1537,11 +1511,9 @@ def get_security_info(esp: ESPLoader) -> None:
log.print("Chip ID: {}".format(si["chip_id"])) log.print("Chip ID: {}".format(si["chip_id"]))
log.print("API Version: {}".format(si["api_version"])) log.print("API Version: {}".format(si["api_version"]))
flags = si["flags"] if parsed_flags["SECURE_BOOT_EN"]:
if get_security_flag_status("SECURE_BOOT_EN", flags):
log.print("Secure Boot: Enabled") log.print("Secure Boot: Enabled")
if get_security_flag_status("SECURE_BOOT_AGGRESSIVE_REVOKE", flags): if parsed_flags["SECURE_BOOT_AGGRESSIVE_REVOKE"]:
log.print("Secure Boot Aggressive key revocation: Enabled") log.print("Secure Boot Aggressive key revocation: Enabled")
revoked_keys = [] revoked_keys = []
@@ -1552,7 +1524,7 @@ def get_security_info(esp: ESPLoader) -> None:
"SECURE_BOOT_KEY_REVOKE2", "SECURE_BOOT_KEY_REVOKE2",
] ]
): ):
if get_security_flag_status(key, flags): if parsed_flags[key]:
revoked_keys.append(i) revoked_keys.append(i)
if len(revoked_keys) > 0: if len(revoked_keys) > 0:
@@ -1575,19 +1547,19 @@ def get_security_info(esp: ESPLoader) -> None:
log.print(f"{CRYPT_CNT_STRING}: {si['flash_crypt_cnt']:#x}") log.print(f"{CRYPT_CNT_STRING}: {si['flash_crypt_cnt']:#x}")
if get_security_flag_status("DIS_DOWNLOAD_DCACHE", flags): if parsed_flags["DIS_DOWNLOAD_DCACHE"]:
log.print("Dcache in UART download mode: Disabled") log.print("Dcache in UART download mode: Disabled")
if get_security_flag_status("DIS_DOWNLOAD_ICACHE", flags): if parsed_flags["DIS_DOWNLOAD_ICACHE"]:
log.print("Icache in UART download mode: Disabled") log.print("Icache in UART download mode: Disabled")
hard_dis_jtag = get_security_flag_status("HARD_DIS_JTAG", flags) hard_dis_jtag = parsed_flags["HARD_DIS_JTAG"]
soft_dis_jtag = get_security_flag_status("SOFT_DIS_JTAG", flags) soft_dis_jtag = parsed_flags["SOFT_DIS_JTAG"]
if hard_dis_jtag: if hard_dis_jtag:
log.print("JTAG: Permanently Disabled") log.print("JTAG: Permanently Disabled")
elif soft_dis_jtag: elif soft_dis_jtag:
log.print("JTAG: Software Access Disabled") log.print("JTAG: Software Access Disabled")
if get_security_flag_status("DIS_USB", flags): if parsed_flags["DIS_USB"]:
log.print("USB Access: Disabled") log.print("USB Access: Disabled")

View File

@@ -292,7 +292,7 @@ class ESPLoader(object):
IROM_MAP_END = 0x40300000 IROM_MAP_END = 0x40300000
# The number of bytes in the UART response that signify command status # The number of bytes in the UART response that signify command status
STATUS_BYTES_LENGTH = 2 STATUS_BYTES_LENGTH = 4
# Bootloader flashing offset # Bootloader flashing offset
BOOTLOADER_FLASH_OFFSET = 0x0 BOOTLOADER_FLASH_OFFSET = 0x0
@@ -345,9 +345,9 @@ class ESPLoader(object):
# Device-and-runtime-specific cache # Device-and-runtime-specific cache
self.cache = { self.cache = {
"flash_id": None, "flash_id": None,
"chip_id": None,
"uart_no": None, "uart_no": None,
"usb_pid": None, "usb_pid": None,
"security_info": None,
} }
if isinstance(port, str): if isinstance(port, str):
@@ -773,17 +773,26 @@ class ESPLoader(object):
if not detecting: if not detecting:
from .targets import ROM_LIST from .targets import ROM_LIST
# Perform a dummy read_reg to check if the chip is in secure download mode
try:
chip_magic_value = self.read_reg(ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR)
except UnsupportedCommandError:
self.secure_download_mode = True
# Check if chip supports reading chip ID from the get-security-info command # Check if chip supports reading chip ID from the get-security-info command
try: try:
# get_chip_id() raises FatalError if the chip does not have a chip ID
# (ESP32-S2)
chip_id = self.get_chip_id() chip_id = self.get_chip_id()
except (UnsupportedCommandError, struct.error, FatalError): si = self.get_security_info()
self.secure_download_mode = si["parsed_flags"]["SECURE_DOWNLOAD_ENABLE"]
except (UnsupportedCommandError, FatalError):
chip_id = None chip_id = None
# Try to read the chip magic value to verify the chip type
# (ESP8266, ESP32, ESP32-S2)
try:
chip_magic_value = self.read_reg(
ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR
)
except UnsupportedCommandError:
# If the chip does not support reading the chip magic value,
# it is ESP32-S2 in SDM
chip_magic_value = None
self.secure_download_mode = True
detected = None detected = None
chip_arg_wrong = False chip_arg_wrong = False
@@ -807,18 +816,14 @@ class ESPLoader(object):
if cls.USES_MAGIC_VALUE and chip_magic_value == cls.MAGIC_VALUE: if cls.USES_MAGIC_VALUE and chip_magic_value == cls.MAGIC_VALUE:
detected = cls detected = cls
break break
# If we can't read chip ID and the chip is in SDM (ESP32 or ESP32-S2), # If we can't read chip ID and the chip is in SDM, it is ESP32-S2
# we can't verify elif (
elif not chip_id and self.secure_download_mode: not chip_id
if self.CHIP_NAME not in ["ESP32", "ESP32-S2"]: and self.secure_download_mode
and self.CHIP_NAME != "ESP32-S2"
):
chip_arg_wrong = True chip_arg_wrong = True
detected = "ESP32 or ESP32-S2" detected = "ESP32-S2"
else:
log.note(
f"Can't verify this chip is {self.CHIP_NAME} "
"because of active Secure Download Mode. "
"Please check it manually."
)
if chip_arg_wrong: if chip_arg_wrong:
if warnings and detected is None: if warnings and detected is None:
@@ -1051,30 +1056,76 @@ class ESPLoader(object):
"""Read flash type bit field from eFuse. Returns 0, 1, None (not present)""" """Read flash type bit field from eFuse. Returns 0, 1, None (not present)"""
return None # not implemented for all chip targets return None # not implemented for all chip targets
def get_security_info(self): def get_security_info(self, cache=True):
"""
Get security information from the ESP device including flags,
flash encryption count,
key purposes, chip ID, and API version.
The security info command response format according to the ESP32-S3
documentation:
- 32 bits flags
- 1 byte flash_crypt_cnt
- 7x1 byte key_purposes
- 32-bit word chip_id (ESP32-S3 and later)
- 32-bit word eco_version/api_version (ESP32-S3 and later)
Returns a dictionary with parsed security information and individual
flag status.
"""
if cache and self.cache["security_info"] is not None:
return self.cache["security_info"]
# The following mapping was taken from the ROM code
# This mapping is same across all targets in the ROM
SECURITY_INFO_FLAG_MAP = {
"SECURE_BOOT_EN": (1 << 0),
"SECURE_BOOT_AGGRESSIVE_REVOKE": (1 << 1),
"SECURE_DOWNLOAD_ENABLE": (1 << 2),
"SECURE_BOOT_KEY_REVOKE0": (1 << 3),
"SECURE_BOOT_KEY_REVOKE1": (1 << 4),
"SECURE_BOOT_KEY_REVOKE2": (1 << 5),
"SOFT_DIS_JTAG": (1 << 6),
"HARD_DIS_JTAG": (1 << 7),
"DIS_USB": (1 << 8),
"DIS_DOWNLOAD_DCACHE": (1 << 9),
"DIS_DOWNLOAD_ICACHE": (1 << 10),
}
def parse_security_flags(flags_value):
"""Parse security flags into individual boolean values"""
parsed_flags = {}
for flag_name, flag_mask in SECURITY_INFO_FLAG_MAP.items():
parsed_flags[flag_name] = (flags_value & flag_mask) != 0
return parsed_flags
res = self.check_command( res = self.check_command(
"get security info", self.ESP_CMDS["GET_SECURITY_INFO"], b"" "get security info", self.ESP_CMDS["GET_SECURITY_INFO"], b""
) )
esp32s2 = True if len(res) == 12 else False esp32s2 = True if len(res) == 12 else False
res = struct.unpack("<IBBBBBBBB" if esp32s2 else "<IBBBBBBBBII", res) res = struct.unpack("<IBBBBBBBB" if esp32s2 else "<IBBBBBBBBII", res)
return {
security_info = {
"flags": res[0], "flags": res[0],
"flash_crypt_cnt": res[1], "flash_crypt_cnt": res[1],
"key_purposes": res[2:9], "key_purposes": res[2:9],
"chip_id": None if esp32s2 else res[9], "chip_id": None if esp32s2 else res[9],
"api_version": None if esp32s2 else res[10], "api_version": None if esp32s2 else res[10],
"parsed_flags": parse_security_flags(res[0]),
} }
self.cache["security_info"] = security_info
return security_info
def get_chip_id(self): def get_chip_id(self):
if self.cache["chip_id"] is None: chip_id = self.get_security_info()["chip_id"]
res = self.check_command( if chip_id is None:
"get security info", self.ESP_CMDS["GET_SECURITY_INFO"], b"" raise FatalError(
"Security info command does not contain chip ID. "
"This is expected for ESP32-S2 which doesn't support chip ID "
"in security info."
) )
res = struct.unpack( return chip_id
"<IBBBBBBBBI", res[:16]
) # 4b flags, 1b flash_crypt_cnt, 7*1b key_purposes, 4b chip_id
self.cache["chip_id"] = res[9] # 2/4 status bytes invariant
return self.cache["chip_id"]
def get_uart_no(self): def get_uart_no(self):
""" """

View File

@@ -29,6 +29,8 @@ class ESP8266ROM(ESPLoader):
UART_CLKDIV_REG = 0x60000014 UART_CLKDIV_REG = 0x60000014
STATUS_BYTES_LENGTH = 2
XTAL_CLK_DIVIDER = 2 XTAL_CLK_DIVIDER = 2
FLASH_SIZES = { FLASH_SIZES = {