Răsfoiți Sursa

esp_mm: cache_msync API

Armando 3 ani în urmă
părinte
comite
fda9746bb8
37 a modificat fișierele cu 740 adăugiri și 39 ștergeri
  1. 1 1
      components/esp_lcd/CMakeLists.txt
  2. 3 6
      components/esp_lcd/src/esp_lcd_panel_rgb.c
  3. 8 9
      components/esp_mm/CMakeLists.txt
  4. 81 0
      components/esp_mm/esp_cache.c
  5. 58 0
      components/esp_mm/include/esp_cache.h
  6. 8 0
      components/esp_mm/linker.lf
  7. 2 2
      components/esp_mm/test_apps/mm/CMakeLists.txt
  8. 0 0
      components/esp_mm/test_apps/mm/README.md
  9. 14 0
      components/esp_mm/test_apps/mm/main/CMakeLists.txt
  10. 66 0
      components/esp_mm/test_apps/mm/main/test_app_main.c
  11. 207 0
      components/esp_mm/test_apps/mm/main/test_cache_msync.c
  12. 0 0
      components/esp_mm/test_apps/mm/main/test_mmap.c
  13. 0 0
      components/esp_mm/test_apps/mm/partitions.csv
  14. 95 0
      components/esp_mm/test_apps/mm/pytest_mmap.py
  15. 5 0
      components/esp_mm/test_apps/mm/sdkconfig.ci.psram_release
  16. 0 0
      components/esp_mm/test_apps/mm/sdkconfig.ci.release
  17. 7 0
      components/esp_mm/test_apps/mm/sdkconfig.ci.xip_psram
  18. 11 0
      components/esp_mm/test_apps/mm/sdkconfig.defaults
  19. 20 0
      components/esp_mm/test_apps/mmap_hw/CMakeLists.txt
  20. 2 0
      components/esp_mm/test_apps/mmap_hw/README.md
  21. 1 2
      components/esp_mm/test_apps/mmap_hw/main/CMakeLists.txt
  22. 0 0
      components/esp_mm/test_apps/mmap_hw/main/test_app_main.c
  23. 0 0
      components/esp_mm/test_apps/mmap_hw/main/test_mmap_hw.c
  24. 6 0
      components/esp_mm/test_apps/mmap_hw/partitions.csv
  25. 1 1
      components/esp_mm/test_apps/mmap_hw/pytest_mmap_hw.py
  26. 6 0
      components/esp_mm/test_apps/mmap_hw/sdkconfig.ci.release
  27. 1 1
      components/esp_mm/test_apps/mmap_hw/sdkconfig.defaults
  28. 61 16
      components/hal/cache_hal.c
  29. 47 1
      components/hal/include/hal/cache_hal.h
  30. 4 0
      components/soc/esp32c6/include/soc/Kconfig.soc_caps.in
  31. 1 0
      components/soc/esp32c6/include/soc/soc_caps.h
  32. 4 0
      components/soc/esp32h2/include/soc/Kconfig.soc_caps.in
  33. 1 0
      components/soc/esp32h2/include/soc/soc_caps.h
  34. 4 0
      components/soc/esp32s2/include/soc/Kconfig.soc_caps.in
  35. 3 0
      components/soc/esp32s2/include/soc/soc_caps.h
  36. 8 0
      components/soc/esp32s3/include/soc/Kconfig.soc_caps.in
  37. 4 0
      components/soc/esp32s3/include/soc/soc_caps.h

+ 1 - 1
components/esp_lcd/CMakeLists.txt

@@ -7,7 +7,7 @@ set(srcs "src/esp_lcd_common.c"
          "src/esp_lcd_panel_st7789.c"
          "src/esp_lcd_panel_ops.c")
 set(includes "include" "interface")
-set(priv_requires "driver")
+set(priv_requires "driver" "esp_mm")
 
 if(CONFIG_SOC_I2S_LCD_I80_VARIANT)
     list(APPEND srcs "src/esp_lcd_panel_io_i2s.c")

+ 3 - 6
components/esp_lcd/src/esp_lcd_panel_rgb.c

@@ -42,6 +42,7 @@
 #include "hal/lcd_ll.h"
 #include "hal/gdma_ll.h"
 #include "rom/cache.h"
+#include "esp_cache.h"
 
 #if CONFIG_LCD_RGB_ISR_IRAM_SAFE
 #define LCD_RGB_INTR_ALLOC_FLAGS     (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED)
@@ -798,7 +799,7 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
     if (rgb_panel->flags.fb_in_psram && !rgb_panel->bb_size) {
         // CPU writes data to PSRAM through DCache, data in PSRAM might not get updated, so write back
         // Note that if we use a bounce buffer, the data gets read by the CPU as well so no need to write back
-        Cache_WriteBack_Addr((uint32_t)(flush_ptr), bytes_to_flush);
+        esp_cache_msync((void *)(flush_ptr), (size_t)bytes_to_flush, 0);
     }
 
     if (!rgb_panel->bb_size) {
@@ -954,11 +955,7 @@ static IRAM_ATTR bool lcd_rgb_panel_fill_bounce_buffer(esp_rgb_panel_t *panel, u
         if (panel->flags.bb_invalidate_cache) {
             // We don't need the bytes we copied from the psram anymore
             // Make sure that if anything happened to have changed (because the line already was in cache) we write the data back.
-            Cache_WriteBack_Addr((uint32_t)&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel], panel->bb_size);
-            // Invalidate the data.
-            // Note: possible race: perhaps something on the other core can squeeze a write between this and the writeback,
-            // in which case that data gets discarded.
-            Cache_Invalidate_Addr((uint32_t)&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel], panel->bb_size);
+            esp_cache_msync(&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel], (size_t)panel->bb_size, ESP_CACHE_MSYNC_FLAG_INVALIDATE);
         }
     }
     panel->bounce_pos_px += panel->bb_size / bytes_per_pixel;

