Commit 8b306e2f authored by Paolo Bonzini's avatar Paolo Bonzini

KVM: VMX: avoid double list add with VT-d posted interrupts

In some cases, for example involving hot-unplug of assigned
devices, pi_post_block can forget to remove the vCPU from the
blocked_vcpu_list.  When this happens, the next call to
pi_pre_block corrupts the list.

Fix this in two ways.  First, check vcpu->pre_pcpu in pi_pre_block
and WARN instead of adding the element twice in the list.  Second,
always do the list removal in pi_post_block if vcpu->pre_pcpu is
set (not -1).

The new code keeps interrupts disabled for the whole duration of
pi_pre_block/pi_post_block.  This is not strictly necessary, but
easier to follow.  For the same reason, PI.ON is checked only
after the cmpxchg, and to handle it we just call the post-block
code.  This removes duplication of the list removal code.

Cc: Huangweidong <weidong.huang@huawei.com>
Cc: Gonglei <arei.gonglei@huawei.com>
Cc: wangxin <wangxinxin.wang@huawei.com>
Cc: Radim Krčmář <rkrcmar@redhat.com>
Tested-by: default avatarLongpeng (Mike) <longpeng2@huawei.com>
Cc: stable@vger.kernel.org
Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
parent cd39e117
...@@ -11710,10 +11710,11 @@ static void __pi_post_block(struct kvm_vcpu *vcpu) ...@@ -11710,10 +11710,11 @@ static void __pi_post_block(struct kvm_vcpu *vcpu)
struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu); struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
struct pi_desc old, new; struct pi_desc old, new;
unsigned int dest; unsigned int dest;
unsigned long flags;
do { do {
old.control = new.control = pi_desc->control; old.control = new.control = pi_desc->control;
WARN(old.nv != POSTED_INTR_WAKEUP_VECTOR,
"Wakeup handler not enabled while the VCPU is blocked\n");
dest = cpu_physical_id(vcpu->cpu); dest = cpu_physical_id(vcpu->cpu);
...@@ -11730,14 +11731,10 @@ static void __pi_post_block(struct kvm_vcpu *vcpu) ...@@ -11730,14 +11731,10 @@ static void __pi_post_block(struct kvm_vcpu *vcpu)
} while (cmpxchg(&pi_desc->control, old.control, } while (cmpxchg(&pi_desc->control, old.control,
new.control) != old.control); new.control) != old.control);
if(vcpu->pre_pcpu != -1) { if (!WARN_ON_ONCE(vcpu->pre_pcpu == -1)) {
spin_lock_irqsave( spin_lock(&per_cpu(blocked_vcpu_on_cpu_lock, vcpu->pre_pcpu));
&per_cpu(blocked_vcpu_on_cpu_lock,
vcpu->pre_pcpu), flags);
list_del(&vcpu->blocked_vcpu_list); list_del(&vcpu->blocked_vcpu_list);
spin_unlock_irqrestore( spin_unlock(&per_cpu(blocked_vcpu_on_cpu_lock, vcpu->pre_pcpu));
&per_cpu(blocked_vcpu_on_cpu_lock,
vcpu->pre_pcpu), flags);
vcpu->pre_pcpu = -1; vcpu->pre_pcpu = -1;
} }
} }
...@@ -11757,7 +11754,6 @@ static void __pi_post_block(struct kvm_vcpu *vcpu) ...@@ -11757,7 +11754,6 @@ static void __pi_post_block(struct kvm_vcpu *vcpu)
*/ */
static int pi_pre_block(struct kvm_vcpu *vcpu) static int pi_pre_block(struct kvm_vcpu *vcpu)
{ {
unsigned long flags;
unsigned int dest; unsigned int dest;
struct pi_desc old, new; struct pi_desc old, new;
struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu); struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
...@@ -11767,34 +11763,20 @@ static int pi_pre_block(struct kvm_vcpu *vcpu) ...@@ -11767,34 +11763,20 @@ static int pi_pre_block(struct kvm_vcpu *vcpu)
!kvm_vcpu_apicv_active(vcpu)) !kvm_vcpu_apicv_active(vcpu))
return 0; return 0;
vcpu->pre_pcpu = vcpu->cpu; WARN_ON(irqs_disabled());
spin_lock_irqsave(&per_cpu(blocked_vcpu_on_cpu_lock, local_irq_disable();
vcpu->pre_pcpu), flags); if (!WARN_ON_ONCE(vcpu->pre_pcpu != -1)) {
list_add_tail(&vcpu->blocked_vcpu_list, vcpu->pre_pcpu = vcpu->cpu;
&per_cpu(blocked_vcpu_on_cpu, spin_lock(&per_cpu(blocked_vcpu_on_cpu_lock, vcpu->pre_pcpu));
vcpu->pre_pcpu)); list_add_tail(&vcpu->blocked_vcpu_list,
spin_unlock_irqrestore(&per_cpu(blocked_vcpu_on_cpu_lock, &per_cpu(blocked_vcpu_on_cpu,
vcpu->pre_pcpu), flags); vcpu->pre_pcpu));
spin_unlock(&per_cpu(blocked_vcpu_on_cpu_lock, vcpu->pre_pcpu));
}
do { do {
old.control = new.control = pi_desc->control; old.control = new.control = pi_desc->control;
/*
* We should not block the vCPU if
* an interrupt is posted for it.
*/
if (pi_test_on(pi_desc) == 1) {
spin_lock_irqsave(&per_cpu(blocked_vcpu_on_cpu_lock,
vcpu->pre_pcpu), flags);
list_del(&vcpu->blocked_vcpu_list);
spin_unlock_irqrestore(
&per_cpu(blocked_vcpu_on_cpu_lock,
vcpu->pre_pcpu), flags);
vcpu->pre_pcpu = -1;
return 1;
}
WARN((pi_desc->sn == 1), WARN((pi_desc->sn == 1),
"Warning: SN field of posted-interrupts " "Warning: SN field of posted-interrupts "
"is set before blocking\n"); "is set before blocking\n");
...@@ -11819,7 +11801,12 @@ static int pi_pre_block(struct kvm_vcpu *vcpu) ...@@ -11819,7 +11801,12 @@ static int pi_pre_block(struct kvm_vcpu *vcpu)
} while (cmpxchg(&pi_desc->control, old.control, } while (cmpxchg(&pi_desc->control, old.control,
new.control) != old.control); new.control) != old.control);
return 0; /* We should not block the vCPU if an interrupt is posted for it. */
if (pi_test_on(pi_desc) == 1)
__pi_post_block(vcpu);
local_irq_enable();
return (vcpu->pre_pcpu == -1);
} }
static int vmx_pre_block(struct kvm_vcpu *vcpu) static int vmx_pre_block(struct kvm_vcpu *vcpu)
...@@ -11835,12 +11822,13 @@ static int vmx_pre_block(struct kvm_vcpu *vcpu) ...@@ -11835,12 +11822,13 @@ static int vmx_pre_block(struct kvm_vcpu *vcpu)
static void pi_post_block(struct kvm_vcpu *vcpu) static void pi_post_block(struct kvm_vcpu *vcpu)
{ {
if (!kvm_arch_has_assigned_device(vcpu->kvm) || if (vcpu->pre_pcpu == -1)
!irq_remapping_cap(IRQ_POSTING_CAP) ||
!kvm_vcpu_apicv_active(vcpu))
return; return;
WARN_ON(irqs_disabled());
local_irq_disable();
__pi_post_block(vcpu); __pi_post_block(vcpu);
local_irq_enable();
} }
static void vmx_post_block(struct kvm_vcpu *vcpu) static void vmx_post_block(struct kvm_vcpu *vcpu)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment