Vela RPMSG 系统框架

1. RPMSG 概述

1.1 什么是 RPMSG

RPMSG (Remote Processor Messaging) 是一个用于多核/多处理器系统间通信的消息传递框架。它提供了一个标准化的API来实现不同处理器之间的数据交换和服务调用。

1.2 主要特性

  • 跨处理器通信: 支持多核、多芯片间的通信

  • 多种传输层支持: SPI、UART、VirtIO、共享内存等

  • 服务发现机制: 支持动态服务绑定和发现

  • 灵活的配置: 支持多种配置选项和优化策略

  • 调试支持: 内置ping功能用于性能测试和调试

2. 整体架构

2.1 架构层次

┌─────────────────────────────────────────┐
│              应用层                      │
│    (各种RPMSG服务: uart, net, fs等)      │
├─────────────────────────────────────────┤
│            RPMSG核心层                   │
│     (rpmsg.c - 设备管理和回调机制)        │
├─────────────────────────────────────────┤
│            传输抽象层                    │
│  (rpmsg_port.c - 端口管理和缓冲区管理)    │
├─────────────────────────────────────────┤
│            物理传输层                    │
│ (SPI/UART/VirtIO 具体传输实现)           │
└─────────────────────────────────────────┘

2.2 目录结构解析

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                   # 配置选项

3. 核心数据结构

3.1 rpmsg_s 结构体 (核心设备结构)

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设备
};

3.2 rpmsg_ops_s 操作接口

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);
};

3.3 rpmsg_port_s 端口结构

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;           // 端口操作
};

4. 核心功能分析

4.1 设备注册与管理 (rpmsg.c)

设备注册流程
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

4.2 命名空间服务发现

服务绑定流程
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. 没找到匹配服务,缓存绑定请求
  // 创建绑定结构并加入待绑定列表
}

4.3 端口抽象层 (rpmsg_port.c)

队列管理

每个端口维护两个队列:

  • 发送队列 (txq): 管理待发送的数据缓冲区

  • 接收队列 (rxq): 管理接收到的数据缓冲区

每个队列包含:

  • 空闲列表 (free): 可用的缓冲区

  • 就绪列表 (ready): 已使用的缓冲区

缓冲区管理API
// 获取可用缓冲区
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);

5. 传输层实现

5.1 VirtIO 传输 (rpmsg_virtio.c)

  • 基于共享内存: 使用VirtIO队列机制

  • 高性能: 适用于虚拟化环境和共享内存场景

  • 支持中断通知: 提供高效的事件通知机制

5.2 SPI 传输 (rpmsg_port_spi.c)

  • 主从模式: 支持SPI主设备和从设备模式

  • CRC校验: 可选的数据完整性校验

  • 流控制: 基于available字段的流量控制

  • 专用线程: 独立的收发线程处理

5.3 UART 传输 (rpmsg_port_uart.c)

  • 串口通信: 基于UART串口的点对点通信

  • 双工通信: 独立的收发线程

  • 帧同步: 基于帧头的数据包同步机制

6. 调试功能

6.1 RPMSG Ping (rpmsg_ping.c)

提供性能测试和调试功能:

Ping消息结构
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);

6.2 系统调试接口

// 触发远程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);

7. 配置选项详解

7.1 基础配置

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调试功能

7.2 传输层配置

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传输支持

7.3 性能调优配置

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缓冲区大小

8. 传输层设备注册详解

在使用 RPMSG 编程接口之前,必须先理解传输层设备的注册过程。这是整个 RPMSG 系统能够正常工作的基础。

8.1 传输层初始化的重要性

关键理解:在调用 rpmsg_register_callback 注册应用层回调之前,必须先有 RPMSG 传输层设备注册到系统中。否则回调将无法被触发。

