3. RPMsg初始化与端点创建流程:从设备树解析到端点注册的完整链路
RPMsg 的初始化流程,说白了就是回答三个问题:谁在跟谁通信?用什么通道?数据往哪送? 这三个问题分别对应设备树解析、vring 创建、端点注册。我见过不少工程师卡在第二步,设备树配对了,vring 地址却对不上,结果通信就是不通。
这一章,咱们就沿着代码执行路径,把从设备树解析到端点注册的完整链路走一遍。我会结合我实际调试过的案例,把那些容易踩的坑提前指出来。
3.1 设备树解析:通信拓扑的“第一份蓝图”
RPMsg 的初始化起点,不在 C 代码里,而在设备树(Device Tree)中。内核通过 of_platform_populate() 遍历设备树节点,找到 compatible 为 "virtio,mmio" 或 "openamp,remoteproc"> 的节点,然后触发对应的 platform driver 的 probe 函数。
我个人习惯在调试时先做一件事:确认设备树节点是否被正确解析。方法很简单,在 /sys/firmware/devicetree/base/ 下查看对应路径是否存在。如果节点没被解析,后面所有流程都是白搭。
compatible— 匹配驱动,必须与驱动中 of_match_table 一致reg— 定义 vring 和共享内存的物理地址范围interrupts— 定义核间中断号,用于通知对方mboxes/mbox-names— 定义 mailbox 通道(部分平台使用)
举个例子,一个典型的 RPMsg 设备树节点长这样:
rpmsg_reserved: rpmsg@10000000 {
compatible = "openamp,remoteproc-virtio";
reg = <0x0 0x10000000 0x0 0x100000>,
<0x0 0x10010000 0x0 0x100000>;
interrupts = <0 55 4>;
vring0 = <0x10020000 0x8000>;
vring1 = <0x10028000 0x8000>;
};
这里 vring0 和 vring1 是自定义属性,不是标准 virtio 属性。我曾经在一个项目里吃过这个亏:设备树里写的是 vring,驱动里读的是 vring-size,结果解析出来全是 0,通信直接挂掉。嗯,这种低级错误,排查起来最费时间。
3.2 virtio 设备注册:从设备树到内核对象
设备树解析完成后,remoteproc 框架会调用 rproc_virtio_probe()。这个函数会做三件事:
- 分配 virtio 设备结构体 — 填充
virtio_device的 id、config、ops - 创建 vring — 调用
rproc_virtio_setup_vring()分配 vring 内存 - 注册 virtio 设备 — 调用
register_virtio_device()将其挂入 virtio 总线
这里有个容易忽略的细节:vring 的内存分配方式。RPMsg 的 vring 必须放在共享内存区域,两端都能访问。如果分配在本地 DDR,远端根本看不到,通信必然失败。
rproc_virtio_setup_vring() 中加打印,输出 vring 的物理地址和大小。然后跟设备树里的值对比。如果地址对不上,八成是内存分配策略出了问题。
3.3 端点创建与注册:通信的“最后一公里”
virtio 设备注册完成后,RPMsg 总线会扫描该设备,并为每个可用的 端点(endpoint) 调用 rpmsg_create_ept()。端点是 RPMsg 通信的最小单元,类似于网络编程中的 socket。
端点的创建流程如下:
| 步骤 | 函数 | 说明 |
|---|---|---|
| 1 | rpmsg_create_ept() |
分配端点结构体,初始化回调函数 |
| 2 | rpmsg_register_endpoint() |
将端点加入 rpmsg_device 的端点链表 |
| 3 | rpmsg_send_ns_message() |
向远端发送名称服务(NS)消息,通告本端端点信息 |
| 4 | rpmsg_recv_ns_message() |
接收远端 NS 消息,创建对应的本地端点 |
我记得有一次调试,发现远端始终收不到本端的消息。查了半天,原来是 rpmsg_send_ns_message() 发送时,vring 的写指针没有更新。说白了,就是通知机制没生效。后来在 virtqueue_kick() 里加了调试打印,才发现中断号配错了。
rpmsg_device 的 ept_list,确保每个端点的 addr 唯一。
3.4 完整链路流程图
下面这张图,把从设备树解析到端点注册的完整流程串起来了。我建议你把它打印出来贴在工位上,调试时对照着看,能省不少时间。
3.5 常见故障模式与定位方法
在实际项目中,RPMsg 初始化阶段的故障,我归纳为以下三类:
| 故障现象 | 根因 | 定位方法 |
|---|---|---|
| probe 函数未调用 | 设备树 compatible 不匹配,或节点未使能 | 检查 /sys/firmware/devicetree/base/ 下节点是否存在 |
| vring 分配失败 | 共享内存区域不足,或地址重叠 | 在 rproc_virtio_setup_vring() 中打印物理地址,与设备树对比 |
| 端点注册失败 | 端点 ID 重复,或回调函数为 NULL | 遍历 rpmsg_device.ept_list,检查 addr 唯一性 |
| NS 消息收发失败 | vring 写指针未更新,或中断未触发 | 在 virtqueue_kick() 和中断处理函数中加打印 |
rpmsg_create_ept() 返回后,立即打印端点的 addr、cb(回调函数指针)和 rpdev。如果 cb 是 NULL,说明上层应用没注册回调,数据到了也没人处理。这种问题,我遇到过不止一次。