背景
本文叙述了中断控制器(pic 8259)的创建
中断注入处理的原理
qemu用户态和内核态的协作
最后举例说明中断处理的过程
虚拟中断控制器的创建
初始化
(1) 用户态空间中断模块初始化
kvm_init==>s->irq_set_ioctl = KVM_IRQ_LINE_STATUS;
kvm_init==>kvm_irqchip_create
a) kvm_vm_ioctl(s,KVM_CREATE_IRQCHIP) 让内核态创建一个中断控制器
b) kvm_init_irq_routing
==> kvm_check_extension(s,KVM_CAP_IRQ_ROUTING) 检察内核态的Irq routing.
–(进入内核态)–>kvm_vm_ioctl_check_extension_generic
–> case KVM_CAP_IRQ_ROUTING:return KVM_MAX_IRQ_ROUTES; (4096)
(2) 内核态空间中断模块初始化
进入kvm_arch_vm_ioctl方法中
(3.1) kvm_create_pic
-
创建虚拟设备
kvm_iodevice_init(&s->dev_master,&picdev_master_ops); kvm_iodevice_init(&s->dev_slave, &picdev_slave_ops); kvm_iodevice_init(&s->dev_eclr, &picdev_eclr_ops);
-
注册端口操作
kvm_io_bus_register_dev(kvm, KVM_PIO_BUS, 0x20, 2, &s->dev_master); kvm_io_bus_register_dev(kvm,KVM_PIO_BUS, 0xa0, 2, &s->dev_slave); kvm_io_bus_register_dev(kvm,KVM_PIO_BUS, 0x4d0, 2, &s->dev_eclr);
-
端口操作
当guestos 对8259做IOport操作时,发生VM-Exit
[EXIT_REASON_IO_INSTRUCTION]= handle_io, 会进入函数handle_io
handle_io
==> emulate_instruction
==>x86_emulate_instruction
==> emulator_pio_in_out
==> kernel_pio
==>kvm_io_bus_read/write(kvm_main.c)
kvm_io_bus_read/write 会首先根据port地址取到dev;然后调用dev->ops->read/write.
也就是对应调用以下ops
kvm_iodevice_init(&s->dev_master, &picdev_master_ops);
kvm_iodevice_init(&s->dev_slave, &picdev_slave_ops);
kvm_iodevice_init(&s->dev_eclr, &picdev_eclr_ops);
(3.2). kvm_ioapic_init
略
(3.3). kvm_setup_default_irq_routing
–>kvm_set_irq_routing
–>setup_routing_entry
–>kvm_set_routing_entry
kvm_setup_default_irq_routing创建默认路由表,中断路由表存在kvm->irq_routing中
default_routing是一个数组,里面是各个中断,每个中断是一个kvm_irq_routing_entry结构体
kvm_setup_default_irq_routing作用是组装对象,放到kvm->irq_routing
struct kvm_irq_routing_table {
int chip[KVM_NR_IRQCHIPS][KVM_IRQCHIP_NUM_PINS];
struct kvm_kernel_irq_routing_entry *rt_entries;
u32 nr_rt_entries;
struct hlist_headmap[0];
};
上面结构体中有kvm_kernel_irq_routing_entry
下面先看看与中断处理相关的几个概念(IRQ, GSI, Vector):
IRQ是PIC时代的产物,由于ISA设备通常是连接到固定的PIC管脚,所以说一个设备的IRQ实际是指它连接的PIC管脚号。
IRQ暗示着中断优先级,例如IRQ0比IRQ3有着更高的优先级。
当前进到APIC时代后,或许是出于习惯,人们仍习惯用IRQ表示一个设备的中断号,但对于16以下的IRQ,它们可能不再与IOAPIC的管脚对应,例如PIT此时接的是2号管脚。
Pin是管脚号,通常它表示IOAPIC的管脚(前面说了,PIC时代我们用IRQ)。Pin的最大值受IOAPIC管脚数限制,目前取值范围是[0,23]。
GSI是ACPI引入的概念,全称是GlobalSystemInterrupt。它为系统中每个中断源指定一个唯一的中断号.
Vector是CPU的概念,是中断在IDT表中的索引。每个IRQ(或GSI)都对应一个Vector。
在PIC模式下,IRQ对应的vector=startvector+IRQ;
在APIC模式下,IRQ/GSI的vector由操作系统分配
struct kvm_kernel_irq_routing_entry {
u32 gsi; //该中断项gsi的值
u32 type; //irq chip 还是msi
int (*set)(structkvm_kernel_irq_routing_entry *e,
struct kvm *kvm, int irq_source_id, intlevel,
bool line_status); //Set是中断的触发函数
union {
struct {
unsigned irqchip;
unsigned pin;
} irqchip;
struct msi_msg msi;//msi消息中断将在第5张pci一节分析
......
};
struct hlist_node link;
};
set方法(中断触发)是按照如下方式指定的
也就是说中断触发后,会根据不同的中断调用到不同方法中
创建关联
(1) 用户空间中断控制器与虚拟pc的关联
pc_init1
-
kvm_pc_setup_irq_routing
将中断信息存储到KVMState->irq_routes中
并通过kvm_irqchip_commit_routes将中断情况同步到内核中
-
qemu_allocate_irqs
生成IRQState列表由gsi指针持有
后续初始化其他设备的时候将pic通过gsi传送过去 -
kvm_i8259_init
初始化8259设备,8259的handler 是kvm_pic_set_irq
(2). 进入内核态
内核态irq routing
kvm_vm_ioctl
==> case KVM_SET_GSI_ROUTING
==> kvm_set_irq_routing(irqchip.c)
将用户态设置的entry 更新到kvm->irq_routing中。
对8259而言设置如下:irq 0-7 关联到PIC_MASTER, 8-15关联到PIC_SLAVE
kvm_set_irq_routing
==> setup_routing_entry
==> kvm_set_routing_entry(irq-_commn.c)
中断注入
qemu调用irq_set_ioctl向内核中注入中断
内核中接受到请求后产生中断请求
cpu-entry的时候检测到中断请求,将中断刷入vmcs向cpu产生中断
(1). 用户空间
通过qemu_set_irq触发中断
通过前文我们知道handler为kvm_pic_set_irq
s->irq_set_ioctl在前文中已经初始化为了KVM_IRQ_LINE_STATUS
(2). 内核态
kvm_vm_ioctl
==>case KVM_IRQ_LINE_STATUS: case KVM_IRQ_LINE:
==>kvm_vm_ioctl_irq_line
==>kvm_set_irq(kvm,KVM_USERSPACE_IRQ_SOURCE_ID,irq_event->irq,irq_event->level, line_status);
==>irq_set[i].set(&irq_set[i], kvm, irq_source_id, level,line_status);
set 对于8259为:kvm_set_pic_irq
kvm_pic_set_irq
==>pic_update_irq(i8259.c)
在VM-Entry时vcpu_enter_guest方法中
==>kvm_cpu_has_injectable_intr
==>kvm_cpu_has_extint
其中output 会在下面这个方法中被设置
因而kvm_x86_ops->enable_irq_window
也就是vmx.c中的enable_irq_window会被调用
将相关中断信息写入vmcs中提供给cpu实现中断
示例:hpet中断
本节以hpet虚拟驱动为例分析虚拟驱动如何来注入中断
hpet_init中完成了mmio的注册
hpet_ram_write是hpet mmio write的实现函数
在该函数中根据寄存器状态决定是否产生中断
中断的注入函数是qemu_set_irq
hpet_realize中会实例化对应的s->irqs[i]
下面剩下的问题就是s->irqs[RTC_ISA_IRQ对应的handler是如何初始化的
pc_basic_device_init中初始化的hpet设备
可见通过中断最终和gsi进行关联的
通过前文我们知道gsi的hander是kvm_pc_gsi_handler
gsi = qemu_allocate_irqs(kvm_pc_gsi_handler, gsi_state,GSI_NUM_PINS);