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
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
si = inst.get_security_info()
inst.secure_download_mode = si["parsed_flags"]["SECURE_DOWNLOAD_ENABLE"]
inst = check_if_stub(inst)
inst._post_connect()
break
else:
err_msg = f"Unexpected chip ID value {chip_id}."
except (UnsupportedCommandError, struct.error, FatalError):
except (UnsupportedCommandError, FatalError):
# UnsupportedCommandError: ESP8266/ESP32 ROM
# struct.error: ESP32-S2
# FatalError: ESP8266/ESP32 STUB
# FatalError: ESP8266/ESP32 STUB or ESP32-S2
try:
chip_magic_value = detect_port.read_reg(
ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR
@@ -1495,30 +1490,9 @@ def get_security_info(esp: ESPLoader) -> None:
Args:
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()
parsed_flags = si["parsed_flags"]
title = "Security Information:"
log.print(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("API Version: {}".format(si["api_version"]))
flags = si["flags"]
if get_security_flag_status("SECURE_BOOT_EN", flags):
if parsed_flags["SECURE_BOOT_EN"]:
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")
revoked_keys = []
@@ -1552,7 +1524,7 @@ def get_security_info(esp: ESPLoader) -> None:
"SECURE_BOOT_KEY_REVOKE2",
]
):
if get_security_flag_status(key, flags):
if parsed_flags[key]:
revoked_keys.append(i)
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}")
if get_security_flag_status("DIS_DOWNLOAD_DCACHE", flags):
if parsed_flags["DIS_DOWNLOAD_DCACHE"]:
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")
hard_dis_jtag = get_security_flag_status("HARD_DIS_JTAG", flags)
soft_dis_jtag = get_security_flag_status("SOFT_DIS_JTAG", flags)
hard_dis_jtag = parsed_flags["HARD_DIS_JTAG"]
soft_dis_jtag = parsed_flags["SOFT_DIS_JTAG"]
if hard_dis_jtag:
log.print("JTAG: Permanently Disabled")
elif soft_dis_jtag:
log.print("JTAG: Software Access Disabled")
if get_security_flag_status("DIS_USB", flags):
if parsed_flags["DIS_USB"]:
log.print("USB Access: Disabled")

View File

@@ -292,7 +292,7 @@ class ESPLoader(object):
IROM_MAP_END = 0x40300000
# The number of bytes in the UART response that signify command status
STATUS_BYTES_LENGTH = 2
STATUS_BYTES_LENGTH = 4
# Bootloader flashing offset
BOOTLOADER_FLASH_OFFSET = 0x0
@@ -345,9 +345,9 @@ class ESPLoader(object):
# Device-and-runtime-specific cache
self.cache = {
"flash_id": None,
"chip_id": None,
"uart_no": None,
"usb_pid": None,
"security_info": None,
}
if isinstance(port, str):
@@ -773,17 +773,26 @@ class ESPLoader(object):
if not detecting:
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
try:
# get_chip_id() raises FatalError if the chip does not have a chip ID
# (ESP32-S2)
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
# 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
chip_arg_wrong = False
@@ -807,18 +816,14 @@ class ESPLoader(object):
if cls.USES_MAGIC_VALUE and chip_magic_value == cls.MAGIC_VALUE:
detected = cls
break
# If we can't read chip ID and the chip is in SDM (ESP32 or ESP32-S2),
# we can't verify
elif not chip_id and self.secure_download_mode:
if self.CHIP_NAME not in ["ESP32", "ESP32-S2"]:
chip_arg_wrong = True
detected = "ESP32 or 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 we can't read chip ID and the chip is in SDM, it is ESP32-S2
elif (
not chip_id
and self.secure_download_mode
and self.CHIP_NAME != "ESP32-S2"
):
chip_arg_wrong = True
detected = "ESP32-S2"
if chip_arg_wrong:
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)"""
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(
"get security info", self.ESP_CMDS["GET_SECURITY_INFO"], b""
)
esp32s2 = True if len(res) == 12 else False
res = struct.unpack("<IBBBBBBBB" if esp32s2 else "<IBBBBBBBBII", res)
return {
security_info = {
"flags": res[0],
"flash_crypt_cnt": res[1],
"key_purposes": res[2:9],
"chip_id": None if esp32s2 else res[9],
"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):
if self.cache["chip_id"] is None:
res = self.check_command(
"get security info", self.ESP_CMDS["GET_SECURITY_INFO"], b""
chip_id = self.get_security_info()["chip_id"]
if chip_id is None:
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(
"<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"]
return chip_id
def get_uart_no(self):
"""

View File

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