virtio驱动如何同设备交互

来源:互联网 发布:linux libevent 安装 编辑:程序博客网 时间:2024/04/27 11:08

转载自:http://blog.chinaunix.net/uid-26000137-id-3826914.html

virtio设备是作为pci设备被使用的,因此具有pci设备的所有属性:

virtio header占用pci设备的24字节的配置空间:32 * (0 - 5)
virtio header后面跟随一个device specific的config结构

virtio header包括:
/* A 32-bit r/o bitmask of the features supported by the host */
#define VIRTIO_PCI_HOST_FEATURES 0

/* A 32-bit r/w bitmask of features activated by the guest */
#define VIRTIO_PCI_GUEST_FEATURES 4

/* A 32-bit r/w PFN for the currently selected queue */
#define VIRTIO_PCI_QUEUE_PFN  8

/* A 16-bit r/o queue size for the currently selected queue */
#define VIRTIO_PCI_QUEUE_NUM  12

/* A 16-bit r/w queue selector */
#define VIRTIO_PCI_QUEUE_SEL  14

/* A 16-bit r/w queue notifier */
#define VIRTIO_PCI_QUEUE_NOTIFY  16

/* An 8-bit device status register.  */
#define VIRTIO_PCI_STATUS  18

/* An 8-bit r/o interrupt status register.  Reading the value will

return the
 * current contents of the ISR and will also clear it.  This is

effectively
 * a read-and-acknowledge. */
#define VIRTIO_PCI_ISR   19

/* The bit of the ISR which indicates a device configuration change. */
#define VIRTIO_PCI_ISR_CONFIG  0x2

/* MSI-X registers: only enabled if MSI-X is enabled. */
/* A 16-bit vector for configuration changes. */
#define VIRTIO_MSI_CONFIG_VECTOR        20
/* A 16-bit vector for selected queue notifications. */
#define VIRTIO_MSI_QUEUE_VECTOR         22
/* Vector value used to disable MSI for queue */
#define VIRTIO_MSI_NO_VECTOR            0xffff

每个特定设备还有自己的配置结构体,以上是通用部分

guest读写方式:
/* Select the queue we're interested in */
iowrite16(index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_SEL);

/* Check if queue is either not available or already active. */
num = ioread16(vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NUM);


virtio驱动通过读取和写入这些配置空间实现通用的guest特性,队列,中断等等

方面同qemu-kvm交互的功能

qemu-kvm提供了下面这个函数结构体来响应guest的不同读写请求。
static const MemoryRegionPortio virtio_portio[] = {
    { 0, 0x10000, 1, .write = virtio_pci_config_writeb, },
    { 0, 0x10000, 2, .write = virtio_pci_config_writew, },
    { 0, 0x10000, 4, .write = virtio_pci_config_writel, },
    { 0, 0x10000, 1, .read = virtio_pci_config_readb, },
    { 0, 0x10000, 2, .read = virtio_pci_config_readw, },
    { 0, 0x10000, 4, .read = virtio_pci_config_readl, },
    PORTIO_END_OF_LIST()
};

static const MemoryRegionOps virtio_pci_config_ops = {
    .old_portio = virtio_portio,
    .endianness = DEVICE_LITTLE_ENDIAN,
};

virtio设备作为pci设备被guest识别,guest通过读写pci设备配置空间的信息来

同qemu-kvm交互。

所以实际上是guest为queue分配vring表的空间,然后通过上面这种交互方式将地

址传递给qemu-kvm,qemu-kvm把地址复制给guest选定的queue,这样guest和

qemu-kvm就共用这一片vring表的空间。

guest通过queue发送消息,实际上就是向这一片共享的vring表空间写入信息并标

识为等待qemu-kvm读取,qemu-kvm读取并处理后又将结果写进去(guest写的信息

里面预留空间给qemu-kvm来写了)再标识为已处理,同时发一个中断,guest接到

中断就去读取处理结果。

