3. RPMsg协议原理与消息流
RPMsg(Remote Processor Messaging)是 OpenAMP 框架中运行在共享内存之上的消息传递协议。它并不负责搬运数据本身,而是定义了一套“谁发给谁、发到哪个端点、消息有多长、如何被接收方确认”的约定。理解 RPMsg,关键要抓住四个概念:端点(Endpoint)、通道(Channel)、地址模型(Address Model)和消息头(Header)。把这四者串起来,就能看清一条消息从 Cortex-A 侧用户程序到 Cortex-M 侧固件、再返回的完整生命周期。
一、端点与通道:通信的两级抽象
RPMsg 的通信对象是端点。一个端点可以理解为一个“消息邮箱”,它由本地地址(local address)唯一标识,并绑定一个回调函数。当消息到达该端点时,RPMsg 核心层调用回调,把数据交给上层应用。端点不是全局唯一的,它只在某个 rpmsg 设备(即一条 vring 通道)内部有效。
多个端点通过通道组织起来。通道用一个字符串名字标识,例如 "rpmsg-openamp-demo-channel"。名字的作用是让通信双方能够“对上号”:A 侧创建一个名为 rpmsg-tty 的端点,M 侧也创建同名端点,双方即可互相收发。通道名是逻辑约定,真正的寻址仍依赖端点地址。一个通道下可以有多个端点,但同一时刻同一名字通常只对应一个服务实例。
二、地址模型:源地址与目的地址
RPMsg 消息头中携带两个 32 位地址字段:src(源端点地址)和 dst(目的端点地址)。地址由本地 RPMsg 核心在创建端点时分配,典型规则是:地址 0 保留给“名称服务”(Name Service),地址 1~1023 用于动态端点。名称服务是一个特殊端点,它负责把“通道名”解析成“端点地址”。
当 A 侧想与 M 侧通信时,A 侧先向名称服务端点(地址 0)发送一条 RPMSG_NS_ANNOUNCE 消息,消息体中包含通道名和 A 侧端点地址。M 侧名称服务收到后,查找本地是否有同名通道;若有,则创建对应端点,并把 A 侧地址与本地端点绑定。此后双方直接用 src/dst 地址收发,不再经过名称服务。这就是 RPMsg 的“先解析、后直连”地址模型。
三、消息头格式:16 字节的约定
RPMsg 消息头固定 16 字节,定义在 struct rpmsg_hdr 中,字段顺序如下:
struct rpmsg_hdr {
uint32_t src; // 源端点地址
uint32_t dst; // 目的端点地址
uint32_t len; // 有效数据长度(不含头部)
uint16_t flags; // 标志位,如 RPMSG_HDR_FLAG_NS
uint16_t reserved;
};
其中 flags 字段用于区分普通数据消息和名称服务消息。当 flags 置位 RPMSG_HDR_FLAG_NS 时,表示这是一条名称服务通告,接收方应交给名称服务端点处理,而不是普通应用端点。数据区紧跟在头部之后,长度由 len 指定。由于 vring 缓冲区大小有限(常见 512 字节或 4096 字节),单条 RPMsg 消息的有效载荷通常不超过 496 字节(512 减去 16 字节头)。
四、消息传递流程与生命周期
一条 RPMsg 消息的完整生命周期可分为五个阶段:
- 创建端点:应用调用
rpmsg_create_ept(),RPMsg 核心分配本地地址,注册回调,并把端点挂到对应 rpmsg 设备上。 - 名称服务解析:若双方尚未建立通道,先通过地址 0 交换
NS_ANNOUNCE,完成通道名到端点地址的映射。 - 发送消息:调用
rpmsg_send(),核心层填充src/dst/len/flags,把消息头和数据写入 TX vring 的空闲缓冲区,然后触发 IPI(处理器间中断)通知对端。 - 接收与回调:对端收到 IPI 后,从 RX vring 取出缓冲区,解析消息头,根据
dst找到对应端点,调用其回调函数,把数据交给应用。 - 释放缓冲区:应用处理完毕后,调用
rpmsg_release_rx_buffer()或由核心层自动回收,把缓冲区归还给 vring,供后续消息复用。
这里有一个容易忽略的细节:RPMsg 的发送是异步的。调用 rpmsg_send() 返回成功,只表示消息已写入 vring 并触发中断,并不代表对端已经处理。如果应用需要确认,必须在协议层自行设计应答消息,例如在消息体中携带序列号,对端回复相同序列号表示已处理。
五、一个可参考的收发示例
假设 A 侧要发送字符串 "hello" 给 M 侧,端点地址分别为 0x400 和 0x401。发送端构造的消息缓冲区内容如下(十六进制示意):
偏移 0x00: 00 04 00 00 // src = 0x400
偏移 0x04: 01 04 00 00 // dst = 0x401
偏移 0x08: 05 00 00 00 // len = 5
偏移 0x0C: 00 00 00 00 // flags = 0, reserved = 0
偏移 0x10: 68 65 6C 6C 6F // "hello"
接收端解析时,先读 dst 确认是发给自己的,再读 len 确定数据长度,最后从偏移 0x10 开始取数据。若 flags 为 RPMSG_HDR_FLAG_NS,则跳过应用回调,直接交给名称服务处理。
dst 匹配本地端点,用 src 记录对端地址以便回复。