侧边栏壁纸
博主头像
蔚然小站博主等级

未来会有的,不要辜负了梦想

  • 累计撰写 39 篇文章
  • 累计创建 15 个标签
  • 累计收到 43 条评论

目 录CONTENT

文章目录

中断虚拟化:4.MSI-X虚拟化

皮蛋熊
2023-08-27 / 0 评论 / 0 点赞 / 232 阅读 / 6880 字
温馨提示:
本文最后更新于 2023-08-27,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

MSI(messge signaled Interrupts)出现的历史原因: 外设中断请求还是要经过IOAPIC才能到达LAPIC(cpu),延迟高,想要中断请求直接发给LAPIC(CPU)。

PCI 2.2引入,作为可选特性 PCIe规范发布后变为强制性特性 PCI 3.3后增强MSI,为MSI-X。

MSI-X的每个设备可以支持更多的中断。

MSI不再受管脚的限制,MSI能够支持的

Pasted image 20220724010903

PCI设备需要扩展某种特性,可以向配置空间中的Capabilities List增加一个Capability,MSI即是使用这个特性,将IO APIC的功能扩展到设备自身。MSI Capability structure:

Pasted image 20220724123205

为了支持多个中断,MSI-X的Capability Structure的基础上增加table,其中Table Offset和BIR定义了Table所在的位置,其中BIR为BAR Indicator Register。即指定哪个BAR寄存器,然后从指定的这个BAR寄存器取出映射在CPU地址空间中的基址,加上table offset就定位了table的位置:

Pasted image 20220724140922

message address:31-20固定为0x0FEE,PCI总线根据消息地址,得知这是一个中断消息,会将其发送给Host to PCI桥,Host to PCI桥会将其发送到目的CPU(vLAPIC) message data:消息体,主要部分是中断向量。

MSI(X) Capability数据结构

前提:设备支持MSI,且设备配置为启用MSI支持。 内核会负责组织MSI Capability数据结构,然后通过MMIO方式写到PCI设备配置空间中。内核中的PCI公共层为驱动提供了接口pci_enable_msix配置MSI Capability数据结构:

linux.git/drivers/pci/msi.c  
int pci_enable_msix(struct pci_dev* dev, …)  
{  
	… 
	status = msix_capability_init(dev, entries, nvec);  
	…  
} 
static int msix_capability_init(…)  
{  
	… 
	pci_read_config_dword(dev, msix_table_offset_reg(pos), &table_offset);  
	bir = (u8)(table_offset & PCI_MSIX_FLAGS_BIRMASK);  
	table_offset &= ~PCI_MSIX_FLAGS_BIRMASK;  
	phys_addr = pci_resource_start (dev, bir) + table_offset;  
	base = ioremap_nocache(phys_addr, nr_entries *  PCI_MSIX_ENTRY_SIZE);  
	… 
	/* MSI-X Table Initialization */  
	for (i = 0; i < nvec; i++) {  
		entry = alloc_msi_entry(dev);  
		… 
		entry->msi_attrib.pos = pos;  
		entry->mask_base = base;  
		… 
		list_add_tail(&entry->list, &dev->msi_list);  
	}  
	ret = arch_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSIX);  
	…  
}

根据bir和table_offset可以计算出table的位置,然后通过iomap将配置空间中table的位置映射到CPU地址空间,此table就可以如访问内存一样访问了。

在内存中组织MSI Capability数据结构,最后将组织好的MSI Capability数据结构,包括这个结构映射到地址空间的位置,传给arch_setup_msi_irqs中构建消息,包括中断向量、目标CPU、触发模式等,写到设备的PCI配置空间。

当Guest内核侧设置设备的MSI的Capability数据结构,即写PCI设备的配置空间时,会导致VM exit,进入虚拟设备一侧。虚拟设备将Guest内核设置的MSI的Capability信息记录到设备的MSI的Capability结构体中。

以虚拟设备virto net为例,函数callback_mmio接收Guest内核侧发来的的配置信息,记录到设备的MSI的Capability数据结构中:

kvmtool.git/virtio/net.c  
void virtio_net__init(const struct virtio_net_parameters *params)  
{  
… 
	ndev.msix_io_block = pci_get_io_space_block();  
	kvm__register_mmio(params->kvm, ndev.msix_io_block, 0x100, callback_mmio, NULL);  
…  
} 
static void callback_mmio(u64 addr, u8 *data, u32 len, u8 is_write…)  
{  
	void *table = pci_header.msix.table;  
	if (is_write)  
		memcpy(table + addr - ndev.msix_io_block, data, len);  
	else  
		memcpy(data, table + addr - ndev.msix_io_block, len);  
}

