6.2 vm host端的virtio

时间:2022-08-19 16:12:08


本节通过三个问题来分析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);

}