/*
函数功能:为目标设备获取一个queue
vdev:目标设备
index:给目标设备使用的queue的编号
callback:queue的回调函数
name:queue的名字
msix_vec:给queue使用的msix vector的编号
*/

static struct virtqueue *setup_vq(struct virtio_device *vdev,  unsigned index, void (*callback)(struct virtqueue *vq),     const char *name, u16 msix_vec){ struct virtio_pci_device *vp_dev = to_vp_device(vdev); struct virtio_pci_vq_info *info; struct virtqueue *vq; unsigned long flags, size; u16 num; int err; /* 通知guest我们想要使用的queue的号码 */ iowrite16(index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_SEL); /* 返回刚刚选中的queue的可用列表数,从而判断该queue是否可用 */ num = ioread16(vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NUM);        /* 如果num为0,则该queue不可用,因为没空闲列表了。    返回刚刚选中的queue的地址,如果该queue的地址非空,则它已经    在被使用了,该queue不可用。  */ if (!num || ioread32(vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN))  return ERR_PTR(-ENOENT); /* allocate and fill out our structure the represents an active  * queue */ info = kmalloc(sizeof(struct virtio_pci_vq_info), GFP_KERNEL); if (!info)  return ERR_PTR(-ENOMEM); info->num = num; info->msix_vector = msix_vec; size = PAGE_ALIGN(vring_size(num, VIRTIO_PCI_VRING_ALIGN)); /* 为vring表分配空间 */ info->queue = alloc_pages_exact(size, GFP_KERNEL|__GFP_ZERO); if (info->queue == NULL) {  err = -ENOMEM;  goto out_info; } /* activate the queue */ /* 将vring表的地址传递给qemu-kvm,guest将和qemu-kvm通过共享    这一共同的vring表来完成信息交互:    1.guest通过queue发信息就是向vring表写入信息并标识为等待      qemu-kvm端virtio驱动读取,同时通过notify设备通知qemu-kvm      端virtio驱动。    2.qemu-kvm端virtio驱动处理后将结果也写入vring表(guest发的      信息预留了返回结果的空间)并标识为已处理,同时发送中断通               知guest。    3.guest中断处理函数判明中断类型,读取返回结果。  */ iowrite32(virt_to_phys(info->queue) >>VIRTIO_PCI_QUEUE_ADDR_SHIFT, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN); /* create the vring */ /* 创建vring_virtqueue结构体,作为queue和vring协同工作的桥梁,    queue和vring都嵌入在vring_virtqueue结构体之中,返回给驱动的queue实际就是vring_virtqueue的成员,queue是vring_virtqueue结构体提供给上层驱动的接口,vring结构体则负责管理vring表,是操作vring表的接口。 */ vq = vring_new_virtqueue(index, info->num, VIRTIO_PCI_VRING_ALIGN, vdev,true, info->queue, vp_notify, callback,name); if (!vq) {  err = -ENOMEM;  goto out_activate_queue; } vq->priv = info; info->vq = vq; /* 如果使用msix vector,则将msix vector编号写入到设备的配置空间    去,再读取的目的就是判断qemu-kvm端是否能够支持。  */ if (msix_vec != VIRTIO_MSI_NO_VECTOR) {  iowrite16(msix_vec, vp_dev->ioaddr +VIRTIO_MSI_QUEUE_VECTOR);  msix_vec = ioread16(vp_dev->ioaddr +VIRTIO_MSI_QUEUE_VECTOR);  if (msix_vec == VIRTIO_MSI_NO_VECTOR) {   err = -EBUSY;   goto out_assign;  } } if (callback) {  spin_lock_irqsave(&vp_dev->lock, flags);  list_add(&info->node, &vp_dev->virtqueues);  spin_unlock_irqrestore(&vp_dev->lock, flags); } else {  INIT_LIST_HEAD(&info->node); } return vq;out_assign: vring_del_virtqueue(vq);out_activate_queue: iowrite32(0, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN); free_pages_exact(info->queue, size);out_info: kfree(info); return ERR_PTR(err);}



0 0