Remote Processor Messaging (rpmsg) Framework
介绍
现代SoC通常采用异构远程处理器设备进行非对称多处理(AMP)配置,这些处理器可能运行不同的操作系统实例,无论是Linux还是任何其他实时操作系统的变种。
例如,OMAP4具有双核Cortex-A9、双核Cortex-M3和一个C64x+ DSP。通常,双核Cortex-A9以SMP配置运行Linux,而其他三个核(两个M3核和一个DSP)以AMP配置运行各自的实时操作系统。
通常,AMP远程处理器使用专用的DSP编解码器和多媒体硬件加速器,因此通常用于从主应用处理器卸载CPU密集型多媒体任务。
这些远程处理器也可以用于控制延迟敏感的传感器、驱动随机硬件块,或者在主CPU空闲时执行后台任务。
这些远程处理器的用户可以是用户空间应用程序(例如,多媒体框架与远程OMX组件通信)或内核驱动程序(控制仅由远程处理器访问的硬件,代表远程处理器保留内核控制的资源等)。
Rpmsg是一种基于virtio的消息总线,允许内核驱动程序与系统上可用的远程处理器进行通信。然后,如果需要,驱动程序可以公开适当的用户空间接口。
在编写将rpmsg通信公开给用户空间的驱动程序时,请记住远程处理器可能直接访问系统的物理内存和其他敏感的硬件资源(例如,在OMAP4上,远程核心和硬件加速器可能直接访问物理内存、gpio bank、dma控制器、i2c总线、gptimer、邮箱设备、hwspinlocks等)。此外,这些远程处理器可能运行实时操作系统,在该操作系统中,每个任务都可以访问暴露给处理器的整个内存/设备。为了最大限度地减少用户空间代码利用远程错误(或有错误的用户空间代码)的风险,并由此接管系统,通常希望将用户空间限制在它可以发送消息的特定rpmsg通道上,并在可能的情况下最小化其对消息内容的控制。
每个rpmsg设备都是与远程处理器的通信通道(因此rpmsg设备称为通道)。
通道由文本名称标识,并具有本地(“源”)rpmsg地址和远程(“目的地”)rpmsg地址。
当驱动程序开始监听通道时,其rx回调将绑定到唯一的rpmsg本地地址(32位整数)。这样,当入站消息到达时,rpmsg核心将根据其目的地地址将它们分派到适当的驱动程序(通过调用驱动程序的rx处理程序处理入站消息的有效载荷)。
User API
int rpmsg_send(struct rpmsg_channel *rpdev, void *data, int len);
在给定的通道上向远程处理器发送消息。调用者应指定通道、要发送的数据及其长度(以字节为单位)。消息将通过指定的通道发送,即其源地址和目标地址字段将设置为通道的src和dst地址。
如果没有可用的TX缓冲区,该函数将阻塞,直到有一个可用(即直到远程处理器消耗一个tx缓冲区并将其放回virtio的已使用描述符环),或者经过15秒的超时。当后者发生时,将返回-ERESTARTSYS。
该函数只能从进程上下文中调用(目前)。成功返回0,失败返回适当的错误值。
int rpmsg_sendto(struct rpmsg_channel *rpdev, void *data, int len, u32 dst);
在给定的通道上向远程处理器发送消息,发送到调用者提供的目标地址。
调用者应指定通道、要发送的数据、其长度(以字节为单位)和显式的目标地址。
然后,消息将通过通道所属的远程处理器发送,使用通道的src地址和用户提供的dst地址(因此通道的dst地址将被忽略)。
如果没有可用的TX缓冲区,该函数将阻塞,直到有一个可用(即直到远程处理器消耗一个tx缓冲区并将其放回virtio的已使用描述符环),或者经过15秒的超时。当后者发生时,将返回-ERESTARTSYS。
该函数只能从进程上下文中调用(目前)。成功返回0,失败返回适当的错误值。
int rpmsg_send_offchannel(struct rpmsg_channel *rpdev, u32 src, u32 dst, void *data, int len);
使用用户提供的源地址和目标地址向远程处理器发送消息。
调用者应指定通道、要发送的数据、其长度(以字节为单位)以及显式的源地址和目标地址。然后,消息将发送到通道所属的远程处理器,但将忽略通道的src和dst地址(而使用用户提供的地址)。
如果没有可用的TX缓冲区,该函数将阻塞,直到有一个可用(即直到远程处理器消耗一个tx缓冲区并将其放回virtio的已使用描述符环),或者经过15秒的超时。当后者发生时,将返回-ERESTARTSYS。
该函数只能从进程上下文中调用(目前)。成功返回0,失败返回适当的错误值。
int rpmsg_trysend(struct rpmsg_channel *rpdev, void *data, int len);
在给定的通道上向远程处理器发送消息。调用者应指定通道、要发送的数据及其长度(以字节为单位)。消息将通过指定的通道发送,即其源地址和目标地址字段将设置为通道的src和dst地址。
如果没有可用的TX缓冲区,该函数将立即返回-ENOMEM,而不等待可用的缓冲区。
该函数只能从进程上下文中调用(目前)。成功返回0,失败返回适当的错误值。
int rpmsg_trysendto(struct rpmsg_channel *rpdev, void *data, int len, u32 dst);
在给定的通道上向远程处理器发送消息,发送到调用者提供的目标地址。
调用者应指定通道、要发送的数据、其长度(以字节为单位)和显式的目标地址。
然后,消息将通过通道所属的远程处理器发送,使用通道的src地址和用户提供的dst地址(因此通道的dst地址将被忽略)。
如果没有可用的TX缓冲区,该函数将立即返回-ENOMEM,而不等待可用的缓冲区。
该函数只能从进程上下文中调用(目前)。成功返回0,失败返回适当的错误值。
int rpmsg_trysend_offchannel(struct rpmsg_channel *rpdev, 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地址都绑定到一个rx回调函数(因此当传入消息到达时,它们通过rpmsg总线使用适当的回调处理程序进行分派),通过rpmsg_endpoint结构实现。
此函数允许驱动程序创建这样的端点,并通过它绑定一个回调函数,可能还有一些私有数据,到一个rpmsg地址(可以是预先知道的地址,也可以是将为它们动态分配的地址)。
简单的rpmsg驱动程序不需要调用rpmsg_create_ept,因为当它们被rpmsg总线探测到时(使用它们在rpmsg总线上注册时提供的rx回调函数),已经为它们创建了一个端点。
因此,对于简单的驱动程序,一切都应该正常工作:它们已经有一个端点,它们的rx回调已绑定到它们的rpmsg地址,当相关的传入消息到达时(即其目标地址等于其rpmsg通道的源地址的消息),驱动程序的处理程序将被调用以处理它。
也就是说,更复杂的驱动程序可能需要分配额外的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, "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);
分配rpmsg通道
目前我们仅支持动态分配rpmsg通道。
这仅适用于具有VIRTIO_RPMSG_F_NS virtio设备特性集的远程处理器。此特性位表示远程处理器支持动态名称服务公告消息。
启用此特性后,rpmsg设备(即通道)的创建完全是动态的:远程处理器通过发送名称服务消息(其中包含远程服务的名称和rpmsg地址,参见struct rpmsg_ns_msg)来公告远程rpmsg服务的存在。
然后,rpmsg总线处理此消息,然后动态创建和注册rpmsg通道(代表远程服务)。如果/当相关的rpmsg驱动程序被注册,它将立即由总线探测,并且随后可以开始向远程服务发送消息。
计划还包括通过virtio配置空间静态创建rpmsg通道,但尚未实现。
注意: 以上内容为英文原文翻译。