# 内存同步

[English]

## 简介

ESP32-S3 可以通过以下方式访问其连接的 PSRAM：

- CPU

- DMA

默认情况下，CPU 通过 cache 访问上述内存，而 DMA 则可以直接访问内存。

这可能导致 cache 数据不一致的问题：

- 当 DMA 事务更改内存块的内容，并且该内容已经加载到 cache 中时：

  > - CPU 可能会读取陈旧数据。

  > - cache 中的陈旧数据可能会被写回到内存中，而 DMA 事务更新的新数据将被覆盖。

- 当 CPU 更改了地址的内容，内容已经加载至 cache 中，但还未存在于内存中（cache 会根据自己的策略将内容写回内存）时：

  > - 下一个 DMA 事务从内存中读取此内容，将会获取陈旧数据。

解决此类 cache 数据不一致问题的常见方法有三种：

1. 基于硬件的 cache 一致性互连，ESP32-S3 没有此功能。

2. 使用来自 non-cacheable 内存的 DMA 缓冲区（CPU 绕过 cache 访问的内存被称为 non-cacheable 内存）。

3. 显式调用内存同步 API 将 cache 中的内容写回到内存，或使 cache 中的内容无效化。

## 内存同步驱动程序

建议使用 ESP-IDF 的 esp_mm 组件所提供的内存同步 API `esp_cache_msync()` 来处理此类 cache 数据不一致的问题。

### 驱动程序的概念

cache 与内存同步的方向：

- `ESP_CACHE_MSYNC_FLAG_DIR_C2M`，用于从 cache 到内存的同步。

- `ESP_CACHE_MSYNC_FLAG_DIR_M2C`，用于从内存到 cache 的同步。

cache 与内存同步的类型：

- `ESP_CACHE_MSYNC_FLAG_TYPE_DATA`，用于同步到数据地址区域。

- `ESP_CACHE_MSYNC_FLAG_TYPE_INST`，用于同步到指令地址区域。

### 驱动程序的行为

调用 `esp_cache_msync()`，可以同步 cache 和内存。第一个参数 addr 和第二个参数 size 共同描述了要同步的内存区域。关于第三个参数 flags：

- `ESP_CACHE_MSYNC_FLAG_DIR_C2M`。使用此标志，指定地址区域中的内容将被写回到内存中。这一方向通常在 CPU 更新地址内容 之后 使用（例如对地址执行 memset 后），且需要在 DMA 对同一地址进行操作 之前 使用。

- `ESP_CACHE_MSYNC_FLAG_DIR_M2C`。使用此标志，指定地址区域中的内容在 cache 中将无效化。这一方向通常在 DMA 更新地址内容 之后 使用，且需要在 CPU 将操作读取到同一地址 之前 使用。

上述两个标志用于选择同步的方向，不能同时使用。如果两个标志都未使用， `esp_cache_msync()` 将默认选择 `ESP_CACHE_MSYNC_FLAG_DIR_C2M`。

- `ESP_CACHE_MSYNC_FLAG_TYPE_DATA`。

- `ESP_CACHE_MSYNC_FLAG_TYPE_INST`。

上述两个标志能帮助选择同步地址的类型，不能同时使用，且如果没有用其中任何一个，则 `esp_cache_msync()` 将默认选择 `ESP_CACHE_MSYNC_FLAG_TYPE_DATA`。

- `ESP_CACHE_MSYNC_FLAG_INVALIDATE`。将特定地址区域写回内存后，可使用此标志触发区域内 cache 失效。此标志主要用于 `ESP_CACHE_MSYNC_FLAG_DIR_C2M` 方向。设置 `ESP_CACHE_MSYNC_FLAG_INVALIDATE` 标志对 `ESP_CACHE_MSYNC_FLAG_DIR_M2C` 方向不会产生任何效果。

- `ESP_CACHE_MSYNC_FLAG_UNALIGNED`。此标志会强制 `esp_cache_msync()` API 在不检查地址对齐和大小对齐的情况下执行同步，详情请参阅下文 地址对齐的要求 章节。

## 地址对齐的要求

使用 `esp_cache_msync()` 时，对地址和大小（以字节为单位）存在来自 cache 的对齐要求。

