## (一)RPMSG简介
现在的SoC芯片在非对称处理(AMP)配置的操作系统中通常采用异构的方式,即不同的内核运行不同的操作系统,可以是裸机、实时RTOS或者Linux等操作系统。
例如,OMAP4 具有双 Cortex-A9、双 Cortex-M3 和 C64x+ DSP。 通常,双 Cortex-A9 在 SMP模式中运行 Linux,并且其他三个内核(两个 M3 内核和一个 DSP)中的每一个都在AMP 模式下运行它自己的 RTOS实例。又比如在常见的ZYNQ7020开发中也需要在两个A9内核之间构建双核通讯的方式。
通常 在AMP 远程处理器采用专用的 DSP编解码器和多媒体硬件加速器,因此通常用于卸载 CPU 等密集型自主应用处理器的多媒体任务。当主 CPU空闲时,这些远程处理器也可用于控制对延迟敏感的传感器,驱动随机硬件块,或者只是执行后台任务。这些远程处理器的用户可以是用户级应用程序(例如多媒体框架与远程OMX 组件通信)或内核驱动程序(控制硬件只能由远程处理器访问,保留内核控制,代表了远程处理器的资源等)。
Rpmsg 是一个基于virtio 的消息总线,它允许内核驱动程序与系统上有可用的远程处理器进行通信反过来,如果需要的话,驱动可以公开用户API接口。
在编写rpmsg 通信机制接口给用户空间的驱动程序时, 请记住,远程处理器可以直接访问系统的物理内存和其他敏感的硬件资源(例如在OMAP4中,远程内核和硬件加速器可以直接访问物理内存、gpio库、dma 控制器、i2c 总线、gptimers、邮箱设备、hwspinlocks 等)。此外,那些远程处理器可能是运行 RTOS,其中每个任务都可以访问整个内存/设备。
为了降低用户空间内出现流氓或错误代码甚至丢失系统控制权的风险,通常将用户空间限制为特定的 rpmsg通道(参见下面的定义)它可以发送消息,同时如果可能的话,需尽可能减少发送超过消息的内容。
每个 rpmsg 设备都是一个与远程处理器的通信通道(因此 rpmsg 设备又被称为通道)。频道组成有文本名称标识,一个本地(“源”)rpmsg地址和一个远程(“目标”)rpmsg 地址。
当驱动程序开始监听通道时,它的 rx 回调函数绑定到一个唯一的 rpmsg 本地地址(一个 32 位整数)。这样当绑定的远程设备发来消息后,rpmsg核心根据它的目标地址来分发(这是根据消息的有效负载,通过调用驱动程序的rx处理函数来完成的)。
1.设备通道
目前我们只支持动态分配 rpmsg 通道。这仅适用于具有virtio功能的远程处理器中 。此功能位意味着远程处理器支持以动态名称命名的公告消息。
启用此功能后,创建rpmsg设备(即通道)是完全动态的:远程处理器通过发送名称服务消息来声明自身的存在(其中包含远程服务的名称和 rpmsg 地址)。
该消息然后由 rpmsg 总线处理,该总线依次动态地创建并注册一个 rpmsg 通道(代表远程服务)。当注册了相关的 rpmsg 驱动程序时,总线将立即对其进行探测,然后可以开始向远程服务器发送消息。
/*RPMSG设备结构体解析
***struct device dev; //任何设备在linux系统中都有一个结构体,包括了
这个设备建模所需要的所有信息
*/
struct rpmsg_device {
struct device dev;
struct rpmsg_device_id id;//设备ID,用于RPMSG设备之间的匹配
char *driver_override;//驱动匹配的名字
u32 src;//当地地址,即本地地址
u32 dst;//目标地址
struct rpmsg_endpoint *ept;//通道的RPMSG设备端点,适用与点对点的通讯
bool announce;
bool little_endian;
const struct rpmsg_device_ops *ops;//关于设备操作的函数指针,包括设备通道,
//端点及声明的创建和销毁
};
struct rpmsg_device *rpmsg_create_channel(struct rpmsg_device *rpdev,
struct rpmsg_channel_info *chinfo)
{
if (WARN_ON(!rpdev))//调用dump_stack,打印堆栈信息,不会OOPS
return NULL;
if (!rpdev->ops || !rpdev->ops->create_channel) {
dev_err(&rpdev->dev, "no create_channel ops found\n");
return NULL;
}
return rpdev->ops->create_channel(rpdev, chinfo);//创建RPMSG设备通道
}
/*下面分析创建通道的具体实现,基于virtio实现,后面再写一篇virtio的文章*/
/*
* create an rpmsg channel using its name and address info.
* this function will be used to create both static and dynamic
* channels.
*/
static struct rpmsg_device *__rpmsg_create_channel(struct virtproc_info *vrp,
struct rpmsg_channel_info *chinfo)
{
struct virtio_rpmsg_channel *vch;
struct rpmsg_device *rpdev;
struct device *tmp, *dev = &vrp->vdev->dev;
int ret;
/* make sure a similar channel doesn't already exist */
tmp = rpmsg_find_device(dev, chinfo);//根据设备名字及绑定的地址查询设备,
确保之前没有创建通道
if (tmp) {
/* decrement the matched device's refcount back */
put_device(tmp);
dev_err(dev, "channel %s:%x:%x already exist\n",
chinfo->name, chinfo->src, chinfo->dst);
return NULL;
}
vch = kzalloc(sizeof(*vch), GFP_KERNEL);//申请一个virtproc_info结构体大小的内存,
并初始化为0,GFP_KERNEL代表内核空间的一个普通调用
if (!vch)
return NULL;
/* Link the channel to our vrp */
vch->vrp = vrp;
/* Assign public information to the rpmsg_device *///将virtio的相关信息赋给设备
rpdev = &vch->rpdev;
rpdev->src = chinfo->src;
rpdev->dst = chinfo->dst;
rpdev->ops = &virtio_rpmsg_ops;
rpdev->little_endian = virtio_is_little_endian(vrp->vdev);
/*
* rpmsg server channels has predefined local address (for now),
* and their existence needs to be announced remotely
*/
rpdev->announce = rpdev->src != RPMSG_ADDR_ANY;
strncpy(rpdev->id.name, chinfo->name, RPMSG_NAME_SIZE);
rpdev->dev.parent = &vrp->vdev->dev;
rpdev->dev.release = virtio_rpmsg_release_device;
ret = rpmsg_register_device(rpdev);//向系统申请RPMSG设备注册
if (ret)
return NULL;
return rpdev;
}
2.创建设备端点
/**
* rpmsg_create_ept() - create a new rpmsg_endpoint
* @rpdev: rpmsg channel device
* @cb: rx callback handler
* @priv: private data for the driver's use
* @chinfo: channel_info with the local rpmsg address to bind with @cb
*
* 系统中的每个rpmsg地址通过rpmsg_endpoint结构绑定到一个rx回调函数(所以当
* 入站消息到达,它们由rpmsg总线使用适当的回调处理程序)
* 此功能允许驱动程序创建这样的一个端点,并通过它绑定一个回调函数,可能还有一些私有
* 数据,到这个rpmsg地址(这个地址要么是预先知道的,要么是动态分配的)
* 简单的 rpmsg 驱动程序不需要调用 rpmsg_create_ept,因为端点
* 在它们被rpmsg总线探测时已经为它们创建了
*(使用注册到rpmsg总线时提供的rx回调函数)。
*
* 所以在简单的驱动程序中不需要再做额外的处理:他们已经有一个
* 端点,他们的rx回调函数已经绑定到rpmsg地址,当
* 相关的入站消息到达(即消息的目的地址的消息
* 等于它们的rpmsg通道的 src 地址),驱动程序的处理程序
* 就会被调用来处理这个消息。
*
* 同时也就是说,有可能需要更复杂的驱动程序分配
* 额外的rpmsg地址,并将它们绑定到不同的rx回调函数。
* 为此,这些驱动程序需要调用这个创建新的端点函数。
*
* 驱动程序应该提供他们的@rpdev通道(这样新的端点就属于
* 他们频道的同一个远程处理器),一个rx回调函数,一个可选的私有数据(当
* rx回调被调用),以及他们想要绑定的地址,
* 如果@addr是RPMSG_ADDR_ANY,那么rpmsg_create_ept将
* 动态地为他们分配一个可用的rpmsg 地址.
*
* 成功时返回指向端点的指针,错误时返回 NULL。
*/
struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *rpdev,
rpmsg_rx_cb_t cb, void *priv,
struct rpmsg_channel_info chinfo)
{
if (WARN_ON(!rpdev))
return NULL;
return rpdev->ops->create_ept(rpdev, cb, priv, chinfo);
}
/**
* __ept_release() - deallocate an rpmsg endpoint
* @kref: the ept's reference count
*
* This function deallocates an ept, and is invoked when its @kref refcount
* drops to zero.
*
* Never invoke this function directly!
*/
static void __ept_release(struct kref *kref)
{
struct rpmsg_endpoint *ept = container_of(kref, struct rpmsg_endpoint,
refcount);
/*
* At this point no one holds a reference to ept anymore,
* so we can directly free it
*/
kfree(ept);
}
/*与通道创建流程相似,将回调函数,私有数据等信息绑定到这个端点,并动态的为这个端点分配一个本地地址,为保证地址的唯一性,需用到互斥锁*/
/* for more info, see below documentation of rpmsg_create_ept() */
static struct rpmsg_endpoint *__rpmsg_create_ept(struct virtproc_info *vrp,
struct rpmsg_device *rpdev,
rpmsg_rx_cb_t cb,
void *priv, u32 addr)
{
int id_min, id_max, id;
struct rpmsg_endpoint *ept;
struct device *dev = rpdev ? &rpdev->dev : &vrp->vdev->dev;
ept = kzalloc(sizeof(*ept), GFP_KERNEL);
if (!ept)
return NULL;
kref_iint(&ept->refcount);
mutex_init(&ept->cb_lock);
ept->rpdev = rpdev;
ept->cb = cb;
ept->priv = priv;
ept->ops = &virtio_endpoint_ops;
/* do we need to allocate a local address ? */
if (addr == RPMSG_ADDR_ANY) {
id_min = RPMSG_RESERVED_ADDRESSES;
id_max = 0;
} else {
id_min = addr;
id_max = addr + 1;
}
mutex_lock(&vrp->endpoints_lock);
/* bind the endpoint to an rpmsg address (and allocate one if needed) */
id = idr_alloc(&vrp->endpoints, ept, id_min, id_max, GFP_KERNEL);
if (id < 0) {
dev_err(dev, "idr_alloc failed: %d\n", id);
goto free_ept;
}
ept->addr = id;
mutex_unlock(&vrp->endpoints_lock);
return ept;
free_ept:
mutex_unlock(&vrp->endpoints_lock);
kref_put(&ept->refcount, __ept_release);
return NULL;
}
3.发送数据
创建好RPMSG设备端点和通道后,就可以向远程设备发送数据,方式有如下几种
函数 | 基本说明 |
virtio_rpmsg_send() |
即在给定通道上向远程处理器发送消息。 如果没有可用的 TX 缓存区,该函数将阻塞,直到一个可用的缓存区(即直到远程处理器消耗一个 tx 缓冲区并将其放到virtio使用的描述符环上), 该函数只能从进程上下文中调用(目前)。 |
virtio_rpmsg_sendto() |
由调用者提供目标地址发送数据 |
virtio_rpmsg_send_offchannel() |
忽略原来通道的源地址和目标地址,并由调用者提供地址,通过这个通道发送数据 |
virtio_rpmsg_trysend() |
对应virtio_rpmsg_send,如果没有tx缓存区,函数将立即返回错误值 |
virtio_rpmsg_trysendto() |
对应virtio_rpmsg_sendto,如果没有tx缓存区,函数将立即返回错误值 |
virtio_rpmsg_trysend_offchannel() |
对应virtio_rpmsg_send_offchannel,如果没有tx缓存区,函数将立即返回错误值 |
下面我们来详细分析RPMSG数据发送的底层函数:
/**
* rpmsg_send_offchannel_raw() - send a message across to the remote processor
* @rpdev: the rpmsg channel
* @src: source address
* @dst: destination address
* @data: payload of message
* @len: length of payload
* @wait: indicates whether caller should block in case no TX buffers available
*
* This function is the base implementation for all of the rpmsg sending API.
*
* It will send @data of length @len to @dst, and say it's from @src. The
* message will be sent to the remote processor which the @rpdev channel
* belongs to.
*
* The message is sent using one of the TX buffers that are available for
* communication with this remote processor.
*
* If @wait is true, the caller will be blocked until either a TX buffer is
* available, or 15 seconds elapses (we don't want callers to
* sleep indefinitely due to misbehaving remote processors), and in that
* case -ERESTARTSYS is returned. The number '15' itself was picked
* arbitrarily; there's little point in asking drivers to provide a timeout
* value themselves.
*
* Otherwise, if @wait is false, and there are no TX buffers available,
* the function will immediately fail, and -ENOMEM will be returned.
*
* Normally drivers shouldn't use this function directly; instead, drivers
* should use the appropriate rpmsg_{try}send{to, _offchannel} API
* (see include/linux/rpmsg.h).
*
* Returns 0 on success and an appropriate error value on failure.
*/
static int rpmsg_send_offchannel_raw(struct rpmsg_device *rpdev,
u32 src, u32 dst,
void *data, int len, bool wait)
{
struct virtio_rpmsg_channel *vch = to_virtio_rpmsg_channel(rpdev);
struct virtproc_info *vrp = vch->vrp;
struct device *dev = &rpdev->dev;
struct scatterlist sg;
struct rpmsg_hdr *msg;//数据头
int err;
/* bcasting isn't allowed */
if (src == RPMSG_ADDR_ANY || dst == RPMSG_ADDR_ANY) {
dev_err(dev, "invalid addr (src 0x%x, dst 0x%x)\n", src, dst);
return -EINVAL;
}
/*
* We currently use fixed-sized buffers, and therefore the payload
* length is limited.
*
* One of the possible improvements here is either to support
* user-provided buffers (and then we can also support zero-copy
* messaging), or to improve the buffer allocator, to support
* variable-length buffer sizes.
*/
if (len > vrp->buf_size - sizeof(struct rpmsg_hdr)) {//对将要发送的数据有长度限制
dev_err(dev, "message is too big (%d)\n", len);
return -EMSGSIZE;
}
/* grab a buffer */
msg = get_a_tx_buf(vrp);//获取TX_BUF
if (!msg && !wait)
return -ENOMEM;
/* no free buffer ? wait for one (but bail after 15 seconds) */
while (!msg) {
/* enable "tx-complete" interrupts, if not already enabled */
rpmsg_upref_sleepers(vrp);
/*
* sleep until a free buffer is available or 15 secs elapse.
* the timeout period is not configurable because there's
* little point in asking drivers to specify that.
* if later this happens to be required, it'd be easy to add.
*/
err = wait_event_interruptible_timeout(vrp->sendq,
(msg = get_a_tx_buf(vrp)),
msecs_to_jiffies(15000));
/* disable "tx-complete" interrupts if we're the last sleeper */
rpmsg_downref_sleepers(vrp);
/* timeout ? */
if (!err) {
dev_err(dev, "timeout waiting for a tx buffer\n");
return -ERESTARTSYS;
}
}
//对获取到的virtio结构体大小的buffer进行赋值
msg->len = cpu_to_rpmsg16(rpdev, len);
msg->flags = 0;
msg->src = cpu_to_rpmsg32(rpdev, src);
msg->dst = cpu_to_rpmsg32(rpdev, dst);
msg->reserved = 0;
memcpy(msg->data, data, len);
dev_dbg(dev, "TX From 0x%x, To 0x%x, Len %d, Flags %d, Reserved %d\n",
src, dst, len, msg->flags, msg->reserved);
#if defined(CONFIG_DYNAMIC_DEBUG)
dynamic_hex_dump("rpmsg_virtio TX: ", DUMP_PREFIX_NONE, 16, 1,
msg, sizeof(*msg) + len, true);
#endif
//初始化一个sg list ,将msg加到这个sg list中
rpmsg_sg_init(&sg, msg, sizeof(*msg) + len);
mutex_lock(&vrp->tx_lock);//数据发送过程中需用到互斥锁保证数据发送完成,不会出现数据错误的情况
/* add message to the remote processor's virtqueue */
err = virtqueue_add_outbuf(vrp->svq, &sg, 1, msg, GFP_KERNEL);
if (err) {
/*
* need to reclaim the buffer here, otherwise it's lost
* (memory won't leak, but rpmsg won't use it again for TX).
* this will wait for a buffer management overhaul.
*/
dev_err(dev, "virtqueue_add_outbuf failed: %d\n", err);
goto out;
}
/* tell the remote processor it has a pending message to read */
virtqueue_kick(vrp->svq);
out:
mutex_unlock(&vrp->tx_lock);
return err;
}