依赖关系图
┌─────────────────────────────────────────┐
│            系统启动顺序                   │
├─────────────────────────────────────────┤
│ 1. 内核基础设施 (内存、调度、同步)        │
│ 2. OpenAMP/Metal 库初始化                │
│ 3. 硬件资源准备 (中断、DMA、共享内存)     │
│ 4. 传输层初始化 ← 关键步骤                │
│    ├── rpmsg_register() 注册设备         │
│    └── rpmsg_device_created() 设备就绪   │
│ 5. 应用层回调注册                        │
│    └── rpmsg_register_callback()         │
└─────────────────────────────────────────┘

8.2 rpmsg_register 函数详解

函数原型
int rpmsg_register(FAR const char *path, 
                   FAR struct rpmsg_s *rpmsg,
                   FAR const struct rpmsg_ops_s *ops);
功能作用

rpmsg_register 是传输层向 RPMSG 核心注册设备的关键函数:

  1. 初始化 OpenAMP 框架:调用 metal_init() 初始化底层库

  2. 注册字符设备:在 /dev 下创建设备节点供用户空间访问

  3. 初始化设备结构:设置绑定列表、互斥锁和操作接口

  4. 加入全局设备列表:将设备添加到 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;
}

8.3 不同传输层的初始化过程

8.3.1 VirtIO 传输层初始化

使用场景: 基于共享内存的高性能通信,适用于虚拟化环境或同芯片多核场景。

/* 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;
}
VirtIO 设备就绪流程
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;
}
8.3.2 SPI 传输层初始化

使用场景: 芯片间高速串行通信,支持主从模式。

/* 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;
}
8.3.3 UART 传输层初始化

使用场景: 基于串口的点对点通信,适用于调试和低速通信场景。

/* 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;
}

8.4 rpmsg_device_created 的作用

当传输层硬件就绪后,必须调用 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
}

8.5 典型的板级初始化代码

8.5.1 板级初始化函数示例
/* 板级 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;
}
8.5.2 系统启动集成
/* 在系统启动过程中调用 */
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;
}

8.6 应用层初始化时机

重要:应用层服务只能在传输层初始化完成后再初始化:

/* 应用层初始化函数 */
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;
}

8.7 初始化顺序总结

正确的初始化顺序
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()
   ↓
结果:回调立即触发,服务正常启动

8.8 调试和故障排除

8.8.1 常见问题及解决方法

问题1: 应用层回调没有被触发

原因:传输层未初始化或未调用 rpmsg_device_created()
解决:检查传输层初始化顺序,确保设备已就绪
调试:使用 rpmsg_dump_all() 查看已注册设备

问题2: rpmsg_register 失败

原因:OpenAMP 库未初始化或硬件资源不可用  
解决:检查 metal_init() 调用和硬件配置
调试:检查系统日志中的错误信息

问题3: 设备节点创建失败

原因:设备路径冲突或权限问题
解决:检查设备路径唯一性,确保目录存在
调试:使用 ls -la /dev/rpmsg/ 检查设备节点
8.8.2 调试工具和方法
/* 调试辅助函数 */
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 系统,确保传输层和应用层之间的正确协作。

9. 上层Service与底层Port关联机制详解

9.1 关联机制概述

核心问题:上层应用注册的service如何与底层传输注册的port关联起来?

答案:通过回调机制和命名空间绑定实现关联。整个过程包含两个阶段:

  1. 设备关联阶段:底层port注册后,通过设备创建回调通知上层service

  2. 服务绑定阶段:通过命名空间匹配和绑定机制实现具体服务的关联

9.2 关联流程的源码分析

9.2.1 数据结构基础

从源码可以看到,系统维护两个关键的全局链表:

// 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;                   // 链表节点
};
9.2.2 第一阶段:设备关联

步骤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);
}
9.2.3 第二阶段:服务绑定

步骤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);
}

9.3 关联时序图

9.4 端点创建的时机详解

您提出的问题很重要!让我详细解释应用层端点创建的两个不同时机:

9.4.1 服务端点创建时机

时机:在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);                       // 不需要服务绑定
}
9.4.2 客户端点创建时机

时机:在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);        // 服务绑定回调
}
9.4.3 两种模式对比

方面

服务端点创建

客户端点创建

创建时机

