KVM: PPC: Book3S HV P9: Fix "lost kick" race
When new work is created that requires attention from the hypervisor (e.g., to inject an interrupt into the guest), fast_vcpu_kick is used to pull the target vcpu out of the guest if it may have been running. Therefore the work creation side looks like this: vcpu->arch.doorbell_request = 1; kvmppc_fast_vcpu_kick_hv(vcpu) { smp_mb(); cpu = vcpu->cpu; if (cpu != -1) send_ipi(cpu); } And the guest entry side *should* look like this: vcpu->cpu = smp_processor_id(); smp_mb(); if (vcpu->arch.doorbell_request) { // do something (abort entry or inject doorbell etc) } But currently the store and load are flipped, so it is possible for the entry to see no doorbell pending, and the doorbell creation misses the store to set cpu, resulting lost work (or at least delayed until the next guest exit). Fix this by reordering the entry operations and adding a smp_mb between them. The P8 path appears to have a similar race which is commented but not addressed yet. Signed-off-by: Nicholas Piggin <npiggin@gmail.com> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Link: https://lore.kernel.org/r/20220303053315.1056880-2-npiggin@gmail.com
This commit is contained in:
Родитель
e40b38a41c
Коммит
c7fa848ff0
|
@ -225,6 +225,13 @@ static void kvmppc_fast_vcpu_kick_hv(struct kvm_vcpu *vcpu)
|
||||||
int cpu;
|
int cpu;
|
||||||
struct rcuwait *waitp;
|
struct rcuwait *waitp;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* rcuwait_wake_up contains smp_mb() which orders prior stores that
|
||||||
|
* create pending work vs below loads of cpu fields. The other side
|
||||||
|
* is the barrier in vcpu run that orders setting the cpu fields vs
|
||||||
|
* testing for pending work.
|
||||||
|
*/
|
||||||
|
|
||||||
waitp = kvm_arch_vcpu_get_wait(vcpu);
|
waitp = kvm_arch_vcpu_get_wait(vcpu);
|
||||||
if (rcuwait_wake_up(waitp))
|
if (rcuwait_wake_up(waitp))
|
||||||
++vcpu->stat.generic.halt_wakeup;
|
++vcpu->stat.generic.halt_wakeup;
|
||||||
|
@ -1089,7 +1096,7 @@ int kvmppc_pseries_do_hcall(struct kvm_vcpu *vcpu)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
tvcpu->arch.prodded = 1;
|
tvcpu->arch.prodded = 1;
|
||||||
smp_mb();
|
smp_mb(); /* This orders prodded store vs ceded load */
|
||||||
if (tvcpu->arch.ceded)
|
if (tvcpu->arch.ceded)
|
||||||
kvmppc_fast_vcpu_kick_hv(tvcpu);
|
kvmppc_fast_vcpu_kick_hv(tvcpu);
|
||||||
break;
|
break;
|
||||||
|
@ -3766,6 +3773,14 @@ static noinline void kvmppc_run_core(struct kvmppc_vcore *vc)
|
||||||
pvc = core_info.vc[sub];
|
pvc = core_info.vc[sub];
|
||||||
pvc->pcpu = pcpu + thr;
|
pvc->pcpu = pcpu + thr;
|
||||||
for_each_runnable_thread(i, vcpu, pvc) {
|
for_each_runnable_thread(i, vcpu, pvc) {
|
||||||
|
/*
|
||||||
|
* XXX: is kvmppc_start_thread called too late here?
|
||||||
|
* It updates vcpu->cpu and vcpu->arch.thread_cpu
|
||||||
|
* which are used by kvmppc_fast_vcpu_kick_hv(), but
|
||||||
|
* kick is called after new exceptions become available
|
||||||
|
* and exceptions are checked earlier than here, by
|
||||||
|
* kvmppc_core_prepare_to_enter.
|
||||||
|
*/
|
||||||
kvmppc_start_thread(vcpu, pvc);
|
kvmppc_start_thread(vcpu, pvc);
|
||||||
kvmppc_create_dtl_entry(vcpu, pvc);
|
kvmppc_create_dtl_entry(vcpu, pvc);
|
||||||
trace_kvm_guest_enter(vcpu);
|
trace_kvm_guest_enter(vcpu);
|
||||||
|
@ -4487,6 +4502,21 @@ int kvmhv_run_single_vcpu(struct kvm_vcpu *vcpu, u64 time_limit,
|
||||||
if (need_resched() || !kvm->arch.mmu_ready)
|
if (need_resched() || !kvm->arch.mmu_ready)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
vcpu->cpu = pcpu;
|
||||||
|
vcpu->arch.thread_cpu = pcpu;
|
||||||
|
vc->pcpu = pcpu;
|
||||||
|
local_paca->kvm_hstate.kvm_vcpu = vcpu;
|
||||||
|
local_paca->kvm_hstate.ptid = 0;
|
||||||
|
local_paca->kvm_hstate.fake_suspend = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Orders set cpu/thread_cpu vs testing for pending interrupts and
|
||||||
|
* doorbells below. The other side is when these fields are set vs
|
||||||
|
* kvmppc_fast_vcpu_kick_hv reading the cpu/thread_cpu fields to
|
||||||
|
* kick a vCPU to notice the pending interrupt.
|
||||||
|
*/
|
||||||
|
smp_mb();
|
||||||
|
|
||||||
if (!nested) {
|
if (!nested) {
|
||||||
kvmppc_core_prepare_to_enter(vcpu);
|
kvmppc_core_prepare_to_enter(vcpu);
|
||||||
if (test_bit(BOOK3S_IRQPRIO_EXTERNAL,
|
if (test_bit(BOOK3S_IRQPRIO_EXTERNAL,
|
||||||
|
@ -4506,13 +4536,6 @@ int kvmhv_run_single_vcpu(struct kvm_vcpu *vcpu, u64 time_limit,
|
||||||
|
|
||||||
tb = mftb();
|
tb = mftb();
|
||||||
|
|
||||||
vcpu->cpu = pcpu;
|
|
||||||
vcpu->arch.thread_cpu = pcpu;
|
|
||||||
vc->pcpu = pcpu;
|
|
||||||
local_paca->kvm_hstate.kvm_vcpu = vcpu;
|
|
||||||
local_paca->kvm_hstate.ptid = 0;
|
|
||||||
local_paca->kvm_hstate.fake_suspend = 0;
|
|
||||||
|
|
||||||
__kvmppc_create_dtl_entry(vcpu, pcpu, tb + vc->tb_offset, 0);
|
__kvmppc_create_dtl_entry(vcpu, pcpu, tb + vc->tb_offset, 0);
|
||||||
|
|
||||||
trace_kvm_guest_enter(vcpu);
|
trace_kvm_guest_enter(vcpu);
|
||||||
|
@ -4614,6 +4637,8 @@ int kvmhv_run_single_vcpu(struct kvm_vcpu *vcpu, u64 time_limit,
|
||||||
run->exit_reason = KVM_EXIT_INTR;
|
run->exit_reason = KVM_EXIT_INTR;
|
||||||
vcpu->arch.ret = -EINTR;
|
vcpu->arch.ret = -EINTR;
|
||||||
out:
|
out:
|
||||||
|
vcpu->cpu = -1;
|
||||||
|
vcpu->arch.thread_cpu = -1;
|
||||||
powerpc_local_irq_pmu_restore(flags);
|
powerpc_local_irq_pmu_restore(flags);
|
||||||
preempt_enable();
|
preempt_enable();
|
||||||
goto done;
|
goto done;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче