远程处理器消息(rpmsg)框架

注意

本文档介绍了 rpmsg 总线以及如何编写 rpmsg 驱动程序。要了解如何为新平台添加 rpmsg 支持,请查看 远程处理器框架 (也是 Documentation/ 中的一个驻留项)。

简介

现代 SoC 通常在非对称多处理 (AMP) 配置中使用异构远程处理器设备,这些设备可能运行不同的操作系统实例,无论是 Linux 还是任何其他实时操作系统。

例如,OMAP4 有双核 Cortex-A9、双核 Cortex-M3 和一个 C64x+ DSP。 通常,双核 Cortex-A9 在 SMP 配置中运行 Linux,而其他三个内核(两个 M3 内核和一个 DSP)中的每一个都在 AMP 配置中运行其自己的 RTOS 实例。

通常,AMP 远程处理器采用专用的 DSP 编解码器和多媒体硬件加速器,因此通常用于将 CPU 密集型多媒体任务从主应用程序处理器卸载。

这些远程处理器还可以用于控制延迟敏感的传感器、驱动随机硬件块,或者在主 CPU 空闲时执行后台任务。

这些远程处理器的用户可以是用户空间应用程序(例如,与远程 OMX 组件通信的多媒体框架)或内核驱动程序(控制仅远程处理器可访问的硬件,代表远程处理器保留内核控制的资源等)。

Rpmsg 是一个基于 virtio 的消息总线,允许内核驱动程序与系统上可用的远程处理器进行通信。反过来,如果需要,驱动程序可以公开适当的用户空间接口。

在编写向用户空间公开 rpmsg 通信的驱动程序时,请记住,远程处理器可能可以直接访问系统的物理内存和其他敏感硬件资源(例如,在 OMAP4 上,远程内核和硬件加速器可能可以直接访问物理内存、gpio banks、dma 控制器、i2c 总线、gptimer、邮箱设备、hwspinlocks 等)。此外,这些远程处理器可能正在运行 RTOS,其中每个任务都可以访问暴露给处理器的整个内存/设备。为了最大限度地降低恶意(或有缺陷的)用户空间代码利用远程错误并因此接管系统的风险,通常希望将用户空间限制在可以发送消息的特定 rpmsg 通道(请参阅下面的定义),并且如果可能,尽量减少它对消息内容的控制。

每个 rpmsg 设备都是与远程处理器的通信通道(因此 rpmsg 设备称为通道)。通道由文本名称标识,并具有本地(“源”)rpmsg 地址和远程(“目标”)rpmsg 地址。

当驱动程序开始在通道上侦听时,其 rx 回调与唯一的 rpmsg 本地地址(一个 32 位整数)绑定。这样,当入站消息到达时,rpmsg 内核会根据其目标地址将它们分派到相应的驱动程序(这是通过使用入站消息的有效负载调用驱动程序的 rx 处理程序来完成的)。

用户 API

int rpmsg_send(struct rpmsg_endpoint *ept, void *data, int len);

从给定的端点向远程处理器发送消息。调用者应指定端点、要发送的数据及其长度(以字节为单位)。该消息将在指定端点的通道上发送,即其源地址和目标地址字段将分别设置为端点的 src 地址及其父通道 dst 地址。

如果没有任何可用的 TX 缓冲区,该函数将阻塞,直到有一个缓冲区可用(即,直到远程处理器使用一个 tx 缓冲区并将其放回 virtio 的已使用描述符环上),或者经过 15 秒的超时。如果发生后一种情况,则返回 -ERESTARTSYS。

该函数只能从进程上下文中调用(目前)。成功时返回 0,失败时返回适当的错误值。

int rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst);

从给定端点向远程处理器发送消息,到调用者提供的目标地址。

调用者应指定端点、要发送的数据及其长度(以字节为单位)和一个显式的目标地址。

然后,该消息将使用端点的 src 地址和用户提供的 dst 地址发送到该端点的通道所属的远程处理器(因此将忽略该通道的 dst 地址)。

