Vfio初始化:
Vfio实例:qemu-kvm -m 16G -smp 8 -net none -device vfio-pci,host=0000:81:10.0
初始化入口函数vfio_realize是从vfio pci class的class init函数vfio_pci_dev_class_init 初始化。
调用栈:
Breakpoint 1, vfio_realize (pdev=0x555557874d00, errp=0x7fffffffdde0) at /usr/src/debug/qemu-kvm-2.12.0/hw/vfio/pci.c:2801
2801 {
(gdb) bt
#0 0x00005555558df180 in vfio_realize (pdev=0x555557874d00, errp=0x7fffffffdde0) at /usr/src/debug/qemu-kvm-2.12.0/hw/vfio/pci.c:2801
#1 0x0000555555a292c1 in pci_qdev_realize (qdev=0x555557874d00, errp=0x7fffffffde80) at hw/pci/pci.c:2028
#2 0x00005555559c849b in device_set_realized (obj=<optimized out>, value=<optimized out>, errp=0x7fffffffdfb8) at hw/core/qdev.c:852
#3 0x0000555555abf5ae in property_set_bool (obj=0x555557874d00, v=<optimized out>, name=<optimized out>, opaque=0x55555882f180, errp=0x7fffffffdfb8) at qom/object.c:1925
#4 0x0000555555ac35df in object_property_set_qobject (obj=0x555557874d00, value=<optimized out>, name=0x555555c72fcf "realized", errp=0x7fffffffdfb8) at qom/qom-qobject.c:27
#5 0x0000555555ac11d5 in object_property_set_bool (obj=0x555557874d00, value=<optimized out>, name=0x555555c72fcf "realized", errp=0x7fffffffdfb8) at qom/object.c:1188
#6 0x0000555555971519 in qdev_device_add (opts=0x555557042f00, errp=errp@entry=0x7fffffffe090) at qdev-monitor.c:626
#7 0x0000555555973807 in device_init_func (opaque=<optimized out>, opts=<optimized out>, errp=<optimized out>) at vl.c:2383
#8 0x0000555555bad33a in qemu_opts_foreach (list=<optimized out>, func=func@entry=0x5555559737e0 <device_init_func>, opaque=opaque@entry=0x0, errp=errp@entry=0x0) at util/qemu-option.c:1104
#9 0x0000555555841277 in main (argc=<optimized out>, argv=<optimized out>, envp=<optimized out>) at vl.c:4667
关键函数分析:入参是vfio设备的pci bfd,通过/sys/目录找到该设备对应的iommu group id,然后调用vfio_get_group:
通过内核提供的/dev/vfio/group接口获取group的fd,在vfio_connect_container打开/dev/vfio/vfio获取container的fd, 在vfio_init_container中将groud放置在container中,并且获取iommu的类型。然后再根据iommu获取具体的iommu信息,设置host win,iommu的pagesize等。
vfio_kvm_device_add_group将vfio设备信息注册给内核的kvm模块。
Container有一个listener,将监听内存的变化情况,在register的时候会触发listener的region_add函数,将虚拟内存信息注册到内核iommu,建立相应地址空间映射。这部分内容后面再详细描述。
接下来的是vfio_populate_device,填充pci信息,主要是进行一些基本检查,搜集bar信息,pci配置空间。
Vfio_region_setup中先获取region信息,然后初始化region对应的内存访问操作vfio_region_ops, 以及支持mmap。
Vfio_region_ops, 根据region的offset,通过内核提供的ioctl接口进行读写
读取硬件的pci配置空间信息来填充vfio设备的pci空间,并根据需要设置vfio需要模拟的空间
设置bar信息:vfio_bars_prepare
初始化msix:vfio_msix_early_setup, 发现硬件的msix能力,根据msi相关信息,主要是msi table bar, offset,pending interrupt bar, offset等。并根据msi的需要的bar调整其空间大小,bar设备的访问需要单独模拟,不能直接对硬件设备进行操作。
Vfio bar register:初始化bar的mr,这个是整个bar空间的container,添加之前的region.mem, 并进行mmap。
最后调用pci_register_bar注册bar信息。
vfio_add_capabilities: 添加pci设备的capabilities,主要分成std和ext的,std中最重要的msi中断相关的:vfio_msix_setup,ext基本透传,除了sriov等特性外
Msix初始化:pci设备的msix table的region访问将由msix_table_mmio_ops提供,这部分的功能后面再详细分析。
vfio_pci_dev_class_init在初始过程中,还初始化将pci 配置空间的读写初始化跟vfio相关的。
Bar空间的映射:
系统在初始化期间,会对pci总线进行扫描和配置,驱动会根据bar的信息分配相应的地址空间等,qemu默认的pci框架实现了对bar访问的操作。
Pci_update_mappings 根据驱动写入的地址进行映射。