The VCPU executes synchronously w.r.t. userspace today, and therefore interrupt injection is pretty straight forward. However, we will soon need to be able to inject interrupts asynchronous to the execution of the VCPU due to the introduction of SMP, paravirtualized drivers, and asynchronous hypercalls. This patch adds support to the interrupt mechanism to force a VCPU to VMEXIT when a new interrupt is pending.
Signed-off-by: Gregory Haskins <[EMAIL PROTECTED]> --- drivers/kvm/kvm.h | 2 ++ drivers/kvm/kvm_main.c | 64 +++++++++++++++++++++++++++++++++++++++++++++--- drivers/kvm/svm.c | 52 +++++++++++++++++++++++++++++++++++++-- drivers/kvm/vmx.c | 38 +++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 7 deletions(-) diff --git a/drivers/kvm/kvm.h b/drivers/kvm/kvm.h index 41e4eaa..d5783dc 100644 --- a/drivers/kvm/kvm.h +++ b/drivers/kvm/kvm.h @@ -331,6 +331,7 @@ struct kvm_vcpu_irq { struct kvm_irqdevice dev; int pending; int deferred; + int guest_cpu; }; struct kvm_vcpu { @@ -346,6 +347,7 @@ struct kvm_vcpu { struct kvm_run *run; int interrupt_window_open; struct kvm_vcpu_irq irq; + pid_t owner; unsigned long regs[NR_VCPU_REGS]; /* for rsp: vcpu_load_rsp_rip() */ unsigned long rip; /* needs vcpu_load_rsp_rip() */ diff --git a/drivers/kvm/kvm_main.c b/drivers/kvm/kvm_main.c index a2e1e50..0c6a62a 100644 --- a/drivers/kvm/kvm_main.c +++ b/drivers/kvm/kvm_main.c @@ -1891,6 +1891,9 @@ static int kvm_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run) kvm_arch_ops->decache_regs(vcpu); } + vcpu->owner = current->pid; + smp_wmb(); + r = kvm_arch_ops->run(vcpu, kvm_run); out: @@ -2332,6 +2335,20 @@ out1: } /* + * This function is invoked whenever we want to interrupt a vcpu that is + * currently executing in guest-mode. It currently is a no-op because + * the simple delivery of the IPI to execute this function accomplishes our + * goal: To cause a VMEXIT. We pass the vcpu (which contains the + * vcpu->irq.task, etc) for future use + */ +static void kvm_vcpu_guest_intr(void *info) +{ +#ifdef NOT_YET + struct kvm_vcpu *vcpu = (struct kvm_vcpu*)info; +#endif +} + +/* * This function will be invoked whenever the vcpu->irq.dev raises its INTR * line */ @@ -2340,11 +2357,50 @@ static void kvm_vcpu_intr(struct kvm_irqsink *this, kvm_irqpin_t pin) { struct kvm_vcpu *vcpu = (struct kvm_vcpu*)this->private; - unsigned long flags; + int direct_ipi = -1; + + spin_lock_irq(&vcpu->irq.lock); + + if (!test_bit(pin, &vcpu->irq.pending)) { + /* + * Record the change.. + */ + __set_bit(pin, &vcpu->irq.pending); - spin_lock_irqsave(&vcpu->irq.lock, flags); - __set_bit(pin, &vcpu->irq.pending); - spin_unlock_irqrestore(&vcpu->irq.lock, flags); + /* + * then wake up the vcpu (if necessary) + */ + if (vcpu->owner != current->pid) { + if (vcpu->irq.guest_cpu != -1) { + /* + * If we are in guest mode, we can optimize + * the interrupt by invoking a host-IPI to the + * CPU which is running the guest. + */ + direct_ipi = vcpu->irq.guest_cpu; + BUG_ON(direct_ipi == smp_processor_id()); + } + } + } + + spin_unlock_irq(&vcpu->irq.lock); + + /* + * we can safely send the IPI outside of the lock-scope because the + * irq.pending has already been updated. This code assumes that + * userspace will not sleep on anything other than HLT instructions. + * HLT is covered in a race-free way because irq.pending was updated + * in the critical section, and handle_halt() which check if any + * interrupts are pending before returning to userspace. + * + * If it turns out that userspace can sleep on conditions other than + * HLT, this code will need to be enhanced to allow the irq.pending + * flags to be exported to userspace + */ + if (direct_ipi != -1) + smp_call_function_single(direct_ipi, + kvm_vcpu_guest_intr, + vcpu, 0, 0); } static void kvm_vcpu_irqsink_init(struct kvm_vcpu *vcpu) diff --git a/drivers/kvm/svm.c b/drivers/kvm/svm.c index dd0a149..6c6228d 100644 --- a/drivers/kvm/svm.c +++ b/drivers/kvm/svm.c @@ -1544,9 +1544,43 @@ static int svm_vcpu_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run) int r; again: + /* + * We disable interrupts until the next VMEXIT to eliminate a race + * condition for delivery of virtual interrutps. Note that this is + * probably not as bad as it sounds, as interrupts will still invoke + * a VMEXIT once transitioned to GUEST mode (and thus exit this lock + * scope) even if they are disabled. + */ + local_irq_disable(); + spin_lock(&vcpu->irq.lock); /* + * If there are any signals pending (virtual interrupt related or + * otherwise), don't even bother trying to enter guest mode... + */ + if (signal_pending(current)) { + kvm_run->exit_reason = KVM_EXIT_INTR; + spin_unlock(&vcpu->irq.lock); + local_irq_enable(); + r = -EINTR; + /* + * FIXME: We probably want to move this whole lock-block below + * the host->guest state loading so we don't restore when + * the system was never saved to begin with + */ + goto out; + } + + /* + * There are optimizations we can make when signaling interrupts + * if we know the VCPU is in GUEST mode, so record the guest's + * CPU to both serve as an indicator of vcpu state and a target + * for our interrupts + */ + vcpu->irq.guest_cpu = task_cpu(current); + + /* * We must inject interrupts (if any) while the irq_lock * is held */ @@ -1688,6 +1722,15 @@ again: #endif : "cc", "memory" ); + /* + * Signal that we have transitioned back to host mode + */ + spin_lock(&vcpu->irq.lock); + vcpu->irq.guest_cpu = -1; + spin_unlock(&vcpu->irq.lock); + + local_irq_enable(); + if (vcpu->fpu_active) { fx_save(vcpu->guest_fx_image); fx_restore(vcpu->host_fx_image); @@ -1734,20 +1777,23 @@ again: if (r > 0) { if (signal_pending(current)) { ++vcpu->stat.signal_exits; - post_kvm_run_save(vcpu, kvm_run); kvm_run->exit_reason = KVM_EXIT_INTR; - return -EINTR; + r = -EINTR; + goto out; } if (dm_request_for_irq_injection(vcpu, kvm_run)) { ++vcpu->stat.request_irq_exits; post_kvm_run_save(vcpu, kvm_run); kvm_run->exit_reason = KVM_EXIT_INTR; - return -EINTR; + r = -EINTR; + goto out; } kvm_resched(vcpu); goto again; } + + out: post_kvm_run_save(vcpu, kvm_run); return r; } diff --git a/drivers/kvm/vmx.c b/drivers/kvm/vmx.c index e4984eb..543c27e 100644 --- a/drivers/kvm/vmx.c +++ b/drivers/kvm/vmx.c @@ -2009,9 +2009,38 @@ preempted: kvm_guest_debug_pre(vcpu); again: + /* + * We disable interrupts until the next VMEXIT to eliminate a race + * condition for delivery of virtual interrutps. Note that this is + * probably not as bad as it sounds, as interrupts will still invoke + * a VMEXIT once transitioned to GUEST mode (and thus exit this lock + * scope) even if they are disabled. + */ + local_irq_disable(); + spin_lock(&vcpu->irq.lock); /* + * If there are any signals pending (virtual interrupt related or + * otherwise), don't even bother trying to enter guest mode... + */ + if (signal_pending(current)) { + kvm_run->exit_reason = KVM_EXIT_INTR; + spin_unlock(&vcpu->irq.lock); + local_irq_enable(); + r = -EINTR; + goto out; + } + + /* + * There are optimizations we can make when signaling interrupts + * if we know the VCPU is in GUEST mode, so record the guest's + * CPU to both serve as an indicator of vcpu state and a target + * for our interrupts + */ + vcpu->irq.guest_cpu = task_cpu(current); + + /* * We must inject interrupts (if any) while the irq.lock * is held */ @@ -2147,6 +2176,15 @@ again: [cr2]"i"(offsetof(struct kvm_vcpu, cr2)) : "cc", "memory" ); + /* + * Signal that we have transitioned back to host mode + */ + spin_lock(&vcpu->irq.lock); + vcpu->irq.guest_cpu = -1; + spin_unlock(&vcpu->irq.lock); + + local_irq_enable(); + ++vcpu->stat.exits; vcpu->interrupt_window_open = (vmcs_read32(GUEST_INTERRUPTIBILITY_INFO) & 3) == 0; ------------------------------------------------------------------------- This SF.net email is sponsored by DB2 Express Download DB2 Express C - the FREE version of DB2 express and take control of your XML. No limits. Just data. Click to get it now. http://sourceforge.net/powerbar/db2/ _______________________________________________ kvm-devel mailing list kvm-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/kvm-devel