device_created 回调中

ns_bind 回调中

触发条件

传输层设备就绪

发现匹配的远端服务

端点地址

RPMSG_ADDR_ANY (自动分配)

指定 dest 地址

目的

提供服务,等待连接

连接到已知服务

回调需求

只需 device_created

需要 ns_match + ns_bind

9.4.4 完整的交互流程
// 时序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. 服务端处理请求并回复
9.4.5 常见误区和最佳实践

❌ 错误做法:客户端在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);
}

总结:

  • 服务端:在设备就绪后立即创建端点,开始提供服务

  • 客户端:等待服务发现后才创建端点,连接到具体服务

  • 这种分离确保了服务的发现-连接模式正确工作

9.5 具体示例:Echo服务的关联过程

9.5.1 Echo服务注册
// 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;
}
9.5.2 SPI传输层port注册
// 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;
}

9.6 关联的关键要素

9.6.1 CPU名称匹配
// 传输层设备的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", ...);
    }
}
9.6.2 服务名称匹配
// 使用命名空间匹配机制
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);
}

9.7 多设备支持

系统支持多个传输层设备同时存在:

// 支持多个传输层设备
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);
    }
}

9.8 关联机制的优势

  1. 解耦设计:应用层和传输层完全解耦,互不依赖

  2. 动态绑定:支持传输层和应用层的动态加载和卸载

  3. 多设备支持:一个应用可以同时使用多个传输设备

  4. 灵活配置:通过CPU名称和服务名称灵活控制关联关系

  5. 缓存机制:支持服务请求的缓存,处理时序不确定性

9.9 调试关联问题

9.9.1 检查设备注册状态
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);
}
9.9.2 常见关联问题
  1. 回调未触发:检查设备是否调用了rpmsg_device_created()

  2. CPU名称不匹配:检查传输层设置的CPU名称是否正确

  3. 时序问题:确保传输层在应用层之前初始化

  4. 内存泄漏:检查rpmsg_bind_s结构体的释放

通过这个详细的关联机制分析,我们可以清楚地理解上层service和底层port是如何通过RPMSG核心层的回调机制和命名空间绑定机制实现关联的。

9.10 深入理解 ns_match 和 ns_bind 机制

9.10.1 命名空间服务发现概念

RPMSG的命名空间(Namespace)服务发现是一套动态服务绑定机制,允许:

  1. 服务提供者:远端CPU上的服务主动通告自己的存在

  2. 服务消费者:本地CPU上的客户端自动发现并连接到远端服务

  3. 动态绑定:无需预先配置,支持服务的动态上线和下线

9.10.2 回调函数类型定义
// 命名空间匹配回调:判断是否对某个服务感兴趣
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: 远端服务的地址

9.9.3 核心代码段详细分析

让我们深入分析您选中的代码段:

// 位于 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);
9.9.4 代码执行流程详解

第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;                         // 重新检查(可能有多个匹配服务)

关键设计要点:

  1. 先移除再绑定:避免重复处理

  2. 释放锁后调用回调:避免回调中再次获取锁导致死锁

  3. goto again:一次可能匹配多个服务,需要重新遍历

  4. 内存管理:及时释放已处理的绑定结构体

9.9.5 实际使用示例

示例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);
    }
}
9.9.6 服务通告和缓存机制

服务通告流程:

// 远端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);
}
9.9.7 时序处理机制

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. 执行延迟绑定(这就是您选中代码段的作用)
9.9.8 多服务匹配处理
// 一个应用可以匹配多个服务
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);
    }
}
9.9.9 调试和监控
// 调试函数:显示待绑定服务
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);
        }
    }
}
9.9.10 总结

核心价值:

  1. 解决时序问题:无论服务通告和客户端注册的先后顺序

  2. 自动服务发现:无需手动配置服务地址

  3. 动态绑定:支持服务的热插拔

  4. 灵活匹配:支持复杂的服务匹配逻辑