- 起始地址和大小都符合 cache 与内存同步对齐要求的地址区域被称为 对齐地址区域。

- 起始地址或大小不符合 cache 与内存同步对齐要求的地址区域被称为 非对齐地址区域。

默认情况下，如果指定了非对齐地址区域，则 `esp_cache_msync()` 将报错 `ESP_ERR_INVALID_ARG`，并告知所需的对齐方式。

### 有关地址对齐要求的警告

可以通过设置 `ESP_CACHE_MSYNC_FLAG_UNALIGNED` 标志跳过此类检查。但请注意，使用此标志需谨慎，因为在非对齐地址区域内同步 cache 和内存可能会在无形中破坏内存。

举个例子，假设：

- 对齐要求为 0x40 字节。

- 调用 `esp_cache_msync()`，并使用 ESP_CACHE_MSYNC_FLAG_DIR_M2C | ESP_CACHE_MSYNC_FLAG_UNALIGNED 标志，指定的地址区域为 0x4000_0020 ~ 0x4000_0060（详见下图中的 data C）。

上述设置将触发地址区域 0x4000_0000 ~ 0x4000_0080 cache 失效，详见下图中的 sync item0 和 sync item1。

如果 0x4000_0000 ~ 0x4000_0020 中的内容（下图中的 data A）或 0x4000_0060 ~ 0x4000_0080 中的内容（下图中的 data B）尚未写回到内存，则 data A 和 data B 将被丢弃。

## API 参考

### API 参考 - ESP Msync 驱动程序

#### Header File

- components/esp_mm/include/esp_cache.h

- This header file can be included with:

  >     #include "esp_cache.h"

- This header file is a part of the API provided by the `esp_mm` component. To declare that your component depends on `esp_mm`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_mm

  > or

  >     PRIV_REQUIRES esp_mm

#### Functions

esp_err_t esp_cache_msync(void *addr, size_t size, int flags)

  Memory sync between Cache and storage memory.

  For cache-to-memory (C2M) direction:

  - 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 storage memory

    - 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

  For memory-to-cache (M2C) direction:

  - This API will by default do an invalidation

  This API is cache-safe and thread-safe

  备注

  If you don't set direction (ESP_CACHE_MSYNC_FLAG_DIR_x flags), this API is by default C2M direction

  备注

  If you don't set type (ESP_CACHE_MSYNC_FLAG_TYPE_x flags), this API is by default doing msync for data

  备注

  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)

  备注

  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

  参数:

    - addr -- [in] Starting address to do the msync

    - size -- [in] Size to do the msync

    - flags -- [in] Flags, see `ESP_CACHE_MSYNC_FLAG_x`

  返回:

    - ESP_OK:

      - Successful msync

      - For C2M direction, 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_NOT_SUPPORTED: Vaddr is not in cacheable range, API will do nothing

size_t esp_cache_get_line_size_by_addr(const void *addr)

  Get the cache line size by address.

  参数:

    addr -- [in] The buffer address to examine

  返回:

    The cache line size in bytes. Return 0 if the memory (that the address points to) is not cacheable.

#### Macros

ESP_CACHE_MSYNC_FLAG_INVALIDATE

  Do an invalidation.

  Cache msync flags

  - For cache-to-memory (C2M) direction: setting this flag will start an invalidation after the cache writeback operation

  - For memory-to-cache (M2C) direction: setting / unsetting this flag will behave similarly, trigger an invalidation

ESP_CACHE_MSYNC_FLAG_UNALIGNED

  Allow msync to a address block that are not aligned to the data cache line size.

ESP_CACHE_MSYNC_FLAG_DIR_C2M

  Cache msync direction: from Cache to memory.

  备注

  If you don't set direction (ESP_CACHE_MSYNC_FLAG_DIR_x flags), it is by default cache-to-memory (C2M) direction

ESP_CACHE_MSYNC_FLAG_DIR_M2C

  Cache msync direction: from memory to Cache.

ESP_CACHE_MSYNC_FLAG_TYPE_DATA

  Cache msync type: data.

  备注

  If you don't set type (ESP_CACHE_MSYNC_FLAG_TYPE_x flags), it is by default data type

ESP_CACHE_MSYNC_FLAG_TYPE_INST

  Cache msync type: instruction.

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。