@@ -290,9 +290,50 @@ def convert(parent, efuse):
290290 "t_sensor" : EfuseTempSensor ,
291291 "adc_tp" : EfuseAdcPointCalibration ,
292292 "wafer" : EfuseWafer ,
293+ "recovery_bootloader" : EfuseBtldrRecoveryField ,
293294 }.get (efuse .class_type , EfuseField )(parent , efuse )
294295
295296
297+ class EfuseBtldrRecoveryField (EfuseField ):
298+ """
299+ Handles composite recovery bootloader flash sector fields for ESP32-P4 ECO5 (v3.0).
300+ Combines/splits the following eFuse fields:
301+ - RECOVERY_BOOTLOADER_FLASH_SECTOR_0_1 (bits 1:0, uint:2)
302+ - RECOVERY_BOOTLOADER_FLASH_SECTOR_2_2 (bit 2, bool)
303+ - RECOVERY_BOOTLOADER_FLASH_SECTOR_3_6 (bits 6:3, uint:4)
304+ - RECOVERY_BOOTLOADER_FLASH_SECTOR_7_7 (bit 7, bool)
305+ - RECOVERY_BOOTLOADER_FLASH_SECTOR_8_10 (bits 10:8, uint:3)
306+ - RECOVERY_BOOTLOADER_FLASH_SECTOR_11_11(bit 11, bool)
307+ """
308+
309+ FIELD_ORDER = [
310+ ("RECOVERY_BOOTLOADER_FLASH_SECTOR_0_1" , 0 , 2 ),
311+ ("RECOVERY_BOOTLOADER_FLASH_SECTOR_2_2" , 2 , 1 ),
312+ ("RECOVERY_BOOTLOADER_FLASH_SECTOR_3_6" , 3 , 4 ),
313+ ("RECOVERY_BOOTLOADER_FLASH_SECTOR_7_7" , 7 , 1 ),
314+ ("RECOVERY_BOOTLOADER_FLASH_SECTOR_8_10" , 8 , 3 ),
315+ ("RECOVERY_BOOTLOADER_FLASH_SECTOR_11_11" , 11 , 1 ),
316+ ]
317+
318+ def get (self , from_read = True ):
319+ value = 0
320+ for field_name , bit_offset , bit_len in self .FIELD_ORDER :
321+ field = self .parent [field_name ]
322+ field_val = field .get (from_read )
323+ assert field .bit_len == bit_len
324+ value |= (field_val & ((1 << bit_len ) - 1 )) << bit_offset
325+ return value
326+
327+ def save (self , new_value ):
328+ for field_name , bit_offset , bit_len in self .FIELD_ORDER :
329+ field = self .parent [field_name ]
330+ field_val = (new_value >> bit_offset ) & ((1 << bit_len ) - 1 )
331+ field .save (field_val )
332+ log .print (
333+ f"\t - '{ field .name } ' { field .get_bitstring ()} -> { field .get_bitstring (from_read = False )} "
334+ )
335+
336+
296337class EfuseWafer (EfuseField ):
297338 def get (self , from_read = True ):
298339 hi_bits = self .parent ["WAFER_VERSION_MAJOR_HI" ].get (from_read )
@@ -389,10 +430,11 @@ def print_field(e, new_value):
389430
390431# fmt: off
391432class EfuseKeyPurposeField (EfuseField ):
392- key_purpose_len = 4 # bits for key purpose
433+ key_purpose_len = 5 # bits for key purpose
393434 KeyPurposeType = tuple [str , int , str | None , str | None , str ]
394435 KEY_PURPOSES : list [KeyPurposeType ] = [
395436 ("USER" , 0 , None , None , "no_need_rd_protect" ), # User purposes (software-only use)
437+ ("ECDSA_KEY_P256" , 1 , None , "Reverse" , "need_rd_protect" ), # ECDSA key P256
396438 ("ECDSA_KEY" , 1 , None , "Reverse" , "need_rd_protect" ), # ECDSA key
397439 ("XTS_AES_256_KEY_1" , 2 , None , "Reverse" , "need_rd_protect" ), # XTS_AES_256_KEY_1 (flash/PSRAM encryption)
398440 ("XTS_AES_256_KEY_2" , 3 , None , "Reverse" , "need_rd_protect" ), # XTS_AES_256_KEY_2 (flash/PSRAM encryption)
@@ -406,6 +448,10 @@ class EfuseKeyPurposeField(EfuseField):
406448 ("SECURE_BOOT_DIGEST2" , 11 , "DIGEST" , None , "no_need_rd_protect" ), # SECURE_BOOT_DIGEST2 (Secure Boot key digest)
407449 ("KM_INIT_KEY" , 12 , None , None , "need_rd_protect" ), # init key that is used for the generation of AES/ECDSA key
408450 ("XTS_AES_256_KEY" , - 1 , "VIRTUAL" , None , "no_need_rd_protect" ), # Virtual purpose splits to XTS_AES_256_KEY_1 and XTS_AES_256_KEY_2
451+ ("ECDSA_KEY_P192" , 16 , None , "Reverse" , "need_rd_protect" ), # ECDSA key P192
452+ ("ECDSA_KEY_P384_L" , 17 , None , "Reverse" , "need_rd_protect" ), # ECDSA key P384 low
453+ ("ECDSA_KEY_P384_H" , 18 , None , "Reverse" , "need_rd_protect" ), # ECDSA key P384 high
454+ ("ECDSA_KEY_P384" , - 3 , "VIRTUAL" , None , "need_rd_protect" ), # Virtual purpose splits to ECDSA_KEY_P384_L and ECDSA_KEY_P384_H
409455 ]
410456 CUSTOM_KEY_PURPOSES : list [KeyPurposeType ] = []
411457 for id in range (0 , 1 << key_purpose_len ):
@@ -445,9 +491,25 @@ def need_rd_protect(self, new_key_purpose):
445491 return key [4 ] == "need_rd_protect"
446492
447493 def get (self , from_read = True ):
448- for p in self .KEY_PURPOSES :
449- if p [1 ] == self .get_raw (from_read ):
450- return p [0 ]
494+ # Handle special case for KEY_PURPOSE_<digit>_H fields (e.g., KEY_PURPOSE_0_H ... KEY_PURPOSE_9_H)
495+ if self .name .startswith ("KEY_PURPOSE_" ) and self .name .endswith ("_H" ):
496+ return self .get_raw (from_read )
497+ else :
498+ if any (
499+ efuse is not None
500+ and getattr (efuse , "name" , None ) == "KEY_PURPOSE_0_H"
501+ for efuse in self .parent
502+ ): # check if the hi bit field for KEY_PURPOSE_.. exists
503+ hi_bits = self .parent [f"{ self .name } _H" ].get_raw (from_read )
504+ assert self .parent [f"{ self .name } _H" ].bit_len == 1
505+ lo_bits = self .parent [f"{ self .name } " ].get_raw (from_read )
506+ assert self .parent [f"{ self .name } " ].bit_len == 4
507+ raw_val = (hi_bits << 4 ) + lo_bits
508+ else :
509+ raw_val = self .get_raw (from_read )
510+ for p in self .KEY_PURPOSES :
511+ if p [1 ] == raw_val :
512+ return p [0 ]
451513 return "FORBIDDEN_STATE"
452514
453515 def get_name (self , raw_val ):
@@ -457,4 +519,31 @@ def get_name(self, raw_val):
457519
458520 def save (self , new_value ):
459521 raw_val = int (self .check_format (str (new_value )))
460- return super ().save (raw_val )
522+ # Check if _H field exists (5-bit key purpose split into lo/hi)
523+ if (any (
524+ efuse is not None
525+ and getattr (efuse , "name" , None ) == "KEY_PURPOSE_0_H"
526+ for efuse in self .parent
527+ )
528+ and self .name .startswith ("KEY_PURPOSE_" )
529+ and not self .name .endswith ("_H" )
530+ ):
531+ FIELD_ORDER = [
532+ (self .name , 0 ), # lo bits (bits 0-3)
533+ (f"{ self .name } _H" , 4 ), # hi bit (bit 4)
534+ ]
535+ for field_name , bit_offset in FIELD_ORDER :
536+ field = self .parent [field_name ]
537+ field_val = (raw_val >> bit_offset ) & ((1 << field .bit_len ) - 1 )
538+ print (field_val , field_name )
539+ if field_val != 0 :
540+ if field_name .endswith ("_H" ):
541+ field .save (field_val )
542+ else :
543+ super ().save (field_val )
544+ log .print (
545+ f"\t - '{ field .name } ' { field .get_bitstring ()} -> { field .get_bitstring (from_read = False )} "
546+ )
547+ else :
548+ # Single field, just save as usual
549+ super ().save (raw_val )
0 commit comments