i.MX8MP 的 A53 和 M7 可以访问约定的共享 RAM,但不能把“两个核能访问同一个地址”理解成“它们会自动看到彼此最新的写入”。M7 的数据缓存、A53 的缓存与映射、内存访问顺序以及缓冲区所有权,都必须参与协议。
1. 先纠正共享地址
跳转到“1. 先纠正共享地址”原图将 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 | 缓存 0x1234 | 0x1234 | M7 第一次读取,缓存了一条 cache line |
| t2 | 仍为 0x1234 | 已变为 0x5678 | 假定 A53 已完成写回和必要同步,将新值交给共享 RAM |
| t3 | 命中旧行,读出 0x1234 | 0x5678 | M7 没有按协议使接收区旧缓存失效 |
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”M7 写,A53 或 DMA 读
跳转到“M7 写,A53 或 DMA 读”- M7 取得缓冲区所有权,确认接收方已经不再使用上一次的数据。
- M7 写入完整 payload。
- 对可缓存的 payload 执行 clean,写回发生在写入之后,并完成平台要求的同步。
- 用既定协议发布长度、序号或 ready 状态,再通过 MU 等约定机制通知对方。控制字段本身的映射与可见性也必须得到处理。
- 接收方按自己的缓存和映射规则同步后读取,在归还所有权前 M7 不再修改该缓冲区。
A53 或 DMA 写,M7 读
跳转到“A53 或 DMA 写,M7 读”- M7 交出接收区前,先处理自身可能存在的脏行;按该协议对完整的独占 cache line 做 clean/invalidate 或其他规定的预处理。
- 生产者写入并完成自己的同步,之后再发布完成状态。
- M7 确认完成且重新取得所有权后,对接收区执行 invalidate,然后读取 payload。
- 在生产者拥有缓冲区期间,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 版本,应核对它的参数约定和屏障实现。
6. 对照现象排查
跳转到“6. 对照现象排查”| 现象 | 先核查 |
|---|---|
| A53 写入后 M7 仍读旧值 | A53 是否完成生产和写回;通知顺序;M7 是否在取得所有权后 invalidate;是否访问同一物理内存 |
| M7 写入后对端仍读旧值 | M7 是否在写 payload 后 clean;对端是否还缓存旧行;控制字段是否也满足同步要求 |
| 偶尔破坏相邻变量 | 缓存行重叠、未对齐范围、错误 invalidate 丢弃了私有脏数据 |
| 关闭 M7 缓存仍出错 | 错误地址/别名、A53 映射、Linux 占用、权限、越界、协议竞态或通知顺序 |
| 第一次正确、后续随机错误 | 所有权未归还、两侧同时写、长度/序号更新顺序、缓冲区复用过早 |
使用序号和不同 payload 图案重复收发,记录双方看到的长度与完成顺序,并检查链接 map 和 Linux 预留范围。编译包装函数只能验证接口和范围检查;真正的跨核可见性、MU 通知及 DMA 行为需要在目标板上验证。
参考资料
跳转到“参考资料”- NXP《i.MX 8M Plus Applications Processor Reference Manual》,Rev.3,Chapter 2 地址图与 Cortex-M7 子系统说明。
- Arm CMSIS 5.9.0:Cortex-M7 按地址缓存维护及屏障实现
- NXP MCUX_2.16.000:EVK-MIMX8MP 的 MPU 初始化
- Linux 6.8:i.MX8MP 的 A53 cache line 描述