+ 8 - 9
components/esp_mm/CMakeLists.txt

@@ -9,16 +9,15 @@ set(srcs)
 
 if(NOT CONFIG_APP_BUILD_TYPE_PURE_RAM_APP)
     set(srcs "esp_mmu_map.c"
-             "port/${target}/ext_mem_layout.c")
+             "port/${target}/ext_mem_layout.c"
+             "esp_cache.c")
+
+    if(CONFIG_IDF_TARGET_ESP32)
+        list(APPEND srcs "cache_esp32.c")
+    endif()
 endif()
 
 idf_component_register(SRCS ${srcs}
                        INCLUDE_DIRS ${includes}
-                       PRIV_REQUIRES ${priv_requires})
-
-if(NOT BOOTLOADER_BUILD)
-    if(CONFIG_SPIRAM)
-        # Use esp_psram for `esp_psram_extram_writeback_cache()` on ESP32
-        idf_component_optional_requires(PRIVATE esp_psram)
-    endif()
-endif()
+                       PRIV_REQUIRES ${priv_requires}
+                       LDFRAGMENTS linker.lf)

+ 81 - 0
components/esp_mm/esp_cache.c

@@ -0,0 +1,81 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <sys/param.h>
+#include <inttypes.h>
+#include "sdkconfig.h"
+#include "esp_check.h"
+#include "esp_log.h"
+#include "soc/soc_caps.h"
+#include "hal/mmu_hal.h"
+#include "hal/cache_hal.h"
+#include "esp_cache.h"
+#include "esp_private/critical_section.h"
+
+
+static const char *TAG = "cache";
+
+#if SOC_CACHE_WRITEBACK_SUPPORTED
+DEFINE_CRIT_SECTION_LOCK_STATIC(s_spinlock);
+
+void s_cache_freeze(void)
+{
+#if SOC_CACHE_FREEZE_SUPPORTED
+    cache_hal_freeze(CACHE_TYPE_DATA | CACHE_TYPE_INSTRUCTION);
+#endif
+
+    /**
+     * For writeback supported, but the freeze not supported chip (Now only S2),
+     * as it's single core, the critical section is enough to prevent preemption from an non-IRAM ISR
+     */
+}
+
+void s_cache_unfreeze(void)
+{
+#if SOC_CACHE_FREEZE_SUPPORTED
+    cache_hal_unfreeze(CACHE_TYPE_DATA | CACHE_TYPE_INSTRUCTION);
+#endif
+
+    /**
+     * Similarly, for writeback supported, but the freeze not supported chip (Now only S2),
+     * we don't need to do more
+     */
+}
+#endif  //#if SOC_CACHE_WRITEBACK_SUPPORTED
+
+
+esp_err_t esp_cache_msync(void *addr, size_t size, int flags)
+{
+    ESP_RETURN_ON_FALSE_ISR(addr, ESP_ERR_INVALID_ARG, TAG, "null pointer");
+    ESP_RETURN_ON_FALSE_ISR(mmu_hal_check_valid_ext_vaddr_region(0, (uint32_t)addr, size, MMU_VADDR_DATA), ESP_ERR_INVALID_ARG, TAG, "invalid address");
+
+#if SOC_CACHE_WRITEBACK_SUPPORTED
+    if ((flags & ESP_CACHE_MSYNC_FLAG_UNALIGNED) == 0) {
+        esp_os_enter_critical_safe(&s_spinlock);
+        uint32_t data_cache_line_size = cache_hal_get_cache_line_size(CACHE_TYPE_DATA);
+        esp_os_exit_critical_safe(&s_spinlock);
+
+        ESP_RETURN_ON_FALSE_ISR(((uint32_t)addr % data_cache_line_size) == 0, ESP_ERR_INVALID_ARG, TAG, "start address isn't aligned with the data cache line size (%d)B", data_cache_line_size);
+        ESP_RETURN_ON_FALSE_ISR((size % data_cache_line_size) == 0, ESP_ERR_INVALID_ARG, TAG, "size isn't aligned with the data cache line size (%d)B", data_cache_line_size);
+        ESP_RETURN_ON_FALSE_ISR((((uint32_t)addr + size) % data_cache_line_size) == 0, ESP_ERR_INVALID_ARG, TAG, "end address isn't aligned with the data cache line size (%d)B", data_cache_line_size);
+    }
+
+    uint32_t vaddr = (uint32_t)addr;
+
+    esp_os_enter_critical_safe(&s_spinlock);
+    s_cache_freeze();
+
+    cache_hal_writeback_addr(vaddr, size);
+    if (flags & ESP_CACHE_MSYNC_FLAG_INVALIDATE) {
+        cache_hal_invalidate_addr(vaddr, size);
+    }
+
+    s_cache_unfreeze();
+    esp_os_exit_critical_safe(&s_spinlock);
+#endif
+
+    return ESP_OK;
+}

+ 58 - 0
components/esp_mm/include/esp_cache.h

