远程处理器协议框架学习

07 基于OpenAMP的核间通信方案-CSDN博客

开发使用 - 全志Linux Tina-SDK开发完全手册

AMP 异构 功能简介 | 全志在线开发者社区 - 在线文档

OpenAMP 框架下 RPMSG 的原理与实现-CSDN博客

RPMsg协议详解:异构多核系统中的核间通信-CSDN博客

OpenAMP 是一个开源框架,用于管理非对称多处理(AMP) 系统中的软件交互。在这种系统中,多个核心可能运行着不同的操作系统(例如,Linux + RTOS/裸机)或独立的软件环境。

OpenAMP 的核心目标是提供一套标准化、可移植的API和组件,以简化AMP系统的开发。它主要包含两大核心组件:

  1. RPMsg(Remote Processor Messaging):

    • 这是一个基于共享内存处理器间中断(IPI) 的通信协议。

    • 它充当了不同处理器核心之间传递结构化消息的"信道"。消息被放入共享内存的环形缓冲区中,并通过中断来通知对端有新消息到达。

    • Linux内核中已经有成熟的 rpmsg 驱动和框架,OpenAMP提供了在裸机或RTOS端与之对应的实现。

  2. Remoteproc(Remote Processor Control):

    • 这个组件负责远程处理器生命周期管理

    • 功能包括:加载远程核心的固件(firmware)、启动(boot)远程核心、停止(stop)远程核心、以及监视其运行状态等。

所以,OpenAMP 的核心功能是高层次的:通信协议和远程核心管理。

RPMsg - 远程处理器消息传递

核心职责:两个独立运行的处理器 之间提供基于消息的、异步的、可靠的通信通道

关键概念与工作原理:
  1. 基于 VirtIO 和 vring:

    • RPMsg 的底层建立在 VirtIO 这个虚拟化I/O标准之上。VirtIO 定义了一种高效、通用的半虚拟化设备通信机制。

    • 其核心是 vring(虚拟环形缓冲区) 。这是一种位于共享内存中的、无锁的环形队列数据结构。它有一个生产者和一个消费者。

    • 通常会有两个 vring:一个用于主处理器发送到协处理器(A -> B),另一个用于协处理器发送到主处理器(B -> A)。

  2. 消息传递流程

    • 发送消息 :当一方(例如 Linux 上的应用程序)要发送消息时,RPMsg 层会将消息数据封装成一个缓冲区,并将其描述符放入发送 vring 中。

    • 通知对端 :发送方然后通过处理器间中断(IPI) "踢"(kick)一下接收方,告知它有新消息到达。

    • 接收消息:接收方(例如 E907 上的任务)在中断服务例程中,从 vring 中取出消息描述符,处理消息数据。

    • 释放缓冲区:处理完毕后,接收方将缓冲区描述符放回 vring,并发送一个中断通知发送方"缓冲区已可用"。

  3. 信道(Channel)与端点(Endpoint):

    • 信道 :一个逻辑通信路径,由一个字符串名称标识(例如 "rpmsg-openamp-demo-channel")。这允许多个独立的服务在同一对处理器上共存。

    • 端点:是信道在本地的一个代表。应用程序通过创建一个端点并将其绑定到一个信道上,来发送和接收消息。

  4. 多平台支持:

    • RPMsg 的实现是双向的。Linux 内核中有 RPMsg 框架(drivers/rpmsg/),同时 OpenAMP 项目也提供了在裸机或 RTOS(协处理器端)上运行的 RPMsg 库。这样,两者就能使用相同的协议进行通信。
在 Linux 中的体现:

在 Linux 端,RPMsg 会为每个动态创建的信道生成一个字符设备(如 /dev/rpmsgX)。用户空间的应用程序可以像读写普通文件一样,使用 readwrite 等系统调用与协处理器进行通信。同时,内核其他模块也可以直接使用 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 的协商与互通。

调用时序(逐步、带细节)

  1. 前提: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 控制逻辑完成。
  1. 在小核(本端)执行 `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 情形。

  1. 通知对端(服务消息 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)。

  1. 对端收到 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 已完成协商并可以读/写。

  1. 发送数据(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 会被唤醒。

  1. 销毁(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 侧通常以两种方式存在:
  1. 内核 driver(或内核端 rpbuf 实现):与小核通过 rpmsg/rproc 建立通道并处理 service 通信(大多情况是内核/固件的一部分)。

  2. 用户态 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 标志)是否一致。

简短示例(序列化步骤)

  1. link 已建(controller registered on small core, service registered on big core)

  2. small core: call rpbuf_alloc_buffer(controller, "X", 64) -> create buffer instance -> add to local_dummy -> notify big core (BUFFER_CREATED)

  3. big core: on receiving BUFFER_CREATED -> create/move buffer -> if available send BUFFER_CREATED back (is_available=1)

  4. small core: gets response -> buffer marked available -> call available_cb (user可开始发送/接收)

  5. small core: write payload to VA; call rpbuf_transmit_buffer(...) -> dcache clean -> notify TRANSMITTED

  6. big core: handle TRANSMITTED -> dcache invalidate -> call rx_cb -> if sync send ACK

  7. 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)。
相关推荐
西岸行者2 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意2 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码2 天前
嵌入式学习路线
学习
毛小茛2 天前
计算机系统概论——校验码
学习
babe小鑫2 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms2 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下2 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。2 天前
2026.2.25监控学习
学习
im_AMBER2 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J2 天前
从“Hello World“ 开始 C++
c语言·c++·学习