Skip to contentSkip to conversation

use smp_send_reschedule in vcpu_kick / assigned dev host intx race fix

PATCH In review [email protected] 4 participants 19 messages Apr 27, 2009 – May 10, 2009

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover use smp_send_reschedule in vcpu_kick / assigned dev host intx race fix

#1 lore raw

PATCH 1/4 qemu: external module: smp_send_reschedule compat

smp_send_reschedule was exported (via smp_ops) in v2.6.24. Create a compat function which schedules the IPI to keventd context, in case interrupts are disabled, for kernels < 2.6.24.

kvm/kernel/external-module-compat-comm.h +8 -0
index c955927..852a8c1 100644
@@ -116,6 +116,10 @@ int kvm_smp_call_function_single(int cpu, void (*func)(void *info),
 
 #endif
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+#define nr_cpu_ids NR_CPUS
+#endif
+
 #include <linux/miscdevice.h>
 #ifndef KVM_MINOR
 #define KVM_MINOR 232
@@ -220,6 +224,10 @@ int kvm_smp_call_function_mask(cpumask_t mask, void (*func) (void *info),
 
 #define smp_call_function_mask kvm_smp_call_function_mask
 
+void kvm_smp_send_reschedule(int cpu);
+
+#define smp_send_reschedule kvm_smp_send_reschedule
+
 #endif
 
 /* empty_zero_page isn't exported in all kernels */
kvm/kernel/external-module-compat.c +52 -0

58 lines not on this page · raw message

kvm/kernel/x86/hack-module.awk +2 -0
index 260eeef..f4d14da 100644
@@ -35,6 +35,8 @@ BEGIN { split("INIT_WORK desc_struct ldttss_desc64 desc_ptr " \
 
 { sub(/match->dev->msi_enabled/, "kvm_pcidev_msi_enabled(match->dev)") }
 
+{ sub(/smp_send_reschedule/, "kvm_smp_send_reschedule") }
+
 /^static void __vmx_load_host_state/ {
     vmx_load_host_state = 1
 }
#2 lore raw

Re: [patch 1/4] qemu: external module: smp_send_reschedule compat

kvm/kernel/external-module-compat-comm.h int kvm_smp_call_function_single(int cpu, void (*func)(void *info), · line 122

Quoted 11 lines · [email protected] wrote:

smp_send_reschedule was exported (via smp_ops) in v2.6.24.
Create a compat function which schedules the IPI to keventd context, in case interrupts are disabled, for kernels < 2.6.24.

#endif

kvm/kernel/external-module-compat-comm.h +4 -0
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+#define nr_cpu_ids NR_CPUS
+#endif
+

Doesn't seem related? Please send as a separate patch.

Quoted 7 lines
kvm/kernel/x86/hack-module.awk +0 -0
index 260eeef..f4d14da 100644

kvm/kernel/x86/hack-module.awk BEGIN { split("INIT_WORK desc_struct ldttss_desc64 desc_ptr " \ · line 42

Quoted 8 lines
kvm/kernel/x86/hack-module.awk +2 -0
@@ -35,6 +35,8 @@ BEGIN { split("INIT_WORK desc_struct ldttss_desc64 desc_ptr " \
 
 { sub(/match->dev->msi_enabled/, "kvm_pcidev_msi_enabled(match->dev)") }
 
+{ sub(/smp_send_reschedule/, "kvm_smp_send_reschedule") }
+
 /^static void __vmx_load_host_state/ {
     vmx_load_host_state = 1
 }

There's a bit on the top of hack-module.awk that does this slightly simpler. Please also adjust ia64.

This now resides in kvm-kmod.git, please patch against that.

Signature · 1 line
error compiling committee.c: too many arguments to function
#3 lore raw

PATCH 2/4 KVM: x86: wake up waitqueue before calling get_cpu()

In reply to [email protected]

From: Jan Blunck <[email protected]>

This moves the get_cpu() call down to be called after we wake up the waiters. Therefore the waitqueue locks can savely be rt mutex.

arch/x86/kvm/x86.c +2 -1
Index: kvm/arch/x86/kvm/x86.c
===================================================================
@@ -4544,7 +4544,7 @@ static void vcpu_kick_intr(void *info)
 void kvm_vcpu_kick(struct kvm_vcpu *vcpu)
 {
 	int ipi_pcpu = vcpu->cpu;
-	int cpu = get_cpu();
+	int cpu;
 
 	if (waitqueue_active(&vcpu->wq)) {
 		wake_up_interruptible(&vcpu->wq);
@@ -4554,6 +4554,7 @@ void kvm_vcpu_kick(struct kvm_vcpu *vcpu
 	 * We may be called synchronously with irqs disabled in guest mode,
 	 * So need not to call smp_call_function_single() in that case.
 	 */
+	cpu = get_cpu();
 	if (vcpu->guest_mode && vcpu->cpu != cpu)
 		smp_call_function_single(ipi_pcpu, vcpu_kick_intr, vcpu, 0);
 	put_cpu();
#4 lore raw

PATCH 3/4 KVM: use smp_send_reschedule in kvm_vcpu_kick

In reply to [email protected]

KVM uses a function call IPI to cause the exit of a guest running on a physical cpu. For virtual interrupt notification there is no need to wait on IPI receival, or to execute any function.

This is exactly what the reschedule IPI does, without the overhead of function IPI. So use it instead of smp_call_function_single in kvm_vcpu_kick.

Also change the "guest_mode" variable to a bit in vcpu->requests, and use that to collapse multiple IPI's that would be issued between the first one and zeroing of guest mode.

This allows kvm_vcpu_kick to called with interrupts disabled.

arch/ia64/kernel/irq_ia64.c +3 -0
Index: kvm/arch/ia64/kernel/irq_ia64.c
===================================================================
@@ -610,6 +610,9 @@ static struct irqaction ipi_irqaction = 
 	.name =		"IPI"
 };
 
+/*
+ * KVM uses this interrupt to force a cpu out of guest mode
+ */
 static struct irqaction resched_irqaction = {
 	.handler =	dummy_handler,
 	.flags =	IRQF_DISABLED,
arch/ia64/kvm/kvm-ia64.c +8 -14
Index: kvm/arch/ia64/kvm/kvm-ia64.c
===================================================================
@@ -668,7 +668,7 @@ again:
 	host_ctx = kvm_get_host_context(vcpu);
 	guest_ctx = kvm_get_guest_context(vcpu);
 
-	vcpu->guest_mode = 1;
+	clear_bit(KVM_REQ_KICK, &vcpu->requests);
 
 	r = kvm_vcpu_pre_transition(vcpu);
 	if (r < 0)
@@ -685,7 +685,7 @@ again:
 	kvm_vcpu_post_transition(vcpu);
 
 	vcpu->arch.launched = 1;
-	vcpu->guest_mode = 0;
+	set_bit(KVM_REQ_KICK, &vcpu->requests);
 	local_irq_enable();
 
 	/*
@@ -1884,24 +1884,18 @@ void kvm_arch_hardware_unsetup(void)
 {
 }
 
-static void vcpu_kick_intr(void *info)
-{
-#ifdef DEBUG
-	struct kvm_vcpu *vcpu = (struct kvm_vcpu *)info;
-	printk(KERN_DEBUG"vcpu_kick_intr %p \n", vcpu);
-#endif
-}
-
 void kvm_vcpu_kick(struct kvm_vcpu *vcpu)
 {
-	int ipi_pcpu = vcpu->cpu;
-	int cpu = get_cpu();
+	int me;
+	int cpu = vcpu->cpu;
 
 	if (waitqueue_active(&vcpu->wq))
 		wake_up_interruptible(&vcpu->wq);
 
-	if (vcpu->guest_mode && cpu != ipi_pcpu)
-		smp_call_function_single(ipi_pcpu, vcpu_kick_intr, vcpu, 0);
+	me = get_cpu();
+	if (cpu != me && (unsigned) cpu < nr_cpu_ids && cpu_online(cpu))
+		if (!test_and_set_bit(KVM_REQ_KICK, &vcpu->requests))
+			smp_send_reschedule(cpu);
 	put_cpu();
 }
 
arch/x86/kernel/smp.c +3 -0
Index: kvm/arch/x86/kernel/smp.c
===================================================================
@@ -172,6 +172,9 @@ void smp_reschedule_interrupt(struct pt_
 {
 	ack_APIC_irq();
 	inc_irq_stat(irq_resched_count);
+	/*
+	 * KVM uses this interrupt to force a cpu out of guest mode
+	 */
 }
 
 void smp_call_function_interrupt(struct pt_regs *regs)
arch/x86/kvm/x86.c +11 -25
Index: kvm/arch/x86/kvm/x86.c
===================================================================
@@ -3197,6 +3197,9 @@ static int vcpu_enter_guest(struct kvm_v
 
 	local_irq_disable();
 
+	clear_bit(KVM_REQ_KICK, &vcpu->requests);
+	smp_mb__after_clear_bit();
+
 	if (vcpu->requests || need_resched() || signal_pending(current)) {
 		local_irq_enable();
 		preempt_enable();
@@ -3204,13 +3207,6 @@ static int vcpu_enter_guest(struct kvm_v
 		goto out;
 	}
 
-	vcpu->guest_mode = 1;
-	/*
-	 * Make sure that guest_mode assignment won't happen after
-	 * testing the pending IRQ vector bitmap.
-	 */
-	smp_wmb();
-
 	if (vcpu->arch.exception.pending)
 		__queue_exception(vcpu);
 	else if (irqchip_in_kernel(vcpu->kvm))
@@ -3252,7 +3248,7 @@ static int vcpu_enter_guest(struct kvm_v
 	set_debugreg(vcpu->arch.host_dr6, 6);
 	set_debugreg(vcpu->arch.host_dr7, 7);
 
-	vcpu->guest_mode = 0;
+	set_bit(KVM_REQ_KICK, &vcpu->requests);
 	local_irq_enable();
 
 	++vcpu->stat.exits;
@@ -4550,30 +4546,20 @@ int kvm_arch_vcpu_runnable(struct kvm_vc
 	       || vcpu->arch.nmi_pending;
 }
 
-static void vcpu_kick_intr(void *info)
-{
-#ifdef DEBUG
-	struct kvm_vcpu *vcpu = (struct kvm_vcpu *)info;
-	printk(KERN_DEBUG "vcpu_kick_intr %p \n", vcpu);
-#endif
-}
-
 void kvm_vcpu_kick(struct kvm_vcpu *vcpu)
 {
-	int ipi_pcpu = vcpu->cpu;
-	int cpu;
+	int me;
+	int cpu = vcpu->cpu;
 
 	if (waitqueue_active(&vcpu->wq)) {
 		wake_up_interruptible(&vcpu->wq);
 		++vcpu->stat.halt_wakeup;
 	}
-	/*
-	 * We may be called synchronously with irqs disabled in guest mode,
-	 * So need not to call smp_call_function_single() in that case.
-	 */
-	cpu = get_cpu();
-	if (vcpu->guest_mode && vcpu->cpu != cpu)
-		smp_call_function_single(ipi_pcpu, vcpu_kick_intr, vcpu, 0);
+
+	me = get_cpu();
+	if (cpu != me && (unsigned)cpu < nr_cpu_ids && cpu_online(cpu))
+		if (!test_and_set_bit(KVM_REQ_KICK, &vcpu->requests))
+			smp_send_reschedule(cpu);
 	put_cpu();
 }
 
include/linux/kvm_host.h +1 -1
Index: kvm/include/linux/kvm_host.h
===================================================================
@@ -38,6 +38,7 @@
 #define KVM_REQ_UNHALT             6
 #define KVM_REQ_MMU_SYNC           7
 #define KVM_REQ_KVMCLOCK_UPDATE    8
+#define KVM_REQ_KICK               9
 
 #define KVM_USERSPACE_IRQ_SOURCE_ID	0
 
@@ -72,7 +73,6 @@ struct kvm_vcpu {
 	struct mutex mutex;
 	int   cpu;
 	struct kvm_run *run;
-	int guest_mode;
 	unsigned long requests;
 	unsigned long guest_debug;
 	int fpu_active;
#5 lore raw

PATCH 4/4 KVM: protect assigned dev workqueue, int handler and irq acker

In reply to [email protected]

kvm_assigned_dev_ack_irq is vulnerable to a race condition with the interrupt handler function. It does:

        if (dev->host_irq_disabled) {
                enable_irq(dev->host_irq);
                dev->host_irq_disabled = false;
        }

If an interrupt triggers before the host->dev_irq_disabled assignment, it will disable the interrupt and set dev->host_irq_disabled to true.

On return to kvm_assigned_dev_ack_irq, dev->host_irq_disabled is set to false, and the next kvm_assigned_dev_ack_irq call will fail to reenable it.

Other than that, having the interrupt handler and work handlers run in parallel sounds like asking for trouble (could not spot any obvious problem, but better not have to, its fragile).

include/linux/kvm_host.h +1 -0
Index: kvm/include/linux/kvm_host.h
===================================================================
@@ -345,6 +345,7 @@ struct kvm_assigned_dev_kernel {
 	int flags;
 	struct pci_dev *dev;
 	struct kvm *kvm;
+	spinlock_t assigned_dev_lock;
 };
 
 struct kvm_irq_mask_notifier {
virt/kvm/kvm_main.c +12 -1
Index: kvm/virt/kvm/kvm_main.c
===================================================================
@@ -42,6 +42,7 @@
 #include <linux/mman.h>
 #include <linux/swap.h>
 #include <linux/bitops.h>
+#include <linux/spinlock.h>
 
 #include <asm/processor.h>
 #include <asm/io.h>
@@ -130,6 +131,7 @@ static void kvm_assigned_dev_interrupt_w
 	 * finer-grained lock, update this
 	 */
 	mutex_lock(&kvm->lock);
+	spin_lock_irq(&assigned_dev->assigned_dev_lock);
 	if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_MSIX) {
 		struct kvm_guest_msix_entry *guest_entries =
 			assigned_dev->guest_msix_entries;
@@ -156,18 +158,21 @@ static void kvm_assigned_dev_interrupt_w
 		}
 	}
 
+	spin_unlock_irq(&assigned_dev->assigned_dev_lock);
 	mutex_unlock(&assigned_dev->kvm->lock);
 }
 
 static irqreturn_t kvm_assigned_dev_intr(int irq, void *dev_id)
 {
+	unsigned long flags;
 	struct kvm_assigned_dev_kernel *assigned_dev =
 		(struct kvm_assigned_dev_kernel *) dev_id;
 
+	spin_lock_irqsave(&assigned_dev->assigned_dev_lock, flags);
 	if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_MSIX) {
 		int index = find_index_from_host_irq(assigned_dev, irq);
 		if (index < 0)
-			return IRQ_HANDLED;
+			goto out;
 		assigned_dev->guest_msix_entries[index].flags |=
 			KVM_ASSIGNED_MSIX_PENDING;
 	}
@@ -177,6 +182,8 @@ static irqreturn_t kvm_assigned_dev_intr
 	disable_irq_nosync(irq);
 	assigned_dev->host_irq_disabled = true;
 
+out:
+	spin_unlock_irqrestore(&assigned_dev->assigned_dev_lock, flags);
 	return IRQ_HANDLED;
 }
 
@@ -184,6 +191,7 @@ static irqreturn_t kvm_assigned_dev_intr
 static void kvm_assigned_dev_ack_irq(struct kvm_irq_ack_notifier *kian)
 {
 	struct kvm_assigned_dev_kernel *dev;
+	unsigned long flags;
 
 	if (kian->gsi == -1)
 		return;
@@ -196,10 +204,12 @@ static void kvm_assigned_dev_ack_irq(str
 	/* The guest irq may be shared so this ack may be
 	 * from another device.
 	 */
+	spin_lock_irqsave(&dev->assigned_dev_lock, flags);
 	if (dev->host_irq_disabled) {
 		enable_irq(dev->host_irq);
 		dev->host_irq_disabled = false;
 	}
+	spin_unlock_irqrestore(&dev->assigned_dev_lock, flags);
 }
 
 static void deassign_guest_irq(struct kvm *kvm,
@@ -615,6 +625,7 @@ static int kvm_vm_ioctl_assign_device(st
 	match->host_devfn = assigned_dev->devfn;
 	match->flags = assigned_dev->flags;
 	match->dev = dev;
+	spin_lock_init(&match->assigned_dev_lock);
 	match->irq_source_id = -1;
 	match->kvm = kvm;
 	match->ack_notifier.irq_acked = kvm_assigned_dev_ack_irq;
#6 lore raw
In reply to [email protected]

Ack all. This also solved one bug by my hand. Thanks!

I observe one point: the performance of high workload interrupt(e.g. 10 gigabyte oplin card) dropped dramatically with smp_send_reschedule() method... In one environment(the speed of oplin card also limited by cpu performance), Using smp_call_function_single() can get more than 1G bit/s stably(native got 1.2G), but smp_send_reschedule() can only got around 600M bit/s... And the rescheduling interrupt number is about 2000/second per cpu. And the interrupt rate is about tens of thousands per second for the device.

Anyway, this method is more elegant and correct. Though there is still room for optimize - but of course, the correctness is first priority.

Signature · 2 lines
regards
Yang, Sheng
#7 lore raw
Quoted 10 lines · Sheng Yang

Ack all. This also solved one bug by my hand. Thanks!

I observe one point: the performance of high workload interrupt(e.g. 10 gigabyte oplin card) dropped dramatically with smp_send_reschedule() method... In one environment(the speed of oplin card also limited by cpu performance), Using smp_call_function_single() can get more than 1G bit/s stably(native got 1.2G), but smp_send_reschedule() can only got around 600M bit/s... And the rescheduling interrupt number is about 2000/second per cpu. And the interrupt rate is about tens of thousands per second for the device.

Quoted 2 lines

Anyway, this method is more elegant and correct. Though there is still room for optimize - but of course, the correctness is first priority.

Are you using the compat code or a kvm.git kernel? Can you remove only the last patch (the spinlock) to confirm its the cause of the slowdown?

#8 lore raw
Quoted 12 lines · On Thursday 30 April 2009 01:47:57 Marcelo Tosatti wrote:
Quoted 11 lines · Sheng Yang

Ack all. This also solved one bug by my hand. Thanks!

I observe one point: the performance of high workload interrupt(e.g. 10 gigabyte oplin card) dropped dramatically with smp_send_reschedule() method... In one environment(the speed of oplin card also limited by cpu performance), Using smp_call_function_single() can get more than 1G bit/s stably(native got 1.2G), but smp_send_reschedule() can only got around 600M bit/s... And the rescheduling interrupt number is about 2000/second per cpu. And the interrupt rate is about tens of thousands per second for the device.

Quoted 5 lines
Quoted 2 lines

Anyway, this method is more elegant and correct. Though there is still room for optimize - but of course, the correctness is first priority.

Are you using the compat code or a kvm.git kernel? Can you remove only the last patch (the spinlock) to confirm its the cause of the slowdown?

I am using kvm.git.

I said this because I tried the old version of patch(which have warning) and it would got more than 1G/sec.

I'd like to take a close look at what's happened.

Signature · 2 lines
regards
Yang, Sheng
#9 lore raw
Quoted 19 lines · On Thursday 30 April 2009 08:56:57 Sheng Yang wrote:
Quoted 12 lines · On Thursday 30 April 2009 01:47:57 Marcelo Tosatti wrote:
Quoted 11 lines · Sheng Yang

Ack all. This also solved one bug by my hand. Thanks!

I observe one point: the performance of high workload interrupt(e.g. 10 gigabyte oplin card) dropped dramatically with smp_send_reschedule() method... In one environment(the speed of oplin card also limited by cpu performance), Using smp_call_function_single() can get more than 1G bit/s stably(native got 1.2G), but smp_send_reschedule() can only got around 600M bit/s... And the rescheduling interrupt number is about 2000/second per cpu. And the interrupt rate is about tens of thousands per second for the device.

Quoted 5 lines
Quoted 2 lines

Anyway, this method is more elegant and correct. Though there is still room for optimize - but of course, the correctness is first priority.

Are you using the compat code or a kvm.git kernel? Can you remove only the last patch (the spinlock) to confirm its the cause of the slowdown?

Quoted 6 lines

I am using kvm.git.

I said this because I tried the old version of patch(which have warning) and it would got more than 1G/sec.

I'd like to take a close look at what's happened.

Still ACK this patchset.

And sorry, my memory messed...

The old version of patch and this one offered the same performance. So the problem is not here.

I get more than 1g per second by one of myself's experiment.

Disable/enable irq purposed to use with level interrupt to prevent it send interrupt again after kernel handler return, but it not applied to MSI/MSI-X. Though some interrupt may be merged with one, but AFAIK the driver can handle it well.

My experiment is discard disable/enable IRQ for MSI/MSI-X, then can get much better performance for oplin card, 2x with disable/enable one.

I would prepare a patch for it.

Signature · 2 lines
regards
Yang, Sheng
#10 lore raw
Quoted 43 lines · On Thursday 30 April 2009 09:59:56 Sheng Yang wrote:
Quoted 21 lines · On Thursday 30 April 2009 08:56:57 Sheng Yang wrote:
Quoted 13 lines · On Thursday 30 April 2009 01:47:57 Marcelo Tosatti wrote:
Quoted 12 lines · Sheng Yang

Ack all. This also solved one bug by my hand. Thanks!

I observe one point: the performance of high workload interrupt(e.g. 10 gigabyte oplin card) dropped dramatically with smp_send_reschedule() method... In one environment(the speed of oplin card also limited by cpu performance), Using smp_call_function_single() can get more than 1G bit/s stably(native got 1.2G), but smp_send_reschedule() can only got around 600M bit/s... And the rescheduling interrupt number is about 2000/second per cpu. And the interrupt rate is about tens of thousands per second for the device.

Quoted 6 lines
Quoted 3 lines

Anyway, this method is more elegant and correct. Though there is still room for optimize - but of course, the correctness is first priority.

Are you using the compat code or a kvm.git kernel? Can you remove only the last patch (the spinlock) to confirm its the cause of the slowdown?

Quoted 6 lines

I am using kvm.git.

I said this because I tried the old version of patch(which have warning) and it would got more than 1G/sec.

I'd like to take a close look at what's happened.

Still ACK this patchset.

And sorry, my memory messed...

The old version of patch and this one offered the same performance. So the problem is not here.

I get more than 1g per second by one of myself's experiment.

Disable/enable irq purposed to use with level interrupt to prevent it send interrupt again after kernel handler return, but it not applied to MSI/MSI-X. Though some interrupt may be merged with one, but AFAIK the driver can handle it well.

Quoted 4 lines

My experiment is discard disable/enable IRQ for MSI/MSI-X, then can get much better performance for oplin card, 2x with disable/enable one.

I would prepare a patch for it.

Hi Avi

Is there any issue blocked this patchset?

Thanks!

Signature · 2 lines
regards
Yang, Sheng
#11 lore raw
Quoted 2 lines · Sheng Yang

Is there any issue blocked this patchset?

Yes, a slow maintainer. I'll go review it now.

Signature · 1 line
error compiling committee.c: too many arguments to function
#12 lore raw

cover smp_send_reschedule / assigned dev host intx race v2

Addressing comments.

#13 lore raw

PATCH 1/4 kvm-kmod: nr_cpu_ids compat

external-module-compat-comm.h +4 -0
Index: kvm-kmod/external-module-compat-comm.h
===================================================================
@@ -116,6 +116,10 @@ int kvm_smp_call_function_single(int cpu
 
 #endif
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+#define nr_cpu_ids NR_CPUS
+#endif
+
 #include <linux/miscdevice.h>
 #ifndef KVM_MINOR
 #define KVM_MINOR 232
#14 lore raw

PATCH 2/4 kvm-kmod: smp_send_reschedule compat

In reply to [email protected]

smp_send_reschedule was exported (via smp_ops) in v2.6.24. Create a compat function which schedules the IPI to keventd context, in case interrupts are disabled, for kernels < 2.6.24.

ia64/hack-module.awk +1 -1
Index: kvm-kmod/ia64/hack-module.awk
===================================================================
@@ -1,4 +1,4 @@
-BEGIN { split("INIT_WORK on_each_cpu smp_call_function " \
+BEGIN { split("INIT_WORK on_each_cpu smp_call_function smp_send_reschedule " \
 	      "hrtimer_add_expires_ns hrtimer_get_expires " \
 	      "hrtimer_get_expires_ns hrtimer_start_expires " \
 	      "hrtimer_expires_remaining " \
x86/hack-module.awk +1 -1
Index: kvm-kmod/x86/hack-module.awk
===================================================================
@@ -1,7 +1,7 @@
 BEGIN { split("INIT_WORK desc_struct ldttss_desc64 desc_ptr " \
 	      "hrtimer_add_expires_ns hrtimer_get_expires " \
 	      "hrtimer_get_expires_ns hrtimer_start_expires " \
-	      "hrtimer_expires_remaining " \
+	      "hrtimer_expires_remaining smp_send_reschedule " \
 	      "on_each_cpu relay_open request_irq free_irq" , compat_apis); }
 
 /^int kvm_init\(/ { anon_inodes = 1 }
external-module-compat-comm.h +4 -0
Index: kvm-kmod/external-module-compat-comm.h
===================================================================
@@ -224,6 +224,10 @@ int kvm_smp_call_function_mask(cpumask_t
 
 #define smp_call_function_mask kvm_smp_call_function_mask
 
+void kvm_smp_send_reschedule(int cpu);
+
+#define smp_send_reschedule kvm_smp_send_reschedule
+
 #endif
 
 /* empty_zero_page isn't exported in all kernels */
external-module-compat.c +52 -0

58 lines not on this page · raw message

#15 lore raw

PATCH 3/4 KVM: use smp_send_reschedule in kvm_vcpu_kick

In reply to [email protected]

KVM uses a function call IPI to cause the exit of a guest running on a physical cpu. For virtual interrupt notification there is no need to wait on IPI receival, or to execute any function.

This is exactly what the reschedule IPI does, without the overhead of function IPI. So use it instead of smp_call_function_single in kvm_vcpu_kick.

Also change the "guest_mode" variable to a bit in vcpu->requests, and use that to collapse multiple IPI's that would be issued between the first one and zeroing of guest mode.

This allows kvm_vcpu_kick to called with interrupts disabled.

arch/ia64/kernel/irq_ia64.c +3 -0
Index: kvm-pending/arch/ia64/kernel/irq_ia64.c
===================================================================
@@ -610,6 +610,9 @@ static struct irqaction ipi_irqaction = 
 	.name =		"IPI"
 };
 
+/*
+ * KVM uses this interrupt to force a cpu out of guest mode
+ */
 static struct irqaction resched_irqaction = {
 	.handler =	dummy_handler,
 	.flags =	IRQF_DISABLED,
arch/ia64/kvm/kvm-ia64.c +8 -14
Index: kvm-pending/arch/ia64/kvm/kvm-ia64.c
===================================================================
@@ -668,7 +668,7 @@ again:
 	host_ctx = kvm_get_host_context(vcpu);
 	guest_ctx = kvm_get_guest_context(vcpu);
 
-	vcpu->guest_mode = 1;
+	clear_bit(KVM_REQ_KICK, &vcpu->requests);
 
 	r = kvm_vcpu_pre_transition(vcpu);
 	if (r < 0)
@@ -685,7 +685,7 @@ again:
 	kvm_vcpu_post_transition(vcpu);
 
 	vcpu->arch.launched = 1;
-	vcpu->guest_mode = 0;
+	set_bit(KVM_REQ_KICK, &vcpu->requests);
 	local_irq_enable();
 
 	/*
@@ -1879,24 +1879,18 @@ void kvm_arch_hardware_unsetup(void)
 {
 }
 
-static void vcpu_kick_intr(void *info)
-{
-#ifdef DEBUG
-	struct kvm_vcpu *vcpu = (struct kvm_vcpu *)info;
-	printk(KERN_DEBUG"vcpu_kick_intr %p \n", vcpu);
-#endif
-}
-
 void kvm_vcpu_kick(struct kvm_vcpu *vcpu)
 {
-	int ipi_pcpu = vcpu->cpu;
-	int cpu = get_cpu();
+	int me;
+	int cpu = vcpu->cpu;
 
 	if (waitqueue_active(&vcpu->wq))
 		wake_up_interruptible(&vcpu->wq);
 
-	if (vcpu->guest_mode && cpu != ipi_pcpu)
-		smp_call_function_single(ipi_pcpu, vcpu_kick_intr, vcpu, 0);
+	me = get_cpu();
+	if (cpu != me && (unsigned) cpu < nr_cpu_ids && cpu_online(cpu))
+		if (!test_and_set_bit(KVM_REQ_KICK, &vcpu->requests))
+			smp_send_reschedule(cpu);
 	put_cpu();
 }
 
arch/x86/kernel/smp.c +3 -0
Index: kvm-pending/arch/x86/kernel/smp.c
===================================================================
@@ -172,6 +172,9 @@ void smp_reschedule_interrupt(struct pt_
 {
 	ack_APIC_irq();
 	inc_irq_stat(irq_resched_count);
+	/*
+	 * KVM uses this interrupt to force a cpu out of guest mode
+	 */
 }
 
 void smp_call_function_interrupt(struct pt_regs *regs)
arch/x86/kvm/x86.c +11 -25
Index: kvm-pending/arch/x86/kvm/x86.c
===================================================================
@@ -3259,6 +3259,9 @@ static int vcpu_enter_guest(struct kvm_v
 
 	local_irq_disable();
 
+	clear_bit(KVM_REQ_KICK, &vcpu->requests);
+	smp_mb__after_clear_bit();
+
 	if (vcpu->requests || need_resched() || signal_pending(current)) {
 		local_irq_enable();
 		preempt_enable();
@@ -3266,13 +3269,6 @@ static int vcpu_enter_guest(struct kvm_v
 		goto out;
 	}
 
-	vcpu->guest_mode = 1;
-	/*
-	 * Make sure that guest_mode assignment won't happen after
-	 * testing the pending IRQ vector bitmap.
-	 */
-	smp_wmb();
-
 	if (vcpu->arch.exception.pending)
 		__queue_exception(vcpu);
 	else
@@ -3317,7 +3313,7 @@ static int vcpu_enter_guest(struct kvm_v
 	set_debugreg(vcpu->arch.host_dr6, 6);
 	set_debugreg(vcpu->arch.host_dr7, 7);
 
-	vcpu->guest_mode = 0;
+	set_bit(KVM_REQ_KICK, &vcpu->requests);
 	local_irq_enable();
 
 	++vcpu->stat.exits;
@@ -4611,30 +4607,20 @@ int kvm_arch_vcpu_runnable(struct kvm_vc
 	       || vcpu->arch.nmi_pending;
 }
 
-static void vcpu_kick_intr(void *info)
-{
-#ifdef DEBUG
-	struct kvm_vcpu *vcpu = (struct kvm_vcpu *)info;
-	printk(KERN_DEBUG "vcpu_kick_intr %p \n", vcpu);
-#endif
-}
-
 void kvm_vcpu_kick(struct kvm_vcpu *vcpu)
 {
-	int ipi_pcpu = vcpu->cpu;
-	int cpu;
+	int me;
+	int cpu = vcpu->cpu;
 
 	if (waitqueue_active(&vcpu->wq)) {
 		wake_up_interruptible(&vcpu->wq);
 		++vcpu->stat.halt_wakeup;
 	}
-	/*
-	 * We may be called synchronously with irqs disabled in guest mode,
-	 * So need not to call smp_call_function_single() in that case.
-	 */
-	cpu = get_cpu();
-	if (vcpu->guest_mode && vcpu->cpu != cpu)
-		smp_call_function_single(ipi_pcpu, vcpu_kick_intr, vcpu, 0);
+
+	me = get_cpu();
+	if (cpu != me && (unsigned)cpu < nr_cpu_ids && cpu_online(cpu))
+		if (!test_and_set_bit(KVM_REQ_KICK, &vcpu->requests))
+			smp_send_reschedule(cpu);
 	put_cpu();
 }
 
include/linux/kvm_host.h +1 -1
Index: kvm-pending/include/linux/kvm_host.h
===================================================================
@@ -38,6 +38,7 @@
 #define KVM_REQ_UNHALT             6
 #define KVM_REQ_MMU_SYNC           7
 #define KVM_REQ_KVMCLOCK_UPDATE    8
+#define KVM_REQ_KICK               9
 
 #define KVM_USERSPACE_IRQ_SOURCE_ID	0
 
@@ -72,7 +73,6 @@ struct kvm_vcpu {
 	struct mutex mutex;
 	int   cpu;
 	struct kvm_run *run;
-	int guest_mode;
 	unsigned long requests;
 	unsigned long guest_debug;
 	int fpu_active;
Acked-by on v1 3/4
#16 lore raw

Re: [patch 3/4] KVM: use smp_send_reschedule in kvm_vcpu_kick

Quoted 8 lines · On Thu, May 07, 2009 at 05:55:12PM -0300, [email protected] wrote:

KVM uses a function call IPI to cause the exit of a guest running on a physical cpu. For virtual interrupt notification there is no need to wait on IPI receival, or to execute any function.

This is exactly what the reschedule IPI does, without the overhead of function IPI. So use it instead of smp_call_function_single in kvm_vcpu_kick.

Quoted 8 lines

Also change the "guest_mode" variable to a bit in vcpu->requests, and use that to collapse multiple IPI's that would be issued between the first one and zeroing of guest mode.

This allows kvm_vcpu_kick to called with interrupts disabled.

Except one small nitpick below.

Quoted 15 lines

Index: kvm-pending/arch/x86/kvm/x86.c
===================================================================

arch/x86/kvm/x86.c +3 -0
@@ -3259,6 +3259,9 @@ static int vcpu_enter_guest(struct kvm_v
 
 	local_irq_disable();
 
+	clear_bit(KVM_REQ_KICK, &vcpu->requests);
+	smp_mb__after_clear_bit();
+
 	if (vcpu->requests || need_resched() || signal_pending(current)) {
 		local_irq_enable();
 		preempt_enable();

arch/x86/kvm/x86.c static int vcpu_enter_guest(struct kvm_v · line 3275

Quoted 10 lines
arch/x86/kvm/x86.c +0 -7
@@ -3266,13 +3269,6 @@ static int vcpu_enter_guest(struct kvm_v
 		goto out;
 	}
 
-	vcpu->guest_mode = 1;
-	/*
-	 * Make sure that guest_mode assignment won't happen after
-	 * testing the pending IRQ vector bitmap.
-	 */
-	smp_wmb();
-

Can you leave this comment. I like it :)

--
Gleb.

#17 lore raw

PATCH 4/4 KVM: protect assigned dev workqueue, int handler and irq acker

In reply to [email protected]

kvm_assigned_dev_ack_irq is vulnerable to a race condition with the interrupt handler function. It does:

        if (dev->host_irq_disabled) {
                enable_irq(dev->host_irq);
                dev->host_irq_disabled = false;
        }

If an interrupt triggers before the host->dev_irq_disabled assignment, it will disable the interrupt and set dev->host_irq_disabled to true.

On return to kvm_assigned_dev_ack_irq, dev->host_irq_disabled is set to false, and the next kvm_assigned_dev_ack_irq call will fail to reenable it.

Other than that, having the interrupt handler and work handlers run in parallel sounds like asking for trouble (could not spot any obvious problem, but better not have to, its fragile).

include/linux/kvm_host.h +1 -0
Index: kvm-pending/include/linux/kvm_host.h
===================================================================
@@ -346,6 +346,7 @@ struct kvm_assigned_dev_kernel {
 	int flags;
 	struct pci_dev *dev;
 	struct kvm *kvm;
+	spinlock_t assigned_dev_lock;
 };
 
 struct kvm_irq_mask_notifier {
virt/kvm/kvm_main.c +12 -1
Index: kvm-pending/virt/kvm/kvm_main.c
===================================================================
@@ -42,6 +42,7 @@
 #include <linux/mman.h>
 #include <linux/swap.h>
 #include <linux/bitops.h>
+#include <linux/spinlock.h>
 
 #include <asm/processor.h>
 #include <asm/io.h>
@@ -130,6 +131,7 @@ static void kvm_assigned_dev_interrupt_w
 	 * finer-grained lock, update this
 	 */
 	mutex_lock(&kvm->lock);
+	spin_lock_irq(&assigned_dev->assigned_dev_lock);
 	if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_MSIX) {
 		struct kvm_guest_msix_entry *guest_entries =
 			assigned_dev->guest_msix_entries;
@@ -156,18 +158,21 @@ static void kvm_assigned_dev_interrupt_w
 		}
 	}
 
+	spin_unlock_irq(&assigned_dev->assigned_dev_lock);
 	mutex_unlock(&assigned_dev->kvm->lock);
 }
 
 static irqreturn_t kvm_assigned_dev_intr(int irq, void *dev_id)
 {
+	unsigned long flags;
 	struct kvm_assigned_dev_kernel *assigned_dev =
 		(struct kvm_assigned_dev_kernel *) dev_id;
 
+	spin_lock_irqsave(&assigned_dev->assigned_dev_lock, flags);
 	if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_MSIX) {
 		int index = find_index_from_host_irq(assigned_dev, irq);
 		if (index < 0)
-			return IRQ_HANDLED;
+			goto out;
 		assigned_dev->guest_msix_entries[index].flags |=
 			KVM_ASSIGNED_MSIX_PENDING;
 	}
@@ -177,6 +182,8 @@ static irqreturn_t kvm_assigned_dev_intr
 	disable_irq_nosync(irq);
 	assigned_dev->host_irq_disabled = true;
 
+out:
+	spin_unlock_irqrestore(&assigned_dev->assigned_dev_lock, flags);
 	return IRQ_HANDLED;
 }
 
@@ -184,6 +191,7 @@ static irqreturn_t kvm_assigned_dev_intr
 static void kvm_assigned_dev_ack_irq(struct kvm_irq_ack_notifier *kian)
 {
 	struct kvm_assigned_dev_kernel *dev;
+	unsigned long flags;
 
 	if (kian->gsi == -1)
 		return;
@@ -196,10 +204,12 @@ static void kvm_assigned_dev_ack_irq(str
 	/* The guest irq may be shared so this ack may be
 	 * from another device.
 	 */
+	spin_lock_irqsave(&dev->assigned_dev_lock, flags);
 	if (dev->host_irq_disabled) {
 		enable_irq(dev->host_irq);
 		dev->host_irq_disabled = false;
 	}
+	spin_unlock_irqrestore(&dev->assigned_dev_lock, flags);
 }
 
 static void deassign_guest_irq(struct kvm *kvm,
@@ -615,6 +625,7 @@ static int kvm_vm_ioctl_assign_device(st
 	match->host_devfn = assigned_dev->devfn;
 	match->flags = assigned_dev->flags;
 	match->dev = dev;
+	spin_lock_init(&match->assigned_dev_lock);
 	match->irq_source_id = -1;
 	match->kvm = kvm;
 	match->ack_notifier.irq_acked = kvm_assigned_dev_ack_irq;