@@ -0,0 +1,58 @@
+/*
+ * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "esp_err.h"
+#include "esp_bit_defs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Cache msync flags
+ */
+/**
+ * @brief Do an invalidation with the values that just written
+ */
+#define ESP_CACHE_MSYNC_FLAG_INVALIDATE    BIT(0)
+/**
+ * @brief Allow writeback a block that are not aligned to the data cache line size
+ */
+#define ESP_CACHE_MSYNC_FLAG_UNALIGNED     BIT(1)
+
+
+/**
+ * @brief Memory sync between Cache and external memory
+ *
+ * - For cache writeback supported chips (you can refer to SOC_CACHE_WRITEBACK_SUPPORTED in soc_caps.h)
+ *   - this API will do a writeback to synchronise between cache and the PSRAM
+ *   - with ESP_CACHE_MSYNC_FLAG_INVALIDATE, this API will also invalidate the values that just written
+ *   - note: although ESP32 is with PSRAM, but cache writeback isn't supported, so this API will do nothing on ESP32
+ * - For other chips, this API will do nothing. The out-of-sync should be already dealt by the SDK
+ *
+ * This API is cache-safe and thread-safe
+ *
+ * @note You should not call this during any Flash operations (e.g. esp_flash APIs, nvs and some other APIs that are based on esp_flash APIs)
+ * @note If XIP_From_PSRAM is enabled (by enabling both CONFIG_SPIRAM_FETCH_INSTRUCTIONS and CONFIG_SPIRAM_RODATA), you can call this API during Flash operations
+ *
+ * @param[in] Starting address to do the msync
+ * @param[in] Size to do the msync
+ * @param[in] Flags, see `ESP_CACHE_MSYNC_FLAG_x`
+ *
+ * @return
+ *        - ESP_OK:
+ *                  - Successful msync
+ *                  - If this chip doesn't support cache writeback, if the input addr is a cache supported one, this API will return ESP_OK
+ *        - ESP_ERR_INVALID_ARG:   Invalid argument, not cache supported addr, see printed logs
+ */
+esp_err_t esp_cache_msync(void *addr, size_t size, int flags);
+
+#ifdef __cplusplus
+}
+#endif

+ 8 - 0
components/esp_mm/linker.lf

@@ -0,0 +1,8 @@
+[mapping:esp_mm]
+archive: libesp_mm.a
+entries:
+
+    esp_cache (noflash)
+
+    if IDF_TARGET_ESP32S3 = y:
+        cache_esp32 (noflash)

+ 2 - 2
components/esp_mm/test_apps/mmap/CMakeLists.txt → components/esp_mm/test_apps/mm/CMakeLists.txt

@@ -4,13 +4,13 @@ cmake_minimum_required(VERSION 3.16)
 set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
 
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
-project(mmap_test)
+project(mm_test)
 
 if(CONFIG_COMPILER_DUMP_RTL_FILES)
     add_custom_target(check_test_app_sections ALL
                       COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
                       --rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/esp_mm/
-                      --elf-file ${CMAKE_BINARY_DIR}/mmap_test.elf
+                      --elf-file ${CMAKE_BINARY_DIR}/mm_test.elf
                       find-refs
                       --from-sections=.iram0.text
                       --to-sections=.flash.text,.flash.rodata

+ 0 - 0
components/esp_mm/test_apps/mmap/README.md → components/esp_mm/test_apps/mm/README.md


+ 14 - 0
components/esp_mm/test_apps/mm/main/CMakeLists.txt

@@ -0,0 +1,14 @@
+set(srcs "test_app_main.c")
+
+list(APPEND srcs "test_mmap.c")
+
+if(CONFIG_SOC_CACHE_WRITEBACK_SUPPORTED)
+    list(APPEND srcs "test_cache_msync.c")
+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 test_utils spi_flash esp_mm driver esp_timer
+                       WHOLE_ARCHIVE)

+ 66 - 0
components/esp_mm/test_apps/mm/main/test_app_main.c

@@ -0,0 +1,66 @@
+/*
+ * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "unity.h"
+#include "unity_test_utils.h"
+#include "esp_heap_caps.h"
+
+/**
+ * Hardware related tests, e.g.
+ * - traversing all vaddr range to check their attributes
+ *
+ * These tests need certain number of internal resources (heap memory), as they uses up the vaddr ranges
+ * On ESP32, it should be around 450
+ * On ESP32S2, it should be around 600
+ * On other chips, it should be around 400
+ */
+#define TEST_MEMORY_LEAK_THRESHOLD (-650)
+
+static size_t before_free_8bit;
+static size_t before_free_32bit;
+
+static void check_leak(size_t before_free, size_t after_free, const char *type)
+{
+    ssize_t delta = after_free - before_free;
+    printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
+    TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
+}
+
+void setUp(void)
+{
+    before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
+    before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
+}
+
+void tearDown(void)
+{
+    size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
+    size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
+    check_leak(before_free_8bit, after_free_8bit, "8BIT");
+    check_leak(before_free_32bit, after_free_32bit, "32BIT");
+}
+
+void app_main(void)
+{
+    /*
+         _____ ___________  ___  ______  ___  ___  ______   _____ ___
+        |  ___/  ___| ___ \ |  \/  ||  \/  ||_   _|  ___/  ___|_   _|
+        | |__ \ `--.| |_/ / | .  . || .  . |  | | | |__ \ `--.  | |
+        |  __| `--. \  __/  | |\/| || |\/| |  | | |  __| `--. \ | |
+        | |___/\__/ / |     | |  | || |  | |  | | | |___/\__/ / | |
+        \____/\____/\_|     \_|  |_/\_|  |_/  \_/ \____/\____/  \_/
+    */
+
+    printf(" _____ ___________  ___  ______  ___  ___  ______   _____ ___\r\n");
+    printf("|  ___/  ___| ___ \\ |  \\/  ||  \\/  ||_   _|  ___/  ___|_   _|\r\n");
+    printf("| |__ \\ `--.| |_/ / | .  . || .  . |  | | | |__ \\ `--.  | |\r\n");
+    printf("|  __| `--. \\  __/  | |\\/| || |\\/| |  | | |  __| `--. \\ | |\r\n");
+    printf("| |___/\\__/ / |     | |  | || |  | |  | | | |___/\\__/ / | |\r\n");
+    printf("\\____/\\____/\\_|     \\_|  |_/\\_|  |_/  \\_/ \\____/\\____/  \\_/\r\n");
+
+
+    unity_run_menu();
+}

