跳转到内容
新建笔记

i.MX8MP A53–M7 共享内存与缓存一致性

i.MX8MP 的 A53 和 M7 可以访问约定的共享 RAM,但不能把“两个核能访问同一个地址”理解成“它们会自动看到彼此最新的写入”。M7 的数据缓存、A53 的缓存与映射、内存访问顺序以及缓冲区所有权,都必须参与协议。

原图将 0x28000000~0x3fffffff 标为共享 DDR,并把缓冲区放在 0x30000000,这不符合 i.MX8MP 的地址图。该范围包含调试和外设映射,不能拿来当普通 RAM 写入。

RM Rev.3 的 M7 系统地址图把 0x40000000~0xbfffffff 列为 DDR 窗口。这个窗口不代表整段 DDR 均已装配、均允许 M7 访问或均可被应用占用。实际共享区必须同时满足板载内存容量、Linux reserved-memory、M7 链接布局、总线/RDC 权限与两侧地址映射约定;以项目已确认的 SHARED_BASE 和 SHARED_SIZE 表达它更合适。

A53 生产者 M7 消费者
CPU / A53 Cache CPU / M7 D-Cache
│ │
└────────── 互连 / 共享 RAM ────────┘
SHARED_BASE
只有一方在约定阶段拥有写权限

Linux 用户进程不能把 SoC 物理地址强制转换成指针后直接访问。实际映射、分配和生命周期应交由相应驱动或 BSP 的 remoteproc/RPMsg 机制处理。

2. 为什么 M7 会读到旧值

跳转到“2. 为什么 M7 会读到旧值”

保留原笔记的 0x1234 → 0x5678 例子,地址改用已确认的共享区:

时刻M7 缓存共享 RAM发生的事
t1缓存 0x12340x1234M7 第一次读取,缓存了一条 cache line
t2仍为 0x1234已变为 0x5678假定 A53 已完成写回和必要同步,将新值交给共享 RAM
t3命中旧行,读出 0x12340x5678M7 没有按协议使接收区旧缓存失效

t2 的前提很关键:如果 A53 的新值还留在自身缓存里,M7 即使不使用缓存,也未必读得到它。另一个方向同样成立:M7 写入 0xAAAA 后,如果脏行尚未写回,A53 或 DMA 可能仍读到旧 RAM 内容。

3. 三种工具各自解决什么

跳转到“3. 三种工具各自解决什么”
工具能做的事仍需另外解决的问题
M7 MPU 将区域设为 Normal、Non-cacheable让 M7 按该属性访问指定 RAM不配置 A53 页表、Linux 预留、外设权限,也不建立收发协议
链接段和对齐宏把对象放进指定 section,满足对齐约束section 必须被链接脚本放到真实共享区,MPU 属性与 A53 映射还要匹配
按范围 clean / invalidate在保留缓存性能的同时管理可见性调用时机、范围、所有权和两端同步必须正确

M7 MPU 的普通区域尺寸为支持的 2 的幂,起点要按区域大小对齐;有重叠区域时还要检查编号优先级和子区域设置。SDK AT_NONCACHEABLE_SECTION_ALIGN(uint8_t buffer[1024], 64) 只是声明放置意图,不能单独完成这整套配置。原文的 MPU_SetRegion(...) 也不是一个已经给出实现、可直接编译使用的通用接口。

在已设计好的协议中,可以把共享控制区设为不可缓存,也可以对数据缓冲区使用显式缓存维护。无需为了一个共享对象关闭整个 M7 的 D-Cache。把 volatile 加到指针上同样不能替代缓存维护、跨核内存屏障或原子性约定。

4. 按所有权交接,而不是随意 flush

跳转到“4. 按所有权交接,而不是随意 flush”
  1. M7 取得缓冲区所有权,确认接收方已经不再使用上一次的数据。
  2. M7 写入完整 payload。
  3. 对可缓存的 payload 执行 clean,写回发生在写入之后,并完成平台要求的同步。
  4. 用既定协议发布长度、序号或 ready 状态,再通过 MU 等约定机制通知对方。控制字段本身的映射与可见性也必须得到处理。
  5. 接收方按自己的缓存和映射规则同步后读取,在归还所有权前 M7 不再修改该缓冲区。
  1. M7 交出接收区前,先处理自身可能存在的脏行;按该协议对完整的独占 cache line 做 clean/invalidate 或其他规定的预处理。
  2. 生产者写入并完成自己的同步,之后再发布完成状态。
  3. M7 确认完成且重新取得所有权后,对接收区执行 invalidate,然后读取 payload。
  4. 在生产者拥有缓冲区期间,M7 不访问或修改它;同步状态应使用独立且定义清楚的控制区。

