fix(espefuse): Fix ECDSA key purposes for ESP32-P4

This commit is contained in:
Konstantin Kondrashov
2025-09-22 19:00:02 +03:00
committed by Radim Karniš
parent 1f9c5e92ee
commit ae23ab254d
4 changed files with 115 additions and 13 deletions

View File

@@ -27,7 +27,7 @@ class EmulateEfuseController(EmulateEfuseControllerBase):
""" esptool method start >>"""
def get_major_chip_version(self):
return 0
return 3
def get_minor_chip_version(self):
return 0

View File

@@ -290,9 +290,50 @@ class EfuseField(base_fields.EfuseFieldBase):
"t_sensor": EfuseTempSensor,
"adc_tp": EfuseAdcPointCalibration,
"wafer": EfuseWafer,
"recovery_bootloader": EfuseBtldrRecoveryField,
}.get(efuse.class_type, EfuseField)(parent, efuse)
class EfuseBtldrRecoveryField(EfuseField):
"""
Handles composite recovery bootloader flash sector fields for ESP32-P4 ECO5 (v3.0).
Combines/splits the following eFuse fields:
- RECOVERY_BOOTLOADER_FLASH_SECTOR_0_1 (bits 1:0, uint:2)
- RECOVERY_BOOTLOADER_FLASH_SECTOR_2_2 (bit 2, bool)
- RECOVERY_BOOTLOADER_FLASH_SECTOR_3_6 (bits 6:3, uint:4)
- RECOVERY_BOOTLOADER_FLASH_SECTOR_7_7 (bit 7, bool)
- RECOVERY_BOOTLOADER_FLASH_SECTOR_8_10 (bits 10:8, uint:3)
- RECOVERY_BOOTLOADER_FLASH_SECTOR_11_11(bit 11, bool)
"""
FIELD_ORDER = [
("RECOVERY_BOOTLOADER_FLASH_SECTOR_0_1", 0, 2),
("RECOVERY_BOOTLOADER_FLASH_SECTOR_2_2", 2, 1),
("RECOVERY_BOOTLOADER_FLASH_SECTOR_3_6", 3, 4),
("RECOVERY_BOOTLOADER_FLASH_SECTOR_7_7", 7, 1),
("RECOVERY_BOOTLOADER_FLASH_SECTOR_8_10", 8, 3),
("RECOVERY_BOOTLOADER_FLASH_SECTOR_11_11", 11, 1),
]
def get(self, from_read=True):
value = 0
for field_name, bit_offset, bit_len in self.FIELD_ORDER:
field = self.parent[field_name]
field_val = field.get(from_read)
assert field.bit_len == bit_len
value |= (field_val & ((1 << bit_len) - 1)) << bit_offset
return value
def save(self, new_value):
for field_name, bit_offset, bit_len in self.FIELD_ORDER:
field = self.parent[field_name]
field_val = (new_value >> bit_offset) & ((1 << bit_len) - 1)
field.save(field_val)
log.print(
f"\t - '{field.name}' {field.get_bitstring()} -> {field.get_bitstring(from_read=False)}"
)
class EfuseWafer(EfuseField):
def get(self, from_read=True):
hi_bits = self.parent["WAFER_VERSION_MAJOR_HI"].get(from_read)
@@ -389,10 +430,11 @@ class EfuseMacField(EfuseField):
# fmt: off
class EfuseKeyPurposeField(EfuseField):
key_purpose_len = 4 # bits for key purpose
key_purpose_len = 5 # bits for key purpose
KeyPurposeType = tuple[str, int, str | None, str | None, str]
KEY_PURPOSES: list[KeyPurposeType] = [
("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use)
("ECDSA_KEY_P256", 1, None, "Reverse", "need_rd_protect"), # ECDSA key P256
("ECDSA_KEY", 1, None, "Reverse", "need_rd_protect"), # ECDSA key
("XTS_AES_256_KEY_1", 2, None, "Reverse", "need_rd_protect"), # XTS_AES_256_KEY_1 (flash/PSRAM encryption)
("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):
("SECURE_BOOT_DIGEST2", 11, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST2 (Secure Boot key digest)
("KM_INIT_KEY", 12, None, None, "need_rd_protect"), # init key that is used for the generation of AES/ECDSA key
("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
("ECDSA_KEY_P192", 16, None, "Reverse", "need_rd_protect"), # ECDSA key P192
("ECDSA_KEY_P384_L", 17, None, "Reverse", "need_rd_protect"), # ECDSA key P384 low
("ECDSA_KEY_P384_H", 18, None, "Reverse", "need_rd_protect"), # ECDSA key P384 high
("ECDSA_KEY_P384", -3, "VIRTUAL", None, "need_rd_protect"), # Virtual purpose splits to ECDSA_KEY_P384_L and ECDSA_KEY_P384_H
]
CUSTOM_KEY_PURPOSES: list[KeyPurposeType] = []
for id in range(0, 1 << key_purpose_len):
@@ -445,9 +491,25 @@ class EfuseKeyPurposeField(EfuseField):
return key[4] == "need_rd_protect"
def get(self, from_read=True):
for p in self.KEY_PURPOSES:
if p[1] == self.get_raw(from_read):
return p[0]
# Handle special case for KEY_PURPOSE_<digit>_H fields (e.g., KEY_PURPOSE_0_H ... KEY_PURPOSE_9_H)
if self.name.startswith("KEY_PURPOSE_") and self.name.endswith("_H"):
return self.get_raw(from_read)
else:
if any(
efuse is not None
and getattr(efuse, "name", None) == "KEY_PURPOSE_0_H"
for efuse in self.parent
): # check if the hi bit field for KEY_PURPOSE_.. exists
hi_bits = self.parent[f"{self.name}_H"].get_raw(from_read)
assert self.parent[f"{self.name}_H"].bit_len == 1
lo_bits = self.parent[f"{self.name}"].get_raw(from_read)
assert self.parent[f"{self.name}"].bit_len == 4
raw_val = (hi_bits << 4) + lo_bits
else:
raw_val = self.get_raw(from_read)
for p in self.KEY_PURPOSES:
if p[1] == raw_val:
return p[0]
return "FORBIDDEN_STATE"
def get_name(self, raw_val):
@@ -457,4 +519,31 @@ class EfuseKeyPurposeField(EfuseField):
def save(self, new_value):
raw_val = int(self.check_format(str(new_value)))
return super().save(raw_val)
# Check if _H field exists (5-bit key purpose split into lo/hi)
if (any(
efuse is not None
and getattr(efuse, "name", None) == "KEY_PURPOSE_0_H"
for efuse in self.parent
)
and self.name.startswith("KEY_PURPOSE_")
and not self.name.endswith("_H")
):
FIELD_ORDER = [
(self.name, 0), # lo bits (bits 0-3)
(f"{self.name}_H", 4), # hi bit (bit 4)
]
for field_name, bit_offset in FIELD_ORDER:
field = self.parent[field_name]
field_val = (raw_val >> bit_offset) & ((1 << field.bit_len) - 1)
print(field_val, field_name)
if field_val != 0:
if field_name.endswith("_H"):
field.save(field_val)
else:
super().save(field_val)
log.print(
f"\t - '{field.name}' {field.get_bitstring()} -> {field.get_bitstring(from_read=False)}"
)
else:
# Single field, just save as usual
super().save(raw_val)

View File

@@ -160,6 +160,21 @@ class EfuseDefineFields(EfuseFieldsBase):
f.description = "calc WAFER VERSION MAJOR from (WAFER_VERSION_MAJOR_HI << 2) + WAFER_VERSION_MAJOR_LO (read only)"
self.CALC.append(f)
if any(
efuse is not None
and getattr(efuse, "name", None) == "RECOVERY_BOOTLOADER_FLASH_SECTOR_0_1"
for efuse in self.ALL_EFUSES
):
f = Field()
f.name = "RECOVERY_BOOTLOADER_FLASH_SECTOR"
f.block = 0
f.bit_len = 12
f.type = f"uint:{f.bit_len}"
f.category = "config"
f.class_type = "recovery_bootloader"
f.description = "recovery_bootloader = RECOVERY_BOOTLOADER_FLASH_SECTOR_0_1 + 2_2 + 3_6 + 7_7 + 8_10 + 11_11"
self.CALC.append(f)
for efuse in self.ALL_EFUSES:
if efuse is not None:
self.EFUSES.append(efuse)

View File

@@ -145,9 +145,12 @@ class EfuseTestCase:
self.espefuse_py("burn-efuse CODING_SCHEME 3")
def _set_target_wafer_version(self):
# ESP32 has to be ECO3 (v3.0) for tests
if arg_chip == "esp32":
# ESP32 has to be ECO3 (v3.0) for tests
self.espefuse_py("burn-efuse CHIP_VER_REV1 1 CHIP_VER_REV2 1")
if arg_chip == "esp32p4":
# ESP32P4 has to be ECO5 (v3.0) for tests
self.espefuse_py("burn-efuse WAFER_VERSION_MAJOR_LO 3")
def check_data_block_in_log(
self, log, file_path, repeat=1, reverse_order=False, offset=0
@@ -280,7 +283,7 @@ class TestReadCommands(EfuseTestCase):
self.espefuse_py("burn-efuse BLK_VERSION_MAJOR 1")
elif arg_chip in ["esp32c2", "esp32s2", "esp32c6"]:
self.espefuse_py("burn-efuse BLK_VERSION_MINOR 1")
elif arg_chip in ["esp32h2", "esp32p4"]:
elif arg_chip in ["esp32h2"]:
self.espefuse_py("burn-efuse BLK_VERSION_MINOR 2")
self.espefuse_py("adc-info")
@@ -1388,15 +1391,10 @@ class TestBurnBlockDataCommands(EfuseTestCase):
self.espefuse_py(
f"burn-block-data \
BLOCK1 {IMAGES_DIR}/192bit \
BLOCK5 {IMAGES_DIR}/256bit_1 \
BLOCK6 {IMAGES_DIR}/256bit_2"
)
output = self.espefuse_py("-d summary")
assert (
"[1 ] read_regs: 00000000 07060500 00000908 00000000 13000000 00161514"
in output
)
self.check_data_block_in_log(output, f"{IMAGES_DIR}/256bit")
self.check_data_block_in_log(output, f"{IMAGES_DIR}/256bit_1")
self.check_data_block_in_log(output, f"{IMAGES_DIR}/256bit_2")