基本逻辑
LPI/vLPI硬件相关的逻辑可以参考这里。
host上不同的GIC版本对vGIC LPI的支持方式是不一样的,GICv3时,需要先trap到KVM,在
KVM里向虚机注入中断,GICv4.0及后续的GIC版本支持vLPI直接注入虚机。
虚拟机中的ITS设备是一个模拟出来的设备,对ITS相关资源的访问需要在KVM模拟。首先KVM
需要模拟ITS的MMIO寄存器,通过模拟的MMIO寄存器,guest ITS上各种配置要被真实的配置
到物理ITS上。
虚机中的ITS的各种table是放到IPA上的。软件通过ITS command访问device table、ITT以及
connection table,向虚机中的ITS发commnand(更新虚拟ITS commnand队列的写入指针)触发
KVM trap,KVM中模拟对应的ITS command,这里的模拟就是在host向物理ITS发ITS command,
把对应的配置下发到硬件。软件直接可以访问LPI config table,虚机中配置LPI config table
后会发INV命令清理硬件中对应的缓存,KVM模拟INV命令时读虚机LPI config table的对应
地址拿到对应中断的配置信息,然后更新对应的vLPI config table域段。
物理上看,完整的vLPI中断上报,需要提前把这个vLPI中断的相关信息配置到物理硬件里,
这些信息包括: 这个vLPI发出设备的device id、eventid(一般就是PCIe设备的BDF和MSI/MSI-X
cap里的data域段),vLPI的中断号,vLPI要发送到的vCPU,vCPU在哪个物理CPU上,当vCPU
不在位时是否需要触发的doorbell中断。从虚机的视角上看,vLPI的配置信息只有device id、
event id、中断号以及vCPU,虚机并不感知物理CPU。
KVM里模拟虚机ITS command,把vLPI的device id、event id、中断号以及vCPU配置到物理
ITS上。KVM在vCPU上下线时,把vCPU和物理CPU的映射关系配置给物理ITS。
ITS模拟
ARM KVM虚拟化的整体逻辑可以参考这里,其中有涉及ITS在KVM里的模拟逻辑。
因为是模拟ITS,所以并不要需要模拟行为当下生效,只要功能正确就好。比如,KVM模拟虚机
的MAPI时没有下ITS command,实际物理配置发生在kvm_vgic_v4_set_forwarding的流程里,
这个是在guest访问VF MSI/MSI-X BAR的时候,截获完成host的配置。
下面展开看下KVM里对ITS的各个命令都是怎么模拟的。KVM里为了模拟ITS,为ITS的device
table/ITT/collection table建立了对应的软件数据结构,为创建的每个虚拟中断也建立了
数据结构(vgic_irq)。对ITS comand的模拟基本上在改动这些数据结构中的内容,部分comand
会涉及到硬件配置的改动。这些数据结构估计大部分是GICv3用的,GICv4.x后vLPI可以直通
了,用的会比较少。
GICv3中断注入
todo: …
GICV4.x中断直通
针对vm fd的操作,本意是通过eventfd,在kvm中把虚机中断注入给虚机。对于支持中断直通
的场景,这里会执行如下irq_bypass_register_consumer,注册架构相关的consumer。这里
的consumer和如下vfio_pci_core_ioctl一起完成直通中断在物理GIC上的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| /* virt/kvm/kvm_main.c */ kvm_vm_ioctl /* KVM_IRQFD */ +-> kvm_irqfd +-> kvm_irqfd_assign /* * 注册irq_bypass_consumer,其中的回调是: * add_producer: kvm_arch_irq_bypass_add_producer * del_producer: kvm_arch_irq_bypass_del_producer * stop: kvm_arch_irq_bypass_stop * start: kvm_arch_irq_bypass_start * * 系统里有producers和consumers对应的链表,注册其实就是加到对应的链表里。 * 使用__connect函数可以把consumer和producer连接在一起,基本逻辑是consumer * 调用add_producer,或者producer调用add_consumer。 */ +-> irq_bypass_register_consumer
|
虚机中访问MSI-X BAR的时候,会导致虚机退出到KVM,KVM进一步退出到qemu的vfio设备的
模拟逻辑里,其中会针对vfio设备的fd调用如下ioctl,在host上配置对应中断的支持通路。
(todo: MSI cap的逻辑是怎么样的?)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| vfio_pci_core_ioctl +-> vfio_pci_ioctl_set_irqs <--- VFIO_DEVICE_SET_IRQS +-> vfio_pci_set_irqs_ioctl +-> vfio_pci_set_msi_trigger <--- VFIO_PCI_MSI_IRQ_INDEX | /* | * ioctl参数VFIO_IRQ_SET_DATA_NONE和count为0,触发vf的MSI被disable。 | * 具体行为是:释放该vfio设备对应的每个MSI中断资源,其中包括尝试对 | * 之前分配host MSI中断的释放(free_irq),经过中断子系统,最后调用到 | * ITS驱动里的its_irq_domain_deactivate,最后会对host MSI中断发discard | * 命令。需要注意的是,如果这时还是有vf上的中断在上报,vf的全部MSI被 | * disable这个行为可能导致对应的中断被discard掉。是否会导致这样的问题, | * 主要控制因素在QEMU里,vfio只是接口提供者。 | */ +-> vfio_msi_disable | +-> vfio_msi_set_vector_signal(..., fd = -1, ...) | +-> free_irq | ... | +-> its_irq_domain_deactivate | +-> its_send_discard | /* | * 根据ioctl的输入参数start和count,配置对应的MSI。vfio_msi_enable里 | * 会配置irq_type,所以对于配置过irq_type的MSI,其已经运行过vfio_msi_enable, | * 所以直接做vfio_msi_set_block,这里估计是一个小的优化。 | */ +-> vfio_msi_set_block | /* 其中,要配置一个中断时,先把之前的配置去掉 */ | +-> vfio_msi_set_vector_signal | /* 分配中断 */ | +-> vfio_msi_alloc_irq | /* 创建vfio里记录中断的数据结构 */ | +-> vfio_irq_ctx_alloc | /* 获取对应的eventfd */ | +-> trigger = eventfd_ctx_fdget | /* | * 注册vf设备的中断,如果host是GICv3,那么vf的中断首先就会触发如下 | * vfio_msihandler中断处理函数。该函数触发一个eventfd的signal, | * KVM收到这个signal会给vCPU注入中断。 | */ | +-> request_irq(irq, vfio_msihandler, 0, ctx->name, trigger) | | /* token: trigger, irq: irq, */ | +-> irq_bypass_register_producer | /* | * consumer和producer的token一样才执行__connect。这里是调用上面 | * 注册的consumer中的add_producer回调函数。add_producer前后会 | * 调用consumer stop/start,stop中挂起VM,start中reseume VM。 | */ | +-> __connect() | +-> cons->stop -> cons->add_producer -> cons->start | +-> vfio_msi_enable +-> ...
|
这里展开分析add_producer函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| /* 注意这里的virq是如上request_irq里的irq,是vf在host上的中断 */ kvm_arch_irq_bypass_add_producer(kvm, virq, struct kvm_kernel_irq_routing_entry) +-> kvm_vgic_v4_set_forwarding /* 拿到虚拟中断对应的vgic_irq,保存在irq里,irq->host_irq为host irq */ +-> vgic_its_resolve_lpi(..., &irq) /* drivers/irqchip/irq-gic-v4.c */ +-> its_map_vlpi(virq, map) +-> irq_set_vcpu_affinity(irq, xxx) ... +-> its_irq_set_vcpu_affinity <--- struct irq_chip its_irq_chip +-> its_vlpi_map <--- MAP_VLPI | +-> its_map_vm <--- 如何确定物理CPU? | +-> its_send_vmapp | +-> lpi_write_config | +-> its_send_discard <--- 去掉如上host irq在ITS里的map,所以这个 | 命令的参数是vf的物理dev_id/event_id | +-> its_send_vmapti
|
(todo: vLPI pending配置enable的分析,应该在active domain)
(todo: 虚机里的GICv3的table和host上的vtable的对应关系)
vCPU上下线逻辑
vCPU上线中关于vLPI的调用流程如下:
1 2 3 4 5 6 7 8 9 10 11
| kvm_sched_out +-> kvm_arch_vcpu_put ... +-> kvm_vgic_put +-> vgic_v3_put +-> vgic_v4_put <-- 没有直通在这里返回 +-> its_make_vpe_non_resident +-> its_send_vpe_cmd <-- DESCHEDULE_VPE +-> irq_set_vcpu_affinity +-> its_vpe_set_vcpu_affinity <-- irq_chip its_vpe_irq_chip +-> its_vpe_deschedule <-- 配置GICR_VPENDBASER invalid
|
如上its_vpe_deschedule具体到硬件,主要配置GICR_VPENDBASER invalid,表示没有上线
的vPE,同时还进行一些硬件状态的同步操作。
vCPU下线中关于vLPI的调用流程如下,核心处理逻辑在vgic_v4_load。
kvm_sched_in -> kvm_arch_vcpu_load -> kvm_vgic_load -> vgic_v3_load -> vgic_v4_load
1 2 3 4 5 6 7 8 9 10 11
| vgic_v4_load +-> irq_set_affinity ... +-> its_vpe_set_affinity <-- chip->irq_set_affinity, its_vpe_irq_chip ... +-> its_send_vmovp
+-> its_make_vpe_resident ... +-> its_vpe_set_vcpu_affinity <-- SCHEDULE_VPE +-> todo: GICR_VPROPBASER/GICR_VPENDBASER
|
vCPU在一个物理CPU上线时,vCPU在线的物理CPU可能发生变化,这时就需要用ITS VMOVP更新
硬件中对应的信息。同时更新GICR_VPROPBASER/GICR_VPENDBASER,为后面vLPI直通做准备。
一个例子
上帝视角看下当虚机系统里linux内核调用enable_irq时整个调用流程是怎么样的。经过如下
调用:enable_irq -> __enable_irq -> irq_startup,irq_startup里两个最主要的操作就
是__irq_startup和irq_setup_affinity,前者使能中断,后者配置中断和core的亲和性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| /* 配置中断使能 */ __irq_startup +-> enable_irq +-> unmask_irq /* * 直接找见最底层的PCIe设备的irq_unmask回调,内核这里有变动,以前的版本 * 是its_unmask_msi_irq */ +-> pci_irq_unmask_msix +-> cond_unmask_parent /* irq-gic-v3-its.c: its_irq_chip的its_unmask_irq */ +-> its_unmask_irq +-> lpi_update_config /* * 对于虚机或者只有host的情况,就是直接配置LPI config table的 * 对应位置,具体位置是从config table的8192 byte起,以hwirq为 * 索引,每个中断占一个byte。 */ +-> lpi_write_config /* * guest上执行这个命令会trap到KVM里面模拟,kvm里的执行流程是: * * vgic_its_cmd_handle_inv * -> update_lpi_config * -> its_prop_update_vlpi * -> irq_set_vcpu_affinity 具体为its_irq_chip里的回调 * -> its_vlpi_prop_update * ... * kvm直接读guest里的LPI config table对应中断的配置,在host中 * 同步到vLPI config table。 */ +-> its_send_inv /* guest里访问MSIX BAR触发退出到qemu里处理 */ +-> pci_msix_unmask
/* 配置中断和core的关系 */ irq_setup_affinity +-> its_set_affinity /* * guest里movi命令触发kvm里对其的模拟,模拟的逻辑是使用its_map_vlpi建立 * virq到vCPU的映射关系。 */ +-> its_send_movi
|