VirtIO是一种共享内存的数据格式,可用于hypervisor做一种I/O半虚拟化解决方案,也可以用于多核之间的相互通信RPmsg的实现方案即RPmsg是一种基于virtIO消息传递的总线,RPmsg有属于自己的协议格式。本篇只简单讲讲virtIO的原理, 该原理方便日后进行Hypervisor开发或者RPmsg开发。
VirtIO主要是由一堆管理结构组成,最外面的结构我这里叫做virtio,我们这里将一个传输队列叫做vring,一个vring只有一个方向,即要么是TX,要么是RX(通常在hypervisor或者rpmsg中的一个虚拟设备只有一组vring,即一个TX和一个RX)。一个vring拥有两个管理结构,即vring_avail和vring_used,当然它还有一个数据内存池叫vring_desc结构。这些结构注释如下(本篇重点放到virtio的另一种模式和linux的使用有点差异,但原理一样):
点击(此处)折叠或打开
-
struct vring_desc { // virtIO的内存池描述
-
uint32_t addr; // 指向当前buffer的物理地址
-
uint32_t padding; // 填充
-
uint32_t len; // 当前buffer的长度大小
-
uint16_t flags; // 当前buffer的操作标记
-
uint16_t next; // 下一个
-
};
-
-
struct vring_avail { // 指明vring里面的空闲的buffer描述
-
uint16_t flags; // 标记位
-
uint16_t idx; // 指明在vring有多少个可用buffer,每一个buffer是一个vring_desc描述
-
uint16_t ring[256];// 这里默认最多支持256个,这是一个头咬尾的环线数组,ring[]指向可用desc的idx。
-
};
-
-
struct vring_used { // 指明vring里面已经填充了数据的buffer描述
-
uint16_t flags; // 标记
-
uint16_t idx; // 已填充buffer的总数量
-
struct vring_used_elem ring[256]; // 这是一个头咬尾的环形数组,ring[]指向填充desc的描述
-
};
-
-
struct vring_used_elem { // 指明vring里面一个可用的buffer描述
-
uint32_t id; // 指向vring里面的desc结构的idx。
-
uint32_t len; // len指向buffer的实际使用长度。
-
};
-
-
struct vring { // 一个vring描述
-
uint32_t num; // 指明这个vring有多少个buffer(真实数量)
-
struct vring_desc *desc; // 这是一个数组,指向buffer的具体地址描述
-
struct vring_avail *avail; // 这是一个指针,指向当前空闲buffer的idx
-
struct vring_used *used; // 这是一个指针,指向当前已填充buffer的描述
-
};
-
-
struct virtio { // 操作vring的结构体
-
struct vring vring; // 指向vring
-
uint16_t last_avail_idx; // avail环形数组的头,当该节点与vring_avil->idx相等时,表示无空闲
-
uint16_t last_used_idx; // 原理同last_avail_idx
-
}
为了完成virtIO的操作,会有如下几个操作函数,如下:
点击(此处)折叠或打开
-
// 向vring结构增加一个已填充buffer
-
int32_t virtio_add_used_buf(virtio vq, int16_t head, int32_t len)
-
{
-
struct vring_used_elem *used;
-
-
// idx始终指向一个空闲节点,这里获取一个空闲节点,用来存储填充buffer信息
-
used = &vq->vring.used->ring[vq->vring.used->idx % vq->vring.num];
-
used->id = head; // 这个head就是vring_desc结构数组的索引号
-
used->len = len; // 这个len表示这个buffer实际消耗大小(buffer申请都是固定长度)
-
-
vq->vring.used->idx++; // 将idx+1指向一个空闲节点,以方便下次使用。
-
-
return 0;
-
}
-
-
// 向vring增加一个空闲buffer
-
void virtio_add_avail_buf(virtio vq, void *buf)
-
{
-
uint16_t avail;
-
-
// avail->idx始终指向一个空闲节点,用于存储空闲buffer的信息
-
avail = vq->vring.avail->idx % vq->vring.num;
-
vq->vring.avail->idx++; // 更改idx,使其指向新的空闲结点
-
-
vq->vring.desc[avail].addr = vtop(buf); // 将增加的buf的物理地址赋值给addr
-
vq->vring.desc[avail].len = RP_MSG_BUF_SIZE;
-
vq->vring.desc[avail].flags = 2; // buffer属性
-
vq->vring.avail->ring[avail] = avail; // 将desc的索引号存到ring[]里面
-
}
-
-
// 从vring里面获取一个已经填充了数据的buffer
-
void *virtio_get_used_buf(virtio vq)
-
{
-
uint16_t head;
-
void *buf = NULL;
-
-
// 这里通过last_used_idx来查找,当相等,即头咬到尾的时候,表示没有填充了数据的buffer
-
if (vq->last_used_idx != vq->vring.used->idx)
-
{
-
head = vq->vring.used->ring[vq->last_used_idx % vq->vring.num].id;
-
vq->last_used_idx++;
-
-
buf = ptov(vq->vring.desc[head].addr);
-
}
-
-
return buf;
-
}
-
-
// 从vring里面获取一个空闲buffer,用于数据填充,最后会添加到填充buffer数组里面
-
int16_t virtio_get_avail_buf(virtio vq, void **buf, int32_t *len)
-
{
-
uint16_t head;
-
int16_t retVal;
-
-
// 这里通过last_avail_idx 来查找,当相等,即头咬到尾的时候,表示没有空闲buffer,即buffer消耗尽
-
if (vq->last_avail_idx == vq->vring.avail->idx)
-
{
-
vq->vring.used->flags &= (uint16_t)~VRING_USED_F_NO_NOTIFY;
-
retVal = -1;
-
}
-
else
-
{ // 通过last_avail_idx获取到vring_desc的空闲所以head
-
head = vq->vring.avail->ring[vq->last_avail_idx % vq->vring.num];
-
vq->last_avail_idx++;// 由于Head将会被使用, 因此将last_avail_idx增加
-
-
*buf = ptov(vq->vring.desc[head].addr);// buf为虚拟地址
-
*len = (int32_t)vq->vring.desc[head].len;
-
retVal = (int16_t)head;
-
}
-
-
return retVal;
-
}
-
-
// 这个函数用于触发通知信息,在hypervisor中,这个是一个中断路由,rpmsg中是一个mailbox
-
void virtio_kick(virtio vq)
-
{
-
if (0 == (vq->vring.avail->flags & VRING_AVAIL_F_NO_INTERRUPT))
-
{
-
//这里由具体情况实现,可以是gpio,也可以是中断,也可以是其它ipc通信,如ti的mailbox
-
}
-
}
通常,初始化这些vring,一般调用两次,一次用于初始化TX的vring,一个是初始化RX的vring。另外初始化Tx vring的时候,还附带为这个vring增加空闲buffer,即向该vring的vring_desc中添加工作使用的内存地址(Linux当中,不管是TX ring还是RX ring,他们的vring_desc内存都是由Linux设置提供)。而RX vring就不需要特别初始化desc(Linux当中,不管是TX ring还是RX ring,他们的vring_desc内存都是由Linux设置提供),因为这个desc应该由对端来分配。
点击(此处)折叠或打开
-
// 初始化vring结构
-
static inline void vring_init(struct vring *vr, uint32_t num, void *p,
-
uint32_t pagesize)
-
{
-
vr->num = num; // 当前vringd buffer数量
-
vr->desc = (struct vring_desc *) p;// 结构需要的指针
-
vr->avail = (struct vring_avail *)
-
((uintptr_t)p + (num * sizeof(struct vring_desc)));
-
// 这里让出vring_desc数组所需要的内存
-
vr->used = (struct vring_used *)(((uintptr_t)&vr->avail->ring[num] + pagesize-1)
-
& ~(pagesize - 1));
-
}
-
-
// 初始化buffer,即向vring_desc中添加空闲buffer。
-
void virtio_prime(virtio *vq, uint32_t addr, uint32_t num)
-
{
-
uint32_t i;
-
uint32_t buf;
-
-
buf = addr;
-
for (i = 0; i < num; i++) {
-
virtio_add_avail_buf(vq, (void *)(uintptr_t)buf);
-
buf += RP_MSG_BUF_SIZE;
-
}
-
}
-
-
// 通常调用如下
-
{
-
struct virtio tx_vq, rx_vq;
-
-
// 初始化一个TX的vring,数量为tx_num, 结构体维护地址是tx_addr,对齐到tx_align
-
vring_init(&(tx_vq->vring), tx_num, (void*)(uintptr_t)tx_addr, tx_align);
-
-
// 初始化一个RX的vring,数量为rx_num, 结构体维护地址是rx_addr,对齐到rx_align
-
vring_init(&(rx_vq->vring), rx_num, (void*)(uintptr_t)rx_addr, rx_align);
-
-
// 为tx的vring 初始化实际状态数据buffer,数量和tx管理数量一致
-
virtio_prime(tx_vq, data_buf, tx_num);
-
-
// 发送一个virtl IO
-
// 从发送vring里面获取一个空闲buffer
-
virtio_get_avail_buf(tx_vq, (void **)&msg, &length);
-
-
// 这里对msg进行数据填充,在rpmsg中,填充的内存是rpmsg报文
-
........
-
// 将这个已经填充了数据的buffer添加到使用队列中。
-
virtio_add_used_buf(tx_vq, token, bufsize);
-
-
// 触发一个远程通知(中断或者mailbox),通知对端来处理该buffer
-
virtio_kick(vq);
-
}
-
如下为Linux virtio结构:
点击(此处)折叠或打开
-
struct vring_desc_state {
-
void *data; /* Data for callback. */
-
struct vring_desc *indir_desc; /* Indirect descriptor, if any. */
-
};
-
-
struct virtqueue {
-
struct list_head list;
-
void (*callback)(struct virtqueue *vq);
-
const char *name;
-
struct virtio_device *vdev;
-
unsigned int index;
-
unsigned int num_free;
-
void *priv;
-
};
-
-
struct vring_virtqueue {
-
struct virtqueue vq;
-
struct vring vring;
-
/* Can we use weak barriers? */
-
bool weak_barriers;
-
/* Other side has made a mess, don't try any more. */
-
bool broken;
-
/* Host supports indirect buffers */
-
bool indirect;
-
/* Host publishes avail event idx */
-
bool event;
-
/* Head of free buffer list. */
-
unsigned int free_head;
-
/* Number we've added since last sync. */
-
unsigned int num_added;
-
/* Last used index we've seen. */
-
u16 last_used_idx;
-
/* Last written value to avail->flags */
-
u16 avail_flags_shadow;
-
/* Last written value to avail->idx in guest byte order */
-
u16 avail_idx_shadow;
-
/* How to notify other side. FIXME: commonalize hcalls! */
-
bool (*notify)(struct virtqueue *vq);
-
-
/* DMA, allocation, and size information */
-
bool we_own_ring;
-
size_t queue_size_in_bytes;
-
dma_addr_t queue_dma_addr;
-
-
/* Per-descriptor state. */
-
struct vring_desc_state desc_state[];
-
}
-
-
// 注册驱动和设备,设备可以为mimo,pci, remoteproc,驱动可以为rpmsg,blk等
-
int register_virtio_driver(struct virtio_driver *drv);
-
int register_virtio_device(struct virtio_device *dev);
linux中,每个virt_queue都通过list挂到virt_device下面,一个virt_queue对应一个virt_ring,一个virt_device可以对应多个virt_queue。通过register_virtio_driver注册驱动,register_virtio_device注册设备。
什么是RPmsg?或者什么是hypervisor的virtual IO?也许会在我的其他博客看到分析。
阅读(3841) | 评论(1) | 转发(0) |