diff --git a/components/esp_driver_dma/src/gdma.c b/components/esp_driver_dma/src/gdma.c index 864fd873d7..930a799b44 100644 --- a/components/esp_driver_dma/src/gdma.c +++ b/components/esp_driver_dma/src/gdma.c @@ -436,7 +436,7 @@ esp_err_t gdma_config_transfer(gdma_channel_handle_t dma_chan, const gdma_transf // While, for AHB GDMA version 2 and AXI GDMA, we need to ensure the alignment by software #if (SOC_PSRAM_DMA_CAPABLE || SOC_DMA_CAN_ACCESS_FLASH) && SOC_AHB_GDMA_VERSION != 1 // if MSPI encryption is enabled, and DMA wants to read/write external memory - if (efuse_hal_flash_encryption_enabled() && config->access_ext_mem) { + if (esp_flash_encryption_enabled() && config->access_ext_mem) { uint32_t enc_mem_alignment = GDMA_LL_GET(ACCESS_ENCRYPTION_MEM_ALIGNMENT); // when DMA access the encrypted external memory, extra alignment is needed for external memory ext_mem_alignment = MAX(ext_mem_alignment, enc_mem_alignment); diff --git a/components/esp_driver_dma/src/gdma_link.c b/components/esp_driver_dma/src/gdma_link.c index 0b4a6bde43..ffaaabad41 100644 --- a/components/esp_driver_dma/src/gdma_link.c +++ b/components/esp_driver_dma/src/gdma_link.c @@ -19,6 +19,7 @@ #include "hal/efuse_hal.h" #include "hal/cache_ll.h" #include "esp_cache.h" +#include "esp_flash_encrypt.h" ESP_LOG_ATTR_TAG(TAG, "gdma-link"); @@ -77,7 +78,7 @@ esp_err_t gdma_new_link_list(const gdma_link_list_config_t *config, gdma_link_li bool items_in_ext_mem = config->flags.items_in_ext_mem; uint32_t list_items_mem_caps = MALLOC_CAP_8BIT | MALLOC_CAP_DMA; if (items_in_ext_mem) { - if (efuse_hal_flash_encryption_enabled()) { + if (esp_flash_encryption_enabled()) { items_in_ext_mem = false; list_items_mem_caps |= MALLOC_CAP_INTERNAL; ESP_LOGW(TAG, "DMA linked list items cannot be placed in PSRAM when external memory encryption is enabled, using internal memory instead"); @@ -194,7 +195,7 @@ esp_err_t gdma_link_mount_buffers(gdma_link_list_handle_t list, int start_item_i size_t max_buffer_mount_length = ALIGN_DOWN(GDMA_MAX_BUFFER_SIZE_PER_LINK_ITEM, buffer_alignment); if (!config->flags.bypass_buffer_align_check) { ESP_RETURN_ON_FALSE_ISR(((uintptr_t)buf & (buffer_alignment - 1)) == 0, ESP_ERR_INVALID_ARG, TAG, "buf misalign idx=%"PRIu32" align=%"PRIu32, bi, buffer_alignment); - if (efuse_hal_flash_encryption_enabled()) { + if (esp_flash_encryption_enabled()) { // buffer size must be aligned to the encryption alignment which should be provided by the upper buffer_alignment ESP_RETURN_ON_FALSE_ISR((len & (buffer_alignment - 1)) == 0, ESP_ERR_INVALID_ARG, TAG, "buf len misalign idx=%"PRIu32" len=%"PRIu32" align=%"PRIu32"", bi, len, buffer_alignment); } diff --git a/components/esp_driver_dma/src/gdma_priv.h b/components/esp_driver_dma/src/gdma_priv.h index fcc8b677f8..940289d923 100644 --- a/components/esp_driver_dma/src/gdma_priv.h +++ b/components/esp_driver_dma/src/gdma_priv.h @@ -36,6 +36,7 @@ #include "esp_private/periph_ctrl.h" #include "esp_private/critical_section.h" #include "esp_private/sleep_retention.h" +#include "esp_flash_encrypt.h" #if CONFIG_GDMA_OBJ_DRAM_SAFE #define GDMA_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) diff --git a/components/esp_driver_dma/test_apps/dma/main/test_async_memcpy.c b/components/esp_driver_dma/test_apps/dma/main/test_async_memcpy.c index 5daff7838a..2f4d85f7e1 100644 --- a/components/esp_driver_dma/test_apps/dma/main/test_async_memcpy.c +++ b/components/esp_driver_dma/test_apps/dma/main/test_async_memcpy.c @@ -17,6 +17,7 @@ #include "ccomp_timer.h" #include "esp_async_memcpy.h" #include "hal/efuse_hal.h" +#include "esp_flash_encrypt.h" #if SOC_GDMA_SUPPORTED #include "hal/gdma_ll.h" @@ -170,7 +171,7 @@ static void test_memory_copy_blocking(async_memcpy_handle_t driver) for (int off = 0; off < 4; off++) { test_context.buffer_size = test_buffer_size[i]; test_context.seed = i; - if (!efuse_hal_flash_encryption_enabled()) { + if (!esp_flash_encryption_enabled()) { test_context.src_offset = off; test_context.dst_offset = off; } @@ -257,7 +258,7 @@ TEST_CASE("memory copy with dest address unaligned", "[async mcp]") }; [[maybe_unused]] async_memcpy_handle_t driver = NULL; - if (efuse_hal_flash_encryption_enabled()) { + if (esp_flash_encryption_enabled()) { TEST_PASS_MESSAGE("Flash encryption is enabled, skip this test"); } diff --git a/components/esp_driver_dma/test_apps/dma/main/test_dw_gdma.c b/components/esp_driver_dma/test_apps/dma/main/test_dw_gdma.c index 37999612cb..2d4591cb25 100644 --- a/components/esp_driver_dma/test_apps/dma/main/test_dw_gdma.c +++ b/components/esp_driver_dma/test_apps/dma/main/test_dw_gdma.c @@ -14,6 +14,7 @@ #include "hal/efuse_hal.h" #include "esp_cache.h" #include "esp_private/esp_cache_private.h" +#include "esp_flash_encrypt.h" TEST_CASE("DW_GDMA channel allocation", "[DW_GDMA]") { @@ -541,7 +542,7 @@ TEST_CASE("DW_GDMA M2M Test: memory set with fixed address", "[DW_GDMA]") size_t int_mem_alignment = 0; TEST_ESP_OK(esp_cache_get_alignment(MALLOC_CAP_SPIRAM, &ext_mem_alignment)); TEST_ESP_OK(esp_cache_get_alignment(0, &int_mem_alignment)); - if (efuse_hal_flash_encryption_enabled()) { + if (esp_flash_encryption_enabled()) { TEST_PASS_MESSAGE("Flash encryption is enabled, skip this test"); } uint8_t *src_buf = heap_caps_aligned_calloc(ext_mem_alignment, 1, 256, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); diff --git a/components/esp_driver_dma/test_apps/dma/main/test_gdma.c b/components/esp_driver_dma/test_apps/dma/main/test_gdma.c index 567ca1fe6c..acefae7a5d 100644 --- a/components/esp_driver_dma/test_apps/dma/main/test_gdma.c +++ b/components/esp_driver_dma/test_apps/dma/main/test_gdma.c @@ -24,6 +24,7 @@ #include "esp_cache.h" #include "esp_memory_utils.h" #include "gdma_test_utils.h" +#include "esp_flash_encrypt.h" #define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) #define ALIGN_DOWN(num, align) ((num) & ~((align) - 1)) @@ -276,7 +277,7 @@ static void test_gdma_m2m_transaction(gdma_channel_handle_t tx_chan, gdma_channe TEST_ASSERT_NOT_NULL(done_sem); TEST_ESP_OK(gdma_register_rx_event_callbacks(rx_chan, &rx_cbs, done_sem)); - if (efuse_hal_flash_encryption_enabled()) { + if (esp_flash_encryption_enabled()) { dma_link_in_ext_mem = false; } @@ -538,7 +539,7 @@ static void test_gdma_m2m_unaligned_buffer_test(uint8_t *dst_data, uint8_t *src_ TEST_CASE("GDMA M2M Unaligned RX Buffer Test", "[GDMA][M2M]") { - if (efuse_hal_flash_encryption_enabled()) { + if (esp_flash_encryption_enabled()) { TEST_PASS_MESSAGE("Flash encryption is enabled, skip this test"); } diff --git a/components/esp_driver_parlio/src/parlio_tx.c b/components/esp_driver_parlio/src/parlio_tx.c index a8e75135ce..36b7aaf62b 100644 --- a/components/esp_driver_parlio/src/parlio_tx.c +++ b/components/esp_driver_parlio/src/parlio_tx.c @@ -666,8 +666,8 @@ esp_err_t parlio_tx_unit_transmit(parlio_tx_unit_handle_t tx_unit, const void *p size_t alignment = esp_ptr_external_ram(payload) ? tx_unit->ext_mem_align : tx_unit->int_mem_align; // check alignment - ESP_RETURN_ON_FALSE(((uint32_t)payload & (alignment - 1)) == 0, ESP_ERR_INVALID_ARG, TAG, "payload address not aligned"); - ESP_RETURN_ON_FALSE((payload_bits & (alignment - 1)) == 0, ESP_ERR_INVALID_ARG, TAG, "payload size not aligned"); + ESP_RETURN_ON_FALSE(((uint32_t)payload & (alignment - 1)) == 0, ESP_ERR_INVALID_ARG, TAG, "payload address %p not aligned to %d", payload, alignment); + ESP_RETURN_ON_FALSE((payload_bits & (alignment - 1)) == 0, ESP_ERR_INVALID_ARG, TAG, "payload size %d not aligned to %d", payload_bits, alignment); if (esp_cache_get_line_size_by_addr(payload) > 0) { // Write back to cache to synchronize the cache before DMA start diff --git a/components/esp_driver_parlio/test_apps/parlio/conftest.py b/components/esp_driver_parlio/test_apps/parlio/conftest.py new file mode 100644 index 0000000000..7b54db3b76 --- /dev/null +++ b/components/esp_driver_parlio/test_apps/parlio/conftest.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from _pytest.fixtures import FixtureRequest +from _pytest.monkeypatch import MonkeyPatch +from pytest_embedded_idf.serial import IdfSerial + + +# This is a custom IdfSerial class to support custom functionality +# which is required only for this test +class EfuseFlashEncSerial(IdfSerial): + @IdfSerial.use_esptool() + def write_flash_no_enc(self) -> None: + self.app.flash_settings['encrypt'] = False + flash_files = [] + for file in self.app.flash_files: + # Set encrypted flag to false for each file. + flash_files.append(file._replace(encrypted=False)) + # Replace the original tuple with modified tuple with all the files marked as unencrypted. + self.app.flash_files = tuple(flash_files) + # Now flash the files + self.flash() + + +@pytest.fixture(scope='module') +def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch: + mp = MonkeyPatch() + request.addfinalizer(mp.undo) + return mp + + +@pytest.fixture(scope='module', autouse=True) +def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None: + monkeypatch_module.setattr('pytest_embedded_idf.IdfSerial', EfuseFlashEncSerial) diff --git a/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_bitscrambler.c b/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_bitscrambler.c index 143f6d84b4..da99d586e1 100644 --- a/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_bitscrambler.c +++ b/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_bitscrambler.c @@ -95,7 +95,7 @@ static void test_parlio_bitscrambler(void) .idle_value = 0x00, .bitscrambler_program = bitscrambler_program_test_tx_LSB_to_MSB, }; - uint8_t tx_payload[TEST_PAYLOAD_SIZE] = {0}; + __attribute__((aligned(TEST_PAYLOAD_SIZE))) uint8_t tx_payload[TEST_PAYLOAD_SIZE] = {0}; for (int i = 0; i < TEST_PAYLOAD_SIZE; i++) { tx_payload[i] = i; } diff --git a/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c b/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c index bf210f10db..ec2ca9a23a 100644 --- a/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c +++ b/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c @@ -541,7 +541,7 @@ TEST_CASE("parallel_rx_unit_receive_external_memory_test", "[parlio_rx]") { parlio_rx_unit_handle_t rx_unit = NULL; parlio_rx_delimiter_handle_t deli = NULL; - size_t payload_size = 1000; + size_t payload_size = 1024; parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_DEFAULT, 1000000); config.flags.free_clk = 1; diff --git a/components/esp_driver_parlio/test_apps/parlio/partitions_efuse_emul.csv b/components/esp_driver_parlio/test_apps/parlio/partitions_efuse_emul.csv new file mode 100644 index 0000000000..c4c8330ef5 --- /dev/null +++ b/components/esp_driver_parlio/test_apps/parlio/partitions_efuse_emul.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +bootloader, bootloader, primary, N/A, N/A, +partition_table, partition_table, primary, N/A, N/A, +nvs, data, nvs, , 0x4000, +phy_init, data, phy, , 0x1000, +emul_efuse, data, efuse, , 0x2000, +factory, app, factory, , 1M, diff --git a/components/esp_driver_parlio/test_apps/parlio/pytest_parlio_unity.py b/components/esp_driver_parlio/test_apps/parlio/pytest_parlio_unity.py index 67bef7ef0c..6ce821ca05 100644 --- a/components/esp_driver_parlio/test_apps/parlio/pytest_parlio_unity.py +++ b/components/esp_driver_parlio/test_apps/parlio/pytest_parlio_unity.py @@ -19,6 +19,28 @@ def test_parlio(dut: Dut) -> None: dut.run_all_single_board_cases() +@pytest.mark.generic +@pytest.mark.parametrize( + 'config, skip_autoflash', + [ + ('virt_flash_enc', 'y'), + ], + indirect=True, +) +@idf_parametrize('target', ['esp32c6', 'esp32h2', 'esp32p4', 'esp32c5'], indirect=['target']) +def test_parlio_with_virt_flash_enc(dut: Dut) -> None: + print(' - Erase flash') + dut.serial.erase_flash() + + print(' - Start app (flash partition_table and app)') + dut.serial.write_flash_no_enc() + dut.expect('Loading virtual efuse blocks from real efuses') + dut.expect('Checking flash encryption...') + dut.expect('Generating new flash encryption key...') + + dut.run_all_single_board_cases() + + @pytest.mark.generic @pytest.mark.parametrize( 'config', diff --git a/components/esp_driver_parlio/test_apps/parlio/sdkconfig.ci.virt_flash_enc b/components/esp_driver_parlio/test_apps/parlio/sdkconfig.ci.virt_flash_enc new file mode 100644 index 0000000000..b0b4e5681a --- /dev/null +++ b/components/esp_driver_parlio/test_apps/parlio/sdkconfig.ci.virt_flash_enc @@ -0,0 +1,12 @@ +# FLASH_ENCRYPTION with EFUSE_VIRTUAL_KEEP_IN_FLASH + +CONFIG_PARTITION_TABLE_OFFSET=0xC000 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_efuse_emul.csv" + +CONFIG_SECURE_FLASH_ENC_ENABLED=y + +# Virtual eFuse mode is enough for driver behaviour test. +# Real encryption tests are guaranteed by DMA tests +CONFIG_EFUSE_VIRTUAL=y +CONFIG_EFUSE_VIRTUAL_KEEP_IN_FLASH=y diff --git a/components/esp_driver_rmt/test_apps/rmt/conftest.py b/components/esp_driver_rmt/test_apps/rmt/conftest.py new file mode 100644 index 0000000000..7b54db3b76 --- /dev/null +++ b/components/esp_driver_rmt/test_apps/rmt/conftest.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from _pytest.fixtures import FixtureRequest +from _pytest.monkeypatch import MonkeyPatch +from pytest_embedded_idf.serial import IdfSerial + + +# This is a custom IdfSerial class to support custom functionality +# which is required only for this test +class EfuseFlashEncSerial(IdfSerial): + @IdfSerial.use_esptool() + def write_flash_no_enc(self) -> None: + self.app.flash_settings['encrypt'] = False + flash_files = [] + for file in self.app.flash_files: + # Set encrypted flag to false for each file. + flash_files.append(file._replace(encrypted=False)) + # Replace the original tuple with modified tuple with all the files marked as unencrypted. + self.app.flash_files = tuple(flash_files) + # Now flash the files + self.flash() + + +@pytest.fixture(scope='module') +def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch: + mp = MonkeyPatch() + request.addfinalizer(mp.undo) + return mp + + +@pytest.fixture(scope='module', autouse=True) +def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None: + monkeypatch_module.setattr('pytest_embedded_idf.IdfSerial', EfuseFlashEncSerial) diff --git a/components/esp_driver_rmt/test_apps/rmt/partitions_efuse_emul.csv b/components/esp_driver_rmt/test_apps/rmt/partitions_efuse_emul.csv new file mode 100644 index 0000000000..c4c8330ef5 --- /dev/null +++ b/components/esp_driver_rmt/test_apps/rmt/partitions_efuse_emul.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +bootloader, bootloader, primary, N/A, N/A, +partition_table, partition_table, primary, N/A, N/A, +nvs, data, nvs, , 0x4000, +phy_init, data, phy, , 0x1000, +emul_efuse, data, efuse, , 0x2000, +factory, app, factory, , 1M, diff --git a/components/esp_driver_rmt/test_apps/rmt/pytest_rmt.py b/components/esp_driver_rmt/test_apps/rmt/pytest_rmt.py index 1a7b262872..aa4b737a82 100644 --- a/components/esp_driver_rmt/test_apps/rmt/pytest_rmt.py +++ b/components/esp_driver_rmt/test_apps/rmt/pytest_rmt.py @@ -19,6 +19,30 @@ def test_rmt(dut: Dut) -> None: dut.run_all_single_board_cases() +@pytest.mark.generic +@pytest.mark.parametrize( + 'config, skip_autoflash', + [ + ('virt_flash_enc', 'y'), + ], + indirect=True, +) +@idf_parametrize( + 'target', ['esp32', 'esp32s2', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32p4', 'esp32c5'], indirect=['target'] +) +def test_rmt_with_virt_flash_enc(dut: Dut) -> None: + print(' - Erase flash') + dut.serial.erase_flash() + + print(' - Start app (flash partition_table and app)') + dut.serial.write_flash_no_enc() + dut.expect('Loading virtual efuse blocks from real efuses') + dut.expect('Checking flash encryption...') + dut.expect('Generating new flash encryption key...') + + dut.run_all_single_board_cases() + + @pytest.mark.generic @pytest.mark.parametrize( 'config', @@ -58,3 +82,25 @@ def test_rmt_esp32c5_eco3(dut: Dut) -> None: @idf_parametrize('target', ['esp32s3'], indirect=['target']) def test_rmt_psram(dut: Dut) -> None: dut.run_all_single_board_cases() + + +@pytest.mark.octal_psram +@pytest.mark.parametrize( + 'config, skip_autoflash', + [ + ('virt_flash_enc', 'y'), + ], + indirect=True, +) +@idf_parametrize('target', ['esp32s3'], indirect=['target']) +def test_rmt_psram_with_virt_flash_enc(dut: Dut) -> None: + print(' - Erase flash') + dut.serial.erase_flash() + + print(' - Start app (flash partition_table and app)') + dut.serial.write_flash_no_enc() + dut.expect('Loading virtual efuse blocks from real efuses') + dut.expect('Checking flash encryption...') + dut.expect('Generating new flash encryption key...') + + dut.run_all_single_board_cases() diff --git a/components/esp_driver_rmt/test_apps/rmt/sdkconfig.ci.virt_flash_enc b/components/esp_driver_rmt/test_apps/rmt/sdkconfig.ci.virt_flash_enc new file mode 100644 index 0000000000..b0b4e5681a --- /dev/null +++ b/components/esp_driver_rmt/test_apps/rmt/sdkconfig.ci.virt_flash_enc @@ -0,0 +1,12 @@ +# FLASH_ENCRYPTION with EFUSE_VIRTUAL_KEEP_IN_FLASH + +CONFIG_PARTITION_TABLE_OFFSET=0xC000 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_efuse_emul.csv" + +CONFIG_SECURE_FLASH_ENC_ENABLED=y + +# Virtual eFuse mode is enough for driver behaviour test. +# Real encryption tests are guaranteed by DMA tests +CONFIG_EFUSE_VIRTUAL=y +CONFIG_EFUSE_VIRTUAL_KEEP_IN_FLASH=y diff --git a/components/esp_lcd/i80/esp_lcd_panel_io_i80.c b/components/esp_lcd/i80/esp_lcd_panel_io_i80.c index 80a877b49b..3738915884 100644 --- a/components/esp_lcd/i80/esp_lcd_panel_io_i80.c +++ b/components/esp_lcd/i80/esp_lcd_panel_io_i80.c @@ -464,7 +464,7 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons trans_desc->data = (param && param_len) ? bus->format_buffer : NULL; trans_desc->data_length = trans_desc->data ? param_len : 4; trans_desc->trans_done_cb = NULL; // no callback for parameter transaction - size_t buffer_alignment = esp_ptr_internal(trans_desc->data) ? bus->int_mem_align : bus->ext_mem_align; + size_t buffer_alignment = (trans_desc->data == NULL || esp_ptr_internal(trans_desc->data)) ? bus->int_mem_align : bus->ext_mem_align; static uint32_t fake_trigger = 0; // mount data to DMA links gdma_buffer_mount_config_t mount_config = { diff --git a/components/esp_lcd/test_apps/i80_lcd/conftest.py b/components/esp_lcd/test_apps/i80_lcd/conftest.py new file mode 100644 index 0000000000..7b54db3b76 --- /dev/null +++ b/components/esp_lcd/test_apps/i80_lcd/conftest.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from _pytest.fixtures import FixtureRequest +from _pytest.monkeypatch import MonkeyPatch +from pytest_embedded_idf.serial import IdfSerial + + +# This is a custom IdfSerial class to support custom functionality +# which is required only for this test +class EfuseFlashEncSerial(IdfSerial): + @IdfSerial.use_esptool() + def write_flash_no_enc(self) -> None: + self.app.flash_settings['encrypt'] = False + flash_files = [] + for file in self.app.flash_files: + # Set encrypted flag to false for each file. + flash_files.append(file._replace(encrypted=False)) + # Replace the original tuple with modified tuple with all the files marked as unencrypted. + self.app.flash_files = tuple(flash_files) + # Now flash the files + self.flash() + + +@pytest.fixture(scope='module') +def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch: + mp = MonkeyPatch() + request.addfinalizer(mp.undo) + return mp + + +@pytest.fixture(scope='module', autouse=True) +def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None: + monkeypatch_module.setattr('pytest_embedded_idf.IdfSerial', EfuseFlashEncSerial) diff --git a/components/esp_lcd/test_apps/i80_lcd/main/test_i80_lcd_panel.c b/components/esp_lcd/test_apps/i80_lcd/main/test_i80_lcd_panel.c index 8b651bec8d..3dc43e55db 100644 --- a/components/esp_lcd/test_apps/i80_lcd/main/test_i80_lcd_panel.c +++ b/components/esp_lcd/test_apps/i80_lcd/main/test_i80_lcd_panel.c @@ -367,7 +367,7 @@ TEST_CASE("lcd_panel_i80_io_test", "[lcd]") TEST_CASE("lcd_panel_with_i80_interface (st7789, 8bits)", "[lcd]") { #define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t)) - uint8_t *img = heap_caps_malloc(TEST_IMG_SIZE, MALLOC_CAP_DMA); + uint8_t *img = heap_caps_malloc(TEST_IMG_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); TEST_ASSERT_NOT_NULL(img); gpio_config_t bk_gpio_config = { diff --git a/components/esp_lcd/test_apps/i80_lcd/partitions_efuse_emul.csv b/components/esp_lcd/test_apps/i80_lcd/partitions_efuse_emul.csv new file mode 100644 index 0000000000..c4c8330ef5 --- /dev/null +++ b/components/esp_lcd/test_apps/i80_lcd/partitions_efuse_emul.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +bootloader, bootloader, primary, N/A, N/A, +partition_table, partition_table, primary, N/A, N/A, +nvs, data, nvs, , 0x4000, +phy_init, data, phy, , 0x1000, +emul_efuse, data, efuse, , 0x2000, +factory, app, factory, , 1M, diff --git a/components/esp_lcd/test_apps/i80_lcd/pytest_i80_lcd.py b/components/esp_lcd/test_apps/i80_lcd/pytest_i80_lcd.py index f8bf0548d2..be29f2ad6a 100644 --- a/components/esp_lcd/test_apps/i80_lcd/pytest_i80_lcd.py +++ b/components/esp_lcd/test_apps/i80_lcd/pytest_i80_lcd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -16,3 +16,25 @@ from pytest_embedded_idf.utils import idf_parametrize @idf_parametrize('target', ['esp32', 'esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) def test_i80_lcd(dut: Dut) -> None: dut.run_all_single_board_cases() + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config, skip_autoflash', + [ + ('virt_flash_enc', 'y'), + ], + indirect=True, +) +@idf_parametrize('target', ['esp32', 'esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +def test_i80_lcd_with_virt_flash_enc(dut: Dut) -> None: + print(' - Erase flash') + dut.serial.erase_flash() + + print(' - Start app (flash partition_table and app)') + dut.serial.write_flash_no_enc() + dut.expect('Loading virtual efuse blocks from real efuses') + dut.expect('Checking flash encryption...') + dut.expect('Generating new flash encryption key...') + + dut.run_all_single_board_cases() diff --git a/components/esp_lcd/test_apps/i80_lcd/sdkconfig.ci.virt_flash_enc b/components/esp_lcd/test_apps/i80_lcd/sdkconfig.ci.virt_flash_enc new file mode 100644 index 0000000000..b0b4e5681a --- /dev/null +++ b/components/esp_lcd/test_apps/i80_lcd/sdkconfig.ci.virt_flash_enc @@ -0,0 +1,12 @@ +# FLASH_ENCRYPTION with EFUSE_VIRTUAL_KEEP_IN_FLASH + +CONFIG_PARTITION_TABLE_OFFSET=0xC000 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_efuse_emul.csv" + +CONFIG_SECURE_FLASH_ENC_ENABLED=y + +# Virtual eFuse mode is enough for driver behaviour test. +# Real encryption tests are guaranteed by DMA tests +CONFIG_EFUSE_VIRTUAL=y +CONFIG_EFUSE_VIRTUAL_KEEP_IN_FLASH=y diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/conftest.py b/components/esp_lcd/test_apps/mipi_dsi_lcd/conftest.py new file mode 100644 index 0000000000..7b54db3b76 --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/conftest.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from _pytest.fixtures import FixtureRequest +from _pytest.monkeypatch import MonkeyPatch +from pytest_embedded_idf.serial import IdfSerial + + +# This is a custom IdfSerial class to support custom functionality +# which is required only for this test +class EfuseFlashEncSerial(IdfSerial): + @IdfSerial.use_esptool() + def write_flash_no_enc(self) -> None: + self.app.flash_settings['encrypt'] = False + flash_files = [] + for file in self.app.flash_files: + # Set encrypted flag to false for each file. + flash_files.append(file._replace(encrypted=False)) + # Replace the original tuple with modified tuple with all the files marked as unencrypted. + self.app.flash_files = tuple(flash_files) + # Now flash the files + self.flash() + + +@pytest.fixture(scope='module') +def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch: + mp = MonkeyPatch() + request.addfinalizer(mp.undo) + return mp + + +@pytest.fixture(scope='module', autouse=True) +def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None: + monkeypatch_module.setattr('pytest_embedded_idf.IdfSerial', EfuseFlashEncSerial) diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/CMakeLists.txt b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/CMakeLists.txt index 2532070141..43a25613de 100644 --- a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/CMakeLists.txt +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/CMakeLists.txt @@ -9,5 +9,5 @@ endif() # In order for the cases defined by `TEST_CASE` to be linked into the final elf, # the component can be registered as WHOLE_ARCHIVE idf_component_register(SRCS ${srcs} - PRIV_REQUIRES esp_lcd unity esp_driver_ppa + PRIV_REQUIRES esp_lcd unity esp_driver_ppa bootloader_support WHOLE_ARCHIVE) diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c index c1131a5905..1d925d8730 100644 --- a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -17,6 +17,10 @@ #include "test_mipi_dsi_board.h" #include "esp_lcd_ek79007.h" #include "driver/ppa.h" +#include "esp_flash_encrypt.h" + +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) +#define ALIGN_DOWN(num, align) ((num) & ~((align) - 1)) TEST_CASE("MIPI DSI Pattern Generator (EK79007)", "[mipi_dsi]") { @@ -152,7 +156,7 @@ TEST_CASE("MIPI DSI draw RGB bitmap (EK79007)", "[mipi_dsi]") int y_start = rand() % (MIPI_DSI_LCD_V_RES - 100); memset(img, color_byte, TEST_IMG_SIZE); esp_lcd_panel_draw_bitmap(mipi_dpi_panel, x_start, y_start, x_start + 100, y_start + 100, img); - vTaskDelay(pdMS_TO_TICKS(100)); + vTaskDelay(pdMS_TO_TICKS(10)); } TEST_ESP_OK(esp_lcd_panel_del(mipi_dpi_panel)); @@ -172,7 +176,14 @@ TEST_CASE("MIPI DSI use DMA2D (EK79007)", "[mipi_dsi]") test_bsp_enable_dsi_phy_power(); - uint8_t *img = malloc(TEST_IMG_SIZE); + uint8_t *img = NULL; + size_t buffer_alignment = 1; + // If flash encryption is enabled, the buffer address and size must be aligned to SOC_GDMA_EXT_MEM_ENC_ALIGNMENT. + if (esp_efuse_is_flash_encryption_enabled()) { + buffer_alignment = SOC_GDMA_EXT_MEM_ENC_ALIGNMENT; + } + img = heap_caps_aligned_calloc(buffer_alignment, 1, TEST_IMG_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(img); esp_lcd_dsi_bus_config_t bus_config = { @@ -234,16 +245,29 @@ TEST_CASE("MIPI DSI use DMA2D (EK79007)", "[mipi_dsi]") } vTaskDelay(pdMS_TO_TICKS(1000)); + size_t test_block_size = 100; + size_t start_alignment = 1; + size_t src_x_start = 50; + size_t src_y_start = 50; + // If flash encryption is enabled, the buffer address and size must be aligned to SOC_GDMA_EXT_MEM_ENC_ALIGNMENT. + if (esp_flash_encryption_enabled()) { + test_block_size = ALIGN_DOWN(test_block_size, SOC_GDMA_EXT_MEM_ENC_ALIGNMENT); + start_alignment = SOC_GDMA_EXT_MEM_ENC_ALIGNMENT; + src_x_start = ALIGN_DOWN(src_x_start, SOC_GDMA_EXT_MEM_ENC_ALIGNMENT); + src_y_start = ALIGN_DOWN(src_y_start, SOC_GDMA_EXT_MEM_ENC_ALIGNMENT); + } + printf("Add Built-in DMA2D draw bitmap hook\r\n"); TEST_ESP_OK(esp_lcd_dpi_panel_enable_dma2d(mipi_dpi_panel)); for (int i = 0; i < 100; i++) { - int x_start = rand() % (MIPI_DSI_LCD_H_RES - 100); - int y_start = rand() % (MIPI_DSI_LCD_V_RES - 100); + int x_start = ALIGN_DOWN(rand() % (MIPI_DSI_LCD_H_RES - test_block_size), start_alignment); + int y_start = ALIGN_DOWN(rand() % (MIPI_DSI_LCD_V_RES - test_block_size), start_alignment); uint8_t color_byte = rand() & 0xFF; memset(img, color_byte, TEST_IMG_SIZE / 2); color_byte = rand() & 0xFF; memset(img + TEST_IMG_SIZE / 2, color_byte, TEST_IMG_SIZE / 2); - esp_lcd_panel_draw_bitmap_2d(mipi_dpi_panel, x_start, y_start, x_start + 100, y_start + 100, img, 200, 200, 50, 50, 150, 150); + esp_lcd_panel_draw_bitmap_2d(mipi_dpi_panel, x_start, y_start, x_start + test_block_size, y_start + test_block_size, + img, 200, 200, src_x_start, src_y_start, src_x_start + test_block_size, src_y_start + test_block_size); vTaskDelay(pdMS_TO_TICKS(10)); } TEST_ESP_OK(esp_lcd_dpi_panel_disable_dma2d(mipi_dpi_panel)); @@ -342,6 +366,10 @@ TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]") esp_lcd_panel_io_handle_t mipi_dbi_io; esp_lcd_panel_handle_t mipi_dpi_panel; + if (esp_efuse_is_flash_encryption_enabled()) { + TEST_PASS_MESSAGE("PPA SRM is not compatible with encrypted memory, skip this test"); + } + test_bsp_enable_dsi_phy_power(); uint8_t *img = malloc(TEST_IMG_SIZE); diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/partitions_efuse_emul.csv b/components/esp_lcd/test_apps/mipi_dsi_lcd/partitions_efuse_emul.csv new file mode 100644 index 0000000000..c4c8330ef5 --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/partitions_efuse_emul.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +bootloader, bootloader, primary, N/A, N/A, +partition_table, partition_table, primary, N/A, N/A, +nvs, data, nvs, , 0x4000, +phy_init, data, phy, , 0x1000, +emul_efuse, data, efuse, , 0x2000, +factory, app, factory, , 1M, diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/pytest_mipi_dsi_lcd.py b/components/esp_lcd/test_apps/mipi_dsi_lcd/pytest_mipi_dsi_lcd.py index 85d7d3fb62..4f1c0a6a89 100644 --- a/components/esp_lcd/test_apps/mipi_dsi_lcd/pytest_mipi_dsi_lcd.py +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/pytest_mipi_dsi_lcd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -17,3 +17,25 @@ from pytest_embedded_idf.utils import idf_parametrize @idf_parametrize('target', ['esp32p4'], indirect=['target']) def test_dsi_lcd(dut: Dut) -> None: dut.run_all_single_board_cases() + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config, skip_autoflash', + [ + ('virt_flash_enc', 'y'), + ], + indirect=True, +) +@idf_parametrize('target', ['esp32p4'], indirect=['target']) +def test_dsi_lcd_with_virt_flash_enc(dut: Dut) -> None: + print(' - Erase flash') + dut.serial.erase_flash() + + print(' - Start app (flash partition_table and app)') + dut.serial.write_flash_no_enc() + dut.expect('Loading virtual efuse blocks from real efuses') + dut.expect('Checking flash encryption...') + dut.expect('Generating new flash encryption key...') + + dut.run_all_single_board_cases() diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/sdkconfig.ci.virt_flash_enc b/components/esp_lcd/test_apps/mipi_dsi_lcd/sdkconfig.ci.virt_flash_enc new file mode 100644 index 0000000000..b0b4e5681a --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/sdkconfig.ci.virt_flash_enc @@ -0,0 +1,12 @@ +# FLASH_ENCRYPTION with EFUSE_VIRTUAL_KEEP_IN_FLASH + +CONFIG_PARTITION_TABLE_OFFSET=0xC000 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_efuse_emul.csv" + +CONFIG_SECURE_FLASH_ENC_ENABLED=y + +# Virtual eFuse mode is enough for driver behaviour test. +# Real encryption tests are guaranteed by DMA tests +CONFIG_EFUSE_VIRTUAL=y +CONFIG_EFUSE_VIRTUAL_KEEP_IN_FLASH=y diff --git a/components/esp_lcd/test_apps/parlio_lcd/conftest.py b/components/esp_lcd/test_apps/parlio_lcd/conftest.py new file mode 100644 index 0000000000..7b54db3b76 --- /dev/null +++ b/components/esp_lcd/test_apps/parlio_lcd/conftest.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from _pytest.fixtures import FixtureRequest +from _pytest.monkeypatch import MonkeyPatch +from pytest_embedded_idf.serial import IdfSerial + + +# This is a custom IdfSerial class to support custom functionality +# which is required only for this test +class EfuseFlashEncSerial(IdfSerial): + @IdfSerial.use_esptool() + def write_flash_no_enc(self) -> None: + self.app.flash_settings['encrypt'] = False + flash_files = [] + for file in self.app.flash_files: + # Set encrypted flag to false for each file. + flash_files.append(file._replace(encrypted=False)) + # Replace the original tuple with modified tuple with all the files marked as unencrypted. + self.app.flash_files = tuple(flash_files) + # Now flash the files + self.flash() + + +@pytest.fixture(scope='module') +def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch: + mp = MonkeyPatch() + request.addfinalizer(mp.undo) + return mp + + +@pytest.fixture(scope='module', autouse=True) +def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None: + monkeypatch_module.setattr('pytest_embedded_idf.IdfSerial', EfuseFlashEncSerial) diff --git a/components/esp_lcd/test_apps/parlio_lcd/main/test_parlio_lcd_panel.c b/components/esp_lcd/test_apps/parlio_lcd/main/test_parlio_lcd_panel.c index 0d85dbe2df..3e810d1e27 100644 --- a/components/esp_lcd/test_apps/parlio_lcd/main/test_parlio_lcd_panel.c +++ b/components/esp_lcd/test_apps/parlio_lcd/main/test_parlio_lcd_panel.c @@ -25,7 +25,7 @@ #define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t)) static void lcd_parlio_panel_with_st7789_interface(esp_lcd_panel_io_handle_t io_handle) { - uint8_t *img = heap_caps_malloc(TEST_IMG_SIZE, MALLOC_CAP_DMA); + uint8_t *img = heap_caps_calloc(1, TEST_IMG_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); TEST_ASSERT_NOT_NULL(img); gpio_config_t bk_gpio_config = { @@ -148,7 +148,7 @@ static void lcd_parlio_send_colors_to_fixed_region(size_t data_width) int x_end = 200; int y_end = 200; size_t color_size = (x_end - x_start) * (y_end - y_start) * 2; - void *color_data = malloc(color_size); + void *color_data = heap_caps_calloc(1, color_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); TEST_ASSERT_NOT_NULL(color_data); uint8_t color_byte = esp_random() & 0xFF; memset(color_data, color_byte, color_size); diff --git a/components/esp_lcd/test_apps/parlio_lcd/partitions_efuse_emul.csv b/components/esp_lcd/test_apps/parlio_lcd/partitions_efuse_emul.csv new file mode 100644 index 0000000000..c4c8330ef5 --- /dev/null +++ b/components/esp_lcd/test_apps/parlio_lcd/partitions_efuse_emul.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +bootloader, bootloader, primary, N/A, N/A, +partition_table, partition_table, primary, N/A, N/A, +nvs, data, nvs, , 0x4000, +phy_init, data, phy, , 0x1000, +emul_efuse, data, efuse, , 0x2000, +factory, app, factory, , 1M, diff --git a/components/esp_lcd/test_apps/parlio_lcd/pytest_parlio_lcd.py b/components/esp_lcd/test_apps/parlio_lcd/pytest_parlio_lcd.py index 56097d32b1..4016ce0654 100644 --- a/components/esp_lcd/test_apps/parlio_lcd/pytest_parlio_lcd.py +++ b/components/esp_lcd/test_apps/parlio_lcd/pytest_parlio_lcd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -16,3 +16,25 @@ from pytest_embedded_idf.utils import idf_parametrize @idf_parametrize('target', ['esp32p4', 'esp32h2', 'esp32c5'], indirect=['target']) def test_parlio_lcd(dut: Dut) -> None: dut.run_all_single_board_cases() + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config, skip_autoflash', + [ + ('virt_flash_enc', 'y'), + ], + indirect=True, +) +@idf_parametrize('target', ['esp32p4', 'esp32h2', 'esp32c5'], indirect=['target']) +def test_parlio_lcd_with_virt_flash_enc(dut: Dut) -> None: + print(' - Erase flash') + dut.serial.erase_flash() + + print(' - Start app (flash partition_table and app)') + dut.serial.write_flash_no_enc() + dut.expect('Loading virtual efuse blocks from real efuses') + dut.expect('Checking flash encryption...') + dut.expect('Generating new flash encryption key...') + + dut.run_all_single_board_cases() diff --git a/components/esp_lcd/test_apps/parlio_lcd/sdkconfig.ci.virt_flash_enc b/components/esp_lcd/test_apps/parlio_lcd/sdkconfig.ci.virt_flash_enc new file mode 100644 index 0000000000..b0b4e5681a --- /dev/null +++ b/components/esp_lcd/test_apps/parlio_lcd/sdkconfig.ci.virt_flash_enc @@ -0,0 +1,12 @@ +# FLASH_ENCRYPTION with EFUSE_VIRTUAL_KEEP_IN_FLASH + +CONFIG_PARTITION_TABLE_OFFSET=0xC000 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_efuse_emul.csv" + +CONFIG_SECURE_FLASH_ENC_ENABLED=y + +# Virtual eFuse mode is enough for driver behaviour test. +# Real encryption tests are guaranteed by DMA tests +CONFIG_EFUSE_VIRTUAL=y +CONFIG_EFUSE_VIRTUAL_KEEP_IN_FLASH=y diff --git a/components/esp_lcd/test_apps/rgb_lcd/conftest.py b/components/esp_lcd/test_apps/rgb_lcd/conftest.py new file mode 100644 index 0000000000..7b54db3b76 --- /dev/null +++ b/components/esp_lcd/test_apps/rgb_lcd/conftest.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from _pytest.fixtures import FixtureRequest +from _pytest.monkeypatch import MonkeyPatch +from pytest_embedded_idf.serial import IdfSerial + + +# This is a custom IdfSerial class to support custom functionality +# which is required only for this test +class EfuseFlashEncSerial(IdfSerial): + @IdfSerial.use_esptool() + def write_flash_no_enc(self) -> None: + self.app.flash_settings['encrypt'] = False + flash_files = [] + for file in self.app.flash_files: + # Set encrypted flag to false for each file. + flash_files.append(file._replace(encrypted=False)) + # Replace the original tuple with modified tuple with all the files marked as unencrypted. + self.app.flash_files = tuple(flash_files) + # Now flash the files + self.flash() + + +@pytest.fixture(scope='module') +def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch: + mp = MonkeyPatch() + request.addfinalizer(mp.undo) + return mp + + +@pytest.fixture(scope='module', autouse=True) +def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None: + monkeypatch_module.setattr('pytest_embedded_idf.IdfSerial', EfuseFlashEncSerial) diff --git a/components/esp_lcd/test_apps/rgb_lcd/partitions_efuse_emul.csv b/components/esp_lcd/test_apps/rgb_lcd/partitions_efuse_emul.csv new file mode 100644 index 0000000000..c4c8330ef5 --- /dev/null +++ b/components/esp_lcd/test_apps/rgb_lcd/partitions_efuse_emul.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +bootloader, bootloader, primary, N/A, N/A, +partition_table, partition_table, primary, N/A, N/A, +nvs, data, nvs, , 0x4000, +phy_init, data, phy, , 0x1000, +emul_efuse, data, efuse, , 0x2000, +factory, app, factory, , 1M, diff --git a/components/esp_lcd/test_apps/rgb_lcd/pytest_rgb_lcd.py b/components/esp_lcd/test_apps/rgb_lcd/pytest_rgb_lcd.py index fb1b6e1fc8..de45e53d9b 100644 --- a/components/esp_lcd/test_apps/rgb_lcd/pytest_rgb_lcd.py +++ b/components/esp_lcd/test_apps/rgb_lcd/pytest_rgb_lcd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -19,6 +19,28 @@ def test_rgb_lcd_esp32s3(dut: Dut) -> None: dut.run_all_single_board_cases() +@pytest.mark.octal_psram +@pytest.mark.parametrize( + 'config, skip_autoflash', + [ + ('virt_flash_enc', 'y'), + ], + indirect=True, +) +@idf_parametrize('target', ['esp32s3'], indirect=['target']) +def test_rgb_lcd_esp32s3_with_virt_flash_enc(dut: Dut) -> None: + print(' - Erase flash') + dut.serial.erase_flash() + + print(' - Start app (flash partition_table and app)') + dut.serial.write_flash_no_enc() + dut.expect('Loading virtual efuse blocks from real efuses') + dut.expect('Checking flash encryption...') + dut.expect('Generating new flash encryption key...') + + dut.run_all_single_board_cases() + + @pytest.mark.generic @pytest.mark.parametrize( 'config', @@ -31,3 +53,25 @@ def test_rgb_lcd_esp32s3(dut: Dut) -> None: @idf_parametrize('target', ['esp32p4'], indirect=['target']) def test_rgb_lcd_esp32p4(dut: Dut) -> None: dut.run_all_single_board_cases() + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config, skip_autoflash', + [ + ('virt_flash_enc', 'y'), + ], + indirect=True, +) +@idf_parametrize('target', ['esp32p4'], indirect=['target']) +def test_rgb_lcd_esp32p4_with_virt_flash_enc(dut: Dut) -> None: + print(' - Erase flash') + dut.serial.erase_flash() + + print(' - Start app (flash partition_table and app)') + dut.serial.write_flash_no_enc() + dut.expect('Loading virtual efuse blocks from real efuses') + dut.expect('Checking flash encryption...') + dut.expect('Generating new flash encryption key...') + + dut.run_all_single_board_cases() diff --git a/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.ci.virt_flash_enc b/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.ci.virt_flash_enc new file mode 100644 index 0000000000..b0b4e5681a --- /dev/null +++ b/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.ci.virt_flash_enc @@ -0,0 +1,12 @@ +# FLASH_ENCRYPTION with EFUSE_VIRTUAL_KEEP_IN_FLASH + +CONFIG_PARTITION_TABLE_OFFSET=0xC000 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_efuse_emul.csv" + +CONFIG_SECURE_FLASH_ENC_ENABLED=y + +# Virtual eFuse mode is enough for driver behaviour test. +# Real encryption tests are guaranteed by DMA tests +CONFIG_EFUSE_VIRTUAL=y +CONFIG_EFUSE_VIRTUAL_KEEP_IN_FLASH=y diff --git a/components/esp_lcd/test_apps/spi_lcd/conftest.py b/components/esp_lcd/test_apps/spi_lcd/conftest.py new file mode 100644 index 0000000000..7b54db3b76 --- /dev/null +++ b/components/esp_lcd/test_apps/spi_lcd/conftest.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from _pytest.fixtures import FixtureRequest +from _pytest.monkeypatch import MonkeyPatch +from pytest_embedded_idf.serial import IdfSerial + + +# This is a custom IdfSerial class to support custom functionality +# which is required only for this test +class EfuseFlashEncSerial(IdfSerial): + @IdfSerial.use_esptool() + def write_flash_no_enc(self) -> None: + self.app.flash_settings['encrypt'] = False + flash_files = [] + for file in self.app.flash_files: + # Set encrypted flag to false for each file. + flash_files.append(file._replace(encrypted=False)) + # Replace the original tuple with modified tuple with all the files marked as unencrypted. + self.app.flash_files = tuple(flash_files) + # Now flash the files + self.flash() + + +@pytest.fixture(scope='module') +def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch: + mp = MonkeyPatch() + request.addfinalizer(mp.undo) + return mp + + +@pytest.fixture(scope='module', autouse=True) +def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None: + monkeypatch_module.setattr('pytest_embedded_idf.IdfSerial', EfuseFlashEncSerial) diff --git a/components/esp_lcd/test_apps/spi_lcd/main/test_spi_lcd_panel.c b/components/esp_lcd/test_apps/spi_lcd/main/test_spi_lcd_panel.c index 1fdce0c417..6f41b80052 100644 --- a/components/esp_lcd/test_apps/spi_lcd/main/test_spi_lcd_panel.c +++ b/components/esp_lcd/test_apps/spi_lcd/main/test_spi_lcd_panel.c @@ -78,7 +78,7 @@ void test_spi_lcd_common_initialize(esp_lcd_panel_io_handle_t *io_handle, esp_lc static void lcd_panel_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle) { - uint8_t *img = heap_caps_malloc(TEST_IMG_SIZE, MALLOC_CAP_DMA); + uint8_t *img = heap_caps_malloc(TEST_IMG_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); TEST_ASSERT_NOT_NULL(img); esp_lcd_panel_reset(panel_handle); diff --git a/components/esp_lcd/test_apps/spi_lcd/partitions_efuse_emul.csv b/components/esp_lcd/test_apps/spi_lcd/partitions_efuse_emul.csv new file mode 100644 index 0000000000..c4c8330ef5 --- /dev/null +++ b/components/esp_lcd/test_apps/spi_lcd/partitions_efuse_emul.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +bootloader, bootloader, primary, N/A, N/A, +partition_table, partition_table, primary, N/A, N/A, +nvs, data, nvs, , 0x4000, +phy_init, data, phy, , 0x1000, +emul_efuse, data, efuse, , 0x2000, +factory, app, factory, , 1M, diff --git a/components/esp_lcd/test_apps/spi_lcd/pytest_spi_lcd.py b/components/esp_lcd/test_apps/spi_lcd/pytest_spi_lcd.py index 26ad646b73..5df66ea347 100644 --- a/components/esp_lcd/test_apps/spi_lcd/pytest_spi_lcd.py +++ b/components/esp_lcd/test_apps/spi_lcd/pytest_spi_lcd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -16,3 +16,25 @@ from pytest_embedded_idf.utils import idf_parametrize @idf_parametrize('target', ['supported_targets'], indirect=['target']) def test_spi_lcd(dut: Dut) -> None: dut.run_all_single_board_cases() + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config, skip_autoflash', + [ + ('virt_flash_enc', 'y'), + ], + indirect=True, +) +@idf_parametrize('target', ['supported_targets'], indirect=['target']) +def test_spi_lcd_with_virt_flash_enc(dut: Dut) -> None: + print(' - Erase flash') + dut.serial.erase_flash() + + print(' - Start app (flash partition_table and app)') + dut.serial.write_flash_no_enc() + dut.expect('Loading virtual efuse blocks from real efuses') + dut.expect('Checking flash encryption...') + dut.expect('Generating new flash encryption key...') + + dut.run_all_single_board_cases() diff --git a/components/esp_lcd/test_apps/spi_lcd/sdkconfig.ci.virt_flash_enc b/components/esp_lcd/test_apps/spi_lcd/sdkconfig.ci.virt_flash_enc new file mode 100644 index 0000000000..b0b4e5681a --- /dev/null +++ b/components/esp_lcd/test_apps/spi_lcd/sdkconfig.ci.virt_flash_enc @@ -0,0 +1,12 @@ +# FLASH_ENCRYPTION with EFUSE_VIRTUAL_KEEP_IN_FLASH + +CONFIG_PARTITION_TABLE_OFFSET=0xC000 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_efuse_emul.csv" + +CONFIG_SECURE_FLASH_ENC_ENABLED=y + +# Virtual eFuse mode is enough for driver behaviour test. +# Real encryption tests are guaranteed by DMA tests +CONFIG_EFUSE_VIRTUAL=y +CONFIG_EFUSE_VIRTUAL_KEEP_IN_FLASH=y diff --git a/components/esp_mm/heap_align_hw.c b/components/esp_mm/heap_align_hw.c index bc0088b458..4aa9c46cb0 100644 --- a/components/esp_mm/heap_align_hw.c +++ b/components/esp_mm/heap_align_hw.c @@ -13,6 +13,7 @@ #if SOC_HAS(GDMA) #include "hal/gdma_ll.h" #include "hal/efuse_hal.h" +#include "esp_flash_encrypt.h" #endif #if CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH @@ -82,7 +83,7 @@ HEAP_IRAM_ATTR void esp_heap_adjust_alignment_to_hw(size_t *p_alignment, size_t #endif #if SOC_HAS(GDMA) && (SOC_PSRAM_DMA_CAPABLE || SOC_DMA_CAN_ACCESS_FLASH) - if ((caps & MALLOC_CAP_DMA) && efuse_hal_flash_encryption_enabled()) { + if ((caps & MALLOC_CAP_DMA) && esp_flash_encryption_enabled()) { alignment = (alignment > GDMA_LL_GET(ACCESS_ENCRYPTION_MEM_ALIGNMENT)) ? alignment : GDMA_LL_GET(ACCESS_ENCRYPTION_MEM_ALIGNMENT); } #endif diff --git a/tools/test_apps/system/g1_components/check_dependencies.py b/tools/test_apps/system/g1_components/check_dependencies.py index 482af9cd04..fb75ebc794 100644 --- a/tools/test_apps/system/g1_components/check_dependencies.py +++ b/tools/test_apps/system/g1_components/check_dependencies.py @@ -56,6 +56,7 @@ expected_dep_violations = { 'esp_system': ['esp_timer', 'bootloader_support', 'esp_pm', 'esp_usb_cdc_rom_console'], 'spi_flash': ['bootloader_support', 'esp_blockdev', 'esp_driver_gpio'], 'esp_hw_support': ['efuse', 'bootloader_support', 'esp_driver_gpio', 'esp_timer', 'esp_pm'], + 'esp_mm': ['bootloader_support'], 'cxx': ['pthread'], }