远程处理器协议框架学习

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)。
相关推荐
hmbbcsm4 小时前
python学习之路(二)
学习
冷崖4 小时前
定时器的学习(二)
linux·c++·学习
好奇龙猫5 小时前
【学习AI-相关路程-mnist手写数字分类-一段学习的结束:自我学习AI-复盘-代码-了解原理-综述(5) 】
人工智能·学习·分类
小苏兮5 小时前
【C++】priority_queue和deque的使用与实现
开发语言·c++·学习
Yurko136 小时前
【C语言】环境安装(图文)与介绍
c语言·开发语言·学习
十安_数学好题速析6 小时前
数论探秘:如何用模4思想破解平方数谜题
笔记·学习·高考
在繁华处7 小时前
C语言初步学习:数组的增删查改
c语言·数据结构·学习
呵呵哒( ̄▽ ̄)"7 小时前
专项智能练习(科尔伯格道德发展阶段理论)
学习
武文斌777 小时前
项目学习总结:CAN总线、摄像头、STM32概述
linux·arm开发·stm32·单片机·嵌入式硬件·学习·c#