virtio驱动如何同设备交互

来源:互联网 发布:全口径数据是什么意思 编辑:程序博客网 时间:2024/04/25 15:37

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);
}