原文“写入前 flush”不能使随后发生的写入自动对外可见。也不能在收到对方数据后,盲目用 clean 把自己的旧脏行写回去覆盖新数据。DMA 和共享内存的具体协议可能要求交出前与收回后各做一次维护,应以实际平台接口为准。

5. cache line 是共享协议的最小隔离单位

跳转到“5. cache line 是共享协议的最小隔离单位”

M7 的数据 cache line 为 32 字节;本 SoC A53 的 cache line 为 64 字节。共享缓冲区及生产者/消费者独立修改的控制字段,应按双方要求隔离完整缓存行。例如只对一个 4 字节变量做 invalidate,硬件仍可能丢弃包含它的一整行;如果同一行还有本核未写回的私有数据,这些数据也会丢失。

64 字节起点对齐加上 64 字节整数倍长度,可满足这里两端的行边界要求,但不会自动解决对象尾部与邻接对象共享一行的问题。应在结构布局或缓冲区分配中包含所需填充,并统一地址、大小、所有权和控制字段布局。

下面的 M7 包装只允许完整的 32 字节范围,拒绝超出 CMSIS 有符号长度参数和 32 位地址窗口的请求。它保留了原笔记中的 clean/invalidate 操作,同时把调用前提写清楚:调用方必须已经拥有该完整范围,且地址确为协议规定的 RAM。

#include "fsl_device_registers.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <limits.h>
static bool m7_cache_span_valid(uint32_t start, size_t length)
{
if (length == 0U) {
return true;
}
return (start % 32U == 0U) && (length % 32U == 0U) &&
(length <= (size_t)INT32_MAX) &&
((uint64_t)start + length <= UINT64_C(0x100000000));
}
bool m7_clean_owned_range(void *address, size_t length)
{
_Static_assert(sizeof(uintptr_t) == 4, "M7 32-bit address wrapper");
if (!m7_cache_span_valid((uint32_t)(uintptr_t)address, length)) {
return false;
}
if (length != 0U) {
SCB_CleanDCache_by_Addr(address, (int32_t)length);
}
return true;
}
bool m7_invalidate_received_range(void *address, size_t length)
{
if (!m7_cache_span_valid((uint32_t)(uintptr_t)address, length)) {
return false;
}
if (length != 0U) {
SCB_InvalidateDCache_by_Addr(address, (int32_t)length);
}
return true;
}

这里的 true 只表示参数检查与维护函数调用完成,不能证明另一端已经完成写入。CMSIS 5.9.0 的上述维护函数内部包含 DSB/ISB;应用仍须定义“先 payload,后发布状态,再通知”的协议顺序,不能认为一次屏障会自动替另一核 clean 缓存。若使用不同 CMSIS/SDK 版本,应核对它的参数约定和屏障实现。

现象先核查
A53 写入后 M7 仍读旧值A53 是否完成生产和写回;通知顺序;M7 是否在取得所有权后 invalidate;是否访问同一物理内存
M7 写入后对端仍读旧值M7 是否在写 payload 后 clean;对端是否还缓存旧行;控制字段是否也满足同步要求
偶尔破坏相邻变量缓存行重叠、未对齐范围、错误 invalidate 丢弃了私有脏数据
关闭 M7 缓存仍出错错误地址/别名、A53 映射、Linux 占用、权限、越界、协议竞态或通知顺序
第一次正确、后续随机错误所有权未归还、两侧同时写、长度/序号更新顺序、缓冲区复用过早

使用序号和不同 payload 图案重复收发,记录双方看到的长度与完成顺序,并检查链接 map 和 Linux 预留范围。编译包装函数只能验证接口和范围检查;真正的跨核可见性、MU 通知及 DMA 行为需要在目标板上验证。