您选中的代码段是整个命名空间服务发现机制的关键部分,它确保了即使在服务通告发生在客户端注册之前的情况下,系统仍能正确地建立服务连接。这种设计极大地提高了RPMSG系统的可靠性和易用性。

9.10.11 回答您的问题总结

关于时序图中的两个端点创建步骤,它们并不矛盾,而是针对不同角色的不同时机:

  1. "创建endpoint,准备服务" (第2阶段)

    • 适用于:服务提供者 (Server)

    • 时机:device_created 回调中

    • 目的:创建服务端点,开始提供服务

    • 地址:RPMSG_ADDR_ANY (自动分配)

  2. "创建具体服务端点" (第4阶段)

    • 适用于:服务消费者 (Client)

    • 时机:ns_bind 回调中

    • 目的:创建客户端点,连接到已发现的服务

    • 地址:指定远端服务的具体地址

核心区别:

  • 服务端:主动提供服务,设备就绪后立即创建端点

  • 客户端:被动发现服务,必须等到知道服务地址后才能创建端点

这种设计实现了RPMSG的服务发现-连接模式,确保了分布式系统中服务的正确关联。

10. 编程接口使用

10.1 基本使用流程

1. 服务注册
// 注册回调函数
rpmsg_register_callback(priv, device_created_cb, device_destroy_cb,
                       ns_match_cb, ns_bind_cb);
2. 端点创建和绑定
// 在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;
}
3. 数据收发
// 发送数据
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;
}

10.2 高级功能

零拷贝发送
// 获取发送缓冲区
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);

10.3 完整使用示例 - Echo 服务

下面是一个完整的 RPMSG Echo 服务示例,展示正确的服务端和客户端实现模式:

10.3.1 服务端实现 (Echo Server)

服务端特点:在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;
    }
}
10.3.2 客户端实现 (Echo Client)

客户端特点:使用服务发现机制,在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);
}
10.3.3 端点创建时机对比

关键理解:服务端和客户端的端点创建时机完全不同!

角色

创建时机

回调函数

目的

服务端

device_created

只需要 device_created

设备就绪后立即提供服务

客户端

ns_bind

需要 ns_match + ns_bind

发现服务后连接到服务

错误的客户端实现 ❌
/* 错误:客户端在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);
为什么要区分?
  1. 服务端:知道自己提供什么服务,设备就绪后立即创建端点开始监听

  2. 客户端:必须等待发现远端服务的确切地址,才能创建端点连接

  3. 时序保证:确保客户端连接时服务端已经准备好接收请求

10.3.4 使用示例代码
/* 应用程序主函数示例 */

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;
}
示例说明

这个完整示例展示了:

  1. 服务端实现:

    • 注册设备创建/销毁回调

    • 创建服务端点并监听指定服务名

    • 处理客户端请求并发送响应

    • 实现 Echo 功能(原样返回接收到的数据)

  2. 客户端实现:

    • 使用服务发现机制自动找到Echo服务

    • 在发现服务后创建客户端端点并连接

    • 发送请求消息到服务端

    • 使用信号量同步等待响应

    • 支持超时机制

  3. 关键特性:

    • 正确的端点创建时机: 服务端在设备就绪时创建,客户端在服务发现时创建

    • 自动服务发现: 客户端通过ns_match/ns_bind机制自动发现和连接服务

    • 零拷贝优化: 使用 rpmsg_get_tx_payload_buffer()rpmsg_send_nocopy()

    • 同步通信: 客户端发送请求后等待响应

    • 错误处理: 完善的错误检查和资源清理

    • 超时控制: 避免无限等待

    • 资源管理: 正确的初始化和清理流程

  4. 实际应用场景:

    • 多核系统中的服务调用

    • 跨芯片的数据交换

    • 分布式系统的远程过程调用

    • 系统间的心跳检测

通过这个示例,您可以了解如何构建基于 RPMSG 的分布式服务,并可以根据实际需求进行扩展和定制。

10.4 rpmsg_create_ept 函数详解

函数原型
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);
参数说明

参数

类型

描述

ept

struct rpmsg_endpoint *

指向要初始化的端点结构的指针

rdev

struct rpmsg_device *

关联的 RPMSG 设备

name

const char *

服务名称字符串(用于服务发现)

src

uint32_t

本地地址(通常使用 RPMSG_ADDR_ANY 自动分配)

dest

uint32_t

目标地址(通常使用 RPMSG_ADDR_ANY 等待绑定)

cb

rpmsg_ept_cb

接收消息时的回调函数

unbind_cb

rpmsg_ns_unbind_cb

端点解绑时的回调函数(可选,传 NULL

重要说明:priv 参数的来源

关键理解:回调函数中的 priv 参数不是通过 rpmsg_create_ept 传入的,而是来自端点结构体的 priv 字段!

正确的使用流程:
  1. 设置端点的 priv 字段:

// 在创建端点之前,先设置 ept.priv
service->ept.priv = service;  // 设置私有数据指针
  1. 创建端点:

// 最后一个参数是 unbind 回调,通常传 NULL
rpmsg_create_ept(&service->ept, rdev, "service-name",
                 RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
                 service_callback, NULL);  // ← NULL 是 unbind 回调
  1. 回调函数接收 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 回调

11. 性能优化建议

11.1 缓冲区配置

  • 适当的缓冲区大小: 根据数据包大小调整 BUFFSIZE

  • 足够的缓冲区数量: 增加 BUFFNUM 避免缓冲区不足

  • 合理的阈值设置: 调整 RX_THRESHOLD 平衡延迟和吞吐量

11.2 线程优先级

  • 高优先级: 对延迟敏感的应用设置高优先级

  • CPU亲和性: 在SMP系统中考虑CPU绑定

  • 栈大小: 根据实际需求调整线程栈大小

11.3 传输层选择

  • 共享内存: VirtIO适用于同芯片多核

  • 高速串行: SPI适用于高速芯片间通信

  • 低速串行: UART适用于调试和低速场景

12. 常见问题和调试

12.1 编译错误处理

当前代码中存在一些未定义的宏,需要检查:

  • RPMSG_NAME_SIZE: 在相关头文件中定义

  • CONFIG_RPMSG_LOCAL_CPUNAME: 在Kconfig中配置

  • RPMSGIOC_*: 在头文件中定义ioctl命令

12.2 运行时调试

  • 使用ping功能: 测试基本连通性和性能

  • 启用调试日志: 通过CONFIG_DEBUG_RPMSG等选项

  • 检查设备状态: 使用rpmsg_dump_all()查看状态

12.3 性能分析

  • 延迟测试: 使用ping功能测量往返时间

  • 吞吐量测试: 发送大量数据包测试带宽

  • 资源监控: 监控CPU使用率和内存使用

13. 扩展和定制

13.1 添加新的传输层

  1. 实现rpmsg_port_ops_s接口

  2. 处理硬件特定的初始化和数据传输

  3. 实现适当的中断处理和流控制

  4. 添加相应的Kconfig配置选项

13.2 自定义服务协议

  1. 定义服务特定的消息格式

  2. 实现服务发现和绑定逻辑

  3. 处理协议特定的错误和异常情况

  4. 提供用户友好的API接口

14. 总结

Vela 的RPMSG子系统提供了一个完整、灵活的多处理器通信解决方案。通过分层的架构设计,它既保持了接口的统一性,又允许底层传输的多样化实现。理解其核心机制和使用模式,有助于更好地利用这个强大的通信框架开发复杂的多核/多芯片应用系统。

关键学习要点:

  • 分层架构: 理解各层的职责和接口

  • 回调机制: 掌握服务发现和绑定流程

  • 缓冲区管理: 熟悉零拷贝和内存管理策略

  • 配置调优: 根据应用需求选择合适的配置

  • 调试技巧: 利用内置工具进行问题诊断

通过深入学习和实践,可以充分发挥RPMSG在分布式系统中的强大能力。

你可能感兴趣的:(Vela,系统源码学习,嵌入式,rpmsg,vela,源码学习)