ci(esp_tee): Optimize the TEE pytest script

This commit is contained in:
Laukik Hase
2026-01-14 12:55:46 +05:30
parent 304adb120b
commit d826448fd0
2 changed files with 220 additions and 189 deletions
@@ -6,57 +6,32 @@ from enum import Enum
import pytest
from pytest_embedded_idf import IdfDut
from pytest_embedded_idf.utils import idf_parametrize
from tee_exception_cfg import TEE_EXCEPTION_TEST_MAP
# ---------------- Pytest build parameters ----------------
# TODO: Enable for ESP32-C5 once support is stable
SUPPORTED_TARGETS = ['esp32c6', 'esp32h2', 'esp32c61']
TESTING_TARGETS = ['esp32c6', 'esp32h2', 'esp32c61']
CONFIG_DEFAULT = [
# 'config, target, markers',
('tee_default', target, (pytest.mark.generic,))
for target in SUPPORTED_TARGETS
for target in TESTING_TARGETS
]
CONFIG_OTA = [
# 'config, target, skip_autoflash, markers',
('tee_ota', target, 'y', (pytest.mark.generic,))
for target in SUPPORTED_TARGETS
for target in TESTING_TARGETS
]
CONFIG_ALL = [
# 'config, target, markers',
(config, target, (pytest.mark.generic,))
for config in ['tee_default', 'tee_ota']
for target in SUPPORTED_TARGETS
for target in TESTING_TARGETS
]
# ---------------- Exception test-cases reasons ----------------
TEE_VIOLATION_TEST_EXC_RSN: dict[str, str] = {
('Reserved-W1'): 'Store access fault',
('Reserved-X1'): 'Instruction access fault',
('IRAM-W1'): 'Store access fault',
('IRAM-W2'): 'Store access fault',
('DRAM-X1'): 'Instruction access fault',
('DRAM-X2'): 'Instruction access fault',
('Illegal Instruction'): 'Illegal instruction',
}
REE_ISOLATION_TEST_EXC_RSN: dict[str, str] = {
('DRAM-R1'): 'Load access fault',
('DRAM-W1'): 'Store access fault',
('IRAM-R1'): 'Load access fault',
('IRAM-W1'): 'Store access fault',
('IROM-R1'): 'Load access fault',
('IROM-W1'): 'Store access fault',
('DROM-R1'): 'Load access fault',
('DROM-W1'): 'Store access fault',
('MMU-spillover'): 'Cache error',
}
TEE_APM_VIOLATION_EXC_CHK = ['eFuse', 'MMU', 'SWDT/BOD', 'AES', 'HMAC', 'DS', 'SHA PCR', 'ECC PCR']
# ---------------- TEE default tests ----------------
@@ -75,9 +50,8 @@ def test_esp_tee(dut: IdfDut) -> None:
CONFIG_ALL,
indirect=['config', 'target'],
)
@pytest.mark.skipif(targets=['esp32c61'], reason='Not supported')
def test_esp_tee_crypto_aes(dut: IdfDut) -> None:
if dut.target == 'esp32c61':
pytest.skip(f'AES not supported on {dut.target}')
dut.run_all_single_board_cases(group='aes')
dut.run_all_single_board_cases(group='aes-gcm')
@@ -98,36 +72,27 @@ def test_esp_tee_crypto_sha(dut: IdfDut) -> None:
CONFIG_ALL,
indirect=['config', 'target'],
)
@pytest.mark.skipif(targets=['esp32c61'], reason='Not supported')
def test_esp_tee_aes_perf(dut: IdfDut) -> None:
if dut.target == 'esp32c61':
pytest.skip(f'AES not supported on {dut.target}')
for i in range(24):
for i in range(10):
dut.run_all_single_board_cases(name=['mbedtls AES performance'])
# ---------------- TEE Exceptions generation Tests ----------------
@idf_parametrize(
'config, target, markers',
CONFIG_DEFAULT,
indirect=['config', 'target'],
)
def test_esp_tee_apm_violation(dut: IdfDut) -> None:
for check in TEE_APM_VIOLATION_EXC_CHK:
if dut.target == 'esp32c61' and check in ('AES', 'HMAC', 'DS'):
continue
dut.expect_exact('Press ENTER to see the list of tests')
dut.write(f'"Test APM violation: {check}"')
exc = dut.expect(r'Core ([01]) panic\'ed \(([^)]+)\)', timeout=30).group(2).decode()
if dut.target in ('esp32c5', 'esp32c61') or (dut.target == 'esp32h2' and check == 'eFuse'):
exp_str = 'APM - Space exception'
elif check == 'SWDT/BOD':
exp_str = 'Store access fault'
else:
exp_str = 'APM - Authority exception'
if exc != exp_str:
raise RuntimeError('Incorrect exception received!')
def run_exception_case(
dut: IdfDut, menu_prefix: str, test_name: str, expected: str, check_origin: bool = False
) -> None:
dut.expect_exact('Press ENTER to see the list of tests')
dut.write(f'"{menu_prefix}: {test_name}"')
actual = dut.expect(r"Core ([01]) panic'ed \(([^)]+)\)", timeout=10).group(2).decode()
if actual != expected:
raise RuntimeError(f"{menu_prefix} / {test_name}: expected '{expected}', got '{actual}'")
if check_origin:
dut.expect('Origin: U-mode')
@idf_parametrize(
@@ -136,17 +101,10 @@ def test_esp_tee_apm_violation(dut: IdfDut) -> None:
indirect=['config', 'target'],
)
def test_esp_tee_violation_checks(dut: IdfDut) -> None:
checks_list = TEE_VIOLATION_TEST_EXC_RSN
for test, expected_exc in checks_list.items():
# NOTE: For ESP32-C5, access to the region before the SRAM does
# not generate exceptions due to TEE PMA configuration
if expected_exc is None or (dut.target == 'esp32c5' and test == 'IRAM-W1'):
continue
dut.expect_exact('Press ENTER to see the list of tests')
dut.write(f'"Test TEE-TEE violation: {test}"')
actual_exc = dut.expect(r'Core ([01]) panic\'ed \(([^)]+)\)', timeout=30).group(2).decode()
if actual_exc != expected_exc:
raise RuntimeError('Incorrect exception received!')
cfg = TEE_EXCEPTION_TEST_MAP[dut.target]['tee_violation']
for test_name, expected in cfg.items():
run_exception_case(dut, 'Test TEE-TEE violation', test_name, expected)
@idf_parametrize(
@@ -155,21 +113,22 @@ def test_esp_tee_violation_checks(dut: IdfDut) -> None:
indirect=['config', 'target'],
)
def test_esp_tee_isolation_checks(dut: IdfDut) -> None:
checks_list = REE_ISOLATION_TEST_EXC_RSN
for test, expected_exc in checks_list.items():
if expected_exc is None:
continue
dut.expect_exact('Press ENTER to see the list of tests')
dut.write(f'"Test REE-TEE isolation: {test}"')
# NOTE: For ESP32-C5 and C61, the MMU-spillover test fails gracefully without raising panic
if dut.target in {'esp32c5', 'esp32c61'} and test == 'MMU-spillover':
dut.expect_exact('Failed MMU operation, rebooting!')
continue
else:
actual_exc = dut.expect(r'Core ([01]) panic\'ed \(([^)]+)\)', timeout=30).group(2).decode()
if actual_exc != expected_exc:
raise RuntimeError('Incorrect exception received!')
dut.expect('Origin: U-mode')
cfg = TEE_EXCEPTION_TEST_MAP[dut.target]['ree_isolation']
for test_name, expected in cfg.items():
run_exception_case(dut, 'Test REE-TEE isolation', test_name, expected, check_origin=True)
@idf_parametrize(
'config, target, markers',
CONFIG_DEFAULT,
indirect=['config', 'target'],
)
def test_esp_tee_apm_violation(dut: IdfDut) -> None:
cfg = TEE_EXCEPTION_TEST_MAP[dut.target]['apm_violation']
for test_name, expected in cfg.items():
run_exception_case(dut, 'Test APM violation', test_name, expected)
@idf_parametrize(
@@ -183,7 +142,7 @@ def test_esp_tee_stack_smashing(dut: IdfDut) -> None:
dut.expect_exact('Press ENTER to see the list of tests')
dut.write(f'"Test {env} stack {case}"')
match = dut.expect(r"Core ([01]) panic'ed \(([^)]+)\)", timeout=30)
match = dut.expect(r"Core ([01]) panic'ed \(([^)]+)\)", timeout=10)
exc = match.group(2).decode()
if exc != 'Stack protection fault':
raise RuntimeError('Incorrect exception received!')
@@ -207,80 +166,89 @@ def expect_panic_rsn(dut: IdfDut, expected_rsn: str) -> None:
def run_multiple_stages(dut: IdfDut, test_case_num: int, stages: int, api: TeeFlashAccessApi) -> None:
expected_ops = {
TeeFlashAccessApi.ESP_PARTITION: [
# Constants
SOC_TEE_FLASH_OP_FAIL_FAULT = ('esp32c6', 'esp32h2')
INVALID_MMU_ACCESS_ERR = re.compile(r'\[_ss_mmu_hal_map_region] Illegal flash access at\s+\S+\s*\|\s*\S+')
INVALID_SPI1_ACCESS_ERR = re.compile(r'\[_ss_spi_flash_hal_(\w+)\] Illegal flash access at \s*(0x[0-9a-fA-F]+)')
EXPECTED_OPS_MAP = {
TeeFlashAccessApi.ESP_PARTITION: (
'read',
'program_page|common_command',
'program_page|common_command',
'erase_sector|common_command',
],
TeeFlashAccessApi.ESP_FLASH: [
),
TeeFlashAccessApi.ESP_FLASH: (
'program_page|common_command',
'read',
'erase_sector|common_command',
'program_page|common_command',
],
),
}
flash_enc_enabled = dut.app.sdkconfig.get('SECURE_FLASH_ENC_ENABLED', True)
target_mmu_fault = dut.target in SOC_TEE_FLASH_OP_FAIL_FAULT
SOC_TEE_FLASH_OP_FAIL_FAULT = ['esp32c6', 'esp32h2']
expected_ops = EXPECTED_OPS_MAP.get(api)
for stage in range(1, stages + 1):
dut.write(str(test_case_num))
dut.expect(r'\s+\((\d+)\)\s+"([^"]+)"\r?\n', timeout=30)
dut.expect(r'\s+\((\d+)\)\s+"([^"]+)"\r?\n', timeout=10)
dut.write(str(stage))
if stage > 1:
if api in {TeeFlashAccessApi.ESP_PARTITION_MMAP, TeeFlashAccessApi.SPI_FLASH_MMAP}:
if dut.target in SOC_TEE_FLASH_OP_FAIL_FAULT:
if api in (TeeFlashAccessApi.ESP_PARTITION_MMAP, TeeFlashAccessApi.SPI_FLASH_MMAP):
if target_mmu_fault:
expect_panic_rsn(dut, 'Cache error')
else:
dut.expect(r'\[_ss_mmu_hal_map_region] Illegal flash access at\s+\S+\s*\|\s*\S+', timeout=10)
elif api in {TeeFlashAccessApi.ESP_PARTITION, TeeFlashAccessApi.ESP_FLASH}:
dut.expect(INVALID_MMU_ACCESS_ERR, timeout=10)
elif api == TeeFlashAccessApi.ESP_ROM_SPIFLASH:
# NOTE: ROM flash APIs are not supported with ESP-TEE,
# thus using them results in APM violations
expected_rsn = 'APM - Authority exception' if target_mmu_fault else 'APM - Space exception'
expect_panic_rsn(dut, expected_rsn)
elif api in (TeeFlashAccessApi.ESP_PARTITION, TeeFlashAccessApi.ESP_FLASH):
assert expected_ops is not None
op_index = stage - 2
curr_op = expected_ops[api][op_index]
if api == TeeFlashAccessApi.ESP_PARTITION and curr_op == 'read' and flash_enc_enabled:
# NOTE: The esp_partition_read API handles both decrypted
# and plaintext reads. When flash encryption is enabled,
# it uses the MMU HAL instead of the SPI flash HAL.
if dut.target in SOC_TEE_FLASH_OP_FAIL_FAULT:
curr_op = expected_ops[op_index]
# NOTE: The esp_partition_read API handles both decrypted
# and plaintext reads. When flash encryption is enabled,
# it uses the MMU HAL instead of the SPI flash HAL.
if flash_enc_enabled and api == TeeFlashAccessApi.ESP_PARTITION and curr_op == 'read':
if target_mmu_fault:
expect_panic_rsn(dut, 'Cache error')
else:
dut.expect(r'\[_ss_mmu_hal_map_region] Illegal flash access at\s+\S+\s*\|\s*\S+', timeout=10)
dut.expect(INVALID_MMU_ACCESS_ERR, timeout=10)
else:
match = dut.expect(
r'\[_ss_spi_flash_hal_(\w+)\] Illegal flash access at \s*(0x[0-9a-fA-F]+)', timeout=10
)
match = dut.expect(INVALID_SPI1_ACCESS_ERR, timeout=10)
actual_op = match.group(1).decode()
if not re.fullmatch(curr_op, actual_op):
raise RuntimeError(f'Unexpected flash operation: {actual_op} (expected: {curr_op})')
elif api == TeeFlashAccessApi.ESP_ROM_SPIFLASH:
if dut.target in SOC_TEE_FLASH_OP_FAIL_FAULT:
expect_panic_rsn(dut, 'APM - Authority exception')
else:
expect_panic_rsn(dut, 'APM - Space exception')
if stage != stages:
dut.expect_exact('Press ENTER to see the list of tests.')
def run_flash_access_test(dut: IdfDut, api: TeeFlashAccessApi, test_name: str) -> None:
dut.serial.custom_flash()
extra_data = dut._parse_test_menu()
test_case = next((tc for tc in extra_data if tc.name == test_name), None)
if not test_case:
raise RuntimeError(f"Test case '{test_name}' not found")
run_multiple_stages(dut, test_case.index, len(test_case.subcases), api)
@idf_parametrize(
'config, target, skip_autoflash, markers',
CONFIG_OTA,
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_flash_prot_esp_partition_mmap(dut: IdfDut) -> None:
# Flash the bootloader, TEE and REE firmware
dut.serial.custom_flash()
# start test
extra_data = dut._parse_test_menu()
for test_case in extra_data:
if test_case.name == 'Test REE-TEE isolation: Flash - SPI0 (esp_partition_mmap)':
run_multiple_stages(dut, test_case.index, len(test_case.subcases), TeeFlashAccessApi.ESP_PARTITION_MMAP)
else:
continue
run_flash_access_test(
dut, TeeFlashAccessApi.ESP_PARTITION_MMAP, 'Test REE-TEE isolation: Flash - SPI0 (esp_partition_mmap)'
)
@idf_parametrize(
@@ -289,16 +257,9 @@ def test_esp_tee_flash_prot_esp_partition_mmap(dut: IdfDut) -> None:
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_flash_prot_spi_flash_mmap(dut: IdfDut) -> None:
# Flash the bootloader, TEE and REE firmware
dut.serial.custom_flash()
# start test
extra_data = dut._parse_test_menu()
for test_case in extra_data:
if test_case.name == 'Test REE-TEE isolation: Flash - SPI0 (spi_flash_mmap)':
run_multiple_stages(dut, test_case.index, len(test_case.subcases), TeeFlashAccessApi.SPI_FLASH_MMAP)
else:
continue
run_flash_access_test(
dut, TeeFlashAccessApi.SPI_FLASH_MMAP, 'Test REE-TEE isolation: Flash - SPI0 (spi_flash_mmap)'
)
@idf_parametrize(
@@ -307,16 +268,9 @@ def test_esp_tee_flash_prot_spi_flash_mmap(dut: IdfDut) -> None:
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_flash_prot_esp_rom_spiflash(dut: IdfDut) -> None:
# Flash the bootloader, TEE and REE firmware
dut.serial.custom_flash()
# start test
extra_data = dut._parse_test_menu()
for test_case in extra_data:
if test_case.name == 'Test REE-TEE isolation: Flash - SPI1 (esp_rom_spiflash)':
run_multiple_stages(dut, test_case.index, len(test_case.subcases), TeeFlashAccessApi.ESP_ROM_SPIFLASH)
else:
continue
run_flash_access_test(
dut, TeeFlashAccessApi.ESP_ROM_SPIFLASH, 'Test REE-TEE isolation: Flash - SPI1 (esp_rom_spiflash)'
)
@idf_parametrize(
@@ -325,16 +279,7 @@ def test_esp_tee_flash_prot_esp_rom_spiflash(dut: IdfDut) -> None:
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_flash_prot_esp_partition(dut: IdfDut) -> None:
# Flash the bootloader, TEE and REE firmware
dut.serial.custom_flash()
# start test
extra_data = dut._parse_test_menu()
for test_case in extra_data:
if test_case.name == 'Test REE-TEE isolation: Flash - SPI1 (esp_partition)':
run_multiple_stages(dut, test_case.index, len(test_case.subcases), TeeFlashAccessApi.ESP_PARTITION)
else:
continue
run_flash_access_test(dut, TeeFlashAccessApi.ESP_PARTITION, 'Test REE-TEE isolation: Flash - SPI1 (esp_partition)')
@idf_parametrize(
@@ -343,16 +288,7 @@ def test_esp_tee_flash_prot_esp_partition(dut: IdfDut) -> None:
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_flash_prot_esp_flash(dut: IdfDut) -> None:
# Flash the bootloader, TEE and REE firmware
dut.serial.custom_flash()
# start test
extra_data = dut._parse_test_menu()
for test_case in extra_data:
if test_case.name == 'Test REE-TEE isolation: Flash - SPI1 (esp_flash)':
run_multiple_stages(dut, test_case.index, len(test_case.subcases), TeeFlashAccessApi.ESP_FLASH)
else:
continue
run_flash_access_test(dut, TeeFlashAccessApi.ESP_FLASH, 'Test REE-TEE isolation: Flash - SPI1 (esp_flash)')
# ---------------- TEE Local OTA tests ----------------
@@ -360,13 +296,10 @@ def test_esp_tee_flash_prot_esp_flash(dut: IdfDut) -> None:
@pytest.mark.generic
@idf_parametrize('config', ['tee_ota'], indirect=['config'])
@idf_parametrize('target', SUPPORTED_TARGETS, indirect=['target'])
@idf_parametrize('target', TESTING_TARGETS, indirect=['target'])
def test_esp_tee_ota_negative(dut: IdfDut) -> None:
# start test
dut.run_all_single_board_cases(group='ota_neg_1', timeout=30)
# erasing TEE otadata
dut.serial.erase_partition('tee_otadata')
dut.run_all_single_board_cases(group='ota_neg_1', timeout=10)
@idf_parametrize(
@@ -379,10 +312,7 @@ def test_esp_tee_ota_corrupted_img(dut: IdfDut) -> None:
dut.serial.custom_flash_w_test_tee_img_gen()
# start test
dut.run_all_single_board_cases(name=['Test TEE OTA - Corrupted image'], timeout=30)
# erasing TEE otadata
dut.serial.erase_partition('tee_otadata')
dut.run_all_single_board_cases(name=['Test TEE OTA - Corrupted image'], timeout=10)
class TeeOtaStage(Enum):
@@ -393,14 +323,14 @@ class TeeOtaStage(Enum):
def tee_ota_stage_checks(dut: IdfDut, stage: TeeOtaStage, offset: str) -> None:
if stage == TeeOtaStage.PRE:
dut.expect(f'Loaded TEE app from partition at offset {offset}', timeout=30)
dut.expect('Current image already has been marked VALID', timeout=30)
dut.expect(f'Loaded TEE app from partition at offset {offset}', timeout=10)
dut.expect('Current image already has been marked VALID', timeout=10)
elif stage == TeeOtaStage.BEGIN:
dut.expect('Starting TEE OTA...', timeout=30)
dut.expect('Running partition - Subtype: 0x30', timeout=30)
dut.expect_exact(f'Next partition - Subtype: 0x31 (Offset: {offset})', timeout=30)
dut.expect('Starting TEE OTA...', timeout=10)
dut.expect('Running partition - Subtype: 0x30', timeout=10)
dut.expect_exact(f'Next partition - Subtype: 0x31 (Offset: {offset})', timeout=10)
elif stage == TeeOtaStage.REBOOT:
dut.expect(f'Loaded TEE app from partition at offset {offset}', timeout=30)
dut.expect(f'Loaded TEE app from partition at offset {offset}', timeout=10)
dut.expect_exact('Press ENTER to see the list of tests')
else:
raise ValueError('Undefined stage!')
@@ -428,9 +358,6 @@ def test_esp_tee_ota_reboot_without_ota_end(dut: IdfDut) -> None:
# after reboot
tee_ota_stage_checks(dut, TeeOtaStage.REBOOT, '0x10000')
# erasing TEE otadata
dut.serial.erase_partition('tee_otadata')
@idf_parametrize(
'config, target, skip_autoflash, markers',
@@ -446,11 +373,11 @@ def test_esp_tee_ota_valid_img(dut: IdfDut) -> None:
# start test
dut.expect_exact('Press ENTER to see the list of tests')
dut.write('[ota_valid_img]')
dut.write('"Test TEE OTA - Valid image"')
# OTA begin checks
tee_ota_stage_checks(dut, TeeOtaStage.BEGIN, '0x40000')
dut.expect('TEE OTA update successful!', timeout=30)
dut.expect('TEE OTA update successful!', timeout=10)
# after reboot 1
tee_ota_stage_checks(dut, TeeOtaStage.REBOOT, '0x40000')
@@ -459,12 +386,9 @@ def test_esp_tee_ota_valid_img(dut: IdfDut) -> None:
dut.serial.hard_reset()
# after reboot 2
dut.expect('TEE otadata - Current image state: VALID', timeout=30)
dut.expect('TEE otadata - Current image state: VALID', timeout=10)
tee_ota_stage_checks(dut, TeeOtaStage.REBOOT, '0x40000')
# erasing TEE otadata
dut.serial.erase_partition('tee_otadata')
@idf_parametrize(
'config, target, skip_autoflash, markers',
@@ -480,27 +404,24 @@ def test_esp_tee_ota_rollback(dut: IdfDut) -> None:
# start test
dut.expect_exact('Press ENTER to see the list of tests')
dut.write('[ota_rollback]')
dut.write('"Test TEE OTA - Rollback"')
# OTA begin checks
tee_ota_stage_checks(dut, TeeOtaStage.BEGIN, '0x40000')
dut.expect('TEE OTA update successful!', timeout=30)
dut.expect('TEE OTA update successful!', timeout=10)
# after reboot 1
dut.expect('TEE otadata - Current image state: NEW', timeout=10)
dut.expect('Loaded TEE app from partition at offset 0x40000', timeout=10)
rst_rsn = dut.expect(r'rst:(0x[0-9A-Fa-f]+) \(([^)]+)\)', timeout=30).group(2).decode()
rst_rsn = dut.expect(r'rst:(0x[0-9A-Fa-f]+) \(([^)]+)\)', timeout=10).group(2).decode()
# NOTE: LP_WDT_SYS (C6/H2) and RTC_WDT_SYS (C5) are expected as bootloader fails to load the dummy TEE app
if rst_rsn not in {'LP_WDT_SYS', 'RTC_WDT_SYS'}:
raise RuntimeError('Incorrect reset reason observed after TEE image failure!')
# after rollback
dut.expect('TEE otadata - Current image state: PENDING_VERIFY', timeout=30)
dut.expect('TEE otadata - Current image state: PENDING_VERIFY', timeout=10)
tee_ota_stage_checks(dut, TeeOtaStage.REBOOT, '0x10000')
# erasing TEE otadata
dut.serial.erase_partition('tee_otadata')
# ---------------- TEE Secure Storage tests ----------------
@@ -0,0 +1,110 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from typing import Any
# Base configuration - shared by all targets
_BASE_CONFIG = {
'tee_violation': {
'Reserved-W1': 'Store access fault',
'Reserved-X1': 'Instruction access fault',
'IRAM-W1': 'Store access fault',
'IRAM-W2': 'Store access fault',
'DRAM-X1': 'Instruction access fault',
'DRAM-X2': 'Instruction access fault',
'Illegal Instruction': 'Illegal instruction',
},
'ree_isolation': {
'DRAM-R1': 'Load access fault',
'DRAM-W1': 'Store access fault',
'IRAM-R1': 'Load access fault',
'IRAM-W1': 'Store access fault',
'IROM-R1': 'Load access fault',
'IROM-W1': 'Store access fault',
'DROM-R1': 'Load access fault',
'DROM-W1': 'Store access fault',
'MMU-spillover': 'Illegal instruction',
},
'apm_violation': {
'eFuse': 'APM - Space exception',
'MMU': 'APM - Space exception',
'SWDT/BOD': 'APM - Space exception',
'AES': 'APM - Space exception',
'HMAC': 'APM - Space exception',
'DS': 'APM - Space exception',
'SHA PCR': 'APM - Space exception',
'ECC PCR': 'APM - Space exception',
},
}
# Per-target overrides only
_TARGET_OVERRIDES: dict[str, dict[str, Any]] = {
'esp32c6': {
'ree_isolation': {
# NOTE: For ESP32-C6, the MMU-spillover test triggers a Cache error
# panic as invalid MMU m'mapings are not handled gracefully
'MMU-spillover': 'Cache error',
},
# NOTE: For ESP32-C6, illegal APM accesses result in Authority exceptions
# due to the fix required for granting unrestricted access in TEE mode
'apm_violation': {
'eFuse': 'APM - Authority exception',
'MMU': 'APM - Authority exception',
# NOTE: For ESP32-C6, the SWDT/BOD peripherals are protected using PMP
# due to lack of sufficient APM entries
'SWDT/BOD': 'Store access fault',
'AES': 'APM - Authority exception',
'HMAC': 'APM - Authority exception',
'DS': 'APM - Authority exception',
'SHA PCR': 'APM - Authority exception',
'ECC PCR': 'APM - Authority exception',
},
},
'esp32h2': {
'ree_isolation': {
# NOTE: For ESP32-H2, the MMU-spillover test triggers a Cache error
# panic as invalid MMU m'mapings are not handled gracefully
'MMU-spillover': 'Cache error',
},
# NOTE: For ESP32-H2, illegal APM accesses result in Authority exceptions
# due to the fix required for granting unrestricted access in TEE mode
'apm_violation': {
'MMU': 'APM - Authority exception',
'SWDT/BOD': 'Store access fault',
'AES': 'APM - Authority exception',
'HMAC': 'APM - Authority exception',
'DS': 'APM - Authority exception',
'SHA PCR': 'APM - Authority exception',
'ECC PCR': 'APM - Authority exception',
},
},
'esp32c61': {
# NOTE: ESP32-C61 does not support the following peripherals
'apm_violation': {
'_remove': ['AES', 'HMAC', 'DS'],
},
},
}
def _build_config(base: dict[str, Any], overrides: dict[str, Any]) -> dict[str, Any]:
import copy
result = copy.deepcopy(base)
for category, changes in overrides.items():
if category not in result:
result[category] = {}
# Handle removals
if '_remove' in changes:
for key in changes['_remove']:
result[category].pop(key, None)
changes = {k: v for k, v in changes.items() if k != '_remove'}
result[category].update(changes)
return result
# Build final map
# TODO: Enable for ESP32-C5 once support is stable
TEE_EXCEPTION_TEST_MAP = {
target: _build_config(_BASE_CONFIG, _TARGET_OVERRIDES.get(target, {}))
for target in ['esp32c6', 'esp32h2', 'esp32c61']
}