3. 异构核间通信机制

在 i.MX8M、i.MX8QXP 这类异构 SoC 上,Cortex-A 核运行 Linux,Cortex-M 核运行 RTOS 或裸机程序,两者之间没有共享的缓存一致性域,也没有统一的地址空间视图。核间通信(IPC)要解决的核心问题不是“怎么发一个字节”,而是“如何在两个独立运行的软件栈之间,建立一条有边界、有通知、有协议约束的数据通道”。本节围绕 RPMsg、Messaging Unit(MU)、共享内存、Mailbox 与中断触发方式,说明它们各自处在通信链路的哪一层,以及如何组合成一条可用的通信协议。

一、先分清“搬运”和“通知”两件事

异构核间通信可以拆成两个正交的动作:数据搬运和事件通知。数据搬运通常由共享内存(Shared Memory / DRAM 或 OCRAM 中的一段保留区域)承担,因为它是双方都能访问的物理介质;事件通知则由 MU、Mailbox 或硬件中断控制器承担,用来告诉对端“数据已经写好了”或“缓冲区已经空了”。把这两件事混在一起,是初学者最常见的错误——例如把数据直接写进 MU 的寄存器,结果发现只能传 4 个 32 位字,根本承载不了图像帧或控制报文。

因此,一条典型的通信链路是:发送方把数据写入共享内存的环形缓冲区 → 更新写指针 → 通过 MU 或 Mailbox 触发对端中断 → 接收方在中断服务程序中读取指针、搬运数据、更新读指针 → 回送确认或触发反向中断。共享内存负责“放”,MU/Mailbox 负责“喊”,中断负责“被听见”。

Cortex-A(Linux) 应用 / 字符设备 /dev/rpmsg_ctrl0 RPMsg 总线 virtio ring + vring MU 驱动 / Mailbox imx-mailbox GIC 中断控制器 SPI 中断号 Cortex-M(RTOS) 任务 / 控制环 1 kHz 周期任务 RPMsg-Lite rpmsg_queue_recv MU 寄存器访问 TR/RR 通道 NVIC 中断 MU IRQ 向量 共享内存(保留区) vring0:A→M 描述符环 vring1:M→A 描述符环 数据缓冲区(512 B × N) Messaging Unit(MU) 4 个 TR / 4 个 RR 通道 · 32 位寄存器 · 中断聚合 数据面:共享内存承载 payload 控制面:MU 承载 doorbell 与短消息

二、RPMsg:把共享内存包装成“消息总线”

RPMsg(Remote Processor Messaging)是 Linux 内核中成熟的异构通信框架,它建立在 virtio 之上,用 vring(描述符环)管理共享内存中的缓冲区。每个方向一个 vring:A→M 的 vring0 和 M→A 的 vring1。发送方把数据放入空闲缓冲区,填写描述符,更新 avail 索引,然后通过 MU 触发对端中断;接收方在中断中读取 used 索引,取出描述符对应的缓冲区,处理完后归还。

RPMsg 的关键抽象是“端点(endpoint)”。每个端点有一个地址(如 0x400)和一个名字(如 rpmsg-openamp-demo-channel),双方通过名字匹配建立通道。Linux 侧通常用 rpmsg_char 或 rpmsg_tty 暴露给用户空间,M 核侧用 RPMsg-Lite 的 rpmsg_queue_recv() 接收。RPMsg 适合传输长度不固定、频率中等的控制报文,单条消息建议不超过 512 字节,避免占用过多 vring 缓冲区。

三、MU 与 Mailbox:通知通道的两种形态

Messaging Unit 是 i.MX 系列上的专用硬件,提供 4 个发送寄存器(TR0–TR3)和 4 个接收寄存器(RR0–RR3),每个 32 位。它既能传短消息(直接写 TR,对端读 RR),也能作为 doorbell 触发中断。MU 的中断可以配置为“收到消息触发”或“寄存器空触发”,前者用于通知对端有新数据,后者用于通知对端可以继续发送。

