From d826448fd0d3a10080ea28d6c7a287c987b3e523 Mon Sep 17 00:00:00 2001 From: Laukik Hase Date: Wed, 14 Jan 2026 12:55:46 +0530 Subject: [PATCH] ci(esp_tee): Optimize the TEE pytest script --- .../tee_test_fw/pytest_esp_tee_ut.py | 299 +++++++----------- .../tee_test_fw/tee_exception_test_map.py | 110 +++++++ 2 files changed, 220 insertions(+), 189 deletions(-) create mode 100644 components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py diff --git a/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py b/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py index 863345071f..c1519eab61 100644 --- a/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py +++ b/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py @@ -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 ---------------- diff --git a/components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py b/components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py new file mode 100644 index 0000000000..2527d4df93 --- /dev/null +++ b/components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py @@ -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'] +}