建立IRQ routing表项

KVM模块为例架构统一处理vPIC,vAPIC,vMSI,设计了IRQ routing表。对于每个终端,在表格irq_routing中都需要建立一个表项。中断发生,根据gsi或者中断号,索引到具体表项,提取表项中的中断向量,调用表项中的函数指针set指向的函数发起中断。

  1. 建立虚拟设备中的MSI-X Capability Structure
  2. 把中断的信息补充到负责IRQ routing的表中。(guest内核驱动发送配置KVM模块)

以Virto标准为例,guest内核驱动定义queue_msix_vector通知虚拟设备配置IRQ routing信息,在配置virto queue时,如果确认虚拟设备支持MSI并且也启用了MSI,则通知虚拟设备建立IRQ routing表项。

linux.git/drivers/virtio/virtio_pci.c  
static struct virtqueue *vp_find_vq(…)  
{  
	… 
	if (callback && vp_dev->msix_enabled) {
		iowrite16(vector, vp_dev->ioaddr + VIRTIO_MSI_QUEUE_VECTOR);  
		…  
	}
	…  
}

虚拟设备收到Guest内核驱动的通知后,则从MSI Capability结构体中提取中断相关信息,向内核发起请求,为这个队列建立相应的IRQ routing表项。

kvmtool.git/virtio/net.c  
static bool virtio_net_pci_io_out(…)  
{  
	…
	case VIRTIO_MSI_QUEUE_VECTOR: {  
	…
		gsi = irq__add_msix_route(kvm,  
			pci_header.msix.table[vec].low,  
			pci_header.msix.table[vec].high,  
			pci_header.msix.table[vec].data);  
	…  
	}…  
}
kvmtool.git/irq.c  
int irq__add_msix_route(struct kvm *kvm, u32 low, u32 high, u32 data)  
{  
	int r;  
	irq_routing->entries[irq_routing->nr++] =  (struct kvm_irq_routing_entry) {  
		.gsi = gsi,  
		.type = KVM_IRQ_ROUTING_MSI,  
		.u.msi.address_lo = low,  
		.u.msi.address_hi = high,  
		.u.msi.data = data,  
	};  
	r = ioctl(kvm->vm_fd, KVM_SET_GSI_ROUTING, irq_routing);  
…  
}

MSI设备中断过程

虚拟设备如果启用了MSI,中断过程不需要vI/O APIC参与。虚拟设备直接从自身的MSI(-X) Capability Structure中提取目的CPU等消息,向目的CPU关联的vLAPIC发送中断请求,vLAPIC的操作与APIC虚拟化完全相同。

Pasted image 20220724233639

启用MSI的虚拟设备的中断过程 KVM收到虚拟设备发来的中断时候,调用统一接口 `kvm_set_irq` ,该函数遍历 `IRQ routing` 表中的每个表项,set指向的函数负责注入中断
  • PCI:set指向kvm_set_pic_irq
  • APIC:set指向kvm_set_ioapic_irq
  • MSI:set指向kvm_set_msi
linux.git/virt/kvm/irq_comm.c  
int setup_routing_entry(…)  
{  
	… 
	case KVM_IRQ_ROUTING_IRQCHIP:  
	… 
	switch (ue->u.irqchip.irqchip) {  
		case KVM_IRQCHIP_PIC_MASTER:  
			e->set = kvm_set_pic_irq;  
	…  
		case KVM_IRQCHIP_IOAPIC:  
			e->set = kvm_set_ioapic_irq;  
	…  
		case KVM_IRQ_ROUTING_MSI:  
			e->set = kvm_set_msi;  
	…  
}

可见IO APIC从中断重定向表中提取中断信息,MSI(-X) Capability提取信息,找到目标CPU,最后都是目标CPU关联的vLAPIC的接口kvm_apic_set_irq向Guest注入中断。

linux.git/virt/kvm/irq_comm.c  
static void kvm_set_msi(…)  
{  
	… 
	int dest_id = (e->msi.address_lo & MSI_ADDR_DEST_ID_MASK)…;  
	int vector = (e->msi.data & MSI_DATA_VECTOR_MASK) …;  
	… 
	switch (delivery_mode) {  
		case IOAPIC_LOWEST_PRIORITY:  
		vcpu = kvm_get_lowest_prio_vcpu(ioapic->kvm, vector,  
		deliver_bitmask);  
		if (vcpu != NULL)  
			kvm_apic_set_irq(vcpu, vector, trig_mode);  
	…  
}

参考文献

  1. 《深度探索Linux系统虚拟化:原理与实现》 - 王柏生 & 谢广军
0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区