如果收到中断响应
/*
* callback when PIC0 irq status changed
*/
static void pic_irq_request(void *opaque, int level)
{
struct kvm *kvm = opaque;
struct kvm_vcpu *vcpu = kvm->bsp_vcpu;
struct kvm_pic *s = pic_irqchip(kvm);
int irq = pic_get_irq(&s->pics[0]);
s->output = level;
if (vcpu && level && (s->pics[0].isr_ack & (1 << irq))) {
s->pics[0].isr_ack &= ~(1 << irq);
s->wakeup_needed = true;
}
}
中断注入
中断注入实际是向客户机CPU注入一个事件,这个事件包括异常和外部中断和NMI。异常我们一般看作为同步,中断被认为异步。
硬件具体实现就中断注入实际就是设置VMCS中字段VM-Entry interruption-infomation字段。中断注入实际在VM运行前完成的,具体代码如下:
static int vcpu_enter_guest(struct kvm_vcpu *vcpu) {
inject_pending_event(vcpu);
}
vcpu_enter_guest函数运行虚拟机,运行虚拟机代码已省掉。中断注入实际在VM运行前,接下来看看具体如何注入。
static void inject_pending_event(struct kvm_vcpu *vcpu)
{
if (vcpu->arch.nmi_injected) {
kvm_x86_ops->set_nmi(vcpu);
return;
}
if (vcpu->arch.interrupt.pending) {
kvm_x86_ops->set_irq(vcpu);
return;
}
/* try to inject new event if pending */
if (vcpu->arch.nmi_pending) {
if (kvm_x86_ops->nmi_allowed(vcpu)) {
vcpu->arch.nmi_pending = false;
vcpu->arch.nmi_injected = true;
kvm_x86_ops->set_nmi(vcpu);
}
} else if (kvm_cpu_has_interrupt(vcpu)) {
if (kvm_x86_ops->interrupt_allowed(vcpu)) {
kvm_queue_interrupt(vcpu, kvm_cpu_get_interrupt(vcpu),
false);
kvm_x86_ops->set_irq(vcpu);
}
}
}
首先用户态实现中断控制器,不可屏蔽中断和其他中断注入过程。用户态中断采集在qemu代码中实现
判断是否有等待注入中断,存在话立即注入
接下来内核态模拟的中断控制器,中断注入过程,不可屏蔽中断和其他中断注入过程。
判断KVM内核态是否有不可屏蔽中断,有并且客户机cpu允许中断话,注入中断到客户机cpu中。
判断KVM内核态是否有中断,有中断并且客户机cpu允许中断话,获取优先级高中断进行排队,注入中断到客户机cpu中。
另外一个情况,如果有中断但是客户机不允许中断,只能等待下一下中断注入。如果下一次有更高级别中断发生,该中断还是不能注入而选择更高级别中断注入。
/*
* check if there is pending interrupt without
* intack.
*/
int kvm_cpu_has_interrupt(struct kvm_vcpu *v)
{
struct kvm_pic *s;
if (!irqchip_in_kernel(v->kvm))
return v->arch.interrupt.pending;
if (kvm_apic_has_interrupt(v) == -1) { /* LAPIC */
if (kvm_apic_accept_pic_intr(v)) {
s = pic_irqchip(v->kvm); /* PIC */
return s->output;
} else
return 0;
}
return 1;
}
int kvm_get_apic_interrupt(struct kvm_vcpu *vcpu)
{
int vector = kvm_apic_has_interrupt(vcpu);
struct kvm_lapic *apic = vcpu->arch.apic;
if (vector == -1)
return -1;
apic_set_vector(vector, apic->regs + APIC_ISR);
apic_update_ppr(apic);
apic_clear_irr(vector, apic);
return vector;
}
找到中断向量,设置ISR,清除中断请求寄存器。
static void apic_update_ppr(struct kvm_lapic *apic)
{
u32 tpr, isrv, ppr;
int isr;
tpr = apic_get_reg(apic, APIC_TASKPRI);
isr = apic_find_highest_isr(apic);
isrv = (isr != -1) ? isr : 0;
if ((tpr & 0xf0) >= (isrv & 0xf0))
ppr = tpr & 0xff;
else
ppr = isrv & 0xf0;
apic_debug("vlapic %p, ppr 0x%x, isr 0x%x, isrv 0x%x",
apic, ppr, isr, isrv);
apic_set_reg(apic, APIC_PROCPRI, ppr);
}
获取tpr寄存器内容,查询当前待处理请求向量,TPR 寄存器接收 0~15 共 16 个值,对应 16 个 CPU 规定的中断优先级级别,值越大优
先级越高。CPU 只处理比 TPR 中值优先级别更高的中断。将Pending 在 IRR 上的中断是否发送给 CPU
TPR,task priority register,任务优先级寄存器,它确定当前 CPU 可处理什么优先级别
范围内的中断。CPU 只处理比 TPR 中值优先级别更高的中断.
PPR,Processor priority register,处理器优先级寄存器。该寄存器决定当前 CPU 正在处
理的中断的优先级级别,以确定一个 Pending 在 IRR 上的中断是否发送给 CPU。与 TPR 不
同,它的值由 CPU 写而不是软件写。
static int vmx_interrupt_allowed(struct kvm_vcpu *vcpu)
{
return (vmcs_readl(GUEST_RFLAGS) & X86_EFLAGS_IF) &&
!(vmcs_read32(GUEST_INTERRUPTIBILITY_INFO) &
(GUEST_INTR_STATE_STI | GUEST_INTR_STATE_MOV_SS));
}
判断客户机中断标志寄存器和判断中断能力信息
static inline void kvm_queue_interrupt(struct kvm_vcpu *vcpu, u8 vector,
bool soft)
{
vcpu->arch.interrupt.pending = true;
vcpu->arch.interrupt.soft = soft;
vcpu->arch.interrupt.nr = vector;
}
static void vmx_inject_irq(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
uint32_t intr;
int irq = vcpu->arch.interrupt.nr;
trace_kvm_inj_virq(irq);
++vcpu->stat.irq_injections;
intr = irq | INTR_INFO_VALID_MASK;
if (vcpu->arch.interrupt.soft) {
intr |= INTR_TYPE_SOFT_INTR;
vmcs_write32(VM_ENTRY_INSTRUCTION_LEN,
vmx->vcpu.arch.event_exit_inst_len);
} else
intr |= INTR_TYPE_EXT_INTR;
vmcs_write32(VM_ENTRY_INTR_INFO_FIELD, intr);
}
由结构体成员vcpu,获取包含该成员结构体vmx, 这个转换由container_of(ptr, type, member) 实现的,由兴趣可以自己分析一下。
接下来设置VM-Entry interruption-infomation字段,字段格式如下:
0-7为中断向量
8-10位为中断类型
11错误代码
12-30为保留
31为有效
设置中断信息字段的中断向量,并将中断信息字段最高位(31)为置1,1表明中断有效。
根据中断向量类型为软中断或者硬件中断,设置中断信息字段。
最后把写入中断信息字段到VMCS的数据域,从而完成中断注入。
int kvm_ioapic_set_irq(struct kvm_ioapic *ioapic, int irq, int level)
{
u32 old_irr;
u32 mask = 1 << irq;
union kvm_ioapic_redirect_entry entry;
int ret = 1;
spin_lock(&ioapic->lock);
old_irr = ioapic->irr;
if (irq >= 0 && irq < IOAPIC_NUM_PINS) {
entry = ioapic->redirtbl[irq];
level ^= entry.fields.polarity;
if (!level)
ioapic->irr &= ~mask;
else {
int edge = (entry.fields.trig_mode == IOAPIC_EDGE_TRIG);
ioapic->irr |= mask;
if ((edge && old_irr != ioapic->irr) ||
(!edge && !entry.fields.remote_irr))
ret = ioapic_service(ioapic, irq);
else
ret = 0; /* report coalesced interrupt */
}
trace_kvm_ioapic_set_irq(entry.bits, irq, ret == 0);
}
spin_unlock(&ioapic->lock);
return ret;
}
注意:中断请求寄存器表示已接受的中断,但是尚未提交
获取中断请求寄存器内容。
判断irq引脚线是否小于24
获取相应引脚重定向表内容,触发电平异或中断管脚的极性,主要因为entry.fields.polarity为0表示高电平有效,level为1将表示产生中断
获取触发模式,设置中断请求寄存器,如果为边沿触发并且没有排队中断,当中断是 level 触发时,LAPIC 接收了该中断,remote_irr内容为1,LAPIC 写 EOI 时,remote_irr内容为0
static int ioapic_service(struct kvm_ioapic *ioapic, unsigned int idx)
{
union kvm_ioapic_redirect_entry *pent;
int injected = -1;
pent = &ioapic->redirtbl[idx];
if (!pent->fields.mask) {
injected = ioapic_deliver(ioapic, idx);
if (injected && pent->fields.trig_mode == IOAPIC_LEVEL_TRIG)
pent->fields.remote_irr = 1;
}
return injected;
}
如果中断屏蔽位没有设置,允许中断。
static int ioapic_deliver(struct kvm_ioapic *ioapic, int irq)
{
union kvm_ioapic_redirect_entry *entry = &ioapic->redirtbl[irq];
struct kvm_lapic_irq irqe;
ioapic_debug("dest=%x dest_mode=%x delivery_mode=%x "
"vector=%x trig_mode=%x\n",
entry->fields.dest, entry->fields.dest_mode,
entry->fields.delivery_mode, entry->fields.vector,
entry->fields.trig_mode);
irqe.dest_id = entry->fields.dest_id;
irqe.vector = entry->fields.vector;
irqe.dest_mode = entry->fields.dest_mode;
irqe.trig_mode = entry->fields.trig_mode;
irqe.delivery_mode = entry->fields.delivery_mode << 8;
irqe.level = 1;
irqe.shorthand = 0;
#ifdef CONFIG_X86
/* Always delivery PIT interrupt to vcpu 0 */
if (irq == 0) {
irqe.dest_mode = 0; /* Physical mode. */
/* need to read apic_id from apic regiest since
* it can be rewritten */
irqe.dest_id = ioapic->kvm->bsp_vcpu->vcpu_id;
}
#endif
return kvm_irq_delivery_to_apic(ioapic->kvm, NULL, &irqe);
}
获取中断目的地,中断向量,中断目的模式,中断触发模式,中断触发方式,中断触发电平。如果中断中断,重新设置中断目的地。将中断发往目的地local apic
int kvm_irq_delivery_to_apic(struct kvm *kvm, struct kvm_lapic *src,
struct kvm_lapic_irq *irq)
{
int i, r = -1;
struct kvm_vcpu *vcpu, *lowest = NULL;
if (irq->dest_mode == 0 && irq->dest_id == 0xff &&
kvm_is_dm_lowest_prio(irq))
printk(KERN_INFO "kvm: apic: phys broadcast and lowest prio\n");
kvm_for_each_vcpu(i, vcpu, kvm) {
if (!kvm_apic_present(vcpu))
continue;
if (!kvm_apic_match_dest(vcpu, src, irq->shorthand,
irq->dest_id, irq->dest_mode))
continue;
if (!kvm_is_dm_lowest_prio(irq)) {
if (r < 0)
r = 0;
r += kvm_apic_set_irq(vcpu, irq);
} else if (kvm_lapic_enabled(vcpu)) {
if (!lowest)
lowest = vcpu;
else if (kvm_apic_compare_prio(vcpu, lowest) < 0)
lowest = vcpu;
}
}
if (lowest)
r = kvm_apic_set_irq(lowest, irq);
}
如果为Physical Mode,并且dest_id=0xff目的地为广播,是lowest priority,打印警告信息。
寻找匹配vpic,找到话,是Delivery mode 为 lowest priority,这样 IOAPIC 的中断消息由优先级最低的 CPU 接收。不是话,触发中断。
static int __apic_accept_irq(struct kvm_lapic *apic, int delivery_mode,
int vector, int level, int trig_mode)
{
int result = 0;
struct kvm_vcpu *vcpu = apic->vcpu;
switch (delivery_mode) {
case APIC_DM_LOWEST:
vcpu->arch.apic_arb_prio++;
case APIC_DM_FIXED:
/* FIXME add logic for vcpu on reset */
if (unlikely(!apic_enabled(apic)))
break;
if (trig_mode) {
apic_debug("level trig mode for vector %d", vector);
apic_set_vector(vector, apic->regs + APIC_TMR);
} else
apic_clear_vector(vector, apic->regs + APIC_TMR);
result = !apic_test_and_set_irr(vector, apic);
trace_kvm_apic_accept_irq(vcpu->vcpu_id, delivery_mode,
trig_mode, vector, !result);
if (!result) {
if (trig_mode)
apic_debug("level trig mode repeatedly for "
"vector %d", vector);
break;
}
kvm_vcpu_kick(vcpu);
break;
Physical Mode(Destination Mode为0时): 其值为APIC ID,用于标识一个唯一的APIC。
Logical Mode(Destination Mode为1时):其值根据LAPIC的不同配置,代表一组CPU(具体见LAPIC相关内容)
Interrupt Vector,中断向量,R/W。指定该中断对应的vector,范围从10h到FEh(x86架构前16个vector被系统预留,见后面相关内容)
表1-1 RTE格式
当IOAPIC某个管脚接收到中断信号后,会根据该管脚对应的RTE,格式化出一条中断消息,发送给某个CPU的LAPIC。从上表我们可以看出,该消息包含了一个中断的所有信息。
Destination Mode,目的地模式,R/W。
0:Physical Mode,解释见Destination Field
1:Logical Mode,同上
Delivery Mode,传送模式,R/W。用于指定该中断以何种方式发送给目的APIC,各种模式需要和相应的触发方式配合。可选的模式如下,字段相应的值以二进制表示:
Fixed: 000b,发送给Destination Filed列出的所有CPU,level、edge触发均可。
Lowest Priority:001b,发送给Destination Filed列出的CPU中,优先级最低的CPU(CPU的优先级见LAPIC相关内容)。Level、edge均可
SMI:010b,System Management Interrupt,系统管理中断。只能为edge触发,并且vector字段写0
NMI:100b,None Mask Interrupt,不可屏蔽中断。发送给Destination Field列出的所有CPU,Vector字段值被忽略。NMI是edge触发,Trigger Mode字段中的值对NMI无影响,但建议配置成edge。
INIT:101b,发送给Destination Filed列出的所有CPU,LAPIC收到后执行INIT中断(详细信息参考相关CPU spec中INIT中断一节)。触发模式同NMI。
ExtINT:111b,发送给Destination Filed列出的所有CPU。CPU收到该中断后,认为这是一个PIC发送的中断请求,并回应INTA信号(该INTA脚连接到的是与该管脚相连的PIC上,而非IOAPIC上)
笔者:ExtINT用于PIC接在APIC上的情况,见后面的Virtual Wire Mode
15
Trigger Mode,触发模式,R/W。指明该管脚的的中断由什么方式触发。
1:Level,电平触发
2:Edge,边沿触发
/*
* callback when PIC0 irq status changed
*/
static void pic_irq_request(void *opaque, int level)
{
struct kvm *kvm = opaque;
struct kvm_vcpu *vcpu = kvm->bsp_vcpu;
struct kvm_pic *s = pic_irqchip(kvm);
int irq = pic_get_irq(&s->pics[0]);
s->output = level;
if (vcpu && level && (s->pics[0].isr_ack & (1 << irq))) {
s->pics[0].isr_ack &= ~(1 << irq);
s->wakeup_needed = true;
}
}
中断注入
中断注入实际是向客户机CPU注入一个事件,这个事件包括异常和外部中断和NMI。异常我们一般看作为同步,中断被认为异步。
硬件具体实现就中断注入实际就是设置VMCS中字段VM-Entry interruption-infomation字段。中断注入实际在VM运行前完成的,具体代码如下:
static int vcpu_enter_guest(struct kvm_vcpu *vcpu) {
inject_pending_event(vcpu);
}
vcpu_enter_guest函数运行虚拟机,运行虚拟机代码已省掉。中断注入实际在VM运行前,接下来看看具体如何注入。
static void inject_pending_event(struct kvm_vcpu *vcpu)
{
if (vcpu->arch.nmi_injected) {
kvm_x86_ops->set_nmi(vcpu);
return;
}
if (vcpu->arch.interrupt.pending) {
kvm_x86_ops->set_irq(vcpu);
return;
}
/* try to inject new event if pending */
if (vcpu->arch.nmi_pending) {
if (kvm_x86_ops->nmi_allowed(vcpu)) {
vcpu->arch.nmi_pending = false;
vcpu->arch.nmi_injected = true;
kvm_x86_ops->set_nmi(vcpu);
}
} else if (kvm_cpu_has_interrupt(vcpu)) {
if (kvm_x86_ops->interrupt_allowed(vcpu)) {
kvm_queue_interrupt(vcpu, kvm_cpu_get_interrupt(vcpu),
false);
kvm_x86_ops->set_irq(vcpu);
}
}
}
首先用户态实现中断控制器,不可屏蔽中断和其他中断注入过程。用户态中断采集在qemu代码中实现
判断是否有等待注入中断,存在话立即注入
接下来内核态模拟的中断控制器,中断注入过程,不可屏蔽中断和其他中断注入过程。
判断KVM内核态是否有不可屏蔽中断,有并且客户机cpu允许中断话,注入中断到客户机cpu中。
判断KVM内核态是否有中断,有中断并且客户机cpu允许中断话,获取优先级高中断进行排队,注入中断到客户机cpu中。
另外一个情况,如果有中断但是客户机不允许中断,只能等待下一下中断注入。如果下一次有更高级别中断发生,该中断还是不能注入而选择更高级别中断注入。
/*
* check if there is pending interrupt without
* intack.
*/
int kvm_cpu_has_interrupt(struct kvm_vcpu *v)
{
struct kvm_pic *s;
if (!irqchip_in_kernel(v->kvm))
return v->arch.interrupt.pending;
if (kvm_apic_has_interrupt(v) == -1) { /* LAPIC */
if (kvm_apic_accept_pic_intr(v)) {
s = pic_irqchip(v->kvm); /* PIC */
return s->output;
} else
return 0;
}
return 1;
}
int kvm_get_apic_interrupt(struct kvm_vcpu *vcpu)
{
int vector = kvm_apic_has_interrupt(vcpu);
struct kvm_lapic *apic = vcpu->arch.apic;
if (vector == -1)
return -1;
apic_set_vector(vector, apic->regs + APIC_ISR);
apic_update_ppr(apic);
apic_clear_irr(vector, apic);
return vector;
}
找到中断向量,设置ISR,清除中断请求寄存器。
static void apic_update_ppr(struct kvm_lapic *apic)
{
u32 tpr, isrv, ppr;
int isr;
tpr = apic_get_reg(apic, APIC_TASKPRI);
isr = apic_find_highest_isr(apic);
isrv = (isr != -1) ? isr : 0;
if ((tpr & 0xf0) >= (isrv & 0xf0))
ppr = tpr & 0xff;
else
ppr = isrv & 0xf0;
apic_debug("vlapic %p, ppr 0x%x, isr 0x%x, isrv 0x%x",
apic, ppr, isr, isrv);
apic_set_reg(apic, APIC_PROCPRI, ppr);
}
获取tpr寄存器内容,查询当前待处理请求向量,TPR 寄存器接收 0~15 共 16 个值,对应 16 个 CPU 规定的中断优先级级别,值越大优
先级越高。CPU 只处理比 TPR 中值优先级别更高的中断。将Pending 在 IRR 上的中断是否发送给 CPU
TPR,task priority register,任务优先级寄存器,它确定当前 CPU 可处理什么优先级别
范围内的中断。CPU 只处理比 TPR 中值优先级别更高的中断.
PPR,Processor priority register,处理器优先级寄存器。该寄存器决定当前 CPU 正在处
理的中断的优先级级别,以确定一个 Pending 在 IRR 上的中断是否发送给 CPU。与 TPR 不
同,它的值由 CPU 写而不是软件写。
static int vmx_interrupt_allowed(struct kvm_vcpu *vcpu)
{
return (vmcs_readl(GUEST_RFLAGS) & X86_EFLAGS_IF) &&
!(vmcs_read32(GUEST_INTERRUPTIBILITY_INFO) &
(GUEST_INTR_STATE_STI | GUEST_INTR_STATE_MOV_SS));
}
判断客户机中断标志寄存器和判断中断能力信息
static inline void kvm_queue_interrupt(struct kvm_vcpu *vcpu, u8 vector,
bool soft)
{
vcpu->arch.interrupt.pending = true;
vcpu->arch.interrupt.soft = soft;
vcpu->arch.interrupt.nr = vector;
}
static void vmx_inject_irq(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
uint32_t intr;
int irq = vcpu->arch.interrupt.nr;
trace_kvm_inj_virq(irq);
++vcpu->stat.irq_injections;
intr = irq | INTR_INFO_VALID_MASK;
if (vcpu->arch.interrupt.soft) {
intr |= INTR_TYPE_SOFT_INTR;
vmcs_write32(VM_ENTRY_INSTRUCTION_LEN,
vmx->vcpu.arch.event_exit_inst_len);
} else
intr |= INTR_TYPE_EXT_INTR;
vmcs_write32(VM_ENTRY_INTR_INFO_FIELD, intr);
}
由结构体成员vcpu,获取包含该成员结构体vmx, 这个转换由container_of(ptr, type, member) 实现的,由兴趣可以自己分析一下。
接下来设置VM-Entry interruption-infomation字段,字段格式如下:
0-7为中断向量
8-10位为中断类型
11错误代码
12-30为保留
31为有效
设置中断信息字段的中断向量,并将中断信息字段最高位(31)为置1,1表明中断有效。
根据中断向量类型为软中断或者硬件中断,设置中断信息字段。
最后把写入中断信息字段到VMCS的数据域,从而完成中断注入。
int kvm_ioapic_set_irq(struct kvm_ioapic *ioapic, int irq, int level)
{
u32 old_irr;
u32 mask = 1 << irq;
union kvm_ioapic_redirect_entry entry;
int ret = 1;
spin_lock(&ioapic->lock);
old_irr = ioapic->irr;
if (irq >= 0 && irq < IOAPIC_NUM_PINS) {
entry = ioapic->redirtbl[irq];
level ^= entry.fields.polarity;
if (!level)
ioapic->irr &= ~mask;
else {
int edge = (entry.fields.trig_mode == IOAPIC_EDGE_TRIG);
ioapic->irr |= mask;
if ((edge && old_irr != ioapic->irr) ||
(!edge && !entry.fields.remote_irr))
ret = ioapic_service(ioapic, irq);
else
ret = 0; /* report coalesced interrupt */
}
trace_kvm_ioapic_set_irq(entry.bits, irq, ret == 0);
}
spin_unlock(&ioapic->lock);
return ret;
}
注意:中断请求寄存器表示已接受的中断,但是尚未提交
获取中断请求寄存器内容。
判断irq引脚线是否小于24
获取相应引脚重定向表内容,触发电平异或中断管脚的极性,主要因为entry.fields.polarity为0表示高电平有效,level为1将表示产生中断
获取触发模式,设置中断请求寄存器,如果为边沿触发并且没有排队中断,当中断是 level 触发时,LAPIC 接收了该中断,remote_irr内容为1,LAPIC 写 EOI 时,remote_irr内容为0
static int ioapic_service(struct kvm_ioapic *ioapic, unsigned int idx)
{
union kvm_ioapic_redirect_entry *pent;
int injected = -1;
pent = &ioapic->redirtbl[idx];
if (!pent->fields.mask) {
injected = ioapic_deliver(ioapic, idx);
if (injected && pent->fields.trig_mode == IOAPIC_LEVEL_TRIG)
pent->fields.remote_irr = 1;
}
return injected;
}
如果中断屏蔽位没有设置,允许中断。
static int ioapic_deliver(struct kvm_ioapic *ioapic, int irq)
{
union kvm_ioapic_redirect_entry *entry = &ioapic->redirtbl[irq];
struct kvm_lapic_irq irqe;
ioapic_debug("dest=%x dest_mode=%x delivery_mode=%x "
"vector=%x trig_mode=%x\n",
entry->fields.dest, entry->fields.dest_mode,
entry->fields.delivery_mode, entry->fields.vector,
entry->fields.trig_mode);
irqe.dest_id = entry->fields.dest_id;
irqe.vector = entry->fields.vector;
irqe.dest_mode = entry->fields.dest_mode;
irqe.trig_mode = entry->fields.trig_mode;
irqe.delivery_mode = entry->fields.delivery_mode << 8;
irqe.level = 1;
irqe.shorthand = 0;
#ifdef CONFIG_X86
/* Always delivery PIT interrupt to vcpu 0 */
if (irq == 0) {
irqe.dest_mode = 0; /* Physical mode. */
/* need to read apic_id from apic regiest since
* it can be rewritten */
irqe.dest_id = ioapic->kvm->bsp_vcpu->vcpu_id;
}
#endif
return kvm_irq_delivery_to_apic(ioapic->kvm, NULL, &irqe);
}
获取中断目的地,中断向量,中断目的模式,中断触发模式,中断触发方式,中断触发电平。如果中断中断,重新设置中断目的地。将中断发往目的地local apic
int kvm_irq_delivery_to_apic(struct kvm *kvm, struct kvm_lapic *src,
struct kvm_lapic_irq *irq)
{
int i, r = -1;
struct kvm_vcpu *vcpu, *lowest = NULL;
if (irq->dest_mode == 0 && irq->dest_id == 0xff &&
kvm_is_dm_lowest_prio(irq))
printk(KERN_INFO "kvm: apic: phys broadcast and lowest prio\n");
kvm_for_each_vcpu(i, vcpu, kvm) {
if (!kvm_apic_present(vcpu))
continue;
if (!kvm_apic_match_dest(vcpu, src, irq->shorthand,
irq->dest_id, irq->dest_mode))
continue;
if (!kvm_is_dm_lowest_prio(irq)) {
if (r < 0)
r = 0;
r += kvm_apic_set_irq(vcpu, irq);
} else if (kvm_lapic_enabled(vcpu)) {
if (!lowest)
lowest = vcpu;
else if (kvm_apic_compare_prio(vcpu, lowest) < 0)
lowest = vcpu;
}
}
if (lowest)
r = kvm_apic_set_irq(lowest, irq);
}
如果为Physical Mode,并且dest_id=0xff目的地为广播,是lowest priority,打印警告信息。
寻找匹配vpic,找到话,是Delivery mode 为 lowest priority,这样 IOAPIC 的中断消息由优先级最低的 CPU 接收。不是话,触发中断。
static int __apic_accept_irq(struct kvm_lapic *apic, int delivery_mode,
int vector, int level, int trig_mode)
{
int result = 0;
struct kvm_vcpu *vcpu = apic->vcpu;
switch (delivery_mode) {
case APIC_DM_LOWEST:
vcpu->arch.apic_arb_prio++;
case APIC_DM_FIXED:
/* FIXME add logic for vcpu on reset */
if (unlikely(!apic_enabled(apic)))
break;
if (trig_mode) {
apic_debug("level trig mode for vector %d", vector);
apic_set_vector(vector, apic->regs + APIC_TMR);
} else
apic_clear_vector(vector, apic->regs + APIC_TMR);
result = !apic_test_and_set_irr(vector, apic);
trace_kvm_apic_accept_irq(vcpu->vcpu_id, delivery_mode,
trig_mode, vector, !result);
if (!result) {
if (trig_mode)
apic_debug("level trig mode repeatedly for "
"vector %d", vector);
break;
}
kvm_vcpu_kick(vcpu);
break;
设置TSR 即 Trigger Mode Register,用于表示当前正在处理中断的触发模式。1 为 level,0 为 edge,置中断请求寄存器。
kvm_vcpu_kick 产生处理器中断ipi ,重新调度,为中断注入做准备。
Physical Mode(Destination Mode为0时): 其值为APIC ID,用于标识一个唯一的APIC。
Logical Mode(Destination Mode为1时):其值根据LAPIC的不同配置,代表一组CPU(具体见LAPIC相关内容)
Interrupt Vector,中断向量,R/W。指定该中断对应的vector,范围从10h到FEh(x86架构前16个vector被系统预留,见后面相关内容)
表1-1 RTE格式
当IOAPIC某个管脚接收到中断信号后,会根据该管脚对应的RTE,格式化出一条中断消息,发送给某个CPU的LAPIC。从上表我们可以看出,该消息包含了一个中断的所有信息。
Destination Mode,目的地模式,R/W。
0:Physical Mode,解释见Destination Field
1:Logical Mode,同上
Delivery Mode,传送模式,R/W。用于指定该中断以何种方式发送给目的APIC,各种模式需要和相应的触发方式配合。可选的模式如下,字段相应的值以二进制表示:
Fixed: 000b,发送给Destination Filed列出的所有CPU,level、edge触发均可。
Lowest Priority:001b,发送给Destination Filed列出的CPU中,优先级最低的CPU(CPU的优先级见LAPIC相关内容)。Level、edge均可
SMI:010b,System Management Interrupt,系统管理中断。只能为edge触发,并且vector字段写0
NMI:100b,None Mask Interrupt,不可屏蔽中断。发送给Destination Field列出的所有CPU,Vector字段值被忽略。NMI是edge触发,Trigger Mode字段中的值对NMI无影响,但建议配置成edge。
INIT:101b,发送给Destination Filed列出的所有CPU,LAPIC收到后执行INIT中断(详细信息参考相关CPU spec中INIT中断一节)。触发模式同NMI。
ExtINT:111b,发送给Destination Filed列出的所有CPU。CPU收到该中断后,认为这是一个PIC发送的中断请求,并回应INTA信号(该INTA脚连接到的是与该管脚相连的PIC上,而非IOAPIC上)
笔者:ExtINT用于PIC接在APIC上的情况,见后面的Virtual Wire Mode
15
Trigger Mode,触发模式,R/W。指明该管脚的的中断由什么方式触发。
1:Level,电平触发
2:Edge,边沿触发