硬件中断发生(qemu模拟设备)
1.硬件产生中断的接口
void qemu_set_irq(qemu_irq irq, int level);
2.中断过程
void qemu_set_irq(qemu_irq irq, int level)
{
if (!irq)
return;
irq->handler(irq->opaque, irq->n, level);
}
设置中断控制器hander,大致分为三种情况
1.cpu_irq的hander===> pic_irq_request
2.内核模拟中断控制器的hander===>kvm_i8259_set_irq
3.用户模拟中断控制器的hander===>i8259_set_irq
/* PC hardware initialisation */
static void pc_init1()
{
cpu_irq = qemu_allocate_irqs(pic_irq_request, NULL, 1);
#ifdef KVM_CAP_IRQCHIP
if (kvm_enabled() && kvm_irqchip_in_kernel()) {
isa_irq_state = qemu_mallocz(sizeof(*isa_irq_state));
isa_irq = i8259 = kvm_i8259_init(cpu_irq[0]);
} else
#endif
{
i8259 = i8259_init(cpu_irq[0]);
isa_irq_state = qemu_mallocz(sizeof(*isa_irq_state));
isa_irq_state->i8259 = i8259;
isa_irq = qemu_allocate_irqs(isa_irq_handler, isa_irq_state, 24);
}
先研究用户空间中断控制器的中断发生过程
static void i8259_set_irq(void *opaque, int irq, int level)
{
PicState2 *s = opaque;
pic_set_irq1(&s->pics[irq >> 3], irq & 7, level);
pic_update_irq(s);
}
中断触发方式分为电平触发和边沿触发,isa设备大多数采用边沿触发,pci设备采用电平触发。
假如采用边沿触发,如果leveld等于1,并且没有等待的中断请求(没有pending中断请求),设置中断请求寄存器为1,另外设置pending中断请求为1.
如果有pending中断请求,并不设置中断请求寄存器,可见允许中断丢失。
/* set irq level. If an edge is detected, then the IRR is set to 1 */
static inline void pic_set_irq1(PicState *s, int irq, int level)
{
int mask;
mask = 1 << irq;
if (s->elcr & mask) {
/* level triggered */
if (level) {
s->irr |= mask;
s->last_irr |= mask;
} else {
s->irr &= ~mask;
s->last_irr &= ~mask;
}
} else {
/* edge triggered */
if (level) {
if ((s->last_irr & mask) == 0)
s->irr |= mask;
s->last_irr |= mask;
} else {
s->last_irr &= ~mask;
}
}
}
每次有中断请求,必须调用该函数。该函数调用造成中断嵌套。另外必须话必须注入中断。什么情况下是必须呢?具体可参照pic_get_irq()函数。
这个函数对产生中断优先级和正在处理中断优先级进行比较,如果大于话,注入请求中断。注入中断时机由qemu_irq_raise触发的,下面列出该函数。
/* raise irq to CPU if necessary. must be called every time the active
irq may change */
void pic_update_irq(PicState2 *s)
{
/* look at requested irq */
irq = pic_get_irq(&s->pics[0]);
if (irq >= 0) {
qemu_irq_raise(s->parent_irq);
}
}
不要认为,好像又循环到中断入口了,实际没有,关键在于参数s->parent_irq,该参数实际调用cpu_irq的hander===> pic_irq_request
static inline void qemu_irq_raise(qemu_irq irq)
{
qemu_set_irq(irq, 1);
}
目前只研究用户态模拟中断控制器i8259(剔除KVM模拟和apic中断控制器), cpu_interrupt函数实际中断目前虚拟处理器运行,为硬件中断注入做好准备,目前就是中断注入时机。如何中断(暂停)虚拟处理器运行呢,通过该函数pthread_kill(env->kvm_cpu_state.thread, SIG_IPI)中断处理器运行;
static void pic_irq_request(void *opaque, int irq, int level)
{
CPUState *env = first_cpu;
if (level)
cpu_interrupt(env, CPU_INTERRUPT_HARD);
else
cpu_reset_interrupt(env, CPU_INTERRUPT_HARD);
}
}
中断注入
中断注入负责将虚拟中断控制器采集的中断请求注入到虚拟处理器。需要处理两个问题,什么时候注入,如何注入?
static int kvm_main_loop_cpu(CPUState *env)
{
while (1) {
int run_cpu = !is_cpu_stopped(env);
if (run_cpu && !kvm_irqchip_in_kernel()) {
process_irqchip_events(env);
run_cpu = !env->halted;
}
if (run_cpu) {
kvm_cpu_exec(env);
kvm_main_loop_wait(env, 0);
} else {
kvm_main_loop_wait(env, 1000);
}
}
pthread_mutex_unlock(&qemu_mutex);
return 0;
}
如果中断控制器不是内核空间模拟(用户空间模拟),进行中断注入。
kvm_main_loop_cpu-->kvm_cpu_exec-->kvm_run
int kvm_run(CPUState *env)
{
#if !defined(__s390__)
if (!kvm->irqchip_in_kernel)
run->request_interrupt_window = kvm_arch_try_push_interrupts(env);
#endif
}
1.首先满足三个条件
1)内核kvm准备好了接受中断注入
2)有中断请求并且为硬件中断请求
3)虚拟处理器运行中断(开中断)
2.获取中断请求号
3.kvm注入中断请求
int kvm_arch_try_push_interrupts(void *opaque)
{
CPUState *env = cpu_single_env;
int r, irq;
1.硬件产生中断的接口
void qemu_set_irq(qemu_irq irq, int level);
2.中断过程
void qemu_set_irq(qemu_irq irq, int level)
{
if (!irq)
return;
irq->handler(irq->opaque, irq->n, level);
}
设置中断控制器hander,大致分为三种情况
1.cpu_irq的hander===> pic_irq_request
2.内核模拟中断控制器的hander===>kvm_i8259_set_irq
3.用户模拟中断控制器的hander===>i8259_set_irq
/* PC hardware initialisation */
static void pc_init1()
{
cpu_irq = qemu_allocate_irqs(pic_irq_request, NULL, 1);
#ifdef KVM_CAP_IRQCHIP
if (kvm_enabled() && kvm_irqchip_in_kernel()) {
isa_irq_state = qemu_mallocz(sizeof(*isa_irq_state));
isa_irq = i8259 = kvm_i8259_init(cpu_irq[0]);
} else
#endif
{
i8259 = i8259_init(cpu_irq[0]);
isa_irq_state = qemu_mallocz(sizeof(*isa_irq_state));
isa_irq_state->i8259 = i8259;
isa_irq = qemu_allocate_irqs(isa_irq_handler, isa_irq_state, 24);
}
先研究用户空间中断控制器的中断发生过程
static void i8259_set_irq(void *opaque, int irq, int level)
{
PicState2 *s = opaque;
pic_set_irq1(&s->pics[irq >> 3], irq & 7, level);
pic_update_irq(s);
}
中断触发方式分为电平触发和边沿触发,isa设备大多数采用边沿触发,pci设备采用电平触发。
假如采用边沿触发,如果leveld等于1,并且没有等待的中断请求(没有pending中断请求),设置中断请求寄存器为1,另外设置pending中断请求为1.
如果有pending中断请求,并不设置中断请求寄存器,可见允许中断丢失。
/* set irq level. If an edge is detected, then the IRR is set to 1 */
static inline void pic_set_irq1(PicState *s, int irq, int level)
{
int mask;
mask = 1 << irq;
if (s->elcr & mask) {
/* level triggered */
if (level) {
s->irr |= mask;
s->last_irr |= mask;
} else {
s->irr &= ~mask;
s->last_irr &= ~mask;
}
} else {
/* edge triggered */
if (level) {
if ((s->last_irr & mask) == 0)
s->irr |= mask;
s->last_irr |= mask;
} else {
s->last_irr &= ~mask;
}
}
}
每次有中断请求,必须调用该函数。该函数调用造成中断嵌套。另外必须话必须注入中断。什么情况下是必须呢?具体可参照pic_get_irq()函数。
这个函数对产生中断优先级和正在处理中断优先级进行比较,如果大于话,注入请求中断。注入中断时机由qemu_irq_raise触发的,下面列出该函数。
/* raise irq to CPU if necessary. must be called every time the active
irq may change */
void pic_update_irq(PicState2 *s)
{
/* look at requested irq */
irq = pic_get_irq(&s->pics[0]);
if (irq >= 0) {
qemu_irq_raise(s->parent_irq);
}
}
不要认为,好像又循环到中断入口了,实际没有,关键在于参数s->parent_irq,该参数实际调用cpu_irq的hander===> pic_irq_request
static inline void qemu_irq_raise(qemu_irq irq)
{
qemu_set_irq(irq, 1);
}
目前只研究用户态模拟中断控制器i8259(剔除KVM模拟和apic中断控制器), cpu_interrupt函数实际中断目前虚拟处理器运行,为硬件中断注入做好准备,目前就是中断注入时机。如何中断(暂停)虚拟处理器运行呢,通过该函数pthread_kill(env->kvm_cpu_state.thread, SIG_IPI)中断处理器运行;
static void pic_irq_request(void *opaque, int irq, int level)
{
CPUState *env = first_cpu;
if (level)
cpu_interrupt(env, CPU_INTERRUPT_HARD);
else
cpu_reset_interrupt(env, CPU_INTERRUPT_HARD);
}
}
中断注入
中断注入负责将虚拟中断控制器采集的中断请求注入到虚拟处理器。需要处理两个问题,什么时候注入,如何注入?
static int kvm_main_loop_cpu(CPUState *env)
{
while (1) {
int run_cpu = !is_cpu_stopped(env);
if (run_cpu && !kvm_irqchip_in_kernel()) {
process_irqchip_events(env);
run_cpu = !env->halted;
}
if (run_cpu) {
kvm_cpu_exec(env);
kvm_main_loop_wait(env, 0);
} else {
kvm_main_loop_wait(env, 1000);
}
}
pthread_mutex_unlock(&qemu_mutex);
return 0;
}
如果中断控制器不是内核空间模拟(用户空间模拟),进行中断注入。
kvm_main_loop_cpu-->kvm_cpu_exec-->kvm_run
int kvm_run(CPUState *env)
{
#if !defined(__s390__)
if (!kvm->irqchip_in_kernel)
run->request_interrupt_window = kvm_arch_try_push_interrupts(env);
#endif
}
1.首先满足三个条件
1)内核kvm准备好了接受中断注入
2)有中断请求并且为硬件中断请求
3)虚拟处理器运行中断(开中断)
2.获取中断请求号
3.kvm注入中断请求
int kvm_arch_try_push_interrupts(void *opaque)
{
CPUState *env = cpu_single_env;
int r, irq;