+ 207 - 0
components/esp_mm/test_apps/mm/main/test_cache_msync.c

@@ -0,0 +1,207 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "sdkconfig.h"
+#include <sys/param.h>
+#include <string.h>
+#include "inttypes.h"
+#include "esp_log.h"
+#include "esp_attr.h"
+#include "unity.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_rom_sys.h"
+#include "esp_memory_utils.h"
+#include "esp_heap_caps.h"
+#include "esp_mmu_map.h"
+#include "esp_cache.h"
+#include "esp_timer.h"
+#include "esp_partition.h"
+#include "esp_flash.h"
+
+const static char *TAG = "CACHE_TEST";
+
+#define TEST_NUM                10
+#define TEST_BUF                {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9}
+
+
+#if CONFIG_IDF_TARGET_ESP32S2
+#define TEST_SYNC_START   0x3F500000
+#define TEST_SYNC_SIZE    0xA80000
+#elif CONFIG_IDF_TARGET_ESP32S3
+#define TEST_SYNC_START   0x3C000000
+#define TEST_SYNC_SIZE    0x2000000
+#endif
+
+
+#define RECORD_TIME_PREPARE()   uint32_t __t1, __t2
+#define RECORD_TIME_START()     do {__t1 = esp_cpu_get_cycle_count();} while(0)
+#define RECORD_TIME_END(p_time) do{__t2 = esp_cpu_get_cycle_count(); p_time = (__t2 - __t1);} while(0)
+#define GET_US_BY_CCOUNT(t)     ((double)(t)/CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ)
+
+static const uint8_t s_test_buf[TEST_NUM] = TEST_BUF;
+static DRAM_ATTR bool diff_res;
+static DRAM_ATTR uint32_t s_check_times = 0;
+
+static void NOINLINE_ATTR IRAM_ATTR s_test_rodata_cb(void *arg)
+{
+    bool sync_flag = *(bool *)arg;
+
+    if (sync_flag) {
+        uint8_t cmp_buf[TEST_NUM] = TEST_BUF;
+        for (int i = 0; i < TEST_NUM; i++) {
+            if (cmp_buf[i] != s_test_buf[i]) {
+                diff_res |= true;
+            }
+        }
+
+        s_check_times++;
+    }
+}
+
+/**
+ * This test tests if the esp_cache_msync() suspending CPU->Cache access is short enough
+ * 1. Register an IRAM callback, but access rodata inside the callback
+ * 2. esp_cache_msync() will suspend the CPU access to the cache
+ * 3. Therefore the rodata access in `s_test_rodata_cb()` should be blocked, most of the times
+ * 4. Note if the callback frequency is less, there might be few successful rodata access, as code execution needs time
+ */
+TEST_CASE("test cache msync short enough when suspending an ISR", "[cache]")
+{
+    uint32_t sync_time = 0;
+    uint32_t sync_time_us = 20;
+    RECORD_TIME_PREPARE();
+
+    //Do msync first, as the first writeback / invalidate takes long time, next msyncs will be shorter and they keep unchanged almost
+    RECORD_TIME_START();
+    TEST_ESP_OK(esp_cache_msync((void *)TEST_SYNC_START, TEST_SYNC_SIZE, ESP_CACHE_MSYNC_FLAG_INVALIDATE));
+    RECORD_TIME_END(sync_time);
+    sync_time_us = GET_US_BY_CCOUNT(sync_time);
+    printf("first sync_time_us: %"PRId32"\n", sync_time_us);
+
+    RECORD_TIME_START();
+    TEST_ESP_OK(esp_cache_msync((void *)TEST_SYNC_START, TEST_SYNC_SIZE, ESP_CACHE_MSYNC_FLAG_INVALIDATE));
+    RECORD_TIME_END(sync_time);
+    sync_time_us = GET_US_BY_CCOUNT(sync_time);
+    printf("sync_time_us: %"PRId32"\n", sync_time_us);
+
+    bool sync_flag = false;
+    esp_timer_handle_t timer;
+    const esp_timer_create_args_t oneshot_timer_args = {
+        .callback = &s_test_rodata_cb,
+        .arg = &sync_flag,
+        .dispatch_method = ESP_TIMER_ISR,
+        .name = "test_ro_suspend"
+    };
+    TEST_ESP_OK(esp_timer_create(&oneshot_timer_args, &timer));
+
+    uint32_t period = sync_time_us / 2;
+    TEST_ESP_OK(esp_timer_start_periodic(timer, period));
+
+    RECORD_TIME_START();
+    sync_flag = true;
+    TEST_ESP_OK(esp_cache_msync((void *)TEST_SYNC_START, TEST_SYNC_SIZE, ESP_CACHE_MSYNC_FLAG_INVALIDATE | ESP_CACHE_MSYNC_FLAG_UNALIGNED));
+    sync_flag = false;
+    RECORD_TIME_END(sync_time);
+
+    TEST_ESP_OK(esp_timer_stop(timer));
+    printf("s_check_times: %"PRId32"\n", s_check_times);
+    sync_time_us = GET_US_BY_CCOUNT(sync_time);
+    printf("sync time: %"PRId32" us\n", sync_time_us);
+    TEST_ASSERT((s_check_times < (sync_time_us / period)));
+    TEST_ASSERT(diff_res == false);
+
+    ESP_LOGI(TAG, "Finish");
+    TEST_ESP_OK(esp_timer_delete(timer));
+}
+
+
+static void s_test_with_msync_cb(void *arg)
+{
+    (void)arg;
+    esp_cache_msync((void *)TEST_SYNC_START, TEST_SYNC_SIZE, ESP_CACHE_MSYNC_FLAG_INVALIDATE);
+}
+
+TEST_CASE("test cache msync short enough to be in an ISR", "[cache]")
+{
+    uint32_t sync_time = 0;
+    uint32_t sync_time_us = 200;
+    RECORD_TIME_PREPARE();
+
+    RECORD_TIME_START();
+    TEST_ESP_OK(esp_cache_msync((void *)TEST_SYNC_START, TEST_SYNC_SIZE, ESP_CACHE_MSYNC_FLAG_INVALIDATE | ESP_CACHE_MSYNC_FLAG_UNALIGNED));
+    RECORD_TIME_END(sync_time);
+    sync_time_us = GET_US_BY_CCOUNT(sync_time);
+    printf("sync_time_us: %"PRId32"\n", sync_time_us);
+
+    esp_timer_handle_t timer;
+    const esp_timer_create_args_t oneshot_timer_args = {
+        .callback = &s_test_with_msync_cb,
+        .arg = NULL,
+        .dispatch_method = ESP_TIMER_ISR,
+        .name = "test_ro_suspend"
+    };
+    TEST_ESP_OK(esp_timer_create(&oneshot_timer_args, &timer));
+
+    uint32_t period = sync_time_us * 2;
+    TEST_ESP_OK(esp_timer_start_periodic(timer, period));
+
+    //1ms
+    esp_rom_delay_us(1000);
+    TEST_ESP_OK(esp_timer_stop(timer));
+
+    ESP_LOGI(TAG, "Finish");
+    TEST_ESP_OK(esp_timer_delete(timer));
+}
+
+#if CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_RODATA
+static const esp_partition_t *s_get_partition(void)
+{
+    //Find the "storage1" partition defined in `partitions.csv`
+    const esp_partition_t *result = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage1");
+    if (!result) {
+        ESP_LOGE(TAG, "Can't find the partition, please define it correctly in `partitions.csv`");
+        abort();
+    }
+    return result;
+}
+
+TEST_CASE("test cache msync work with Flash operation when XIP from PSRAM", "[cache]")
+{
+    uint32_t sync_time = 0;
+    RECORD_TIME_PREPARE();
+
+    RECORD_TIME_START();
+    TEST_ESP_OK(esp_cache_msync((void *)TEST_SYNC_START, TEST_SYNC_SIZE, ESP_CACHE_MSYNC_FLAG_INVALIDATE));
+    RECORD_TIME_END(sync_time);
+    uint32_t sync_time_us = GET_US_BY_CCOUNT(sync_time);
+    printf("sync_time_us: %"PRId32"\n", sync_time_us);
+
+    //Get the partition used for SPI1 erase operation
+    const esp_partition_t *part = s_get_partition();
+    ESP_LOGI(TAG, "found partition '%s' at offset 0x%"PRIx32" with size 0x%"PRIx32, part->label, part->address, part->size);
+    //Erase whole region
+    TEST_ESP_OK(esp_flash_erase_region(part->flash_chip, part->address, part->size));
+
+    esp_timer_handle_t timer;
+    const esp_timer_create_args_t oneshot_timer_args = {
+        .callback = &s_test_with_msync_cb,
+        .arg = NULL,
+        .dispatch_method = ESP_TIMER_ISR,
+        .name = "test_ro_suspend"
+    };
+    TEST_ESP_OK(esp_timer_create(&oneshot_timer_args, &timer));
+
+    uint32_t period = sync_time_us * 2;
+    TEST_ESP_OK(esp_timer_start_periodic(timer, period));
+
+    ESP_ERROR_CHECK(esp_flash_erase_region(part->flash_chip, part->address, part->size));
+
+    TEST_ESP_OK(esp_timer_stop(timer));
+    ESP_LOGI(TAG, "Finish");
+    TEST_ESP_OK(esp_timer_delete(timer));
+}
+#endif  //#if CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_RODATA

