IOMMU(五)-interrupt remmaping

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

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寄存器中,索引值可以很大。

代码分析

初始化硬件,准备数据和注册函数。

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也增加了这两个东西。

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,post interrupt的内容后面的笔记中分析。

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模式把虚拟中断中断注入。

编辑于 2021-05-18 10:50