如果没有任何可用的 TX 缓冲区,该函数将阻塞,直到有一个缓冲区可用(即,直到远程处理器使用一个 tx 缓冲区并将其放回 virtio 的已使用描述符环上),或者经过 15 秒的超时。如果发生后一种情况,则返回 -ERESTARTSYS。

该函数只能从进程上下文中调用(目前)。成功时返回 0,失败时返回适当的错误值。

int rpmsg_send_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
                                                      void *data, int len);

使用用户提供的 src 和 dst 地址向远程处理器发送消息。

调用者应指定端点、要发送的数据、其长度(以字节为单位)以及显式的源地址和目标地址。然后,该消息将发送到端点的通道所属的远程处理器,但将忽略端点的 src 地址和通道 dst 地址(而是使用用户提供的地址)。

如果没有任何可用的 TX 缓冲区,该函数将阻塞,直到有一个缓冲区可用(即,直到远程处理器使用一个 tx 缓冲区并将其放回 virtio 的已使用描述符环上),或者经过 15 秒的超时。如果发生后一种情况,则返回 -ERESTARTSYS。

该函数只能从进程上下文中调用(目前)。成功时返回 0,失败时返回适当的错误值。

int rpmsg_trysend(struct rpmsg_endpoint *ept, void *data, int len);

从给定的端点向远程处理器发送消息。调用者应指定端点、要发送的数据及其长度(以字节为单位)。该消息将在指定端点的通道上发送,即其源地址和目标地址字段将分别设置为端点的 src 地址及其父通道 dst 地址。

如果没有可用的 TX 缓冲区,该函数将立即返回 -ENOMEM,而无需等待直到有一个缓冲区可用。

该函数只能从进程上下文中调用(目前)。成功时返回 0,失败时返回适当的错误值。

int rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst)

从给定的端点向远程处理器发送消息,到用户提供的目标地址。

用户应指定通道、要发送的数据、其长度(以字节为单位)以及显式的目标地址。

然后,该消息将使用通道的 src 地址和用户提供的 dst 地址发送到该通道所属的远程处理器(因此将忽略该通道的 dst 地址)。

如果没有可用的 TX 缓冲区,该函数将立即返回 -ENOMEM,而无需等待直到有一个缓冲区可用。

该函数只能从进程上下文中调用(目前)。成功时返回 0,失败时返回适当的错误值。

int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
                                                      void *data, int len);

使用用户提供的源地址和目标地址向远程处理器发送消息。

用户应指定通道、要发送的数据、其长度(以字节为单位)以及显式的源地址和目标地址。然后,该消息将发送到该通道所属的远程处理器,但将忽略该通道的 src 和 dst 地址(而是使用用户提供的地址)。

如果没有可用的 TX 缓冲区,该函数将立即返回 -ENOMEM,而无需等待直到有一个缓冲区可用。

该函数只能从进程上下文中调用(目前)。成功时返回 0,失败时返回适当的错误值。

struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *rpdev,
                                        rpmsg_rx_cb_t cb, void *priv,
                                        struct rpmsg_channel_info chinfo);

系统中的每个 rpmsg 地址都通过 rpmsg_endpoint 结构绑定到一个 rx 回调(因此当入站消息到达时,它们由 rpmsg 总线使用适当的回调处理程序分派)。

此函数允许驱动程序创建这样的端点,并因此将回调以及一些私有数据绑定到 rpmsg 地址(无论是预先知道的地址,还是将为它们动态分配的地址)。

简单的 rpmsg 驱动程序不需要调用 rpmsg_create_ept,因为当它们被 rpmsg 总线探测时(使用它们注册到 rpmsg 总线时提供的 rx 回调),已经为它们创建了一个端点。

因此,对于简单的驱动程序,一切都应该正常工作:它们已经有一个端点,它们的 rx 回调绑定到它们的 rpmsg 地址,当相关的入站消息到达时(即其 dst 地址等于其 rpmsg 通道的 src 地址的消息),会调用驱动程序的处理程序来处理它。