Mailbox 是 Linux 内核提供的通用框架,i.MX 的 MU 驱动注册为 mailbox controller。A 核通过 mbox_send_message() 发送一个带通道号的通知,M 核在中断中读取 MU 状态寄存器判断来源。相比直接操作 MU 寄存器,Mailbox 框架屏蔽了不同 SoC 的差异,但代价是增加了抽象层,延迟略高。对于 1 kHz 以下的控制环,这个开销可以接受;对于 10 kHz 以上的硬实时场景,建议 M 核侧直接操作 MU 寄存器。

四、中断触发方式与实时性权衡

中断触发方式直接决定通信延迟和 CPU 占用。常见有三种:

  • 边沿触发 + 中断聚合:MU 收到消息时产生一次边沿中断,适合低频控制报文。若短时间内多条消息到达,可配置 MU 的 GIER 寄存器做中断合并,减少中断次数。
  • 轮询 + 中断兜底:M 核在实时任务中周期性轮询 vring 的 used 索引,只有长时间无数据时才依赖中断唤醒。这种方式延迟确定,适合 1 kHz 以上的控制环。
  • Doorbell 中断 + 忙等确认:A 核写 TR 后触发中断,M 核在 ISR 中读取数据并立即回写 RR 作为 ACK,A 核在发送后短暂忙等 RR 置位。适合对往返延迟敏感但数据量小的场景。
在 i.MX8M 上,MU 中断号通常为 GIC SPI 276(A 核侧)和 NVIC IRQ 118(M 核侧),具体以参考手册和 DTS 为准。配置时需确认 fsl,mu 节点的 interrupts 属性与 M 核的向量表一致。

五、通信协议设计:从“能通”到“可靠”

有了 RPMsg 和 MU,通信链路已经能跑通,但要让它在实际系统中可靠工作,还需要一层协议设计。建议在 payload 头部固定 8 字节头:

struct ipc_msg_hdr {
    uint16_t magic;      // 0x4950 ("IP")
    uint8_t  version;    // 协议版本,如 0x01
    uint8_t  type;       // 0x01=控制, 0x02=数据, 0x03=心跳
    uint16_t seq;        // 序列号,用于丢包检测
    uint16_t len;        // payload 长度
    uint32_t crc32;      // 可选,对 payload 校验
} __packed;

发送方按 magic → version → type → seq → len → payload 的顺序填充,接收方先校验 magic 和 version,再按 len 读取 payload,最后用 seq 判断是否丢包。若连续两个 seq 不连续,说明中间有消息丢失,应触发重传或告警。对于控制类报文,建议在应用层实现“请求-应答”模型:A 核发送 type=0x01 的请求,M 核在 1 ms 内回送同 seq 的应答,超时则重发,最多重试 3 次。

共享内存的布局也需要在链接脚本和 DTS 中显式保留。例如在 i.MX8M 上,可以把 0x8FF00000–0x8FFFFFFF 这 1 MB 保留给 RPMsg,A 核侧在 DTS 中标记 no-map,M 核侧在链接脚本中把 .resource_table 和 vring 放到该区域。双方必须使用同一份地址映射,否则会出现“写进去了但读出来是旧值”的问题。

共享内存区域不要与 Linux 的 CMA、保留内存或 M 核的代码段重叠。修改 DTS 后需重新编译 dtb 并确认 /proc/iomem 中该区域未被 System RAM 占用。

六、选型建议

场景推荐机制理由
控制报文,≤512 B,1 kHzRPMsg + MU 中断框架成熟,端点管理方便
短命令,≤16 B,事件驱动MU TR/RR 直传延迟最低,无需共享内存
大块数据,如图像帧共享内存 + MU doorbell避免拷贝,带宽高
多核多通道Mailbox + RPMsg通道抽象清晰,便于扩展

实际项目中,往往是“共享内存 + MU 中断 + RPMsg 协议头”三者组合:共享内存提供带宽,MU 提供低延迟通知,RPMsg 提供端点与缓冲区管理,自定义头部提供可靠性与类型区分。理解每一层负责什么,才能在调试时快速定位问题——是数据没写进共享内存,还是 MU 中断没触发,还是协议头校验失败。