RPMSG (Remote Processor Messaging) 是一个用于多核/多处理器系统间通信的消息传递框架。它提供了一个标准化的API来实现不同处理器之间的数据交换和服务调用。
跨处理器通信: 支持多核、多芯片间的通信
多种传输层支持: SPI、UART、VirtIO、共享内存等
服务发现机制: 支持动态服务绑定和发现
灵活的配置: 支持多种配置选项和优化策略
调试支持: 内置ping功能用于性能测试和调试
┌─────────────────────────────────────────┐
│ 应用层 │
│ (各种RPMSG服务: uart, net, fs等) │
├─────────────────────────────────────────┤
│ RPMSG核心层 │
│ (rpmsg.c - 设备管理和回调机制) │
├─────────────────────────────────────────┤
│ 传输抽象层 │
│ (rpmsg_port.c - 端口管理和缓冲区管理) │
├─────────────────────────────────────────┤
│ 物理传输层 │
│ (SPI/UART/VirtIO 具体传输实现) │
└─────────────────────────────────────────┘
nuttx/drivers/rpmsg/
├── rpmsg.c # 核心管理层
├── rpmsg_ping.c/h # 调试和性能测试
├── rpmsg_port.c/h # 端口抽象层
├── rpmsg_virtio.c # VirtIO传输实现
├── rpmsg_port_spi.c # SPI传输实现
├── rpmsg_port_spi_slave.c # SPI从设备实现
├── rpmsg_port_uart.c # UART传输实现
├── rpmsg_router_*.c # 路由功能
└── Kconfig # 配置选项
struct rpmsg_s
{
bool init; // 初始化标志
struct metal_list bind; // 绑定列表
rmutex_t lock; // 互斥锁
struct metal_list node; // 链表节点
FAR const struct rpmsg_ops_s *ops; // 操作接口
#ifdef CONFIG_RPMSG_PING
struct rpmsg_endpoint ping; // ping端点
#endif
struct rpmsg_device rdev[0]; // RPMSG设备
};
struct rpmsg_ops_s
{
CODE int (*wait)(FAR struct rpmsg_s *rpmsg, FAR sem_t *sem);
CODE int (*post)(FAR struct rpmsg_s *rpmsg, FAR sem_t *sem);
CODE int (*ioctl)(FAR struct rpmsg_s *rpmsg, int cmd, unsigned long arg);
CODE void (*panic)(FAR struct rpmsg_s *rpmsg);
CODE void (*dump)(FAR struct rpmsg_s *rpmsg);
CODE FAR const char *(*get_local_cpuname)(FAR struct rpmsg_s *rpmsg);
CODE FAR const char *(*get_cpuname)(FAR struct rpmsg_s *rpmsg);
};
struct rpmsg_port_s
{
struct rpmsg_s rpmsg; // 基础RPMSG结构
struct rpmsg_device rdev; // RPMSG设备
struct rpmsg_port_queue_s txq; // 发送队列
struct rpmsg_port_queue_s rxq; // 接收队列
char local_cpuname[RPMSG_NAME_SIZE];
char cpuname[RPMSG_NAME_SIZE];
const FAR struct rpmsg_port_ops_s *ops; // 端口操作
};
int rpmsg_register(FAR const char *path, FAR struct rpmsg_s *rpmsg,
FAR const struct rpmsg_ops_s *ops)
{
// 1. 初始化OpenAMP框架
ret = metal_init(¶ms);
// 2. 注册字符设备驱动
ret = register_driver(path, &g_rpmsg_dev_ops, 0222, rpmsg);
// 3. 初始化绑定列表和锁
metal_list_init(&rpmsg->bind);
nxrmutex_init(&rpmsg->lock);
rpmsg->ops = ops;
// 4. 添加到全局设备列表
metal_list_add_tail(&g_rpmsg, &rpmsg->node);
}
系统维护一个全局回调列表 g_rpmsg_cb
,支持:
设备创建回调: device_created
设备销毁回调: device_destroy
命名空间匹配: ns_match
命名空间绑定: ns_bind
void rpmsg_ns_bind(FAR struct rpmsg_device *rdev,
FAR const char *name, uint32_t dest)
{
// 1. 遍历已注册回调,寻找匹配的服务
metal_list_for_each(&g_rpmsg_cb, node)
{
if (cb->ns_match && cb->ns_match(rdev, cb->priv, name, dest))
{
// 找到匹配服务,执行绑定回调
ns_bind(rdev, cb_priv, name, dest);
return;
}
}
// 2. 没找到匹配服务,缓存绑定请求
// 创建绑定结构并加入待绑定列表
}
每个端口维护两个队列:
发送队列 (txq): 管理待发送的数据缓冲区
接收队列 (rxq): 管理接收到的数据缓冲区
每个队列包含:
空闲列表 (free): 可用的缓冲区
就绪列表 (ready): 已使用的缓冲区
// 获取可用缓冲区
FAR struct rpmsg_port_header_s *
rpmsg_port_queue_get_available_buffer(FAR struct rpmsg_port_queue_s *queue,
bool wait);
// 归还缓冲区到空闲列表
void rpmsg_port_queue_return_buffer(FAR struct rpmsg_port_queue_s *queue,
FAR struct rpmsg_port_header_s *hdr);
// 获取已就绪的缓冲区
FAR struct rpmsg_port_header_s *
rpmsg_port_queue_get_buffer(FAR struct rpmsg_port_queue_s *queue,
bool wait);
// 添加缓冲区到就绪列表
void rpmsg_port_queue_add_buffer(FAR struct rpmsg_port_queue_s *queue,
FAR struct rpmsg_port_header_s *hdr);
基于共享内存: 使用VirtIO队列机制
高性能: 适用于虚拟化环境和共享内存场景
支持中断通知: 提供高效的事件通知机制
主从模式: 支持SPI主设备和从设备模式
CRC校验: 可选的数据完整性校验
流控制: 基于available字段的流量控制
专用线程: 独立的收发线程处理
串口通信: 基于UART串口的点对点通信
双工通信: 独立的收发线程
帧同步: 基于帧头的数据包同步机制
提供性能测试和调试功能:
struct rpmsg_ping_msg_s
{
uint32_t cmd; // 命令和标志位
uint32_t len; // 数据长度
uint64_t cookie; // 同步cookie (信号量地址)
uint8_t data[1]; // 载荷数据
};
往返时间测试: 测量消息往返延迟
吞吐量测试: 测量数据传输速率
数据完整性检查: 可选的数据内容校验
随机长度测试: 支持随机长度的ping包
# 通过 ioctl 调用
ioctl(fd, RPMSGIOC_PING, &ping_params);
// 触发远程CPU panic
int rpmsg_panic(FAR const char *cpuname);
// 转储所有RPMSG状态信息
void rpmsg_dump_all(void);
// 通用ioctl接口
int rpmsg_ioctl(FAR const char *cpuname, int cmd, unsigned long arg);
config RPMSG
bool
default n
select OPENAMP
# 启用RPMSG子系统
config RPMSG_LOCAL_CPUNAME
string "Rpmsg Local Cpuname"
default LIBC_HOSTNAME
# 本地CPU名称
config RPMSG_PING
bool "rpmsg ping support"
default n
# 启用ping调试功能
config RPMSG_PORT_SPI
bool "Rpmsg SPI Port Driver Support"
select RPMSG_PORT
# SPI传输支持
config RPMSG_PORT_UART
bool "Rpmsg Uart Port Driver Support"
select RPMSG_PORT
# UART传输支持
config RPMSG_VIRTIO
bool "rpmsg virtio transport support"
select RPMSG
# VirtIO传输支持
config RPMSG_PORT_SPI_THREAD_PRIORITY
int "Rpmsg SPI Port Thread Priority"
default 224
# SPI传输线程优先级
config RPMSG_VIRTIO_IVSHMEM_BUFFSIZE
int "rpmsg virtio ivshmem rpmsg buffer size"
default 2048
# VirtIO缓冲区大小
在使用 RPMSG 编程接口之前,必须先理解传输层设备的注册过程。这是整个 RPMSG 系统能够正常工作的基础。
关键理解:在调用 rpmsg_register_callback
注册应用层回调之前,必须先有 RPMSG 传输层设备注册到系统中。否则回调将无法被触发。
┌─────────────────────────────────────────┐
│ 系统启动顺序 │
├─────────────────────────────────────────┤
│ 1. 内核基础设施 (内存、调度、同步) │
│ 2. OpenAMP/Metal 库初始化 │
│ 3. 硬件资源准备 (中断、DMA、共享内存) │
│ 4. 传输层初始化 ← 关键步骤 │
│ ├── rpmsg_register() 注册设备 │
│ └── rpmsg_device_created() 设备就绪 │
│ 5. 应用层回调注册 │
│ └── rpmsg_register_callback() │
└─────────────────────────────────────────┘
int rpmsg_register(FAR const char *path,
FAR struct rpmsg_s *rpmsg,
FAR const struct rpmsg_ops_s *ops);
rpmsg_register
是传输层向 RPMSG 核心注册设备的关键函数:
初始化 OpenAMP 框架:调用 metal_init()
初始化底层库
注册字符设备:在 /dev
下创建设备节点供用户空间访问
初始化设备结构:设置绑定列表、互斥锁和操作接口
加入全局设备列表:将设备添加到 g_rpmsg
全局链表中
int rpmsg_register(FAR const char *path, FAR struct rpmsg_s *rpmsg,
FAR const struct rpmsg_ops_s *ops)
{
struct metal_init_params params = METAL_INIT_DEFAULTS;
int ret;
/* 步骤1: 初始化 OpenAMP Metal 框架 */
ret = metal_init(¶ms);
if (ret < 0)
{
return ret;
}
/* 步骤2: 注册字符设备驱动 (例如 /dev/rpmsg/cpu1) */
ret = register_driver(path, &g_rpmsg_dev_ops, 0222, rpmsg);
if (ret < 0)
{
metal_finish();
return ret;
}
/* 步骤3: 初始化设备内部结构 */
metal_list_init(&rpmsg->bind); // 初始化绑定列表
nxrmutex_init(&rpmsg->lock); // 初始化设备锁
rpmsg->ops = ops; // 设置操作接口
/* 步骤4: 添加到全局设备列表 - 关键步骤! */
nxrmutex_lock(&g_rpmsg_lock);
metal_list_add_tail(&g_rpmsg, &rpmsg->node); // 加入全局链表
nxrmutex_unlock(&g_rpmsg_lock);
return ret;
}
使用场景: 基于共享内存的高性能通信,适用于虚拟化环境或同芯片多核场景。
/* VirtIO 传输层初始化函数 */
int rpmsg_virtio_initialize(FAR struct rpmsg_virtio_s *dev)
{
FAR struct rpmsg_virtio_priv_s *priv;
char name[32];
int ret;
/* 步骤1: 分配私有数据结构 */
priv = kmm_zalloc(sizeof(struct rpmsg_virtio_priv_s));
if (priv == NULL)
{
return -ENOMEM;
}
/* 步骤2: 初始化传输层特定资源 */
priv->dev = dev;
nxsem_init(&priv->semrx, 0, 0); // 接收信号量
nxsem_init(&priv->semtx, 0, 0); // 发送信号量
/* 步骤3: 注册 RPMSG 设备 - 关键步骤 */
snprintf(name, sizeof(name), "/dev/rpmsg/%s",
RPMSG_VIRTIO_GET_CPUNAME(dev));
ret = rpmsg_register(name, &priv->rpmsg, &g_rpmsg_virtio_ops);
if (ret < 0)
{
goto err_driver;
}
/* 步骤4: 创建后台处理线程 */
ret = kthread_create("rpmsg_virtio", CONFIG_RPMSG_VIRTIO_PRIORITY,
CONFIG_RPMSG_VIRTIO_STACKSIZE,
rpmsg_virtio_thread, argv);
if (ret < 0)
{
goto err_thread;
}
return OK;
err_thread:
rpmsg_unregister(name, &priv->rpmsg);
err_driver:
nxsem_destroy(&priv->semrx);
nxsem_destroy(&priv->semtx);
kmm_free(priv);
return ret;
}
static int rpmsg_virtio_start(FAR struct rpmsg_virtio_priv_s *priv)
{
/* ... VirtIO 硬件初始化 ... */
/* 初始化 VirtIO 设备和队列 */
ret = rpmsg_init_vdev_with_config(&priv->rvdev, vdev, rpmsg_ns_bind,
metal_io_get_region(),
priv->pool, &config);
/* 注册底层回调 */
RPMSG_VIRTIO_REGISTER_CALLBACK(priv->dev, rpmsg_virtio_callback, priv);
/* 通知系统设备已就绪 - 触发应用层回调 */
rpmsg_device_created(&priv->rpmsg); // ← 关键调用
return 0;
}
使用场景: 芯片间高速串行通信,支持主从模式。
/* SPI 端口初始化示例 */
int rpmsg_port_spi_initialize(FAR struct rpmsg_port_spi_config_s *cfg)
{
FAR struct rpmsg_port_spi_s *port;
int ret;
/* 步骤1: 分配端口结构 */
port = kmm_zalloc(sizeof(struct rpmsg_port_spi_s));
if (!port)
{
return -ENOMEM;
}
/* 步骤2: SPI 硬件初始化 */
port->spi = spi_dev_initialize(cfg->spi_devid);
if (!port->spi)
{
ret = -ENODEV;
goto err_spi;
}
/* 步骤3: 配置 SPI 参数 */
SPI_SETMODE(port->spi, cfg->spi_mode);
SPI_SETBITS(port->spi, 8);
SPI_SETFREQUENCY(port->spi, cfg->spi_freq);
/* 步骤4: 初始化端口抽象层 */
ret = rpmsg_port_initialize(&port->port, cfg, &g_rpmsg_port_spi_ops);
if (ret < 0)
{
goto err_port;
}
/* 步骤5: 注册 RPMSG 设备 */
ret = rpmsg_port_register(&port->port, cfg->local_cpuname);
if (ret < 0)
{
goto err_register;
}
/* 步骤6: 创建收发线程 */
port->rx_pid = kthread_create("rpmsg_spi_rx",
CONFIG_RPMSG_PORT_SPI_THREAD_PRIORITY,
CONFIG_RPMSG_PORT_SPI_THREAD_STACKSIZE,
rpmsg_port_spi_rx_thread, port);
port->tx_pid = kthread_create("rpmsg_spi_tx",
CONFIG_RPMSG_PORT_SPI_THREAD_PRIORITY,
CONFIG_RPMSG_PORT_SPI_THREAD_STACKSIZE,
rpmsg_port_spi_tx_thread, port);
return OK;
err_register:
rpmsg_port_uninitialize(&port->port);
err_port:
/* SPI 清理 */
err_spi:
kmm_free(port);
return ret;
}
使用场景: 基于串口的点对点通信,适用于调试和低速通信场景。
/* UART 端口初始化示例 */
int rpmsg_port_uart_initialize(FAR struct rpmsg_port_uart_config_s *cfg)
{
FAR struct rpmsg_port_uart_s *port;
int ret;
/* 步骤1: 分配端口结构 */
port = kmm_zalloc(sizeof(struct rpmsg_port_uart_s));
if (!port)
{
return -ENOMEM;
}
/* 步骤2: 打开 UART 设备 */
port->fd = open(cfg->uart_path, O_RDWR);
if (port->fd < 0)
{
ret = -errno;
goto err_open;
}
/* 步骤3: 配置串口参数 */
struct termios tio;
tcgetattr(port->fd, &tio);
tio.c_speed = cfg->uart_baud;
tio.c_cflag = CS8 | CREAD | CLOCAL; // 8位数据,无校验
tcsetattr(port->fd, TCSANOW, &tio);
/* 步骤4: 初始化端口抽象层 */
ret = rpmsg_port_initialize(&port->port, cfg, &g_rpmsg_port_uart_ops);
if (ret < 0)
{
goto err_port;
}
/* 步骤5: 注册 RPMSG 设备 */
ret = rpmsg_port_register(&port->port, cfg->local_cpuname);
if (ret < 0)
{
goto err_register;
}
/* 步骤6: 创建收发线程 */
port->rx_pid = kthread_create("rpmsg_uart_rx",
CONFIG_RPMSG_PORT_UART_RX_THREAD_PRIORITY,
CONFIG_RPMSG_PORT_UART_RX_THREAD_STACKSIZE,
rpmsg_port_uart_rx_thread, port);
port->tx_pid = kthread_create("rpmsg_uart_tx",
CONFIG_RPMSG_PORT_UART_TX_THREAD_PRIORITY,
CONFIG_RPMSG_PORT_UART_TX_THREAD_STACKSIZE,
rpmsg_port_uart_tx_thread, port);
return OK;
err_register:
rpmsg_port_uninitialize(&port->port);
err_port:
close(port->fd);
err_open:
kmm_free(port);
return ret;
}
当传输层硬件就绪后,必须调用 rpmsg_device_created
通知系统:
void rpmsg_device_created(FAR struct rpmsg_s *rpmsg)
{
FAR struct metal_list *node;
FAR struct metal_list *tmp;
nxrmutex_lock(&g_rpmsg_lock);
/* 遍历所有已注册的回调 */
metal_list_for_each_safe(&g_rpmsg_cb, tmp, node)
{
FAR struct rpmsg_cb_s *cb =
metal_container_of(node, struct rpmsg_cb_s, node);
/* 调用设备创建回调 */
if (cb->device_created)
{
cb->device_created(rpmsg->rdev, cb->priv); // ← 触发应用层回调
}
}
/* 标记设备为已初始化状态 */
rpmsg->init = true; // ← 关键:标记设备就绪
nxrmutex_unlock(&g_rpmsg_lock);
#ifdef CONFIG_RPMSG_PING
/* 初始化 ping 功能 */
rpmsg_ping_init(rpmsg->rdev, &rpmsg->ping);
#endif
}
/* 板级 RPMSG 初始化函数 */
int board_rpmsg_initialize(void)
{
int ret;
/* 第一步:初始化传输层硬件 */
#ifdef CONFIG_RPMSG_VIRTIO
/* VirtIO 基于共享内存的传输 */
ret = rpmsg_virtio_initialize(&g_rpmsg_virtio_dev);
if (ret < 0)
{
syslog(LOG_ERR, "Failed to initialize VirtIO transport: %d\n", ret);
return ret;
}
#endif
#ifdef CONFIG_RPMSG_PORT_SPI
/* SPI 传输层初始化 */
struct rpmsg_port_spi_config_s spi_cfg =
{
.spi_devid = 1,
.spi_mode = SPIDEV_MODE0,
.spi_freq = 10000000, // 10MHz
.local_cpuname = "cpu0",
.buffer_size = 512,
.buffer_nums = 8,
};
ret = rpmsg_port_spi_initialize(&spi_cfg);
if (ret < 0)
{
syslog(LOG_ERR, "Failed to initialize SPI transport: %d\n", ret);
return ret;
}
#endif
#ifdef CONFIG_RPMSG_PORT_UART
/* UART 传输层初始化 */
struct rpmsg_port_uart_config_s uart_cfg =
{
.uart_path = "/dev/ttyS1",
.uart_baud = 115200,
.local_cpuname = "cpu0",
.buffer_size = 256,
.buffer_nums = 4,
};
ret = rpmsg_port_uart_initialize(&uart_cfg);
if (ret < 0)
{
syslog(LOG_ERR, "Failed to initialize UART transport: %d\n", ret);
return ret;
}
#endif
syslog(LOG_INFO, "RPMSG transport layers initialized successfully\n");
return OK;
}
/* 在系统启动过程中调用 */
int board_late_initialize(void)
{
int ret;
/* ... 其他硬件初始化 ... */
/* 初始化 RPMSG 传输层 - 关键时机 */
ret = board_rpmsg_initialize();
if (ret < 0)
{
syslog(LOG_ERR, "RPMSG initialization failed: %d\n", ret);
return ret;
}
/* 初始化应用层服务 */
ret = board_app_initialize();
if (ret < 0)
{
syslog(LOG_ERR, "Application initialization failed: %d\n", ret);
return ret;
}
return OK;
}
重要:应用层服务只能在传输层初始化完成后再初始化:
/* 应用层初始化函数 */
int board_app_initialize(void)
{
int ret;
/* 现在可以安全地注册应用层回调 */
/* 初始化 Echo 服务 */
ret = echo_service_init();
if (ret < 0)
{
syslog(LOG_ERR, "Echo service init failed: %d\n", ret);
return ret;
}
/* 初始化其他 RPMSG 服务 */
ret = rpmsgdev_server_init(); // 设备代理服务
ret = rpmsgblk_server_init(); // 块设备服务
ret = rpmsgmtd_server_init(); // MTD 服务
return OK;
}
1. 系统基础设施启动
├── 内存管理器
├── 调度器
└── 同步原语
2. 硬件资源准备
├── 中断控制器
├── DMA 控制器
├── 共享内存区域
└── SPI/UART 控制器
3. OpenAMP 框架初始化
└── metal_init() 调用
4. 传输层初始化 ← 关键步骤
├── rpmsg_register() 注册设备
├── 硬件特定初始化
└── rpmsg_device_created() 设备就绪
5. 应用层服务初始化
└── rpmsg_register_callback() 注册回调
❌ 错误:先注册应用回调,再初始化传输层
应用层 rpmsg_register_callback()
↓
传输层 rpmsg_register()
↓
结果:回调不会被触发,服务无法启动
✅ 正确:先初始化传输层,再注册应用回调
传输层 rpmsg_register()
↓
传输层 rpmsg_device_created()
↓
应用层 rpmsg_register_callback()
↓
结果:回调立即触发,服务正常启动
问题1: 应用层回调没有被触发
原因:传输层未初始化或未调用 rpmsg_device_created()
解决:检查传输层初始化顺序,确保设备已就绪
调试:使用 rpmsg_dump_all() 查看已注册设备
问题2: rpmsg_register 失败
原因:OpenAMP 库未初始化或硬件资源不可用
解决:检查 metal_init() 调用和硬件配置
调试:检查系统日志中的错误信息
问题3: 设备节点创建失败
原因:设备路径冲突或权限问题
解决:检查设备路径唯一性,确保目录存在
调试:使用 ls -la /dev/rpmsg/ 检查设备节点
/* 调试辅助函数 */
void debug_rpmsg_status(void)
{
/* 1. 查看已注册设备 */
syslog(LOG_INFO, "=== RPMSG Device Status ===\n");
rpmsg_dump_all();
/* 2. 检查全局设备列表 */
nxrmutex_lock(&g_rpmsg_lock);
if (metal_list_is_empty(&g_rpmsg))
{
syslog(LOG_WARNING, "No RPMSG devices registered!\n");
}
else
{
syslog(LOG_INFO, "RPMSG devices found in global list\n");
}
nxrmutex_unlock(&g_rpmsg_lock);
/* 3. 检查回调列表 */
if (metal_list_is_empty(&g_rpmsg_cb))
{
syslog(LOG_WARNING, "No RPMSG callbacks registered!\n");
}
else
{
syslog(LOG_INFO, "RPMSG callbacks found\n");
}
}
通过理解这些传输层初始化的细节,您可以正确地构建 RPMSG 系统,确保传输层和应用层之间的正确协作。
核心问题:上层应用注册的service如何与底层传输注册的port关联起来?
答案:通过回调机制和命名空间绑定实现关联。整个过程包含两个阶段:
设备关联阶段:底层port注册后,通过设备创建回调通知上层service
服务绑定阶段:通过命名空间匹配和绑定机制实现具体服务的关联
从源码可以看到,系统维护两个关键的全局链表:
// rpmsg.c 中定义的全局链表
static METAL_DECLARE_LIST(g_rpmsg_cb); // 应用层回调链表
static METAL_DECLARE_LIST(g_rpmsg); // 传输层设备链表
// 回调结构体
struct rpmsg_cb_s
{
FAR void *priv; // 应用层私有数据
rpmsg_dev_cb_t device_created; // 设备创建回调
rpmsg_dev_cb_t device_destroy; // 设备销毁回调
rpmsg_match_cb_t ns_match; // 命名空间匹配回调
rpmsg_bind_cb_t ns_bind; // 命名空间绑定回调
struct metal_list node; // 链表节点
};
// 绑定结构体 (用于缓存待绑定的服务)
struct rpmsg_bind_s
{
char name[RPMSG_NAME_SIZE]; // 服务名称
uint32_t dest; // 目标地址
struct metal_list node; // 链表节点
};
步骤1: 底层传输层注册设备
// rpmsg_port.c: rpmsg_port_register()
int rpmsg_port_register(FAR struct rpmsg_port_s *port,
FAR const char *local_cpuname)
{
// 1. 设置本地CPU名称
if (local_cpuname)
{
strlcpy(port->local_cpuname, local_cpuname, RPMSG_NAME_SIZE);
}
// 2. 注册RPMSG设备到核心层
snprintf(name, sizeof(name), "/dev/rpmsg/%s", port->cpuname);
ret = rpmsg_register(name, &port->rpmsg, &g_rpmsg_port_ops);
if (ret < 0)
{
return ret;
}
// 3. 关键步骤:通知设备已创建
rpmsg_device_created(&port->rpmsg); // ← 触发设备关联
return ret;
}
步骤2: 设备创建通知机制
// rpmsg.c: rpmsg_device_created()
void rpmsg_device_created(FAR struct rpmsg_s *rpmsg)
{
FAR struct metal_list *node;
FAR struct metal_list *tmp;
nxrmutex_lock(&g_rpmsg_lock);
// 遍历所有已注册的应用层回调
metal_list_for_each_safe(&g_rpmsg_cb, tmp, node)
{
FAR struct rpmsg_cb_s *cb =
metal_container_of(node, struct rpmsg_cb_s, node);
// 调用应用层的设备创建回调
if (cb->device_created)
{
cb->device_created(rpmsg->rdev, cb->priv); // ← 关键调用
}
}
// 标记设备为已初始化
rpmsg->init = true;
nxrmutex_unlock(&g_rpmsg_lock);
}
步骤1: 应用层注册回调时的立即检查
// rpmsg.c: rpmsg_register_callback()
int rpmsg_register_callback(FAR void *priv,
rpmsg_dev_cb_t device_created,
rpmsg_dev_cb_t device_destroy,
rpmsg_match_cb_t ns_match,
rpmsg_bind_cb_t ns_bind)
{
// ... 创建回调结构体 ...
nxrmutex_lock(&g_rpmsg_lock);
// 检查已存在的设备,立即触发回调
metal_list_for_each(&g_rpmsg, node)
{
FAR struct rpmsg_s *rpmsg =
metal_container_of(node, struct rpmsg_s, node);
if (!rpmsg->init) // 只处理已初始化的设备
{
continue;
}
// 触发设备创建回调
if (device_created)
{
device_created(rpmsg->rdev, priv);
}
// 检查是否有待绑定的服务
if (ns_bind == NULL)
{
continue;
}
DEBUGASSERT(ns_match != NULL);
again:
nxrmutex_lock(&rpmsg->lock);
metal_list_for_each(&rpmsg->bind, bnode)
{
FAR struct rpmsg_bind_s *bind =
metal_container_of(bnode, struct rpmsg_bind_s, node);
// 检查服务名称是否匹配
if (ns_match(rpmsg->rdev, priv, bind->name, bind->dest))
{
metal_list_del(bnode);
nxrmutex_unlock(&rpmsg->lock);
// 执行服务绑定
ns_bind(rpmsg->rdev, priv, bind->name, bind->dest);
kmm_free(bind);
goto again; // 继续检查其他待绑定服务
}
}
nxrmutex_unlock(&rpmsg->lock);
}
// 将回调加入全局链表
metal_list_add_tail(&g_rpmsg_cb, &cb->node);
nxrmutex_unlock(&g_rpmsg_lock);
return 0;
}
步骤2: 命名空间绑定机制
// rpmsg.c: rpmsg_ns_bind()
void rpmsg_ns_bind(FAR struct rpmsg_device *rdev,
FAR const char *name, uint32_t dest)
{
FAR struct rpmsg_s *rpmsg = rpmsg_get_by_rdev(rdev);
FAR struct rpmsg_bind_s *bind;
FAR struct metal_list *node;
nxrmutex_lock(&g_rpmsg_lock);
// 遍历所有已注册的回调,寻找匹配的服务
metal_list_for_each(&g_rpmsg_cb, node)
{
FAR struct rpmsg_cb_s *cb =
metal_container_of(node, struct rpmsg_cb_s, node);
// 检查是否有匹配的服务
if (cb->ns_match && cb->ns_match(rdev, cb->priv, name, dest))
{
rpmsg_bind_cb_t ns_bind = cb->ns_bind;
FAR void *cb_priv = cb->priv;
nxrmutex_unlock(&g_rpmsg_lock);
// 找到匹配服务,执行绑定
ns_bind(rdev, cb_priv, name, dest);
return;
}
}
nxrmutex_unlock(&g_rpmsg_lock);
// 没有找到匹配的服务,缓存绑定请求
bind = kmm_malloc(sizeof(struct rpmsg_bind_s));
if (bind == NULL)
{
return;
}
bind->dest = dest;
strlcpy(bind->name, name, RPMSG_NAME_SIZE);
// 将绑定请求加入设备的待绑定列表
nxrmutex_lock(&rpmsg->lock);
metal_list_add_tail(&rpmsg->bind, &bind->node);
nxrmutex_unlock(&rpmsg->lock);
}
您提出的问题很重要!让我详细解释应用层端点创建的两个不同时机:
时机:在device_created
回调中 目的:提供服务,让其他CPU能够发现和连接
// 服务提供者的device_created回调
static void echo_server_device_created(FAR struct rpmsg_device *rdev,
FAR void *priv)
{
FAR struct echo_server_s *server = (FAR struct echo_server_s *)priv;
// 检查是否是目标设备
if (strcmp(rpmsg_get_cpuname(rdev), "target_cpu") != 0)
{
return;
}
// 立即创建服务端点 - 关键时机1
server->ept.priv = server;
int ret = rpmsg_create_ept(&server->ept, rdev, "rpmsg-echo",
RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
echo_server_callback, NULL);
if (ret == 0)
{
server->ready = true;
printf("Echo服务已就绪,等待客户端连接\n");
}
}
// 服务端注册:只需device_created回调,不需要ns_match/ns_bind
int echo_server_init(void)
{
return rpmsg_register_callback(&g_echo_server,
echo_server_device_created, // 设备创建回调
echo_server_device_destroy, // 设备销毁回调
NULL, // 不需要服务匹配
NULL); // 不需要服务绑定
}
时机:在ns_bind
回调中 目的:连接到已发现的远端服务
// 客户端的ns_bind回调
static void echo_client_ns_bind(FAR struct rpmsg_device *rdev,
FAR void *priv,
FAR const char *name,
uint32_t dest)
{
FAR struct echo_client_s *client = (FAR struct echo_client_s *)priv;
printf("发现Echo服务:%s,地址:%u\n", name, dest);
// 创建客户端端点连接到服务 - 关键时机2
client->ept.priv = client;
int ret = rpmsg_create_ept(&client->ept, rdev, name,
RPMSG_ADDR_ANY, dest, // 指定目标地址
echo_client_callback, NULL);
if (ret == 0)
{
client->connected = true;
printf("成功连接到Echo服务\n");
}
}
// 客户端的device_created回调
static void echo_client_device_created(FAR struct rpmsg_device *rdev,
FAR void *priv)
{
// 注意:客户端在device_created中通常不创建端点
// 而是等待服务发现后再创建
printf("设备就绪,开始等待Echo服务发现\n");
}
// 客户端注册:需要ns_match和ns_bind回调
int echo_client_init(void)
{
return rpmsg_register_callback(&g_echo_client,
echo_client_device_created, // 设备创建回调
echo_client_device_destroy, // 设备销毁回调
echo_client_ns_match, // 服务匹配回调
echo_client_ns_bind); // 服务绑定回调
}
方面 |
服务端点创建 |
客户端点创建 |
创建时机 |
|
|
触发条件 |
传输层设备就绪 |
发现匹配的远端服务 |
端点地址 |
|
指定 |
目的 |
提供服务,等待连接 |
连接到已知服务 |
回调需求 |
只需 |
需要 |
// 时序1:传输层就绪
// 1. rpmsg_device_created() 被调用
// 时序2:服务端应用启动
// 2. echo_server_init() 注册回调
// 3. device_created 回调立即被触发(设备已存在)
// 4. 服务端创建端点,服务变为可用状态
// 时序3:客户端应用启动
// 5. echo_client_init() 注册回调
// 6. device_created 回调被触发,客户端准备就绪
// 时序4:服务发现
// 7. 远端接收到服务端的名称通告
// 8. rpmsg_ns_bind("rpmsg-echo", server_addr) 被调用
// 9. 查找匹配的 ns_match 回调,找到客户端
// 10. 客户端的 ns_bind 回调被执行
// 11. 客户端创建端点连接到服务端
// 时序5:通信开始
// 12. 客户端可以向服务端发送请求
// 13. 服务端处理请求并回复
❌ 错误做法:客户端在device_created中创建端点
// 错误:客户端过早创建端点
static void client_device_created(FAR struct rpmsg_device *rdev, FAR void *priv)
{
// 这时候还不知道服务的地址,无法连接
rpmsg_create_ept(&client->ept, rdev, "rpmsg-echo",
RPMSG_ADDR_ANY, RPMSG_ADDR_ANY); // 错误!
}
✅ 正确做法:分阶段创建端点
// 正确:服务端在device_created中创建
static void server_device_created(FAR struct rpmsg_device *rdev, FAR void *priv)
{
// 服务端知道自己要提供什么服务,可以立即创建
rpmsg_create_ept(&server->ept, rdev, "rpmsg-echo",
RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
server_callback, NULL);
}
// 正确:客户端在ns_bind中创建
static void client_ns_bind(FAR struct rpmsg_device *rdev, FAR void *priv,
FAR const char *name, uint32_t dest)
{
// 客户端现在知道了服务的确切地址,可以连接
rpmsg_create_ept(&client->ept, rdev, name,
RPMSG_ADDR_ANY, dest, // 指定服务地址
client_callback, NULL);
}
总结:
服务端:在设备就绪后立即创建端点,开始提供服务
客户端:等待服务发现后才创建端点,连接到具体服务
这种分离确保了服务的发现-连接模式正确工作
// echo_service.c
static struct echo_service_s g_echo_service;
int echo_service_init(void)
{
// 应用层注册回调
return rpmsg_register_callback(&g_echo_service,
echo_device_created, // 设备创建回调
echo_device_destroy, // 设备销毁回调
NULL, // 不需要命名空间匹配
NULL); // 不需要命名空间绑定
}
// 设备创建回调 - 与传输层port关联的关键点
static void echo_device_created(FAR struct rpmsg_device *rdev, FAR void *priv)
{
FAR struct echo_service_s *service = (FAR struct echo_service_s *)priv;
// 检查CPU名称是否匹配
if (strcmp(target_cpu, rpmsg_get_cpuname(rdev)) != 0)
{
return; // 不是目标CPU,忽略
}
// 设置端点私有数据
service->ept.priv = service;
// 创建服务端点 - 与特定rdev关联
rpmsg_create_ept(&service->ept, rdev, "rpmsg-echo",
RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
echo_ept_callback, NULL);
service->ready = true;
}
// rpmsg_port_spi.c
int rpmsg_port_spi_initialize(FAR struct rpmsg_port_spi_config_s *cfg)
{
// ... SPI硬件初始化 ...
// 初始化port抽象层
ret = rpmsg_port_initialize(&port->port, cfg, &g_rpmsg_port_spi_ops);
// 注册port到核心层
ret = rpmsg_port_register(&port->port, cfg->local_cpuname);
// rpmsg_port_register 内部会调用:
// 1. rpmsg_register() - 注册到g_rpmsg链表
// 2. rpmsg_device_created() - 触发所有已注册的device_created回调
return ret;
}
// 传输层设备的CPU名称
struct rpmsg_port_s {
char cpuname[RPMSG_NAME_SIZE]; // 远端CPU名称
char local_cpuname[RPMSG_NAME_SIZE]; // 本地CPU名称
};
// 应用层根据CPU名称选择关联的设备
static void service_device_created(FAR struct rpmsg_device *rdev, FAR void *priv)
{
// 只关联特定CPU的设备
if (strcmp(rpmsg_get_cpuname(rdev), "target_cpu") == 0)
{
// 在这个设备上创建服务端点
rpmsg_create_ept(&ept, rdev, "service-name", ...);
}
}
// 使用命名空间匹配机制
static bool service_ns_match(FAR struct rpmsg_device *rdev, FAR void *priv,
FAR const char *name, uint32_t dest)
{
return strncmp(name, "my-service", RPMSG_NAME_SIZE) == 0;
}
static void service_ns_bind(FAR struct rpmsg_device *rdev, FAR void *priv,
FAR const char *name, uint32_t dest)
{
// 创建与远端服务对应的客户端端点
rpmsg_create_ept(&client_ept, rdev, name, RPMSG_ADDR_ANY, dest,
client_callback, NULL);
}
系统支持多个传输层设备同时存在:
// 支持多个传输层设备
void multi_device_created(FAR struct rpmsg_device *rdev, FAR void *priv)
{
const char *cpu_name = rpmsg_get_cpuname(rdev);
if (strcmp(cpu_name, "dsp") == 0)
{
// 在DSP设备上创建音频处理服务
create_audio_service(rdev);
}
else if (strcmp(cpu_name, "mcu") == 0)
{
// 在MCU设备上创建控制服务
create_control_service(rdev);
}
else if (strcmp(cpu_name, "gpu") == 0)
{
// 在GPU设备上创建图形服务
create_graphics_service(rdev);
}
}
解耦设计:应用层和传输层完全解耦,互不依赖
动态绑定:支持传输层和应用层的动态加载和卸载
多设备支持:一个应用可以同时使用多个传输设备
灵活配置:通过CPU名称和服务名称灵活控制关联关系
缓存机制:支持服务请求的缓存,处理时序不确定性
void debug_device_association(void)
{
FAR struct metal_list *node;
// 检查已注册设备
printf("=== Registered Devices ===\n");
nxrmutex_lock(&g_rpmsg_lock);
metal_list_for_each(&g_rpmsg, node)
{
FAR struct rpmsg_s *rpmsg =
metal_container_of(node, struct rpmsg_s, node);
printf("Device: %s, init: %s\n",
rpmsg_get_cpuname(rpmsg->rdev),
rpmsg->init ? "YES" : "NO");
}
nxrmutex_unlock(&g_rpmsg_lock);
// 检查已注册回调
printf("=== Registered Callbacks ===\n");
nxrmutex_lock(&g_rpmsg_lock);
metal_list_for_each(&g_rpmsg_cb, node)
{
FAR struct rpmsg_cb_s *cb =
metal_container_of(node, struct rpmsg_cb_s, node);
printf("Callback: priv=%p, device_created=%p\n",
cb->priv, cb->device_created);
}
nxrmutex_unlock(&g_rpmsg_lock);
}
回调未触发:检查设备是否调用了rpmsg_device_created()
CPU名称不匹配:检查传输层设置的CPU名称是否正确
时序问题:确保传输层在应用层之前初始化
内存泄漏:检查rpmsg_bind_s
结构体的释放
通过这个详细的关联机制分析,我们可以清楚地理解上层service和底层port是如何通过RPMSG核心层的回调机制和命名空间绑定机制实现关联的。
RPMSG的命名空间(Namespace)服务发现是一套动态服务绑定机制,允许:
服务提供者:远端CPU上的服务主动通告自己的存在
服务消费者:本地CPU上的客户端自动发现并连接到远端服务
动态绑定:无需预先配置,支持服务的动态上线和下线
// 命名空间匹配回调:判断是否对某个服务感兴趣
typedef bool (*rpmsg_match_cb_t)(FAR struct rpmsg_device *rdev,
FAR void *priv,
FAR const char *name,
uint32_t dest);
// 命名空间绑定回调:当匹配的服务出现时执行绑定操作
typedef void (*rpmsg_bind_cb_t)(FAR struct rpmsg_device *rdev,
FAR void *priv,
FAR const char *name,
uint32_t dest);
参数说明:
rdev
: 发现服务的RPMSG设备
priv
: 应用层私有数据指针
name
: 服务名称字符串
dest
: 远端服务的地址
让我们深入分析您选中的代码段:
// 位于 rpmsg_register_callback() 函数中
again:
nxrmutex_lock(&rpmsg->lock);
metal_list_for_each(&rpmsg->bind, bnode)
{
FAR struct rpmsg_bind_s *bind =
metal_container_of(bnode, struct rpmsg_bind_s, node);
if (ns_match(rpmsg->rdev, priv, bind->name, bind->dest))
{
metal_list_del(bnode);
nxrmutex_unlock(&rpmsg->lock);
ns_bind(rpmsg->rdev, priv, bind->name, bind->dest);
kmm_free(bind);
goto again;
}
}
nxrmutex_unlock(&rpmsg->lock);
第1步:遍历待绑定服务列表
metal_list_for_each(&rpmsg->bind, bnode)
rpmsg->bind
是每个RPMSG设备维护的待绑定服务列表
这个列表存储了远端已通告但本地尚未处理的服务信息
每个元素是 rpmsg_bind_s
结构体,包含服务名称和地址
第2步:提取绑定信息
FAR struct rpmsg_bind_s *bind =
metal_container_of(bnode, struct rpmsg_bind_s, node);
从链表节点获取完整的绑定信息结构体
bind->name
: 服务名称(如 "rpmsg-echo", "file-server")
bind->dest
: 远端服务地址
第3步:服务匹配检查
if (ns_match(rpmsg->rdev, priv, bind->name, bind->dest))
调用应用层注册的 ns_match
回调函数
应用层决定是否对这个服务感兴趣
返回 true
表示匹配成功,需要绑定
第4步:执行绑定操作
metal_list_del(bnode); // 从待绑定列表移除
nxrmutex_unlock(&rpmsg->lock); // 释放锁(避免死锁)
ns_bind(rpmsg->rdev, priv, bind->name, bind->dest); // 执行绑定
kmm_free(bind); // 释放内存
goto again; // 重新检查(可能有多个匹配服务)
关键设计要点:
先移除再绑定:避免重复处理
释放锁后调用回调:避免回调中再次获取锁导致死锁
goto again:一次可能匹配多个服务,需要重新遍历
内存管理:及时释放已处理的绑定结构体
示例1:Echo客户端的服务发现
// Echo客户端实现
struct echo_client_s
{
struct rpmsg_endpoint ept;
bool connected;
char target_service[RPMSG_NAME_SIZE];
};
// 匹配回调:只关心echo服务
static bool echo_client_ns_match(FAR struct rpmsg_device *rdev,
FAR void *priv,
FAR const char *name,
uint32_t dest)
{
FAR struct echo_client_s *client = (FAR struct echo_client_s *)priv;
// 只匹配名为 "rpmsg-echo" 的服务
return strncmp(name, "rpmsg-echo", RPMSG_NAME_SIZE) == 0;
}
// 绑定回调:连接到echo服务
static void echo_client_ns_bind(FAR struct rpmsg_device *rdev,
FAR void *priv,
FAR const char *name,
uint32_t dest)
{
FAR struct echo_client_s *client = (FAR struct echo_client_s *)priv;
printf("发现Echo服务:%s,地址:%u\n", name, dest);
// 设置端点私有数据
client->ept.priv = client;
// 创建客户端端点连接到服务
int ret = rpmsg_create_ept(&client->ept, rdev, name,
RPMSG_ADDR_ANY, dest,
echo_client_callback, NULL);
if (ret == 0)
{
client->connected = true;
printf("成功连接到Echo服务\n");
}
}
// 注册客户端
int echo_client_init(void)
{
return rpmsg_register_callback(&g_echo_client,
NULL, // 设备创建回调
NULL, // 设备销毁回调
echo_client_ns_match, // 服务匹配回调
echo_client_ns_bind); // 服务绑定回调
}
示例2:路由器的服务发现
// 路由器匹配:只关心路由器服务
static bool rpmsg_router_match(FAR struct rpmsg_device *rdev,
FAR void *priv,
FAR const char *name,
uint32_t dest)
{
// 匹配以 "rpmsg-router" 开头的服务
return !strncmp(name, RPMSG_ROUTER_NAME, RPMSG_ROUTER_NAME_LEN);
}
// 路由器绑定:创建路由端点
static void rpmsg_router_bind(FAR struct rpmsg_device *rdev,
FAR void *priv,
FAR const char *name,
uint32_t dest)
{
FAR struct rpmsg_endpoint *ept;
// 分配端点结构体
ept = kmm_zalloc(sizeof(*ept));
DEBUGASSERT(ept);
// 创建路由端点
int ret = rpmsg_create_ept(ept, rdev, name, RPMSG_ADDR_ANY, dest,
rpmsg_router_cb, rpmsg_router_unbind);
if (ret < 0)
{
rpmsgerr("创建路由端点失败: %d\n", ret);
kmm_free(ept);
}
}
服务通告流程:
// 远端CPU上的服务通告
void announce_service(void)
{
// 远端服务启动时,传输层会接收到名称服务通告
// 调用 rpmsg_ns_bind("rpmsg-echo", remote_addr)
}
// rpmsg_ns_bind() 的处理逻辑
void rpmsg_ns_bind(FAR struct rpmsg_device *rdev,
FAR const char *name, uint32_t dest)
{
// 步骤1:查找已注册的匹配回调
metal_list_for_each(&g_rpmsg_cb, node)
{
if (cb->ns_match && cb->ns_match(rdev, cb->priv, name, dest))
{
// 找到匹配的服务,立即绑定
cb->ns_bind(rdev, cb->priv, name, dest);
return;
}
}
// 步骤2:没找到匹配服务,缓存绑定请求
bind = kmm_malloc(sizeof(struct rpmsg_bind_s));
bind->dest = dest;
strlcpy(bind->name, name, RPMSG_NAME_SIZE);
// 加入设备的待绑定列表
metal_list_add_tail(&rpmsg->bind, &bind->node);
}
RPMSG的命名空间机制解决了时序不确定性问题:
情况1:先注册服务,后通告
1. 应用层注册 ns_match/ns_bind 回调
2. 远端服务通告
3. rpmsg_ns_bind() 立即找到匹配回调并执行绑定
情况2:先通告,后注册服务
1. 远端服务通告
2. rpmsg_ns_bind() 未找到匹配回调,缓存到 bind 列表
3. 应用层注册回调
4. rpmsg_register_callback() 检查 bind 列表,发现匹配服务
5. 执行延迟绑定(这就是您选中代码段的作用)
// 一个应用可以匹配多个服务
static bool multi_service_match(FAR struct rpmsg_device *rdev,
FAR void *priv,
FAR const char *name,
uint32_t dest)
{
// 匹配多种服务
return (strncmp(name, "file-", 5) == 0) || // 文件服务
(strncmp(name, "audio-", 6) == 0) || // 音频服务
(strncmp(name, "video-", 6) == 0); // 视频服务
}
static void multi_service_bind(FAR struct rpmsg_device *rdev,
FAR void *priv,
FAR const char *name,
uint32_t dest)
{
if (strncmp(name, "file-", 5) == 0)
{
create_file_client(rdev, name, dest);
}
else if (strncmp(name, "audio-", 6) == 0)
{
create_audio_client(rdev, name, dest);
}
else if (strncmp(name, "video-", 6) == 0)
{
create_video_client(rdev, name, dest);
}
}
// 调试函数:显示待绑定服务
void debug_pending_binds(void)
{
FAR struct metal_list *node;
FAR struct metal_list *bnode;
printf("=== 待绑定服务列表 ===\n");
metal_list_for_each(&g_rpmsg, node)
{
FAR struct rpmsg_s *rpmsg =
metal_container_of(node, struct rpmsg_s, node);
printf("设备: %s\n", rpmsg_get_cpuname(rpmsg->rdev));
metal_list_for_each(&rpmsg->bind, bnode)
{
FAR struct rpmsg_bind_s *bind =
metal_container_of(bnode, struct rpmsg_bind_s, node);
printf(" 待绑定服务: %s, 地址: %u\n", bind->name, bind->dest);
}
}
}
核心价值:
解决时序问题:无论服务通告和客户端注册的先后顺序
自动服务发现:无需手动配置服务地址
动态绑定:支持服务的热插拔
灵活匹配:支持复杂的服务匹配逻辑
您选中的代码段是整个命名空间服务发现机制的关键部分,它确保了即使在服务通告发生在客户端注册之前的情况下,系统仍能正确地建立服务连接。这种设计极大地提高了RPMSG系统的可靠性和易用性。
关于时序图中的两个端点创建步骤,它们并不矛盾,而是针对不同角色的不同时机:
"创建endpoint,准备服务" (第2阶段)
适用于:服务提供者 (Server)
时机:device_created
回调中
目的:创建服务端点,开始提供服务
地址:RPMSG_ADDR_ANY
(自动分配)
"创建具体服务端点" (第4阶段)
适用于:服务消费者 (Client)
时机:ns_bind
回调中
目的:创建客户端点,连接到已发现的服务
地址:指定远端服务的具体地址
核心区别:
服务端:主动提供服务,设备就绪后立即创建端点
客户端:被动发现服务,必须等到知道服务地址后才能创建端点
这种设计实现了RPMSG的服务发现-连接模式,确保了分布式系统中服务的正确关联。
// 注册回调函数
rpmsg_register_callback(priv, device_created_cb, device_destroy_cb,
ns_match_cb, ns_bind_cb);
// 在device_created回调中创建端点
int device_created_cb(struct rpmsg_device *rdev, void *priv)
{
// 步骤1: 设置端点的私有数据指针
ept.priv = priv; // 设置回调函数中能访问的私有数据
// 步骤2: 创建端点
rpmsg_create_ept(&ept, rdev, "service-name",
RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
ept_callback, NULL); // 最后参数是unbind回调
return 0;
}
// 发送数据
int ret = rpmsg_send(&ept, data, len);
// 端点回调处理接收数据
int ept_callback(struct rpmsg_endpoint *ept, void *data,
size_t len, uint32_t src, void *priv)
{
// 处理接收到的数据
process_received_data(data, len);
return 0;
}
// 获取发送缓冲区
uint32_t buf_len;
void *tx_buf = rpmsg_get_tx_payload_buffer(&ept, &buf_len, true);
// 填充数据
memcpy(tx_buf, data, data_len);
// 零拷贝发送
rpmsg_send_nocopy(&ept, tx_buf, data_len);
// 使用RPMSG提供的等待机制
sem_t sem;
nxsem_init(&sem, 0, 0);
// RPMSG优化的等待 (可能实现为忙等待等优化)
rpmsg_wait(&ept, &sem);
下面是一个完整的 RPMSG Echo 服务示例,展示正确的服务端和客户端实现模式:
服务端特点:在device_created
回调中立即创建端点,开始提供服务
#include
#include
#include
#include
#include
#include
#define ECHO_SERVICE_NAME "rpmsg-echo"
#define ECHO_MAX_MSG_SIZE 512
/* Echo 服务私有数据结构 */
struct echo_service_s
{
struct rpmsg_endpoint ept; /* RPMSG 端点 */
bool ready; /* 服务就绪标志 */
char name[32]; /* 服务名称 */
};
/* Echo 消息格式 */
struct echo_msg_s
{
uint32_t cmd; /* 命令类型 */
uint32_t len; /* 数据长度 */
char data[0]; /* 数据载荷 */
};
#define ECHO_CMD_REQUEST 0x01
#define ECHO_CMD_RESPONSE 0x02
static struct echo_service_s g_echo_service;
/* Echo 端点回调函数 - 处理接收到的消息 */
static int echo_ept_callback(FAR struct rpmsg_endpoint *ept,
FAR void *data, size_t len,
uint32_t src, FAR void *priv)
{
FAR struct echo_service_s *service = (FAR struct echo_service_s *)priv;
FAR struct echo_msg_s *msg = (FAR struct echo_msg_s *)data;
FAR struct echo_msg_s *resp;
uint32_t resp_len;
int ret;
if (!service || !msg || len < sizeof(struct echo_msg_s))
{
rpmsgerr("Invalid echo message received\n");
return -EINVAL;
}
rpmsginfo("Echo service received: cmd=0x%x, len=%d, data='%.*s'\n",
msg->cmd, msg->len, msg->len, msg->data);
/* 只处理请求消息 */
if (msg->cmd != ECHO_CMD_REQUEST)
{
return 0;
}
/* 分配响应缓冲区 */
resp_len = sizeof(struct echo_msg_s) + msg->len;
resp = rpmsg_get_tx_payload_buffer(ept, &resp_len, true);
if (!resp)
{
rpmsgerr("Failed to get tx buffer\n");
return -ENOMEM;
}
/* 构造响应消息 - Echo 回原数据 */
resp->cmd = ECHO_CMD_RESPONSE;
resp->len = msg->len;
if (msg->len > 0)
{
memcpy(resp->data, msg->data, msg->len);
}
/* 发送响应 */
ret = rpmsg_send_nocopy(ept, resp, sizeof(struct echo_msg_s) + resp->len);
if (ret < 0)
{
rpmsgerr("Failed to send echo response: %d\n", ret);
rpmsg_release_tx_buffer(ept, resp);
return ret;
}
rpmsginfo("Echo response sent successfully\n");
return 0;
}
/* 设备创建回调 - 当 RPMSG 设备就绪时创建 Echo 服务端点 */
static void echo_device_created(FAR struct rpmsg_device *rdev, FAR void *priv)
{
FAR struct echo_service_s *service = (FAR struct echo_service_s *)priv;
int ret;
if (service->ready)
{
return; /* 服务已经创建 */
}
rpmsginfo("Creating echo service on device: %s\n",
rpmsg_get_cpuname(rdev));
/* 设置端点私有数据 */
service->ept.priv = service;
/* 创建 Echo 服务端点 */
ret = rpmsg_create_ept(&service->ept, rdev, ECHO_SERVICE_NAME,
RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
echo_ept_callback, NULL);
if (ret < 0)
{
rpmsgerr("Failed to create echo endpoint: %d\n", ret);
return;
}
service->ready = true;
rpmsginfo("Echo service ready on %s\n", rpmsg_get_cpuname(rdev));
}
/* 设备销毁回调 - 清理 Echo 服务 */
static void echo_device_destroy(FAR struct rpmsg_device *rdev, FAR void *priv)
{
FAR struct echo_service_s *service = (FAR struct echo_service_s *)priv;
if (service->ready && service->ept.rdev == rdev)
{
rpmsginfo("Destroying echo service\n");
rpmsg_destroy_ept(&service->ept);
service->ready = false;
}
}
/* 初始化 Echo 服务 */
int echo_service_init(void)
{
memset(&g_echo_service, 0, sizeof(g_echo_service));
strlcpy(g_echo_service.name, "echo-server", sizeof(g_echo_service.name));
/* 服务端注册:只需device_created回调,不需要服务发现机制 */
return rpmsg_register_callback(&g_echo_service,
echo_device_created, /* 设备创建时立即创建服务端点 */
echo_device_destroy, /* 设备销毁回调 */
NULL, /* 服务端不需要匹配回调 */
NULL); /* 服务端不需要绑定回调 */
}
/* 清理 Echo 服务 */
void echo_service_deinit(void)
{
/* 注销回调 */
rpmsg_unregister_callback(&g_echo_service,
echo_device_created,
echo_device_destroy,
NULL, NULL);
/* 如果端点还存在,销毁它 */
if (g_echo_service.ready)
{
rpmsg_destroy_ept(&g_echo_service.ept);
g_echo_service.ready = false;
}
}
客户端特点:使用服务发现机制,在ns_bind
回调中创建端点连接服务
#include
#include
#include
#include
#include
#include
/* Echo 客户端私有数据 */
struct echo_client_s
{
struct rpmsg_endpoint ept; /* 客户端端点 */
sem_t resp_sem; /* 响应同步信号量 */
bool device_ready; /* 设备就绪标志 */
bool connected; /* 服务连接标志 */
char resp_data[ECHO_MAX_MSG_SIZE]; /* 响应数据缓冲区 */
size_t resp_len; /* 响应数据长度 */
int resp_result; /* 响应结果 */
};
static struct echo_client_s g_echo_client;
/* 客户端端点回调 - 处理 Echo 响应 */
static int echo_client_callback(FAR struct rpmsg_endpoint *ept,
FAR void *data, size_t len,
uint32_t src, FAR void *priv)
{
FAR struct echo_client_s *client = (FAR struct echo_client_s *)priv;
FAR struct echo_msg_s *msg = (FAR struct echo_msg_s *)data;
if (!client || !msg || len < sizeof(struct echo_msg_s))
{
return -EINVAL;
}
/* 检查是否是响应消息 */
if (msg->cmd != ECHO_CMD_RESPONSE)
{
return 0;
}
rpmsginfo("Echo client received response: len=%d, data='%.*s'\n",
msg->len, msg->len, msg->data);
/* 保存响应数据 */
client->resp_len = MIN(msg->len, sizeof(client->resp_data));
if (client->resp_len > 0)
{
memcpy(client->resp_data, msg->data, client->resp_len);
}
client->resp_result = 0;
/* 通知等待的线程 */
nxsem_post(&client->resp_sem);
return 0;
}
/* 客户端服务匹配回调 - 检查是否是我们感兴趣的服务 */
static bool echo_client_ns_match(FAR struct rpmsg_device *rdev,
FAR void *priv,
FAR const char *name,
uint32_t dest)
{
/* 只匹配Echo服务 */
return strncmp(name, ECHO_SERVICE_NAME, RPMSG_NAME_SIZE) == 0;
}
/* 客户端服务绑定回调 - 发现Echo服务时创建客户端点 */
static void echo_client_ns_bind(FAR struct rpmsg_device *rdev,
FAR void *priv,
FAR const char *name,
uint32_t dest)
{
FAR struct echo_client_s *client = (FAR struct echo_client_s *)priv;
int ret;
if (client->connected)
{
return; /* 已经连接 */
}
rpmsginfo("发现Echo服务: %s, 地址: %u\n", name, dest);
/* 设置端点私有数据 */
client->ept.priv = client;
/* 创建客户端端点连接到服务 - 关键:在服务发现后创建 */
ret = rpmsg_create_ept(&client->ept, rdev, name,
RPMSG_ADDR_ANY, dest, /* 指定目标服务地址 */
echo_client_callback, NULL);
if (ret < 0)
{
rpmsgerr("Failed to create echo client endpoint: %d\n", ret);
return;
}
client->connected = true;
rpmsginfo("成功连接到Echo服务\n");
}
/* 客户端设备创建回调 - 设备就绪,但不创建端点 */
static void echo_client_device_created(FAR struct rpmsg_device *rdev,
FAR void *priv)
{
FAR struct echo_client_s *client = (FAR struct echo_client_s *)priv;
if (client->device_ready)
{
return;
}
rpmsginfo("RPMSG设备就绪: %s,开始等待Echo服务发现\n",
rpmsg_get_cpuname(rdev));
/* 注意:客户端在这里不创建端点,而是等待服务发现 */
client->device_ready = true;
}
/* 客户端设备销毁回调 */
static void echo_client_device_destroy(FAR struct rpmsg_device *rdev,
FAR void *priv)
{
FAR struct echo_client_s *client = (FAR struct echo_client_s *)priv;
if (client->connected && client->ept.rdev == rdev)
{
rpmsginfo("Destroying echo client\n");
rpmsg_destroy_ept(&client->ept);
client->connected = false;
}
if (client->device_ready)
{
client->device_ready = false;
}
}
/* 初始化 Echo 客户端 */
int echo_client_init(void)
{
int ret;
memset(&g_echo_client, 0, sizeof(g_echo_client));
/* 初始化响应同步信号量 */
ret = nxsem_init(&g_echo_client.resp_sem, 0, 0);
if (ret < 0)
{
return ret;
}
/* 客户端注册:需要完整的回调集合实现服务发现 */
ret = rpmsg_register_callback(&g_echo_client,
echo_client_device_created, /* 设备就绪回调 */
echo_client_device_destroy, /* 设备销毁回调 */
echo_client_ns_match, /* 服务匹配回调 */
echo_client_ns_bind); /* 服务绑定回调 */
if (ret < 0)
{
nxsem_destroy(&g_echo_client.resp_sem);
return ret;
}
return 0;
}
/* 发送 Echo 请求并等待响应 */
int echo_client_send(FAR const char *data, size_t len,
FAR char *response, size_t *resp_len,
uint32_t timeout_ms)
{
FAR struct echo_msg_s *msg;
uint32_t msg_len;
int ret;
struct timespec timeout;
if (!g_echo_client.connected)
{
rpmsgerr("Echo client not connected to service\n");
return -ENOTCONN;
}
if (!data || len == 0 || len > ECHO_MAX_MSG_SIZE)
{
return -EINVAL;
}
/* 获取发送缓冲区 */
msg_len = sizeof(struct echo_msg_s) + len;
msg = rpmsg_get_tx_payload_buffer(&g_echo_client.ept, &msg_len, true);
if (!msg)
{
rpmsgerr("Failed to get tx buffer\n");
return -ENOMEM;
}
/* 构造请求消息 */
msg->cmd = ECHO_CMD_REQUEST;
msg->len = len;
memcpy(msg->data, data, len);
/* 重置响应状态 */
g_echo_client.resp_result = -ETIMEDOUT;
g_echo_client.resp_len = 0;
/* 发送请求 */
ret = rpmsg_send_nocopy(&g_echo_client.ept, msg,
sizeof(struct echo_msg_s) + len);
if (ret < 0)
{
rpmsgerr("Failed to send echo request: %d\n", ret);
rpmsg_release_tx_buffer(&g_echo_client.ept, msg);
return ret;
}
/* 等待响应 */
if (timeout_ms > 0)
{
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += timeout_ms / 1000;
timeout.tv_nsec += (timeout_ms % 1000) * 1000000;
if (timeout.tv_nsec >= 1000000000)
{
timeout.tv_sec++;
timeout.tv_nsec -= 1000000000;
}
ret = nxsem_timedwait(&g_echo_client.resp_sem, &timeout);
}
else
{
ret = nxsem_wait(&g_echo_client.resp_sem);
}
if (ret < 0)
{
rpmsgerr("Echo request timeout or failed: %d\n", ret);
return ret;
}
/* 检查响应结果 */
if (g_echo_client.resp_result < 0)
{
return g_echo_client.resp_result;
}
/* 复制响应数据 */
if (response && resp_len)
{
size_t copy_len = MIN(g_echo_client.resp_len, *resp_len);
memcpy(response, g_echo_client.resp_data, copy_len);
*resp_len = copy_len;
}
rpmsginfo("Echo request completed successfully\n");
return 0;
}
/* 清理 Echo 客户端 */
void echo_client_deinit(void)
{
/* 注销回调 */
rpmsg_unregister_callback(&g_echo_client,
echo_client_device_created,
echo_client_device_destroy,
NULL, NULL);
/* 销毁端点 */
if (g_echo_client.connected)
{
rpmsg_destroy_ept(&g_echo_client.ept);
g_echo_client.connected = false;
}
/* 销毁信号量 */
nxsem_destroy(&g_echo_client.resp_sem);
}
关键理解:服务端和客户端的端点创建时机完全不同!
角色 |
创建时机 |
回调函数 |
目的 |
服务端 |
|
只需要 |
设备就绪后立即提供服务 |
客户端 |
|
需要 |
发现服务后连接到服务 |
/* 错误:客户端在device_created中创建端点 */
static void wrong_client_device_created(FAR struct rpmsg_device *rdev, FAR void *priv)
{
/* 这里不知道服务地址,无法正确连接 */
rpmsg_create_ept(&ept, rdev, "rpmsg-echo",
RPMSG_ADDR_ANY, RPMSG_ADDR_ANY); // 错误!
}
/* 错误:只注册device_created */
rpmsg_register_callback(priv, wrong_client_device_created, NULL, NULL, NULL);
/* 正确:客户端等待服务发现 */
static bool client_ns_match(FAR struct rpmsg_device *rdev, FAR void *priv,
FAR const char *name, uint32_t dest)
{
return strncmp(name, "rpmsg-echo", RPMSG_NAME_SIZE) == 0;
}
static void client_ns_bind(FAR struct rpmsg_device *rdev, FAR void *priv,
FAR const char *name, uint32_t dest)
{
/* 现在知道了服务地址,可以正确连接 */
ept->priv = priv;
rpmsg_create_ept(ept, rdev, name, RPMSG_ADDR_ANY, dest); // 正确!
}
/* 正确:注册完整的回调集合 */
rpmsg_register_callback(priv, device_created, device_destroy,
client_ns_match, client_ns_bind);
服务端:知道自己提供什么服务,设备就绪后立即创建端点开始监听
客户端:必须等待发现远端服务的确切地址,才能创建端点连接
时序保证:确保客户端连接时服务端已经准备好接收请求
/* 应用程序主函数示例 */
int main(int argc, char *argv[ ])
{
char response[256];
size_t resp_len;
int ret;
/* 初始化 Echo 服务端(在服务端 CPU 上运行) */
ret = echo_service_init();
if (ret < 0)
{
printf("Failed to initialize echo service: %d\n", ret);
return ret;
}
/* 初始化 Echo 客户端(在客户端 CPU 上运行) */
ret = echo_client_init();
if (ret < 0)
{
printf("Failed to initialize echo client: %d\n", ret);
echo_service_deinit();
return ret;
}
/* 等待服务就绪和连接建立 */
sleep(2); /* 需要更长时间等待服务发现完成 */
/* 发送 Echo 请求 */
const char *test_msg = "Hello, RPMSG Echo Service!";
resp_len = sizeof(response);
ret = echo_client_send(test_msg, strlen(test_msg),
response, &resp_len, 5000); /* 5秒超时 */
if (ret < 0)
{
printf("Echo request failed: %d\n", ret);
}
else
{
response[resp_len] = '\0';
printf("Echo response: '%s'\n", response);
/* 验证响应是否正确 */
if (strcmp(test_msg, response) == 0)
{
printf("Echo test PASSED!\n");
}
else
{
printf("Echo test FAILED - response mismatch\n");
}
}
/* 清理资源 */
echo_client_deinit();
echo_service_deinit();
return 0;
}
这个完整示例展示了:
服务端实现:
注册设备创建/销毁回调
创建服务端点并监听指定服务名
处理客户端请求并发送响应
实现 Echo 功能(原样返回接收到的数据)
客户端实现:
使用服务发现机制自动找到Echo服务
在发现服务后创建客户端端点并连接
发送请求消息到服务端
使用信号量同步等待响应
支持超时机制
关键特性:
正确的端点创建时机: 服务端在设备就绪时创建,客户端在服务发现时创建
自动服务发现: 客户端通过ns_match/ns_bind机制自动发现和连接服务
零拷贝优化: 使用 rpmsg_get_tx_payload_buffer()
和 rpmsg_send_nocopy()
同步通信: 客户端发送请求后等待响应
错误处理: 完善的错误检查和资源清理
超时控制: 避免无限等待
资源管理: 正确的初始化和清理流程
实际应用场景:
多核系统中的服务调用
跨芯片的数据交换
分布式系统的远程过程调用
系统间的心跳检测
通过这个示例,您可以了解如何构建基于 RPMSG 的分布式服务,并可以根据实际需求进行扩展和定制。
int rpmsg_create_ept(struct rpmsg_endpoint *ept,
struct rpmsg_device *rdev,
const char *name,
uint32_t src,
uint32_t dest,
rpmsg_ept_cb cb,
rpmsg_ns_unbind_cb unbind_cb);
参数 |
类型 |
描述 |
|
|
指向要初始化的端点结构的指针 |
|
|
关联的 RPMSG 设备 |
|
|
服务名称字符串(用于服务发现) |
|
|
本地地址(通常使用 |
|
|
目标地址(通常使用 |
|
|
接收消息时的回调函数 |
|
|
端点解绑时的回调函数(可选,传 |
关键理解:回调函数中的 priv
参数不是通过 rpmsg_create_ept
传入的,而是来自端点结构体的 priv
字段!
设置端点的 priv 字段:
// 在创建端点之前,先设置 ept.priv
service->ept.priv = service; // 设置私有数据指针
创建端点:
// 最后一个参数是 unbind 回调,通常传 NULL
rpmsg_create_ept(&service->ept, rdev, "service-name",
RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
service_callback, NULL); // ← NULL 是 unbind 回调
回调函数接收 priv:
static int service_callback(FAR struct rpmsg_endpoint *ept,
FAR void *data, size_t len, uint32_t src,
FAR void *priv) // ← 这个 priv 来自 ept->priv
{
// priv 实际上就是 ept->priv 的值
FAR struct my_service_s *service = (FAR struct my_service_s *)priv;
// ...
}
/* 消息接收回调 */
typedef int (*rpmsg_ept_cb)(struct rpmsg_endpoint *ept,
void *data, size_t len,
uint32_t src, void *priv);
/* 解绑回调(可选) */
typedef void (*rpmsg_ns_unbind_cb)(struct rpmsg_endpoint *ept);
❌ 错误用法:
// 错误:试图通过 rpmsg_create_ept 传递 priv
rpmsg_create_ept(&ept, rdev, "service",
RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
callback, my_private_data); // ← 错误!最后参数不是 priv
✅ 正确用法:
// 正确:先设置 ept.priv,再创建端点
ept.priv = my_private_data; // ← 关键步骤
rpmsg_create_ept(&ept, rdev, "service",
RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
callback, NULL); // ← NULL 是 unbind 回调
适当的缓冲区大小: 根据数据包大小调整 BUFFSIZE
足够的缓冲区数量: 增加 BUFFNUM
避免缓冲区不足
合理的阈值设置: 调整 RX_THRESHOLD
平衡延迟和吞吐量
高优先级: 对延迟敏感的应用设置高优先级
CPU亲和性: 在SMP系统中考虑CPU绑定
栈大小: 根据实际需求调整线程栈大小
共享内存: VirtIO适用于同芯片多核
高速串行: SPI适用于高速芯片间通信
低速串行: UART适用于调试和低速场景
当前代码中存在一些未定义的宏,需要检查:
RPMSG_NAME_SIZE
: 在相关头文件中定义
CONFIG_RPMSG_LOCAL_CPUNAME
: 在Kconfig中配置
RPMSGIOC_*
: 在头文件中定义ioctl命令
使用ping功能: 测试基本连通性和性能
启用调试日志: 通过CONFIG_DEBUG_RPMSG
等选项
检查设备状态: 使用rpmsg_dump_all()
查看状态
延迟测试: 使用ping功能测量往返时间
吞吐量测试: 发送大量数据包测试带宽
资源监控: 监控CPU使用率和内存使用
实现rpmsg_port_ops_s
接口
处理硬件特定的初始化和数据传输
实现适当的中断处理和流控制
添加相应的Kconfig配置选项
定义服务特定的消息格式
实现服务发现和绑定逻辑
处理协议特定的错误和异常情况
提供用户友好的API接口
Vela 的RPMSG子系统提供了一个完整、灵活的多处理器通信解决方案。通过分层的架构设计,它既保持了接口的统一性,又允许底层传输的多样化实现。理解其核心机制和使用模式,有助于更好地利用这个强大的通信框架开发复杂的多核/多芯片应用系统。
关键学习要点:
分层架构: 理解各层的职责和接口
回调机制: 掌握服务发现和绑定流程
缓冲区管理: 熟悉零拷贝和内存管理策略
配置调优: 根据应用需求选择合适的配置
调试技巧: 利用内置工具进行问题诊断
通过深入学习和实践,可以充分发挥RPMSG在分布式系统中的强大能力。