+ 0 - 0
components/esp_mm/test_apps/mmap/main/test_mmap.c → components/esp_mm/test_apps/mm/main/test_mmap.c


+ 0 - 0
components/esp_mm/test_apps/mmap/partitions.csv → components/esp_mm/test_apps/mm/partitions.csv


+ 95 - 0
components/esp_mm/test_apps/mm/pytest_mmap.py

@@ -0,0 +1,95 @@
+# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: CC0-1.0
+
+import pytest
+from pytest_embedded import Dut
+
+
+# normal mmu tests
+@pytest.mark.supported_targets
+@pytest.mark.generic
+@pytest.mark.parametrize(
+    'config',
+    [
+        'release',
+    ],
+    indirect=True,
+)
+def test_mmap(dut: Dut) -> None:
+    dut.run_all_single_board_cases(group='mmu')
+
+
+# mmu tests with psram enabled
+@pytest.mark.esp32
+@pytest.mark.esp32s2
+@pytest.mark.esp32s3
+@pytest.mark.generic
+@pytest.mark.parametrize(
+    'config',
+    [
+        'psram_release',
+    ],
+    indirect=True,
+)
+def test_mmap_psram(dut: Dut) -> None:
+    dut.run_all_single_board_cases(group='mmu')
+
+
+# mmu tests with xip_psram
+@pytest.mark.esp32s2
+@pytest.mark.esp32s3
+@pytest.mark.generic
+@pytest.mark.parametrize(
+    'config',
+    [
+        'xip_psram'
+    ],
+    indirect=True,
+)
+def test_mmap_xip_psram(dut: Dut) -> None:
+    dut.run_all_single_board_cases(group='mmu')
+
+
+# normal cache tests
+@pytest.mark.supported_targets
+@pytest.mark.generic
+@pytest.mark.parametrize(
+    'config',
+    [
+        'release',
+    ],
+    indirect=True,
+)
+def test_cache(dut: Dut) -> None:
+    dut.run_all_single_board_cases(group='cache')
+
+
+# cache tests with psram enabled
+@pytest.mark.esp32
+@pytest.mark.esp32s2
+@pytest.mark.esp32s3
+@pytest.mark.generic
+@pytest.mark.parametrize(
+    'config',
+    [
+        'psram_release',
+    ],
+    indirect=True,
+)
+def test_cache_psram(dut: Dut) -> None:
+    dut.run_all_single_board_cases(group='cache')
+
+
+# cache tests with xip_psram
+@pytest.mark.esp32s2
+@pytest.mark.esp32s3
+@pytest.mark.generic
+@pytest.mark.parametrize(
+    'config',
+    [
+        'xip_psram'
+    ],
+    indirect=True,
+)
+def test_cache_xip_psram(dut: Dut) -> None:
+    dut.run_all_single_board_cases(group='cache')

