@@ -292,7 +292,7 @@ class ESPLoader(object):
292292 IROM_MAP_END = 0x40300000
293293
294294 # The number of bytes in the UART response that signify command status
295- STATUS_BYTES_LENGTH = 2
295+ STATUS_BYTES_LENGTH = 4
296296
297297 # Bootloader flashing offset
298298 BOOTLOADER_FLASH_OFFSET = 0x0
@@ -345,9 +345,9 @@ def __init__(self, port=DEFAULT_PORT, baud=ESP_ROM_BAUD, trace_enabled=False):
345345 # Device-and-runtime-specific cache
346346 self .cache = {
347347 "flash_id" : None ,
348- "chip_id" : None ,
349348 "uart_no" : None ,
350349 "usb_pid" : None ,
350+ "security_info" : None ,
351351 }
352352
353353 if isinstance (port , str ):
@@ -773,17 +773,26 @@ def connect(
773773 if not detecting :
774774 from .targets import ROM_LIST
775775
776- # Perform a dummy read_reg to check if the chip is in secure download mode
777- try :
778- chip_magic_value = self .read_reg (ESPLoader .CHIP_DETECT_MAGIC_REG_ADDR )
779- except UnsupportedCommandError :
780- self .secure_download_mode = True
781-
782776 # Check if chip supports reading chip ID from the get-security-info command
783777 try :
778+ # get_chip_id() raises FatalError if the chip does not have a chip ID
779+ # (ESP32-S2)
784780 chip_id = self .get_chip_id ()
785- except (UnsupportedCommandError , struct .error , FatalError ):
781+ si = self .get_security_info ()
782+ self .secure_download_mode = si ["parsed_flags" ]["SECURE_DOWNLOAD_ENABLE" ]
783+ except (UnsupportedCommandError , FatalError ):
786784 chip_id = None
785+ # Try to read the chip magic value to verify the chip type
786+ # (ESP8266, ESP32, ESP32-S2)
787+ try :
788+ chip_magic_value = self .read_reg (
789+ ESPLoader .CHIP_DETECT_MAGIC_REG_ADDR
790+ )
791+ except UnsupportedCommandError :
792+ # If the chip does not support reading the chip magic value,
793+ # it is ESP32-S2 in SDM
794+ chip_magic_value = None
795+ self .secure_download_mode = True
787796
788797 detected = None
789798 chip_arg_wrong = False
@@ -807,18 +816,14 @@ def connect(
807816 if cls .USES_MAGIC_VALUE and chip_magic_value == cls .MAGIC_VALUE :
808817 detected = cls
809818 break
810- # If we can't read chip ID and the chip is in SDM (ESP32 or ESP32-S2),
811- # we can't verify
812- elif not chip_id and self .secure_download_mode :
813- if self .CHIP_NAME not in ["ESP32" , "ESP32-S2" ]:
814- chip_arg_wrong = True
815- detected = "ESP32 or ESP32-S2"
816- else :
817- log .note (
818- f"Can't verify this chip is { self .CHIP_NAME } "
819- "because of active Secure Download Mode. "
820- "Please check it manually."
821- )
819+ # If we can't read chip ID and the chip is in SDM, it is ESP32-S2
820+ elif (
821+ not chip_id
822+ and self .secure_download_mode
823+ and self .CHIP_NAME != "ESP32-S2"
824+ ):
825+ chip_arg_wrong = True
826+ detected = "ESP32-S2"
822827
823828 if chip_arg_wrong :
824829 if warnings and detected is None :
@@ -1051,30 +1056,76 @@ def flash_type(self):
10511056 """Read flash type bit field from eFuse. Returns 0, 1, None (not present)"""
10521057 return None # not implemented for all chip targets
10531058
1054- def get_security_info (self ):
1059+ def get_security_info (self , cache = True ):
1060+ """
1061+ Get security information from the ESP device including flags,
1062+ flash encryption count,
1063+ key purposes, chip ID, and API version.
1064+
1065+ The security info command response format according to the ESP32-S3
1066+ documentation:
1067+ - 32 bits flags
1068+ - 1 byte flash_crypt_cnt
1069+ - 7x1 byte key_purposes
1070+ - 32-bit word chip_id (ESP32-S3 and later)
1071+ - 32-bit word eco_version/api_version (ESP32-S3 and later)
1072+
1073+ Returns a dictionary with parsed security information and individual
1074+ flag status.
1075+ """
1076+ if cache and self .cache ["security_info" ] is not None :
1077+ return self .cache ["security_info" ]
1078+
1079+ # The following mapping was taken from the ROM code
1080+ # This mapping is same across all targets in the ROM
1081+ SECURITY_INFO_FLAG_MAP = {
1082+ "SECURE_BOOT_EN" : (1 << 0 ),
1083+ "SECURE_BOOT_AGGRESSIVE_REVOKE" : (1 << 1 ),
1084+ "SECURE_DOWNLOAD_ENABLE" : (1 << 2 ),
1085+ "SECURE_BOOT_KEY_REVOKE0" : (1 << 3 ),
1086+ "SECURE_BOOT_KEY_REVOKE1" : (1 << 4 ),
1087+ "SECURE_BOOT_KEY_REVOKE2" : (1 << 5 ),
1088+ "SOFT_DIS_JTAG" : (1 << 6 ),
1089+ "HARD_DIS_JTAG" : (1 << 7 ),
1090+ "DIS_USB" : (1 << 8 ),
1091+ "DIS_DOWNLOAD_DCACHE" : (1 << 9 ),
1092+ "DIS_DOWNLOAD_ICACHE" : (1 << 10 ),
1093+ }
1094+
1095+ def parse_security_flags (flags_value ):
1096+ """Parse security flags into individual boolean values"""
1097+ parsed_flags = {}
1098+ for flag_name , flag_mask in SECURITY_INFO_FLAG_MAP .items ():
1099+ parsed_flags [flag_name ] = (flags_value & flag_mask ) != 0
1100+ return parsed_flags
1101+
10551102 res = self .check_command (
10561103 "get security info" , self .ESP_CMDS ["GET_SECURITY_INFO" ], b""
10571104 )
10581105 esp32s2 = True if len (res ) == 12 else False
10591106 res = struct .unpack ("<IBBBBBBBB" if esp32s2 else "<IBBBBBBBBII" , res )
1060- return {
1107+
1108+ security_info = {
10611109 "flags" : res [0 ],
10621110 "flash_crypt_cnt" : res [1 ],
10631111 "key_purposes" : res [2 :9 ],
10641112 "chip_id" : None if esp32s2 else res [9 ],
10651113 "api_version" : None if esp32s2 else res [10 ],
1114+ "parsed_flags" : parse_security_flags (res [0 ]),
10661115 }
10671116
1117+ self .cache ["security_info" ] = security_info
1118+ return security_info
1119+
10681120 def get_chip_id (self ):
1069- if self .cache ["chip_id" ] is None :
1070- res = self .check_command (
1071- "get security info" , self .ESP_CMDS ["GET_SECURITY_INFO" ], b""
1121+ chip_id = self .get_security_info ()["chip_id" ]
1122+ if chip_id is None :
1123+ raise FatalError (
1124+ "Security info command does not contain chip ID. "
1125+ "This is expected for ESP32-S2 which doesn't support chip ID "
1126+ "in security info."
10721127 )
1073- res = struct .unpack (
1074- "<IBBBBBBBBI" , res [:16 ]
1075- ) # 4b flags, 1b flash_crypt_cnt, 7*1b key_purposes, 4b chip_id
1076- self .cache ["chip_id" ] = res [9 ] # 2/4 status bytes invariant
1077- return self .cache ["chip_id" ]
1128+ return chip_id
10781129
10791130 def get_uart_no (self ):
10801131 """
0 commit comments