本节通过三个问题来分析vm host端的virtio架构。1.虚拟设备的添加,2.数据传输 3.中断注入。 仍然以virtio_blk为例,代码位于qemu中
6.2.1 虚拟设备管理
(1) 注册虚拟驱动设备
static TypeInfovirtio_blk_info = { (virtio_pci.c)
.name = "virtio-blk-pci",
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(VirtIOPCIProxy),
.class_init = virtio_blk_class_init,
}; //该设备的父类型为pci
virtio_blk_init_pci ==》 virtio_blk_init(&pci_dev->qdev,&proxy->blk);
virtio_init_pci(proxy,vdev);
virtio_blk_init(DeviceState*dev, VirtIOBlkConf *blk)
s->vdev.get_config =virtio_blk_update_config;
s->vdev.set_config =virtio_blk_set_config;
s->vdev.get_features =virtio_blk_get_features;
s->vdev.set_status =virtio_blk_set_status;
s->vdev.reset = virtio_blk_reset;
s->vq =virtio_add_queue(&s->vdev, 128, virtio_blk_handle_output);
bdrv_set_dev_ops(s->bs,&virtio_block_ops, s);
voidvirtio_init_pci(VirtIOPCIProxy *proxy, VirtIODevice *vdev)
proxy->pci_dev.config_write = virtio_write_config; //重载了pci的config write
memory_region_init_io(&proxy->bar,&virtio_pci_config_ops, proxy,
"virtio-pci", size);
pci_register_bar(&proxy->pci_dev, 0,PCI_BASE_ADDRESS_SPACE_IO,
&proxy->bar);
virtio_bind_device(vdev,&virtio_pci_bindings, proxy); //vdev->binding = binding;
bdrv_set_dev_ops(s->bs,&virtio_block_ops, s);
proxy->host_features =vdev->get_features(vdev, proxy->host_features); //设置feature
static constMemoryRegionPortio 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 MemoryRegionOpsvirtio_pci_config_ops = {
.old_portio = virtio_portio,
.endianness = DEVICE_LITTLE_ENDIAN,
};
和普通的pci虚拟设备一样 cfg寄存器的写虚拟化会调用proxy->pci_dev.config_write= virtio_write_config ==> virtio_ioport_write
(2)虚拟设备的添加
drive_init (blockdev.c)==》 根据用户配置调用
opts =qemu_opts_create(qemu_find_opts("device"), NULL, 0, NULL);
//创建device
if (arch_type == QEMU_ARCH_S390X) {
qemu_opt_set(opts,"driver", "virtio-blk-s390");
} else {
qemu_opt_set(opts,"driver", "virtio-blk-pci"); //添加driver
}
main (vl.c)
if(qemu_opts_foreach(qemu_find_opts("drive"), drive_init_func,&machine->use_scsi, 1) != 0)
exit(1);
if(qemu_opts_foreach(qemu_find_opts("device"), device_init_func, NULL,1) != 0)
exit(1);
drive_init_func ==>drive_init (第5章已分析过)
device_init_func ==> qdev_device_add
device_init ==>qdev_device_add
a) path =qemu_opt_get(opts, "bus");
bus = qbus_find(path); 得到要动态创建的设备的bus对象
b) qdev =DEVICE(object_new(driver));
qdev_set_parent_bus(qdev, bus); //创建设备并设置其bus
c) qdev_init(qdev)调用设备的init;动态创建的代码位于qdev-monitor.c
这样最终会调用virtio_blk_init
6.2.2数据传输
(1) guest os 到VMM接口
guest os 在setup_vq中会
info->queue =alloc_pages_exact(size, GFP_KERNEL|__GFP_ZERO);
iowrite32(virt_to_phys(info->queue) >>VIRTIO_PCI_QUEUE_ADDR_SHIFT,
vp_dev->ioaddr +VIRTIO_PCI_QUEUE_PFN);
VMM端会调用virtio_ioport_write ==》 case VIRTIO_PCI_QUEUE_PFN 来处理:
pa= (target_phys_addr_t)val << VIRTIO_PCI_QUEUE_ADDR_SHIFT;
virtio_queue_set_addr(vdev,vdev->queue_sel, pa);
voidvirtio_queue_set_addr(VirtIODevice *vdev, int n, target_phys_addr_t addr)
{
vdev->vq[n].pa = addr;
virtqueue_init(&vdev->vq[n]);
}
static voidvirtqueue_init(VirtQueue *vq)
{
target_phys_addr_t pa = vq->pa;
vq->vring.desc = pa;
vq->vring.avail = pa + vq->vring.num* sizeof(VRingDesc);
vq->vring.used =vring_align(vq->vring.avail +
offsetof(VRingAvail, ring[vq->vring.num]),
VIRTIO_PCI_VRING_ALIGN);
}
由此获得了guest os queue的信息。
当guest os 数据准备完成后,调用vp_notify :通知vmm启动队列
iowrite16(vq->index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NOTIFY);
VMM调用virtio_ioport_write ==》 case VIRTIO_PCI_QUEUE_NOTIFY:
if (val < VIRTIO_PCI_QUEUE_MAX) {
virtio_queue_notify(vdev, val);
}
virtio_queue_notify==》virtio_queue_notify_vq ==》 vq->handle_output
virtio_blk_init时调用s->vq = virtio_add_queue(&s->vdev,128, virtio_blk_handle_output)以注册vq->handle_output=virtio_blk_handle_output:
static voidvirtio_blk_handle_output(VirtIODevice *vdev, VirtQueue *vq)
{
。。。。。。。
while ((req = virtio_blk_get_request(s))) {
virtio_blk_handle_request(req,&mrb);
}
virtio_submit_multiwrite(s->bs,&mrb);
}
(2) vmm 对request的处理
virtio_blk_get_request 读取guest os 的request==> virtqueue_pop
a. i = head =virtqueue_get_head(vq, vq->last_avail_idx++); 得到要传输的desc起始索引
b. 循环取出所有的desc, 将读写请求分开处理
do {
struct iovec *sg;
if (vring_desc_flags(desc_pa, i) &VRING_DESC_F_WRITE) {
elem->in_addr[elem->in_num] =vring_desc_addr(desc_pa, i);
sg =&elem->in_sg[elem->in_num++];
} else {
elem->out_addr[elem->out_num]= vring_desc_addr(desc_pa, i);
sg =&elem->out_sg[elem->out_num++];
}
sg->iov_len = vring_desc_len(desc_pa,i);
} while ((i = virtqueue_next_desc(desc_pa,i, max)) != max);
static inline uint64_tvring_desc_addr(target_phys_addr_t desc_pa, int i)
{
target_phys_addr_t pa;
pa = desc_pa + sizeof(VRingDesc) * i +offsetof(VRingDesc, addr);
return ldq_phys(pa); //pa 为gpa要转为hva才能使用
}
c取出的sglist中的地址也为gpa 因此
virtqueue_map_sg(elem->in_sg,elem->in_addr, elem->in_num, 1);
virtqueue_map_sg(elem->out_sg,elem->out_addr, elem->out_num, 0);
到hva.
virtio_blk_handle_request==> virtio_blk_handle_read/write处理读写请求
virtio_blk_handle_read==》 bdrv_aio_readv(req->dev->bs, sector, &req->qiov,
req->qiov.size /BDRV_SECTOR_SIZE,
virtio_blk_rw_complete,req);
来完成传输
回调函数virtio_blk_rw_complete
static void virtio_blk_rw_complete(void*opaque, int ret)
{
。。。。。。
if (ret) {
bool is_read =!(ldl_p(&req->out->type) & VIRTIO_BLK_T_OUT);
if (virtio_blk_handle_rw_error(req,-ret, is_read))
return;
}
virtio_blk_req_complete(req,VIRTIO_BLK_S_OK);
bdrv_acct_done(req->dev->bs,&req->acct);
g_free(req);
}
static voidvirtio_blk_req_complete(VirtIOBlockReq *req, int status)
{
VirtIOBlock *s = req->dev;
stb_p(&req->in->status, status);
//virt queue中取出已经处理的request
virtqueue_push(s->vq, &req->elem,req->qiov.size + sizeof(*req->in));
virtio_notify(&s->vdev, s->vq);//一个io处理完成
}
voidvirtio_notify(VirtIODevice *vdev, VirtQueue *vq)
{
if (!vring_notify(vdev, vq)) {
return;
}
vdev->isr |= 0x01;
virtio_notify_vector(vdev, vq->vector);
}
static voidvirtio_notify_vector(VirtIODevice *vdev, uint16_t vector)
{
if (vdev->binding->notify) {
vdev->binding->notify(vdev->binding_opaque, vector);
}
}
vdev->binding->notify = virtio_pci_notify ==>
static voidvirtio_pci_notify(void *opaque, uint16_t vector)
{
VirtIOPCIProxy *proxy = opaque;
if (msix_enabled(&proxy->pci_dev))
msix_notify(&proxy->pci_dev,vector);
else
qemu_set_irq(proxy->pci_dev.irq[0],proxy->vdev->isr & 1);
}
根据类别调用msix或qemu_set_irq. msix与msi类似.
(3) Config change
virtio_init_pci ==> bdrv_set_dev_ops(s->bs,&virtio_block_ops, s);
static const BlockDevOpsvirtio_block_ops = {
.resize_cb = virtio_blk_resize,
};
当块设备对应的大小发生变化时
virtio_blk_resize ==> virtio_notify_config
voidvirtio_notify_config(VirtIODevice *vdev)
{
if (!(vdev->status &VIRTIO_CONFIG_S_DRIVER_OK))
return;
vdev->isr |= 0x03;
virtio_notify_vector(vdev,vdev->config_vector);
}