+ 5 - 0
components/esp_mm/test_apps/mm/sdkconfig.ci.psram_release

@@ -0,0 +1,5 @@
+CONFIG_COMPILER_OPTIMIZATION_SIZE=y
+CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
+CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
+
+CONFIG_SPIRAM=y

+ 0 - 0
components/esp_mm/test_apps/mmap/sdkconfig.ci.release → components/esp_mm/test_apps/mm/sdkconfig.ci.release


+ 7 - 0
components/esp_mm/test_apps/mm/sdkconfig.ci.xip_psram

@@ -0,0 +1,7 @@
+CONFIG_COMPILER_OPTIMIZATION_SIZE=y
+CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
+CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
+
+CONFIG_SPIRAM=y
+CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y
+CONFIG_SPIRAM_RODATA=y

+ 11 - 0
components/esp_mm/test_apps/mm/sdkconfig.defaults

@@ -0,0 +1,11 @@
+CONFIG_ESP_TASK_WDT_EN=n
+
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
+CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
+
+CONFIG_COMPILER_DUMP_RTL_FILES=y
+
+CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
+
+CONFIG_HAL_ASSERTION_SILENT=y

+ 20 - 0
components/esp_mm/test_apps/mmap_hw/CMakeLists.txt

@@ -0,0 +1,20 @@
+# This is the project CMakeLists.txt file for the test subproject
+cmake_minimum_required(VERSION 3.16)
+
+set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components")
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(mmap_hw_test)
+
+if(CONFIG_COMPILER_DUMP_RTL_FILES)
+    add_custom_target(check_test_app_sections ALL
+                      COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
+                      --rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/esp_mm/
+                      --elf-file ${CMAKE_BINARY_DIR}/mmap_hw_test.elf
+                      find-refs
+                      --from-sections=.iram0.text
+                      --to-sections=.flash.text,.flash.rodata
+                      --exit-code
+                      DEPENDS ${elf}
+                      )
+endif()

+ 2 - 0
components/esp_mm/test_apps/mmap_hw/README.md

@@ -0,0 +1,2 @@
+| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
+| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |

+ 1 - 2
components/esp_mm/test_apps/mmap/main/CMakeLists.txt → components/esp_mm/test_apps/mmap_hw/main/CMakeLists.txt

@@ -1,7 +1,6 @@
 set(srcs "test_app_main.c")
 
-list(APPEND srcs "test_mmap.c"
-                 "test_mmap_hw.c")
+list(APPEND srcs "test_mmap_hw.c")
 
 
 # In order for the cases defined by `TEST_CASE` to be linked into the final elf,

+ 0 - 0
components/esp_mm/test_apps/mmap/main/test_app_main.c → components/esp_mm/test_apps/mmap_hw/main/test_app_main.c


+ 0 - 0
components/esp_mm/test_apps/mmap/main/test_mmap_hw.c → components/esp_mm/test_apps/mmap_hw/main/test_mmap_hw.c


+ 6 - 0
components/esp_mm/test_apps/mmap_hw/partitions.csv

@@ -0,0 +1,6 @@
+# Name,   Type, SubType, Offset,  Size, Flags
+# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
+nvs,        data, nvs,      0x9000,  0x6000,
+phy_init,   data, phy,      0xf000,  0x1000,
+factory,    app,  factory,  0x10000, 1M,
+storage1,   data, fat,             , 512K,

+ 1 - 1
components/esp_mm/test_apps/mmap/pytest_mmap.py → components/esp_mm/test_apps/mmap_hw/pytest_mmap_hw.py

@@ -14,5 +14,5 @@ from pytest_embedded import Dut
     ],
     indirect=True,
 )
-def test_mmap(dut: Dut) -> None:
+def test_mmap_hw(dut: Dut) -> None:
     dut.run_all_single_board_cases(group='mmu', timeout=600)

+ 6 - 0
components/esp_mm/test_apps/mmap_hw/sdkconfig.ci.release

@@ -0,0 +1,6 @@
+# set compilier optimization level
+CONFIG_COMPILER_OPTIMIZATION_SIZE=y
+CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
+
+# we can silent the assertion to save the binary footprint
+CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y

