AMP 异构 功能简介 | 全志在线开发者社区 - 在线文档
OpenAMP 框架下 RPMSG 的原理与实现-CSDN博客
OpenAMP 是一个开源框架,用于管理非对称多处理(AMP) 系统中的软件交互。在这种系统中,多个核心可能运行着不同的操作系统(例如,Linux + RTOS/裸机)或独立的软件环境。
OpenAMP 的核心目标是提供一套标准化、可移植的API和组件,以简化AMP系统的开发。它主要包含两大核心组件:
-
RPMsg(Remote Processor Messaging):
-
这是一个基于共享内存 和处理器间中断(IPI) 的通信协议。
-
它充当了不同处理器核心之间传递结构化消息的"信道"。消息被放入共享内存的环形缓冲区中,并通过中断来通知对端有新消息到达。
-
Linux内核中已经有成熟的
rpmsg
驱动和框架,OpenAMP提供了在裸机或RTOS端与之对应的实现。
-
-
Remoteproc(Remote Processor Control):
-
这个组件负责远程处理器 的生命周期管理。
-
功能包括:加载远程核心的固件(firmware)、启动(boot)远程核心、停止(stop)远程核心、以及监视其运行状态等。
-
所以,OpenAMP 的核心功能是高层次的:通信协议和远程核心管理。
RPMsg - 远程处理器消息传递
核心职责: 在两个独立运行的处理器 之间提供基于消息的、异步的、可靠的通信通道。
关键概念与工作原理:
-
基于 VirtIO 和 vring:
-
RPMsg 的底层建立在 VirtIO 这个虚拟化I/O标准之上。VirtIO 定义了一种高效、通用的半虚拟化设备通信机制。
-
其核心是 vring(虚拟环形缓冲区) 。这是一种位于共享内存中的、无锁的环形队列数据结构。它有一个生产者和一个消费者。
-
通常会有两个 vring:一个用于主处理器发送到协处理器(A -> B),另一个用于协处理器发送到主处理器(B -> A)。
-
-
消息传递流程:
-
发送消息 :当一方(例如 Linux 上的应用程序)要发送消息时,RPMsg 层会将消息数据封装成一个缓冲区,并将其描述符放入发送 vring 中。
-
通知对端 :发送方然后通过处理器间中断(IPI) "踢"(kick)一下接收方,告知它有新消息到达。
-
接收消息:接收方(例如 E907 上的任务)在中断服务例程中,从 vring 中取出消息描述符,处理消息数据。
-
释放缓冲区:处理完毕后,接收方将缓冲区描述符放回 vring,并发送一个中断通知发送方"缓冲区已可用"。
-
-
信道(Channel)与端点(Endpoint):
-
信道 :一个逻辑通信路径,由一个字符串名称标识(例如
"rpmsg-openamp-demo-channel"
)。这允许多个独立的服务在同一对处理器上共存。 -
端点:是信道在本地的一个代表。应用程序通过创建一个端点并将其绑定到一个信道上,来发送和接收消息。
-
-
多平台支持:
- RPMsg 的实现是双向的。Linux 内核中有 RPMsg 框架(
drivers/rpmsg/
),同时 OpenAMP 项目也提供了在裸机或 RTOS(协处理器端)上运行的 RPMsg 库。这样,两者就能使用相同的协议进行通信。
- RPMsg 的实现是双向的。Linux 内核中有 RPMsg 框架(
在 Linux 中的体现:
在 Linux 端,RPMsg 会为每个动态创建的信道生成一个字符设备(如 /dev/rpmsgX
)。用户空间的应用程序可以像读写普通文件一样,使用 read
、write
等系统调用与协处理器进行通信。同时,内核其他模块也可以直接使用 RPMsg API。
下面用中文把"小核上调用 `rpbuf_alloc_buffer` 的原理"做一次系统讲解,并把流程和"大核(Linux)侧"如何配合对比讲清楚。目标是让你能理解端到端的信令、内存与同步要点,方便调试或做改动。
我会分为:概念预备、调用时序(步骤化说明)、角色差异(Master/Slave)、内存与缓存一致性、锁与同步机制、常见问题与调试建议、以及最终的端到端测试示例。
概念预备(术语)
-
controller:本端的 rpbuf 控制器实例,代表一条通信链路(link)。
-
link:连接两端(大核/小核)的抽象,包含 controller <-> service 的绑定关系。
-
service:对端(通常是控制/管理消息的通道)回调集合,用于发送/接收控制消息(BUFFER_CREATED/TRANSMITTED/ACK/DESTROYED)。
-
buffer:rpbuf 的逻辑缓冲区对象,含 VA(虚拟地址)、PA(物理地址)、DA(device address/远端地址)、len、id、回调(available/rx/destroyed)等。
-
dummy_buffers:占位 buffer,用于记录对端已创建但本端尚未完成映射/分配的 buffer 信息。
-
role:RPBUF_ROLE_MASTER / RPBUF_ROLE_SLAVE(通常 Master 在本地分配 payload,Slave 需要 remap master 的物理地址)。
高层意图(一句话)
- `rpbuf_alloc_buffer` 的目的:在本端创建一个缓冲描述(并在必要时分配/映射 payload 内存),然后通过"服务消息"通知对端"我创建了这个 buffer(或我已知这个 buffer)",并等待/触发对端回执或让回调可用,从而完成两端对同名 buffer 的协商与互通。
调用时序(逐步、带细节)
- 前提:link/controller/service 关系已建立
- 在调用 `rpbuf_alloc_buffer(controller, name, len, ops, cbs, priv)` 之前,必须先有 `rpbuf_register_controller()`(把 controller 注册到 link)和相应的 service 在对端注册(两端通过 token 建立 link)。link->service 和 link->controller 通常由较早的初始化或 rpmsg/rproc 控制逻辑完成。
- 在小核(本端)执行 `rpbuf_alloc_buffer(...)`:
-
新建 buffer 实例:`rpbuf_alloc_buffer_instance(name, len)`,初始化字段(name、len、wait queue、flags、state)。
-
将 buffer 的 controller/ops/cbs/priv/allocated 等字段填入。
-
取 `link = controller->link` 并获取 `role = controller->role`(Master/Slave)。
-
在 controller 的三张表中检查同名冲突:
-
查 local_dummy_buffers(本地占位表)
-
查 buffers(已完全分配的表)
-
查 remote_dummy_buffers(对端提前通知的表)
-
三种情况:
A. 若在 `remote_dummy_buffers` 找到同名 entry(说明对端先创建并通知了我们):
-
如果 role == SLAVE:取对端给出的 pa/da,调用 `rpbuf_addr_remap()`(一般走 `mem_pa_to_va`)把对端 PA 映射为本地 VA,于是本端只需要分配一个本地 id(`rpbuf_get_free_local_buffers_id`),并把 buffer 插入 `controller->buffers[id]`,buffer->allocated = false(payload 来自远端)。
-
如果 role == MASTER:通常是本端也要分配 payload(`rpbuf_alloc_buffer_payload_memory`)并把自己变为 buffers[id](并删除 remote_dummy)。
-
删除 remote_dummy 记录(并 free 该 dummy 实例)。
B. 若未在 remote_dummy 找到,就要在本端为 buffer 分配或标记:
-
如果 role == SLAVE:本端分配 local id(rpbuf_get_free_local_buffers_id),把 buffer 放到 `local_dummy_buffers`(等待 remote later 创建并通知)。
-
如果 role == MASTER:本端调用 `rpbuf_alloc_buffer_payload_memory(controller, buffer)` 分配 payload 内存(一般使用 `hal_malloc_coherent`),将 VA/PA/DA 填入 buffer,然后将 buffer 放入 `local_dummy_buffers`(等待 remote confirm)。
-
关键:`local_dummy_buffers` 和 `remote_dummy_buffers` 是不同语义的占位表,便于处理两端谁先创建的 race 情形。
- 通知对端(服务消息 BUFFER_CREATED)
-
准备 service 消息结构 `rpbuf_service_content_buffer_created`(包含 name/id/len/pa/da/is_available)。
-
调用 `rpbuf_notify_by_link(link, RPBUF_SERVICE_CMD_BUFFER_CREATED, &content)`,该函数取得 link->service,并调用 `service->ops->notify(...)`。
-
在具体实现中,`service->ops->notify` 会将该消息通过 RPMSG(或 remoteproc control)发送到对端(在 Linux 端这由 `rpmsg_ctrl` 的实现来做,或 librpmsg 通过 /dev/rpmsg-ctrl)。
- 对端收到 BUFFER_CREATED(大核或小核另一端)
-
对端解析 service 消息(`rpbuf_service_get_notification`),调用对应 handler `rpbuf_service_command_buffer_created_handler`。
-
Handler 会检查是否已有重名 buffer:
-
若对端本来也有相同 buffer(可能是本地已分配),会把信息整合并可能直接回复 BUFFER_CREATED 回执(把 is_available 设为 1),最终触发 available_cb。
-
否则对端可能把信息放到自己的 remote_dummy_buffers,等待本地 later 分配。
-
当 buffer 被"可用"时(is_available),实现会调用注册在 buffer 上的 `available_cb(buffer, priv)`,这个回调通常用于通知上层 buffer 已完成协商并可以读/写。
- 发送数据(rpbuf_transmit_buffer)
-
调用方在 buffer 的 VA + offset 写入数据后,调用 `rpbuf_transmit_buffer(buffer, offset, data_len)`:
-
会做 dcache clean(`rpbuf_dcache_clean(buffer->va + offset, data_len)`)保证可见性(物理内存对另一核可读)。
-
构造 `RPBUF_SERVICE_CMD_BUFFER_TRANSMITTED` 内容并 `rpbuf_notify_by_link` 发送通知。
-
若 buffer flags 含 `BUFFER_SYNC_TRANSMIT`,则会清除 GETNOTIFY 标志并随后等待远端回 ack:调用 `rpbuf_wait_for_remotebuffer_complete()`(该函数会在 wait queue 上等待直到收到 BUFFER_ACK 或 DESTROYED)。
-
对端收到 TRANSMITTED:
-
对端 handler `rpbuf_service_command_buffer_transmitted_handler` 进行数据回调处理:先做 dcache invalidate(针对接收区域),然后调用 `rx_cb(buffer, buffer->va + offset, data_len, priv)`,rx_cb 在上层做数据校验/处理。
-
如果是同步传输 (BUFFER_SYNC_TRANSMIT),接收方会调用 `rpbuf_notify_remotebuffer_complete()` 发送 BUFFER_ACK 回通知发送方;发送方 wait 会被唤醒。
- 销毁(rpbuf_free_buffer)
-
调用 `rpbuf_free_buffer` -> `rpbuf_free_buffer_internal(buffer, do_notify)`:
-
标记 buffer DESTROYED、发送 BUFFER_DESTROYED 通知给对端(如果 do_notify),并在本地根据 current state 把 buffer 从 buffers 删到 local_dummy 或 remote_dummy(取决于对端的状态),并释放 payload 内存(如果本端负责)。
-
可能需要等待正在进行的 callback (WORKING) 完成(wait queue)。
-
唤醒等待方、销毁本端对象。
角色差异(Master vs Slave,重点)
-
Master(通常是控制内存的一端)
-
在 `rpbuf_alloc_buffer` 时会分配 payload 内存(hal_malloc_coherent),获得 VA/PA/DA,并把地址信息发送给对端(在 content.pa/da)。然后通知对端 "buffer created and available"。
-
在 free 时,Master 负责释放 payload 内存。
-
Slave(通常是远端)
-
不直接分配 payload(payload 由 Master 分配)。收到 master 提供的 PA/DA 时调用 `rpbuf_addr_remap()`(mem_pa_to_va)把物理地址映射到本地 VA(可能通过 device tree/IO mapping)。
-
为 Slave 分配 local id(本端 buffer id)并把其放入 buffers。
-
两端通过 is_available 标志与 service 消息完成协商;dummy lists 帮助处理谁先创建的竞态。
内存与缓存一致性(非常关键)
-
Master 分配的 payload 必须是 coherent(`hal_malloc_coherent`),或在发送前做 dcache clean,接收端在读取前做 dcache invalidate。代码中:
-
发送前:`rpbuf_dcache_clean(buffer->va + offset, data_len);`
-
接收端收到后:`rpbuf_dcache_invalidate(buffer->va + offset, data_len);`
-
如果 payload 涉及跨域 DMA/IO,使用 mem_va_to_pa / mem_pa_to_va 获取并映射 device 地址(DA),确保两端可以使用同一物理地址访问内存。
锁与同步(避免竞态)
-
全局 link 列表:`__rpbuf_links_lock`(创建/删除 link 时用)。
-
每个 link 有两个 mutex:`service_lock` 和 `controller_lock`,并用 spinlock (`link->lock`) 做短时临界区保护(irqsave 版本)。
-
register/unregister controller/service 时使用这些锁保护 link。
-
在处理 service 消息(handler)时会持有 `link->controller_lock` 以访问 controller->buffers/local_dummy/remote_dummy。
-
buffer 内用 wait queue `buffer->wait` 实现同步等待(eg. 等 ACK,等待 WORKING 完成)。
-
buffer->state 标志位:RPBUF_FLAGS_WORKING、RPBUF_FLAGS_DESTROYED、RPBUF_FLAGS_GETNOTIFY 等,用以标记回调中/销毁中的状态。
与大核(Linux 用户态 / 内核)配合点(整体映射)
- Linux 侧通常以两种方式存在:
-
内核 driver(或内核端 rpbuf 实现):与小核通过 rpmsg/rproc 建立通道并处理 service 通信(大多情况是内核/固件的一部分)。
-
用户态 librpbuf/librpmsg:用户程序通过 librpmsg -> rpmsg_ctrl device 发送/接收 `BUFFER_CREATED/TRANSMITTED` 服务消息(`rpmsg_ctrl` 在用户态和内核之间桥接到 rpmsg drivers)。
-
在你的 repo 中:
-
小核的 `rpbuf_core.c` 实现了上面所有逻辑(alloc -> notify -> transmit -> free)。
-
大核/用户态 `rpbuf_demo` / `rpbuf_test` 通过 librpbuf/librpmsg 实现类似行为(调用库函数,库里通过 rpmsg 控制设备把消息发送到远端)。
-
`rpmsg_master.c` / rpmsg_ctrl 负责在另一端维护 client 列表、把 ctrl 请求(alloc endpoint、notify)映射成 `/dev/rpmsgN` 或把 service 消息路由到对应端点。
端到端调试/验证流程(实用步骤)
-
1)在小核(固件/RTOS)上 run `rpbuf_test -c -N "bufname" -L 64`(创建 buffer 并注册回调)。
-
2)在大核(Linux)上 run `rpbuf_demo -s "hello" -N "bufname" -L 64`,它会通过 librpmsg 发 `BUFFER_CREATED/TRANSMITTED` 等消息。
-
3)观察小核日志(available_cb/rx_cb 是否触发,checkdata 是否 success)。
-
关键检查点:
-
双方 buffer 名称/长度必须相同。
-
检查两端是否都成功 map PA/VA(小核上检查 buffer->va/pa/da 是否有值)。
-
检查 dcache clean/invalidate 是否被执行(在 ARM 平台常因缓存问题导致数据不一致)。
-
若使用同步传输,确认 ACK 流程(发送端等待,接收端发送 ACK)。
常见问题与定位建议
-
"回调不触发" -> 检查 service 消息是否到达(在 Linux 侧看 rpmsg_ctrl 日志 / dmesg),检查 link->service 是否正常(注册是否成功)。
-
"数据错乱/MD5 校验失败" -> 先确认 dcache clean/invalidate,确认 payload 分配是否 coherent;其次确认 offset/len 边界(offset + len <= buffer->len)。
-
"id 分配冲突/无可用 id" -> 检查 RPBUF_BUFFERS_NUM_MAX 的大小和 `rpbuf_get_free_local_buffers_id` 的实现(是否被不当放置/释放)。
-
"死等 ACK" -> 检查对端是否正确在 TRANSMITTED handler 中调用 `rpbuf_notify_remotebuffer_complete`,并且发送方在 `rpbuf_wait_for_remotebuffer_complete` 中等待的条件(GETNOTIFY 标志)是否一致。
简短示例(序列化步骤)
-
link 已建(controller registered on small core, service registered on big core)
-
small core: call rpbuf_alloc_buffer(controller, "X", 64) -> create buffer instance -> add to local_dummy -> notify big core (BUFFER_CREATED)
-
big core: on receiving BUFFER_CREATED -> create/move buffer -> if available send BUFFER_CREATED back (is_available=1)
-
small core: gets response -> buffer marked available -> call available_cb (user可开始发送/接收)
-
small core: write payload to VA; call rpbuf_transmit_buffer(...) -> dcache clean -> notify TRANSMITTED
-
big core: handle TRANSMITTED -> dcache invalidate -> call rx_cb -> if sync send ACK
-
small core: (if sync) wait wakeup on ACK -> proceed
结语与建议
-
`rpbuf_alloc_buffer` 的核心是"协商 + 内存映射 + 通知"三步:本地创建/分配 -> 通过 service 消息通知对端 -> 双方达成一致后触发 available 回调。
-
关键点:内存(PA/VA/DA)与缓存一致性、link 锁保护、dummy list 处理竞态、以及对端 notify 的可靠性(rpmsg 层)。
-
如果你想,我可以:
-
基于当前代码为你画一张简化的序列时序图(文本版或 mermaid);
-
或直接在 `rpbuf_core.c`/`rpbuf_test.c` 增加额外的 debug 日志点(例如在 alloc/notify/tx/ack 路径打印更详细的 PA/VA/DA/id/state),便于排查交互问题;
-
或给出一个端到端测试脚本(在大核与小核上运行的命令序列)和一组检查点命令(cat /proc/... / dmesg 等)。
rpbuf_transmit_buffer 本身并不直接调用 rpmsg API 。但它会通过 notify 路径间接调用 rpmsg API(如果当前的 service 实现是基于 rpmsg 的话)。换言之:rpbuf_transmit_buffer → rpbuf_notify_by_link → rpbuf_notify_by_service → service->ops->notify,而在仓库中默认的 notify 实现(rpmsg_rpbuf_service_notify
)会调用 rpmsg API(rpmsg_trysend
)。
关键调用链(函数与文件)
rpbuf_transmit_buffer(...)
(在 rpbuf_core.c):- 在发送前调用了数据缓存清理
rpbuf_dcache_clean(...)
,然后调用: rpbuf_notify_by_link(link, RPBUF_SERVICE_CMD_BUFFER_TRANSMITTED, &content);
- 在发送前调用了数据缓存清理
rpbuf_notify_by_link(...)
(在 rpbuf_core.c):- 获取
service = link->service
,然后调用rpbuf_notify_by_service(...)
- 获取
rpbuf_notify_by_service(...)
(在 rpbuf_core.c):- 调用
service->ops->notify(msg, msg_len, service->priv)
- 调用
- 在仓库中的实现 rpbuf_service_rpmsg.c:
- 定义了
rpmsg_rpbuf_service_ops
,其.notify = rpmsg_rpbuf_service_notify
rpmsg_rpbuf_service_notify(void *msg, int msg_len, void *priv)
调用了:rpmsg_trysend(ept, msg, msg_len);
(这是 open-amp / rpmsg API)
- 文件还使用了
rpmsg_create_ept
,rpmsg_register_driver
,rpmsg_destroy_ept
等 openamp/rpmsg API
- 定义了
因此,当你使用的是这个 rpmsg-based service 实现(即 rpbuf_init_service
被传入的 token/rpmsg 设备,并注册了 rpmsg_rpbuf_service_ops
),rpbuf 的 transmit 路径最终会走到 rpmsg_trysend
去把 service 消息发出去。
额外说明 / 触发的后续动作
- 对于同步发送(BUFFER_SYNC_TRANSMIT 标志),
rpbuf_transmit_buffer
在 notify 后会等待 ACK:这依赖于对端通过 rpmsg 接收到 BUFFER_TRANSMITTED 并返回 BUFFER_ACK(在rpbuf_service_command_buffer_ack_handler
中处理)。因此完整的数据/确认往返就是通过 rpmsg endpoint 完成的。 - 如果你要确认运行时是否实际使用 rpmsg,请检查初始化路径是否调用了
rpbuf_init_service(..., token)
且 token 指向 rpmsg 设备(rpbuf_service_rpmsg.c 中的rpbuf_init_service
)。