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()。这个函数会做三件事:

  1. 分配 virtio 设备结构体 — 填充 virtio_device 的 id、config、ops
  2. 创建 vring — 调用 rproc_virtio_setup_vring() 分配 vring 内存
  3. 注册 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() 里加了调试打印,才发现中断号配错了。

避坑指南: 我曾经遇到过一个 case,设备树里配了两个 vring,但驱动只创建了一个端点。结果就是:本端能发,远端能收,但远端回的消息本端收不到。原因是端点 ID 冲突,两个端点用了同一个 ID。检查 rpmsg_device 的 ept_list,确保每个端点的 addr 唯一。

3.4 完整链路流程图

下面这张图,把从设备树解析到端点注册的完整流程串起来了。我建议你把它打印出来贴在工位上,调试时对照着看,能省不少时间。

RPMsg初始化与端点创建完整链路 RPMsg初始化与端点创建完整链路 1. 设备树解析 of_platform_populate() → 匹配 compatible 2. virtio设备注册 rproc_virtio_probe() → register_virtio_device() 3. vring创建 rproc_virtio_setup_vring() → 分配共享内存 4. RPMsg总线扫描 rpmsg_bus_scan() → 发现可用端点 5. 端点创建 rpmsg_create_ept() → 分配端点结构体 6. 端点注册 rpmsg_register_endpoint() → 加入端点链表 7. NS消息通告(双向) 常见问题 • 设备树属性名不匹配 • vring地址超出共享内存 • 端点ID冲突 • 中断号配置错误

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,说明上层应用没注册回调,数据到了也没人处理。这种问题,我遇到过不止一次。