+ 1 - 1
components/esp_mm/test_apps/mmap/sdkconfig.defaults → components/esp_mm/test_apps/mmap_hw/sdkconfig.defaults

@@ -1,4 +1,4 @@
-CONFIG_ESP_TASK_WDT=n
+CONFIG_ESP_TASK_WDT_EN=n
 
 CONFIG_PARTITION_TABLE_CUSTOM=y
 CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"

+ 61 - 16
components/hal/cache_hal.c

@@ -15,22 +15,7 @@
 #include "hal/mmu_hal.h"
 #include "hal/mmu_ll.h"
 #include "soc/soc_caps.h"
-
-#if CONFIG_IDF_TARGET_ESP32S2
-#include "esp32s2/rom/cache.h"
-#elif CONFIG_IDF_TARGET_ESP32S3
-#include "esp32s3/rom/cache.h"
-#elif CONFIG_IDF_TARGET_ESP32C3
-#include "esp32c3/rom/cache.h"
-#elif CONFIG_IDF_TARGET_ESP32C2
-#include "esp32c2/rom/cache.h"
-#elif CONFIG_IDF_TARGET_ESP32H4
-#include "esp32h4/rom/cache.h"
-#elif CONFIG_IDF_TARGET_ESP32C6
-#include "esp32c6/rom/cache.h"
-#elif CONFIG_IDF_TARGET_ESP32H2
-#include "esp32h2/rom/cache.h"
-#endif
+#include "rom/cache.h"
 
 /*------------------------------------------------------------------------------
  * Unified Cache Control
@@ -121,3 +106,63 @@ void cache_hal_invalidate_addr(uint32_t vaddr, uint32_t size)
     HAL_ASSERT(mmu_hal_check_valid_ext_vaddr_region(0, vaddr, size, MMU_VADDR_DATA | MMU_VADDR_INSTRUCTION));
     Cache_Invalidate_Addr(vaddr, size);
 }
+
+#if SOC_CACHE_WRITEBACK_SUPPORTED
+void cache_hal_writeback_addr(uint32_t vaddr, uint32_t size)
+{
+    HAL_ASSERT(mmu_hal_check_valid_ext_vaddr_region(0, vaddr, size, MMU_VADDR_DATA));
+    Cache_WriteBack_Addr(vaddr, size);
+}
+#endif  //#if SOC_CACHE_WRITEBACK_SUPPORTED
+
+
+#if SOC_CACHE_FREEZE_SUPPORTED
+void cache_hal_freeze(cache_type_t type)
+{
+#if SOC_SHARED_IDCACHE_SUPPORTED
+    Cache_Freeze_ICache_Enable(CACHE_FREEZE_ACK_BUSY);
+#else
+    if (type == CACHE_TYPE_DATA) {
+        Cache_Freeze_DCache_Enable(CACHE_FREEZE_ACK_BUSY);
+    } else if (type == CACHE_TYPE_INSTRUCTION) {
+        Cache_Freeze_ICache_Enable(CACHE_FREEZE_ACK_BUSY);
+    } else {
+        Cache_Freeze_ICache_Enable(CACHE_FREEZE_ACK_BUSY);
+        Cache_Freeze_DCache_Enable(CACHE_FREEZE_ACK_BUSY);
+    }
+#endif
+}
+
+void cache_hal_unfreeze(cache_type_t type)
+{
+#if SOC_SHARED_IDCACHE_SUPPORTED
+    Cache_Freeze_ICache_Disable();
+#else
+    if (type == CACHE_TYPE_DATA) {
+        Cache_Freeze_DCache_Disable();
+    } else if (type == CACHE_TYPE_INSTRUCTION) {
+        Cache_Freeze_ICache_Disable();
+    } else {
+        Cache_Freeze_DCache_Disable();
+        Cache_Freeze_ICache_Disable();
+    }
+#endif
+}
+#endif  //#if SOC_CACHE_FREEZE_SUPPORTED
+
+uint32_t cache_hal_get_cache_line_size(cache_type_t type)
+{
+#if SOC_SHARED_IDCACHE_SUPPORTED
+    return Cache_Get_ICache_Line_Size();
+#else
+    uint32_t size = 0;
+    if (type == CACHE_TYPE_DATA) {
+        size = Cache_Get_DCache_Line_Size();
+    } else if (type == CACHE_TYPE_INSTRUCTION) {
+        size = Cache_Get_ICache_Line_Size();
+    } else {
+        HAL_ASSERT(false);
+    }
+    return size;
+#endif
+}

+ 47 - 1
components/hal/include/hal/cache_hal.h

@@ -15,11 +15,12 @@ extern "C" {
 
 /**
  * Cache init and cache hal context init
- *
  */
 void cache_hal_init(void);
 
 /**
+ * @brief Disable cache
+ *
  * Disable the ICache or DCache or both, all the items in the corresponding Cache(s) will be invalideated.
  * Next request to these items will trigger a transaction to the external memory (flash / psram)
  *
@@ -30,6 +31,8 @@ void cache_hal_init(void);
 void cache_hal_disable(cache_type_t type);
 
 /**
+ * @brief Enable cache
+ *
  * Enable the ICache or DCache or both.
  *
  * @param type  see `cache_type_t`
@@ -37,6 +40,8 @@ void cache_hal_disable(cache_type_t type);
 void cache_hal_enable(cache_type_t type);
 
 /**
+ * @brief Invalidate cache supported addr
+ *
  * Invalidate a Cache item for either ICache or DCache.
  *
  * @param vaddr  Start address of the region to be invalidated
@@ -44,6 +49,47 @@ void cache_hal_enable(cache_type_t type);
  */
 void cache_hal_invalidate_addr(uint32_t vaddr, uint32_t size);
 
