QEMU-KVM中的VFIO-MSI机制
当Guest的bios-kernel通过写入vfio-device的配置空间,来配置msi、msi-x时(也就是向msi/msi-x的capability structure中写入msi/msi-x enable bit),就会调用提前注册好的处理函数,即vfio_pci_write_config
。
其中pdev是vfio-device设备,addr是待写入的设备地址,val是待写入的数据,len是数据长度(bytes).
如果填入该函数中的数据为能够正确配置MSI、MSI-X功能的数据,那么就会调用vfio_msi_enable
和vfio_msix_enable
.
MSI初始化
vfio_msi_enable的主要代码路径如下:
以下是vfio_msi_enable
函数中相关函数的框架分析。
vfio_add_kvm_msi_virq(VFIOPCIDevice *vdev, VFIOMSIVector *vector,
int vector_n, bool msix)
{
event_notifier_init(&vector->kvm_interrupt, 0)
virq = kvm_irqchip_add_msi_route(kvm_state, vector_n, &vdev->pdev);
kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, &vector->kvm_interrupt,
NULL, virq)
}
kvm_irqchip_add_msi_route(KVMState *s, int vector, PCIDevice *dev)
{
msg = pci_get_msi_message(dev, vector);
virq = kvm_irqchip_get_virq(s);
{
}
kvm_add_routing_entry(s, &kroute);
kvm_arch_add_msi_route_post(&kroute, vector, dev);
kvm_irqchip_commit_routes(s);
return virq;
}
int kvm_irqchip_add_irqfd_notifier_gsi(KVMState *s, EventNotifier *n,
EventNotifier *rn, int virq)
{
return kvm_irqchip_assign_irqfd(kvm_state, &vector->kvm_interrupt, NULL, virq, true);
}
kvm_irqchip_assign_irqfd(KVMState *s, EventNotifier *event,
EventNotifier *resample, int virq,
bool assign)
{
fd = fd_of(event);
rfd = -1;
struct kvm_irqfd irqfd = {
.fd = fd,
.gsi = virq,
.flags = 0,
};
kvm_vm_ioctl(s, KVM_IRQFD, &irqfd);
}
kvm_irqfd(kvm_irqfd args)
{
return kvm_irqfd_assign(kvm, 0);
}
kvm_irqfd_assign(struct kvm *kvm, struct kvm_irqfd *args)
{
irqfd->gsi = args->gsi;
INIT_WORK(&irqfd->inject, irqfd_inject);
f = fdget(args->fd);
eventfd = eventfd_ctx_fileget(f.file);
irqfd->eventfd = eventfd;
init_waitqueue_func_entry(&irqfd->wait, irqfd_wakeup);
init_poll_funcptr(&irqfd->pt, irqfd_ptable_queue_proc);
irqfd_update(kvm, irqfd);
#ifdef CONFIG_HAVE_KVM_IRQ_BYPASS
if (kvm_arch_has_irq_bypass()) {
irqfd->consumer.token = (void *)irqfd->eventfd;
irqfd->consumer.add_producer = kvm_arch_irq_bypass_add_producer;
irqfd->consumer.del_producer = kvm_arch_irq_bypass_del_producer;
irqfd->consumer.stop = kvm_arch_irq_bypass_stop;
irqfd->consumer.start = kvm_arch_irq_bypass_start;
ret = irq_bypass_register_consumer(&irqfd->consumer);
if (ret)
pr_info("irq bypass consumer (token %p) registration fails: %d\n",
irqfd->consumer.token, ret);
}
#endif
}
vfio_enable_vectors
=>
=> ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set);
=> vfio_pci_set_irqs_ioctl
=> vfio_pci_set_msi_trigger
=> vfio_msi_enable
=> vfio_msi_set_block
MSI-X初始化
vfio_msi_enable在Guest写MSI-X Capability Structure的Enable Bit时触发。
vfio_msix_enbale
函数的主要代码如下:
以上,MSI-X中断的功能被Enable,Message Control的最高bit置了1.
接下来是当Guest对MSI-X Table中的Vector-msg地址-msg数据进行写入时,具体的处理情况。
由于MSI-X Table存储在某个BAR中,而BAR又分为可mmap的BAR和不可mmap的BAR,现在一般都是用可mmap的BAR作为MSI-X Table的存储BAR。
之后调用到:
总结
在Guest配置MSI/MSI-X功能时,需要对设备的配置空间进行写操作
,而VFIO设备在初始化时,就注册了Guest写操作
对应的callback,即vfio_pci_write_config
。
该函数中,对于MSI/MSI-X的大致配置路径如下图所示。经过一系列的条件检查,最终对VFIO设备的MSI、MSI-X配置会落实到vfio_msi_enable()
和vfio_msix_enable()
上。

MSI配置
在qemu配置msi中断时,有3种情况:
- 如果只是qemu单独运行guest,没有kvm,那么qemu会用自身的eventfd机制,每当需要发送中断时,就读eventfd,导致调用
vfio_msi_interrupt
,进而发送中断。
- 如果qemu+kvm运行guest,那么qemu会注册eventfd+irqfd,使vfio设备的msi中断在触发之后,改变eventfd的状态,进而触发kvm中的irq_routing中的某entry,最终通过Local APIC注入到Guest中。
- 在2的情况下,如果kvm支持irq_bypass feature,且CPU支持posted Interrupt,那么会将设备的在vfio设备的msi中断触发后,会改变eventfd的状态,然后导致host kernel中与msi中断绑定的host_irq触发,进而触发IOMMU的中断重定向机制(发送一个Posted Interrupt),最终将中断发送到Guest内部。

MSI-X配置
相比于msi,msi-x多了一步提前使能硬件设备的msi-x功能的工作,其它步骤与msi大同小异。

__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了