Skip to content

Commit ae23ab2

Browse files
KonstantinKondrashovradimkarnis
authored andcommitted
fix(espefuse): Fix ECDSA key purposes for ESP32-P4
1 parent 1f9c5e9 commit ae23ab2

File tree

4 files changed

+115
-13
lines changed

4 files changed

+115
-13
lines changed

espefuse/efuse/esp32p4/emulate_efuse_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __init__(self, efuse_file=None, debug=False):
2727
""" esptool method start >>"""
2828

2929
def get_major_chip_version(self):
30-
return 0
30+
return 3
3131

3232
def get_minor_chip_version(self):
3333
return 0

espefuse/efuse/esp32p4/fields.py

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
296337
class 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
391432
class 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)

espefuse/efuse/esp32p4/mem_definition.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,21 @@ def __init__(self, extend_efuse_table, revision=None) -> None:
160160
f.description = "calc WAFER VERSION MAJOR from (WAFER_VERSION_MAJOR_HI << 2) + WAFER_VERSION_MAJOR_LO (read only)"
161161
self.CALC.append(f)
162162

163+
if any(
164+
efuse is not None
165+
and getattr(efuse, "name", None) == "RECOVERY_BOOTLOADER_FLASH_SECTOR_0_1"
166+
for efuse in self.ALL_EFUSES
167+
):
168+
f = Field()
169+
f.name = "RECOVERY_BOOTLOADER_FLASH_SECTOR"
170+
f.block = 0
171+
f.bit_len = 12
172+
f.type = f"uint:{f.bit_len}"
173+
f.category = "config"
174+
f.class_type = "recovery_bootloader"
175+
f.description = "recovery_bootloader = RECOVERY_BOOTLOADER_FLASH_SECTOR_0_1 + 2_2 + 3_6 + 7_7 + 8_10 + 11_11"
176+
self.CALC.append(f)
177+
163178
for efuse in self.ALL_EFUSES:
164179
if efuse is not None:
165180
self.EFUSES.append(efuse)

test/test_espefuse.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,12 @@ def _set_none_recovery_coding_scheme(self):
145145
self.espefuse_py("burn-efuse CODING_SCHEME 3")
146146

147147
def _set_target_wafer_version(self):
148-
# ESP32 has to be ECO3 (v3.0) for tests
149148
if arg_chip == "esp32":
149+
# ESP32 has to be ECO3 (v3.0) for tests
150150
self.espefuse_py("burn-efuse CHIP_VER_REV1 1 CHIP_VER_REV2 1")
151+
if arg_chip == "esp32p4":
152+
# ESP32P4 has to be ECO5 (v3.0) for tests
153+
self.espefuse_py("burn-efuse WAFER_VERSION_MAJOR_LO 3")
151154

152155
def check_data_block_in_log(
153156
self, log, file_path, repeat=1, reverse_order=False, offset=0
@@ -280,7 +283,7 @@ def test_adc_info_2(self):
280283
self.espefuse_py("burn-efuse BLK_VERSION_MAJOR 1")
281284
elif arg_chip in ["esp32c2", "esp32s2", "esp32c6"]:
282285
self.espefuse_py("burn-efuse BLK_VERSION_MINOR 1")
283-
elif arg_chip in ["esp32h2", "esp32p4"]:
286+
elif arg_chip in ["esp32h2"]:
284287
self.espefuse_py("burn-efuse BLK_VERSION_MINOR 2")
285288
self.espefuse_py("adc-info")
286289

@@ -1388,15 +1391,10 @@ def test_burn_block_data_with_6_keys(self):
13881391

13891392
self.espefuse_py(
13901393
f"burn-block-data \
1391-
BLOCK1 {IMAGES_DIR}/192bit \
13921394
BLOCK5 {IMAGES_DIR}/256bit_1 \
13931395
BLOCK6 {IMAGES_DIR}/256bit_2"
13941396
)
13951397
output = self.espefuse_py("-d summary")
1396-
assert (
1397-
"[1 ] read_regs: 00000000 07060500 00000908 00000000 13000000 00161514"
1398-
in output
1399-
)
14001398
self.check_data_block_in_log(output, f"{IMAGES_DIR}/256bit")
14011399
self.check_data_block_in_log(output, f"{IMAGES_DIR}/256bit_1")
14021400
self.check_data_block_in_log(output, f"{IMAGES_DIR}/256bit_2")

0 commit comments

Comments
 (0)