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 | 5 +++ drivers/kvm/kvm_main.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++- drivers/kvm/svm.c | 43 ++++++++++++++++++++++++++++ drivers/kvm/vmx.c | 43 ++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletions(-) diff --git a/drivers/kvm/kvm.h b/drivers/kvm/kvm.h index d41d653..15c8bec 100644 --- a/drivers/kvm/kvm.h +++ b/drivers/kvm/kvm.h @@ -321,6 +321,8 @@ void kvm_io_bus_register_dev(struct kvm_io_bus *bus, #define NR_IRQ_WORDS KVM_IRQ_BITMAP_SIZE(unsigned long) +#define KVM_SIGNAL_VIRTUAL_INTERRUPT 33 /* Hardcoded for now */ + /* * structure for maintaining info for interrupting an executing VCPU */ @@ -329,6 +331,9 @@ struct kvm_vcpu_irq { struct kvm_irqdevice dev; int pending; int deferred; + struct task_struct *task; + int signo; + int guest_mode; }; struct kvm_vcpu { diff --git a/drivers/kvm/kvm_main.c b/drivers/kvm/kvm_main.c index 9aeb2f7..6acbd9b 100644 --- a/drivers/kvm/kvm_main.c +++ b/drivers/kvm/kvm_main.c @@ -304,6 +304,10 @@ static struct kvm *kvm_create_vm(void) memset(&vcpu->irq, 0, sizeof(vcpu->irq)); spin_lock_init(&vcpu->irq.lock); vcpu->irq.deferred = -1; + /* + * This should be settable by userspace someday + */ + vcpu->irq.signo = KVM_SIGNAL_VIRTUAL_INTERRUPT; vcpu->cpu = -1; vcpu->kvm = kvm; @@ -366,13 +370,20 @@ static void free_pio_guest_pages(struct kvm_vcpu *vcpu) static void kvm_free_vcpu(struct kvm_vcpu *vcpu) { + unsigned long irqsave; + if (!vcpu->vmcs) return; vcpu_load(vcpu); kvm_mmu_destroy(vcpu); vcpu_put(vcpu); + + spin_lock_irqsave(&vcpu->irq.lock, irqsave); + vcpu->irq.task = NULL; + spin_unlock_irqrestore(&vcpu->irq.lock, irqsave); kvm_irqdevice_destructor(&vcpu->irq.dev); + kvm_arch_ops->vcpu_free(vcpu); free_page((unsigned long)vcpu->run); vcpu->run = NULL; @@ -1831,6 +1842,7 @@ static int kvm_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run) { int r; sigset_t sigsaved; + unsigned long irqsaved; vcpu_load(vcpu); @@ -1868,6 +1880,10 @@ static int kvm_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run) kvm_arch_ops->decache_regs(vcpu); } + spin_lock_irqsave(&vcpu->irq.lock, irqsaved); + vcpu->irq.task = current; + spin_unlock_irqrestore(&vcpu->irq.lock, irqsaved); + r = kvm_arch_ops->run(vcpu, kvm_run); out: @@ -2309,6 +2325,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 */ @@ -2318,10 +2348,52 @@ static void kvm_vcpu_intr(struct kvm_irqsink *this, { struct kvm_vcpu *vcpu = (struct kvm_vcpu*)this->private; unsigned long flags; + int direct_ipi = -1; spin_lock_irqsave(&vcpu->irq.lock, flags); - __set_bit(pin, &vcpu->irq.pending); + + if (!test_bit(pin, &vcpu->irq.pending)) { + /* + * Record the change.. + */ + __set_bit(pin, &vcpu->irq.pending); + + /* + * then wake up the vcpu (if necessary) + */ + if (vcpu->irq.task && (vcpu->irq.task != current)) { + if (vcpu->irq.guest_mode) { + /* + * If we are in guest mode, we can optimize + * the IPI by executing a function directly + * on the owning processor. + */ + direct_ipi = task_cpu(vcpu->irq.task); + BUG_ON(direct_ipi == smp_processor_id()); + } else + /* + * otherwise, we must assume that we could be + * blocked anywhere, including userspace. Send + * a signal to give everyone a chance to get + * notification + */ + send_sig(vcpu->irq.signo, vcpu->irq.task, 0); + } + } + spin_unlock_irqrestore(&vcpu->irq.lock, flags); + + if (direct_ipi != -1) { + /* + * Not sure if disabling preemption is needed. + * The kick_process() code does this so I copied it + */ + preempt_disable(); + smp_call_function_single(direct_ipi, + kvm_vcpu_guest_intr, + vcpu, 0, 0); + preempt_enable(); + } } static void kvm_vcpu_irqsink_init(struct kvm_vcpu *vcpu) diff --git a/drivers/kvm/svm.c b/drivers/kvm/svm.c index 39cc596..a73232b 100644 --- a/drivers/kvm/svm.c +++ b/drivers/kvm/svm.c @@ -1528,11 +1528,40 @@ static int svm_vcpu_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run) u16 gs_selector; u16 ldt_selector; int r; + unsigned long irq_flags; 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. + * + * FIXME: Do we need to do anything additional to mask IPI/NMIs? + */ + local_irq_save(irq_flags); + 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_restore(irq_flags); + return -EINTR; + } + + /* + * There are optimizations we can make when signaling interrupts + * if we know the VCPU is in GUEST mode, so mark that here + */ + vcpu->irq.guest_mode = 1; + + /* * We must inject interrupts (if any) while the irq_lock * is held */ @@ -1674,6 +1703,13 @@ again: #endif : "cc", "memory" ); + /* + * FIXME: We'd like to turn on interrupts ASAP, but is this so early + * that we will mess up the state of the CPU before we fully + * transition from guest to host? + */ + local_irq_restore(irq_flags); + if (vcpu->fpu_active) { fx_save(vcpu->guest_fx_image); fx_restore(vcpu->host_fx_image); @@ -1696,6 +1732,13 @@ again: reload_tss(vcpu); /* + * Signal that we have transitioned back to host mode + */ + spin_lock_irqsave(&vcpu->irq.lock, irq_flags); + vcpu->irq.guest_mode = 0; + spin_unlock_irqrestore(&vcpu->irq.lock, irq_flags); + + /* * Profile KVM exit RIPs: */ if (unlikely(prof_on == KVM_PROFILING)) diff --git a/drivers/kvm/vmx.c b/drivers/kvm/vmx.c index 5665286..1aac001 100644 --- a/drivers/kvm/vmx.c +++ b/drivers/kvm/vmx.c @@ -1890,6 +1890,7 @@ static int vmx_vcpu_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run) u16 fs_sel, gs_sel, ldt_sel; int fs_gs_ldt_reload_needed; int r; + unsigned long irq_flags; preempted: /* @@ -1924,9 +1925,37 @@ preempted: if (vcpu->guest_debug.enabled) kvm_guest_debug_pre(vcpu); + /* + * 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. + * + * FIXME: Do we need to do anything additional to mask IPI/NMIs? + */ + local_irq_save(irq_flags); + 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_restore(irq_flags); + return -EINTR; + } + + /* + * There are optimizations we can make when signaling interrupts + * if we know the VCPU is in GUEST mode, so mark that here + */ + vcpu->irq.guest_mode = 1; + + /* * We must inject interrupts (if any) while the irq.lock * is held */ @@ -2067,12 +2096,26 @@ again: [cr2]"i"(offsetof(struct kvm_vcpu, cr2)) : "cc", "memory" ); + /* + * FIXME: We'd like to turn on interrupts ASAP, but is this so early + * that we will mess up the state of the CPU before we fully + * transition from guest to host? + */ + local_irq_restore(irq_flags); + ++vcpu->stat.exits; vcpu->interrupt_window_open = (vmcs_read32(GUEST_INTERRUPTIBILITY_INFO) & 3) == 0; asm ("mov %0, %%ds; mov %0, %%es" : : "r"(__USER_DS)); + /* + * Signal that we have transitioned back to host mode + */ + spin_lock_irqsave(&vcpu->irq.lock, irq_flags); + vcpu->irq.guest_mode = 0; + spin_unlock_irqrestore(&vcpu->irq.lock, irq_flags); + if (unlikely(fail)) { kvm_run->exit_reason = KVM_EXIT_FAIL_ENTRY; kvm_run->fail_entry.hardware_entry_failure_reason ------------------------------------------------------------------------- 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