惠伟:IOMMU(四)-dma remappingzhuanlan.zhihu.com
MSI
通过DMA写物理地址0x0FEE_XXXX来产生中断,PCI config space中有MSI Address和Data寄存器,驱动配置这两个寄存器,Address寄存器中有Destination ID,表示Local APIC ID,Address寄存器所有字段组合起来就是x0FEE_XXXX,Data寄存器有vector号,表示中断号。
如果request-without-PASID,不进行DMA remapping,并且目的地址是0x0FEE_xxxxh就是中断。如果request-with-PASID,DMA转换后地址是0x0FEE_xxxxh就报错。如果request-with-PASID,转换前的地址是0x0FEE_xxxxh正常转换,但如果转换模式是passthrough就报错。
no interrupt remapping
物理中断一般情况下不能直接投递到虚拟机中,只能先到物理机上,物理机再通过event inject机制把中断投递到虚拟机中。那么vt-d物理中断先由哪个物理CPU处理呢?当然最好是虚拟机运行在哪个物理CPU,物理中断就由那个物理CPU处理,物理中断来了,虚拟机正好由于external interrupt exiting出来,物理CPU处理物理中断,然后重新进入虚拟机时正好把中断注入。假如物理中断到了其它物理CPU,接收到外部中断的物理CPU需要给虚拟机所在的物理CPU发送一个IPI中断,把虚拟机exit出来,再进入虚拟机进行中断注入。KVM要拦截guest对passthrough pci设备MSI addr和data寄存器的读写,然后host分配一个p_vector,外设中断来了,VMM把p_vector转换成v_vector,再把v_vector注入给虚拟机。
interrupt remapping
MSI Address和Data中不再包含destination id和vector,换成handle和subhandle。
handle和subhandle算出interrupt_index
代码语言:javascript复制if (address.SHV == 0) {
interrupt_index = address.handle;
} else {
interrupt_index = (address.handle data.subhandle);
}
再根据interrupt_index查Interrupt Remap Table,查到的Entry中有Destination ID和vector。
那interrupt remapping有什么好处:
- 中断隔离和迁移
passthrough设备1给虚拟1,虚拟机1运行在物理CPU1,passthrough外设2给虚拟机2,虚拟机2运行在物理CPU2上,外设1产生的中断最好不要给了物理CPU2,外设2产生的中断最好不要给了物理CPU1。虚拟CPU从一个物理CPU迁移到另一个物理CPU,设备产生的中断需要投递到新的物理CPU上。有interrupt remapping效率更高,原来需要更新设备的MSI Address和Data寄存器,现在只需要更新中断重映射表。
- 支持的中断更多
原来MSI Data vector只有8位,用了interrupt remapping,vector存在放IRTE中,IRTE表的索引放置在MSI Address和Data寄存器中,索引值可以很大。
代码分析
初始化硬件,准备数据和注册函数。
代码语言:javascript复制start_kernel
└─x86_late_time_init
└─apic_intr_mode_init
├─default_setup_apic_routing
| └─enable_IR_x2apic
| ├─irq_remapping_prepare
| | └─intel_prepare_irq_remapping
| | └─intel_setup_irq_remapping
| └─irq_remapping_enable
| └─intel_enable_irq_remapping
└─apic_bsp_setup
└─irq_remap_enable_fault_handling
└─enable_drhd_fault_handling
linux中断处理子系统有两个很重要的概念就是irq_chip和irq_domain,IOMMU为了支持interrupt remapping也增加了这两个东西。
代码语言:javascript复制static struct irq_chip intel_ir_chip = {
.name = "INTEL-IR",
.irq_ack = apic_ack_irq,
.irq_set_affinity = intel_ir_set_affinity,
.irq_compose_msi_msg = intel_ir_compose_msi_msg,
.irq_set_vcpu_affinity = intel_ir_set_vcpu_affinity,
};
static const struct irq_domain_ops intel_ir_domain_ops = {
.select = intel_irq_remapping_select,
.alloc = intel_irq_remapping_alloc,
.free = intel_irq_remapping_free,
.activate = intel_irq_remapping_activate,
.deactivate = intel_irq_remapping_deactivate,
};
最重要的函数就是intel_ir_set_vcpu_affinity,irq_set_vcpu_affinity调用到它,irq_set_vcpu_affinity函数的第一参数是物理中断号,另一个参数是vcpu_info,kvm中函数update_pi_irte调用到这个函数,虽然函数名字有pi(post interrupt),但硬件不支持post interrupt的情况也可以搞定,kvm调用irq_set_vcpu_affinity时参数vcpu_info设置为空即可,这样IOMMU中IRTE只支持interrupt remaping不支持post interrupt。
代码语言:javascript复制update_pi_irte
└─irq_set_vcpu_affinity
└─intel_ir_set_vcpu_affinity
└─modify_irte
kvm的物理中断号来自于vfio,vfio_msi_set_vector_signal向系统申请物理中断号,传递给kvm,当外设触发中断后,IOMMU先处理,再给vcpu所有的物理cpu发起一个物理中断,物理cpu从not-root exit出来,vfio的vfio_msihandler进行中断处理,通过eventfd给kvm一个信号,kvm更新VMCS中虚拟中断字段,物理cpu重新enter non-root模式把虚拟中断中断注入。