IOMMU之Interrupt Posting

Interrupt Posting是在Interrupt Remapping的基础上进一步提升了直通设备的中断处理效率,使用Posting模式时,vcpu可以直接在non-root模式下处理中断而不会被vm-exit到宿主机。

1、为了支持中断Posting,每个vcpu数据结构新增了一个Posted-Interrupt Descriptor结构体,该结构体主要保存post的部分信息。

struct pi_desc {
    //每个bit代表一个vector,总共表示256个vector,posting哪个vector,对应bit就置1
    u32 pir[8];     /* Posted interrupt requested */
    union {
        struct {
            /* bit 256 - Outstanding Notification */
            //描述有中断Posting事件
            u16	on	: 1,
                /* bit 257 - Suppress Notification */
                //非紧急中断,是否要立即通知
                sn	: 1,
                /* bit 271:258 - Reserved */
                rsvd_1	: 14;
                /* bit 279:272 - Notification Vector */
            //有两个值:
            //1)、POSTED_INTR_VECTOR  
            //vcpu新启动,vcpu因为被block重新被调到执行等场景会设置该值,表示硬件通知的中断直接通知给vcpu处理
            //2)、POSTED_INTR_WAKEUP_VECTOR
            //vcpu被block时,会设置该值,表明硬件通知的中断是通知的vcpu所在的物理cpu,物理cpu收到中断事件后,会唤醒vcpu执行Posting的中断
            u8	nv;
                /* bit 287:280 - Reserved */
            u8	rsvd_2;
                /* bit 319:288 - Notification Destination */
            u32	ndst;
        };
        u64 control;
    };
    u32 rsvd[6];
} __aligned(64);

2、vmcs page新增了两个域:POSTED_INTR_NV、POSTED_INTR_DESC_ADDR。create vcpu的时候,kvm检查硬件是否使能apic virtualtion(kvm_intel模块参数enable_apicv可开关控制),如果有使能,则将pi_desc对应物理地址写到vmcs的POSTED_INTR_DESC_ADDR处,并将POSTED_INTR_NV设置为POSTED_INTR_VECTOR。同时在vcpu调度过程中,如果vcpu处于block状态,那kvm会将pi_desc.nv标记为POSTED_INTR_WAKEUP_VECTOR,这样IOMMU硬件有中断posting需要处理,发送Notification vector时就会发送到vcpu所在的物理cpu,然后物理cpu再唤醒vcpu处理;如果vcpu处于running状态时,pi_desc.nv会被重新标记为POSTED_INTR_VECTOR,这样IOMMU的Notification vector就直接发送到vcpu处理,不需要vm-exit。

	if (kvm_vcpu_apicv_active(&vmx->vcpu)) {
		vmcs_write64(EOI_EXIT_BITMAP0, 0);
		vmcs_write64(EOI_EXIT_BITMAP1, 0);
		vmcs_write64(EOI_EXIT_BITMAP2, 0);
		vmcs_write64(EOI_EXIT_BITMAP3, 0);

		vmcs_write16(GUEST_INTR_STATUS, 0);

		vmcs_write16(POSTED_INTR_NV, POSTED_INTR_VECTOR);
		vmcs_write64(POSTED_INTR_DESC_ADDR, __pa((&vmx->pi_desc)));
	}

3、IOMMU的irte表默认开启的是Remapping模式,kvm如果需要使用Posting模式,需要修改irte的mode为posting模式,并将pi_desc地址信息告知给IOMMU硬件。kvm使用生产者、消费者的模式来完成中断模式信息的变更。

Qemu首先通过VFIO_PCI_MSIX_IRQ_INDEX为vfio分配一个eventfd,得到该eventfd对应的文件描述符,然后将该文件描述符通过KVM_IRQFD通知给kvm。

1)、vfio注册生产者信息,并为producer初始化token和irq,其中token为eventfd的文件描述符,irq为Host irq;

vfio_msi_set_vector_signal
    vfio_msi_set_block
        vfio_msi_set_vector_signal
            irq_bypass_register_producer(注册的producer)

	trigger = eventfd_ctx_fdget(fd);
	vdev->ctx[vector].producer.token = trigger;
	vdev->ctx[vector].producer.irq = irq;

2)、kvm使用vfio生成的eventfd注册一个consumer,然后调用vmx_update_pi_irte判断是否通知硬件使用Posting模式;

kvm_irqfd_assign
    irq_bypass_register_consumer
        __connect
            add_producer
                kvm_arch_irq_bypass_add_producer
                    vmx_update_pi_irte

