提交 e5ac4200 编写于 作者: A Andrew Jones 提交者: Peter Maydell

target/arm/kvm: Implement virtual time adjustment

When a VM is stopped (such as when it's paused) guest virtual time
should stop counting. Otherwise, when the VM is resumed it will
experience time jumps and its kernel may report soft lockups. Not
counting virtual time while the VM is stopped has the side effect
of making the guest's time appear to lag when compared with real
time, and even with time derived from the physical counter. For
this reason, this change, which is enabled by default, comes with
a KVM CPU feature allowing it to be disabled, restoring legacy
behavior.

This patch only provides the implementation of the virtual time
adjustment. A subsequent patch will provide the CPU property
allowing the change to be enabled and disabled.
Reported-by: NBijan Mottahedeh <bijan.mottahedeh@oracle.com>
Signed-off-by: NAndrew Jones <drjones@redhat.com>
Message-id: 20200120101023.16030-6-drjones@redhat.com
Reviewed-by: NPeter Maydell <peter.maydell@linaro.org>
Signed-off-by: NPeter Maydell <peter.maydell@linaro.org>
上级 789a35ef
...@@ -821,6 +821,13 @@ struct ARMCPU { ...@@ -821,6 +821,13 @@ struct ARMCPU {
/* KVM init features for this CPU */ /* KVM init features for this CPU */
uint32_t kvm_init_features[7]; uint32_t kvm_init_features[7];
/* KVM CPU state */
/* KVM virtual time adjustment */
bool kvm_adjvtime;
bool kvm_vtime_dirty;
uint64_t kvm_vtime;
/* Uniprocessor system with MP extensions */ /* Uniprocessor system with MP extensions */
bool mp_is_up; bool mp_is_up;
......
...@@ -357,6 +357,22 @@ static int compare_u64(const void *a, const void *b) ...@@ -357,6 +357,22 @@ static int compare_u64(const void *a, const void *b)
return 0; return 0;
} }
/*
* cpreg_values are sorted in ascending order by KVM register ID
* (see kvm_arm_init_cpreg_list). This allows us to cheaply find
* the storage for a KVM register by ID with a binary search.
*/
static uint64_t *kvm_arm_get_cpreg_ptr(ARMCPU *cpu, uint64_t regidx)
{
uint64_t *res;
res = bsearch(&regidx, cpu->cpreg_indexes, cpu->cpreg_array_len,
sizeof(uint64_t), compare_u64);
assert(res);
return &cpu->cpreg_values[res - cpu->cpreg_indexes];
}
/* Initialize the ARMCPU cpreg list according to the kernel's /* Initialize the ARMCPU cpreg list according to the kernel's
* definition of what CPU registers it knows about (and throw away * definition of what CPU registers it knows about (and throw away
* the previous TCG-created cpreg list). * the previous TCG-created cpreg list).
...@@ -510,6 +526,23 @@ bool write_list_to_kvmstate(ARMCPU *cpu, int level) ...@@ -510,6 +526,23 @@ bool write_list_to_kvmstate(ARMCPU *cpu, int level)
return ok; return ok;
} }
void kvm_arm_cpu_pre_save(ARMCPU *cpu)
{
/* KVM virtual time adjustment */
if (cpu->kvm_vtime_dirty) {
*kvm_arm_get_cpreg_ptr(cpu, KVM_REG_ARM_TIMER_CNT) = cpu->kvm_vtime;
}
}
void kvm_arm_cpu_post_load(ARMCPU *cpu)
{
/* KVM virtual time adjustment */
if (cpu->kvm_adjvtime) {
cpu->kvm_vtime = *kvm_arm_get_cpreg_ptr(cpu, KVM_REG_ARM_TIMER_CNT);
cpu->kvm_vtime_dirty = true;
}
}
void kvm_arm_reset_vcpu(ARMCPU *cpu) void kvm_arm_reset_vcpu(ARMCPU *cpu)
{ {
int ret; int ret;
...@@ -577,6 +610,50 @@ int kvm_arm_sync_mpstate_to_qemu(ARMCPU *cpu) ...@@ -577,6 +610,50 @@ int kvm_arm_sync_mpstate_to_qemu(ARMCPU *cpu)
return 0; return 0;
} }
void kvm_arm_get_virtual_time(CPUState *cs)
{
ARMCPU *cpu = ARM_CPU(cs);
struct kvm_one_reg reg = {
.id = KVM_REG_ARM_TIMER_CNT,
.addr = (uintptr_t)&cpu->kvm_vtime,
};
int ret;
if (cpu->kvm_vtime_dirty) {
return;
}
ret = kvm_vcpu_ioctl(cs, KVM_GET_ONE_REG, &reg);
if (ret) {
error_report("Failed to get KVM_REG_ARM_TIMER_CNT");
abort();
}
cpu->kvm_vtime_dirty = true;
}
void kvm_arm_put_virtual_time(CPUState *cs)
{
ARMCPU *cpu = ARM_CPU(cs);
struct kvm_one_reg reg = {
.id = KVM_REG_ARM_TIMER_CNT,
.addr = (uintptr_t)&cpu->kvm_vtime,
};
int ret;
if (!cpu->kvm_vtime_dirty) {
return;
}
ret = kvm_vcpu_ioctl(cs, KVM_SET_ONE_REG, &reg);
if (ret) {
error_report("Failed to set KVM_REG_ARM_TIMER_CNT");
abort();
}
cpu->kvm_vtime_dirty = false;
}
int kvm_put_vcpu_events(ARMCPU *cpu) int kvm_put_vcpu_events(ARMCPU *cpu)
{ {
CPUARMState *env = &cpu->env; CPUARMState *env = &cpu->env;
...@@ -688,6 +765,21 @@ MemTxAttrs kvm_arch_post_run(CPUState *cs, struct kvm_run *run) ...@@ -688,6 +765,21 @@ MemTxAttrs kvm_arch_post_run(CPUState *cs, struct kvm_run *run)
return MEMTXATTRS_UNSPECIFIED; return MEMTXATTRS_UNSPECIFIED;
} }
void kvm_arm_vm_state_change(void *opaque, int running, RunState state)
{
CPUState *cs = opaque;
ARMCPU *cpu = ARM_CPU(cs);
if (running) {
if (cpu->kvm_adjvtime) {
kvm_arm_put_virtual_time(cs);
}
} else {
if (cpu->kvm_adjvtime) {
kvm_arm_get_virtual_time(cs);
}
}
}
int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run)
{ {
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "qemu-common.h" #include "qemu-common.h"
#include "cpu.h" #include "cpu.h"
#include "qemu/timer.h" #include "qemu/timer.h"
#include "sysemu/runstate.h"
#include "sysemu/kvm.h" #include "sysemu/kvm.h"
#include "kvm_arm.h" #include "kvm_arm.h"
#include "internals.h" #include "internals.h"
...@@ -198,6 +199,8 @@ int kvm_arch_init_vcpu(CPUState *cs) ...@@ -198,6 +199,8 @@ int kvm_arch_init_vcpu(CPUState *cs)
return -EINVAL; return -EINVAL;
} }
qemu_add_vm_change_state_handler(kvm_arm_vm_state_change, cs);
/* Determine init features for this CPU */ /* Determine init features for this CPU */
memset(cpu->kvm_init_features, 0, sizeof(cpu->kvm_init_features)); memset(cpu->kvm_init_features, 0, sizeof(cpu->kvm_init_features));
if (cpu->start_powered_off) { if (cpu->start_powered_off) {
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "qemu/host-utils.h" #include "qemu/host-utils.h"
#include "qemu/main-loop.h" #include "qemu/main-loop.h"
#include "exec/gdbstub.h" #include "exec/gdbstub.h"
#include "sysemu/runstate.h"
#include "sysemu/kvm.h" #include "sysemu/kvm.h"
#include "sysemu/kvm_int.h" #include "sysemu/kvm_int.h"
#include "kvm_arm.h" #include "kvm_arm.h"
...@@ -734,6 +735,8 @@ int kvm_arch_init_vcpu(CPUState *cs) ...@@ -734,6 +735,8 @@ int kvm_arch_init_vcpu(CPUState *cs)
return -EINVAL; return -EINVAL;
} }
qemu_add_vm_change_state_handler(kvm_arm_vm_state_change, cs);
/* Determine init features for this CPU */ /* Determine init features for this CPU */
memset(cpu->kvm_init_features, 0, sizeof(cpu->kvm_init_features)); memset(cpu->kvm_init_features, 0, sizeof(cpu->kvm_init_features));
if (cpu->start_powered_off) { if (cpu->start_powered_off) {
......
...@@ -127,6 +127,23 @@ bool write_list_to_kvmstate(ARMCPU *cpu, int level); ...@@ -127,6 +127,23 @@ bool write_list_to_kvmstate(ARMCPU *cpu, int level);
*/ */
bool write_kvmstate_to_list(ARMCPU *cpu); bool write_kvmstate_to_list(ARMCPU *cpu);
/**
* kvm_arm_cpu_pre_save:
* @cpu: ARMCPU
*
* Called after write_kvmstate_to_list() from cpu_pre_save() to update
* the cpreg list with KVM CPU state.
*/
void kvm_arm_cpu_pre_save(ARMCPU *cpu);
/**
* kvm_arm_cpu_post_load:
* @cpu: ARMCPU
*
* Called from cpu_post_load() to update KVM CPU state from the cpreg list.
*/
void kvm_arm_cpu_post_load(ARMCPU *cpu);
/** /**
* kvm_arm_reset_vcpu: * kvm_arm_reset_vcpu:
* @cpu: ARMCPU * @cpu: ARMCPU
...@@ -292,6 +309,24 @@ int kvm_arm_sync_mpstate_to_kvm(ARMCPU *cpu); ...@@ -292,6 +309,24 @@ int kvm_arm_sync_mpstate_to_kvm(ARMCPU *cpu);
*/ */
int kvm_arm_sync_mpstate_to_qemu(ARMCPU *cpu); int kvm_arm_sync_mpstate_to_qemu(ARMCPU *cpu);
/**
* kvm_arm_get_virtual_time:
* @cs: CPUState
*
* Gets the VCPU's virtual counter and stores it in the KVM CPU state.
*/
void kvm_arm_get_virtual_time(CPUState *cs);
/**
* kvm_arm_put_virtual_time:
* @cs: CPUState
*
* Sets the VCPU's virtual counter to the value stored in the KVM CPU state.
*/
void kvm_arm_put_virtual_time(CPUState *cs);
void kvm_arm_vm_state_change(void *opaque, int running, RunState state);
int kvm_arm_vgic_probe(void); int kvm_arm_vgic_probe(void);
void kvm_arm_pmu_set_irq(CPUState *cs, int irq); void kvm_arm_pmu_set_irq(CPUState *cs, int irq);
...@@ -339,6 +374,9 @@ static inline void kvm_arm_pmu_set_irq(CPUState *cs, int irq) {} ...@@ -339,6 +374,9 @@ static inline void kvm_arm_pmu_set_irq(CPUState *cs, int irq) {}
static inline void kvm_arm_pmu_init(CPUState *cs) {} static inline void kvm_arm_pmu_init(CPUState *cs) {}
static inline void kvm_arm_sve_get_vls(CPUState *cs, unsigned long *map) {} static inline void kvm_arm_sve_get_vls(CPUState *cs, unsigned long *map) {}
static inline void kvm_arm_get_virtual_time(CPUState *cs) {}
static inline void kvm_arm_put_virtual_time(CPUState *cs) {}
#endif #endif
static inline const char *gic_class_name(void) static inline const char *gic_class_name(void)
......
...@@ -642,6 +642,12 @@ static int cpu_pre_save(void *opaque) ...@@ -642,6 +642,12 @@ static int cpu_pre_save(void *opaque)
/* This should never fail */ /* This should never fail */
abort(); abort();
} }
/*
* kvm_arm_cpu_pre_save() must be called after
* write_kvmstate_to_list()
*/
kvm_arm_cpu_pre_save(cpu);
} else { } else {
if (!write_cpustate_to_list(cpu, false)) { if (!write_cpustate_to_list(cpu, false)) {
/* This should never fail. */ /* This should never fail. */
...@@ -744,6 +750,7 @@ static int cpu_post_load(void *opaque, int version_id) ...@@ -744,6 +750,7 @@ static int cpu_post_load(void *opaque, int version_id)
* we're using it. * we're using it.
*/ */
write_list_to_cpustate(cpu); write_list_to_cpustate(cpu);
kvm_arm_cpu_post_load(cpu);
} else { } else {
if (!write_list_to_cpustate(cpu)) { if (!write_list_to_cpustate(cpu)) {
return -1; return -1;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册