+#if SOC_CACHE_WRITEBACK_SUPPORTED
+/**
+ * @brief Writeback cache supported addr
+ *
+ * Writeback the DCache item to external memory
+ *
+ * @param vaddr  Start address of the region to writeback
+ * @param size   Size of the region to writeback
+ */
+void cache_hal_writeback_addr(uint32_t vaddr, uint32_t size);
+#endif  //#if SOC_CACHE_WRITEBACK_SUPPORTED
+
+#if SOC_CACHE_FREEZE_SUPPORTED
+/**
+ * @brief Freeze cache
+ *
+ * Freeze cache, CPU access to cache will be suspended, until the cache is unfrozen.
+ *
+ * @param type  see `cache_type_t`
+ */
+void cache_hal_freeze(cache_type_t type);
+
+/**
+ * @brief Unfreeze cache
+ *
+ * Unfreeze cache, CPU access to cache will be restored
+ *
+ * @param type  see `cache_type_t`
+ */
+void cache_hal_unfreeze(cache_type_t type);
+#endif  //#if SOC_CACHE_FREEZE_SUPPORTED
+
+/**
+ * @brief Get cache line size, in bytes
+ *
+ * @param type  see `cache_type_t`
+ *
+ * @return cache line size, in bytes
+ */
+uint32_t cache_hal_get_cache_line_size(cache_type_t type);
+
 #ifdef __cplusplus
 }
 #endif

+ 4 - 0
components/soc/esp32c6/include/soc/Kconfig.soc_caps.in

@@ -279,6 +279,10 @@ config SOC_SHARED_IDCACHE_SUPPORTED
     bool
     default y
 
+config SOC_CACHE_FREEZE_SUPPORTED
+    bool
+    default y
+
 config SOC_CPU_CORES_NUM
     int
     default 1

+ 1 - 0
components/soc/esp32c6/include/soc/soc_caps.h

@@ -121,6 +121,7 @@
 
 /*-------------------------- CACHE CAPS --------------------------------------*/
 #define SOC_SHARED_IDCACHE_SUPPORTED            1   //Shared Cache for both instructions and data
+#define SOC_CACHE_FREEZE_SUPPORTED              1
 
 /*-------------------------- CPU CAPS ----------------------------------------*/
 #define SOC_CPU_CORES_NUM               (1U)

+ 4 - 0
components/soc/esp32h2/include/soc/Kconfig.soc_caps.in

@@ -239,6 +239,10 @@ config SOC_SHARED_IDCACHE_SUPPORTED
     bool
     default y
 
+config SOC_CACHE_FREEZE_SUPPORTED
+    bool
+    default y
+
 config SOC_CPU_CORES_NUM
     int
     default 1

+ 1 - 0
components/soc/esp32h2/include/soc/soc_caps.h

@@ -117,6 +117,7 @@
 
 /*-------------------------- CACHE CAPS --------------------------------------*/
 #define SOC_SHARED_IDCACHE_SUPPORTED            1   //Shared Cache for both instructions and data
+#define SOC_CACHE_FREEZE_SUPPORTED              1
 
 /*-------------------------- CPU CAPS ----------------------------------------*/
 #define SOC_CPU_CORES_NUM               (1U)

+ 4 - 0
components/soc/esp32s2/include/soc/Kconfig.soc_caps.in

@@ -267,6 +267,10 @@ config SOC_BROWNOUT_RESET_SUPPORTED
     bool
     default y
 
+config SOC_CACHE_WRITEBACK_SUPPORTED
+    bool
+    default y
+
 config SOC_CP_DMA_MAX_BUFFER_SIZE
     int
     default 4095

+ 3 - 0
components/soc/esp32s2/include/soc/soc_caps.h

@@ -122,6 +122,9 @@
 /*-------------------------- BROWNOUT CAPS -----------------------------------*/
 #define SOC_BROWNOUT_RESET_SUPPORTED 1
 
+/*-------------------------- CACHE CAPS --------------------------------------*/
+#define SOC_CACHE_WRITEBACK_SUPPORTED           1
+
 /*-------------------------- CP-DMA CAPS -------------------------------------*/
 #define SOC_CP_DMA_MAX_BUFFER_SIZE (4095) /*!< Maximum size of the buffer that can be attached to descriptor */
 

+ 8 - 0
components/soc/esp32s3/include/soc/Kconfig.soc_caps.in

@@ -307,6 +307,14 @@ config SOC_BROWNOUT_RESET_SUPPORTED
     bool
     default y
 
+config SOC_CACHE_WRITEBACK_SUPPORTED
+    bool
+    default y
+
+config SOC_CACHE_FREEZE_SUPPORTED
+    bool
+    default y
+
 config SOC_CPU_CORES_NUM
     int
     default 2

+ 4 - 0
components/soc/esp32s3/include/soc/soc_caps.h

@@ -117,6 +117,10 @@
 /*-------------------------- BROWNOUT CAPS -----------------------------------*/
 #define SOC_BROWNOUT_RESET_SUPPORTED 1
 
+/*-------------------------- CACHE CAPS --------------------------------------*/
+#define SOC_CACHE_WRITEBACK_SUPPORTED           1
+#define SOC_CACHE_FREEZE_SUPPORTED              1
+
 /*-------------------------- CPU CAPS ----------------------------------------*/
 #define SOC_CPU_CORES_NUM               2
 #define SOC_CPU_INTR_NUM                32