static int vmx_update_pi_irte(struct kvm *kvm, unsigned int host_irq,
			      uint32_t guest_irq, bool set)
{
	//1、判断是否有直通设备以及iommu硬件是否有posting能力
	if (!kvm_arch_has_assigned_device(kvm) ||
		!irq_remapping_cap(IRQ_POSTING_CAP))
		return 0;
	
	hlist_for_each_entry(e, &irq_rt->map[guest_irq], link) {
		if (e->type != KVM_IRQ_ROUTING_MSI)
			continue;
		/*
		 * VT-d PI cannot support posting multicast/broadcast
		 * interrupts to a vCPU, we still use interrupt remapping
		 * for these kind of interrupts.
		 *
		 * For lowest-priority interrupts, we only support
		 * those with single CPU as the destination, e.g. user
		 * configures the interrupts via /proc/irq or uses
		 * irqbalance to make the interrupts single-CPU.
		 *
		 * We will support full lowest-priority interrupt later.
		 */

		kvm_set_msi_irq(kvm, e, &irq);
		//判断虚拟中断是否只能路由到1个vcpu,如果不是,则只能使用remapping模式
		if (!kvm_intr_is_single_vcpu(kvm, &irq, &vcpu)) {
			/*
			 * Make sure the IRTE is in remapped mode if
			 * we don't handle it in posted mode.
			 */
			ret = irq_set_vcpu_affinity(host_irq, NULL);
			if (ret < 0) {
				printk(KERN_INFO
				   "failed to back to remapped mode, irq: %u\n",
				   host_irq);
				goto out;
			}

			continue;
		}
		//设置pi_desc及vector信息
		vcpu_info.pi_desc_addr = __pa(vcpu_to_pi_desc(vcpu));
		vcpu_info.vector = irq.vector;
		//调用iommu修改irte表项信息
		irq_set_vcpu_affinity(host_irq, &vcpu_info);
	}
}

irq_set_vcpu_affinity最终会调用intel_ir_set_vcpu_affinity,这里就是最终修改irte的地方:

static int intel_ir_set_vcpu_affinity(int irq, void *info)
{
	struct irq_2_iommu *ir_data = irq_2_iommu(irq);
	struct vcpu_data *vcpu_pi_info = info;

	/* stop posting interrupts, back to remapping mode */
	if (!vcpu_pi_info) {
		modify_irte(irq, get_irte(ir_data));
	} else {
		struct irte irte_pi;

		/*
		 * We are not caching the posted interrupt entry. We
		 * copy the data from the remapped entry and modify
		 * the fields which are relevant for posted mode. The
		 * cached remapped entry is used for switching back to
		 * remapped mode.
		 */
		memset(&irte_pi, 0, sizeof(irte_pi));
		dmar_copy_shared_irte(&irte_pi, get_irte(ir_data));

		/* Update the posted mode fields */
		//设置irte为posting模式
		irte_pi.p_pst = 1;
		irte_pi.p_urgent = 0;
		//设置vector
		irte_pi.p_vector = vcpu_pi_info->vector;
		//设置pi_desc信息
		irte_pi.pda_l = (vcpu_pi_info->pi_desc_addr >>
				(32 - PDA_LOW_BIT)) & ~(-1UL << PDA_LOW_BIT);
		irte_pi.pda_h = (vcpu_pi_info->pi_desc_addr >> 32) &
				~(-1UL << PDA_HIGH_BIT);
		//修改irte
		modify_irte(irq, &irte_pi);
	}
	return 0;
}

5、按以上步骤初始化好posting模式后,当IOMMU收到中断时,就会根据irte的mode标志来使用Posting模式,使用Posting时的中断映射表项如下所示,这些信息初始值就是在第4步里设置好的,在vcpu调度过程中也会有变化(参考第2步描述)。

      

6、想要使用Interrupt Posting特性,cpu还需要支持apicv功能,apicv会在vmcs的virtual-APIC page里模拟一些虚拟的apic寄存器,如:

    VTPR(virtual task-priority register);

    VPPR(virtual processor-priority register);

    VEOI(virtual end-of-interrupt register);

    VISR(virtual interrupt-service register);

    VIRR(virtual interrupt-request register);

当IOMMU判断需要使用Posting发送中断时,会将中断信息写在pi_desc里,然后通过Notification event中断通知vcpu有外部中断要处理(假设这里vcpu处于running,发送POSTED_INTR_VECTOR给vcpu,如果是发送POSTED_INTR_WAKEUP_VECTOR,kvm会完成中断的注入,注入过程也会使用apicv功能),cpu根据pending的中断将VIRR对应bit为置1,然后使用virtual interrupt delivery能力进一步处理,处理完成后,执行EOI操作,virtual interrupt delivery的处理逻辑如下,其中:RVI、SVI保存在vmcs的Guest interrupt status区域(RVI:Requesting virtual interrupt;SVI:serviceing virtual interrupt)。这样,外设中断就直接在non-root模式下执行完成了。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值