也就是说,更复杂的驱动程序可能需要分配额外的 rpmsg 地址,并将它们绑定到不同的 rx 回调。为了实现这一点,这些驱动程序需要调用此函数。驱动程序应提供其通道(以便新端点绑定到其通道所属的同一远程处理器)、一个 rx 回调函数、一个可选的私有数据(在调用 rx 回调时会返回),以及它们希望与回调绑定的地址。如果 addr 是 RPMSG_ADDR_ANY,则 rpmsg_create_ept 将动态分配它们一个可用的 rpmsg 地址(驱动程序应该有充分的理由说明为什么不总是在此处使用 RPMSG_ADDR_ANY)。

成功时返回指向端点的指针,错误时返回 NULL。

void rpmsg_destroy_ept(struct rpmsg_endpoint *ept);

销毁现有的 rpmsg 端点。用户应提供一个指向先前使用 rpmsg_create_ept() 创建的 rpmsg 端点的指针。

int register_rpmsg_driver(struct rpmsg_driver *rpdrv);

向 rpmsg 总线注册一个 rpmsg 驱动程序。用户应提供一个指向 rpmsg_driver 结构的指针,该结构包含驱动程序的 ->probe() 和 ->remove() 函数、一个 rx 回调和一个 id_table,指定此驱动程序感兴趣探测的通道的名称。

void unregister_rpmsg_driver(struct rpmsg_driver *rpdrv);

从 rpmsg 总线注销一个 rpmsg 驱动程序。用户应提供一个指向先前注册的 rpmsg_driver 结构的指针。成功时返回 0,失败时返回适当的错误值。

典型用法

以下是一个简单的 rpmsg 驱动程序,它在 probe() 上发送“hello!”消息,并且每当它收到传入消息时,它都会将其内容转储到控制台。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/rpmsg.h>

static void rpmsg_sample_cb(struct rpmsg_channel *rpdev, void *data, int len,
                                              void *priv, u32 src)
{
      print_hex_dump(KERN_INFO, "incoming message:", DUMP_PREFIX_NONE,
                                              16, 1, data, len, true);
}

static int rpmsg_sample_probe(struct rpmsg_channel *rpdev)
{
      int err;

      dev_info(&rpdev->dev, "chnl: 0x%x -> 0x%x\n", rpdev->src, rpdev->dst);

      /* send a message on our channel */
      err = rpmsg_send(rpdev->ept, "hello!", 6);
      if (err) {
              pr_err("rpmsg_send failed: %d\n", err);
              return err;
      }

      return 0;
}

static void rpmsg_sample_remove(struct rpmsg_channel *rpdev)
{
      dev_info(&rpdev->dev, "rpmsg sample client driver is removed\n");
}

static struct rpmsg_device_id rpmsg_driver_sample_id_table[] = {
      { .name = "rpmsg-client-sample" },
      { },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sample_id_table);

static struct rpmsg_driver rpmsg_sample_client = {
      .drv.name       = KBUILD_MODNAME,
      .id_table       = rpmsg_driver_sample_id_table,
      .probe          = rpmsg_sample_probe,
      .callback       = rpmsg_sample_cb,
      .remove         = rpmsg_sample_remove,
};
module_rpmsg_driver(rpmsg_sample_client);

注意

可以在 samples/rpmsg/ 中找到一个类似的可以构建和加载的示例。

rpmsg 通道的分配

目前,我们仅支持 rpmsg 通道的动态分配。

这仅适用于具有 VIRTIO_RPMSG_F_NS virtio 设备功能集的远程处理器。此功能位表示远程处理器支持动态名称服务声明消息。

启用此功能后,rpmsg 设备(即通道)的创建是完全动态的:远程处理器通过发送名称服务消息(其中包含远程服务的名称和 rpmsg 地址,请参阅 struct rpmsg_ns_msg)来声明远程 rpmsg 服务的存在。

然后,该消息由 rpmsg 总线处理,该总线会动态创建并注册一个 rpmsg 通道(代表远程服务)。如果/当注册了相关的 rpmsg 驱动程序,总线将立即对其进行探测,然后可以开始向远程服务发送消息。

计划还通过 virtio 配置空间添加静态创建 rpmsg 通道的功能,但这尚未实现。