331 lines
9.6 KiB
Diff
331 lines
9.6 KiB
Diff
|
From 5388ea3fc0737d1a659256ff3663057bef484c19 Mon Sep 17 00:00:00 2001
|
||
|
From: Andrew Jones <drjones@redhat.com>
|
||
|
Date: Fri, 31 Jan 2020 14:23:13 +0000
|
||
|
Subject: [PATCH 11/15] target/arm/kvm: Implement virtual time adjustment
|
||
|
MIME-Version: 1.0
|
||
|
Content-Type: text/plain; charset=UTF-8
|
||
|
Content-Transfer-Encoding: 8bit
|
||
|
|
||
|
RH-Author: Andrew Jones <drjones@redhat.com>
|
||
|
Message-id: <20200131142314.13175-5-drjones@redhat.com>
|
||
|
Patchwork-id: 93622
|
||
|
O-Subject: [RHEL-AV-8.2.0 qemu-kvm PATCH 4/5] target/arm/kvm: Implement virtual time adjustment
|
||
|
Bugzilla: 1647366
|
||
|
RH-Acked-by: Philippe Mathieu-Daudé <philmd@redhat.com>
|
||
|
RH-Acked-by: Auger Eric <eric.auger@redhat.com>
|
||
|
RH-Acked-by: Gavin Shan <gshan@redhat.com>
|
||
|
|
||
|
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1647366
|
||
|
|
||
|
Author: Andrew Jones <drjones@redhat.com>
|
||
|
Date: Thu, 30 Jan 2020 16:02:06 +0000
|
||
|
|
||
|
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: Bijan Mottahedeh <bijan.mottahedeh@oracle.com>
|
||
|
Signed-off-by: Andrew Jones <drjones@redhat.com>
|
||
|
Message-id: 20200120101023.16030-6-drjones@redhat.com
|
||
|
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
|
||
|
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
|
||
|
|
||
|
(cherry picked from commit e5ac4200b4cddf44df9adbef677af0d1f1c579c6)
|
||
|
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
|
||
|
---
|
||
|
target/arm/cpu.h | 7 ++++
|
||
|
target/arm/kvm.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
|
target/arm/kvm32.c | 3 ++
|
||
|
target/arm/kvm64.c | 3 ++
|
||
|
target/arm/kvm_arm.h | 38 ++++++++++++++++++++++
|
||
|
target/arm/machine.c | 7 ++++
|
||
|
6 files changed, 150 insertions(+)
|
||
|
|
||
|
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
|
||
|
index 82dd3cc..fbd8ea0 100644
|
||
|
--- a/target/arm/cpu.h
|
||
|
+++ b/target/arm/cpu.h
|
||
|
@@ -821,6 +821,13 @@ struct ARMCPU {
|
||
|
/* KVM init features for this CPU */
|
||
|
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 */
|
||
|
bool mp_is_up;
|
||
|
|
||
|
diff --git a/target/arm/kvm.c b/target/arm/kvm.c
|
||
|
index 5b82cef..26d7f8b 100644
|
||
|
--- a/target/arm/kvm.c
|
||
|
+++ b/target/arm/kvm.c
|
||
|
@@ -359,6 +359,22 @@ static int compare_u64(const void *a, const void *b)
|
||
|
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(®idx, 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
|
||
|
* definition of what CPU registers it knows about (and throw away
|
||
|
* the previous TCG-created cpreg list).
|
||
|
@@ -512,6 +528,23 @@ bool write_list_to_kvmstate(ARMCPU *cpu, int level)
|
||
|
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)
|
||
|
{
|
||
|
int ret;
|
||
|
@@ -579,6 +612,50 @@ int kvm_arm_sync_mpstate_to_qemu(ARMCPU *cpu)
|
||
|
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, ®);
|
||
|
+ 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, ®);
|
||
|
+ 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)
|
||
|
{
|
||
|
CPUARMState *env = &cpu->env;
|
||
|
@@ -690,6 +767,21 @@ MemTxAttrs kvm_arch_post_run(CPUState *cs, struct kvm_run *run)
|
||
|
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)
|
||
|
{
|
||
|
diff --git a/target/arm/kvm32.c b/target/arm/kvm32.c
|
||
|
index 32bf8d6..3a8b437 100644
|
||
|
--- a/target/arm/kvm32.c
|
||
|
+++ b/target/arm/kvm32.c
|
||
|
@@ -16,6 +16,7 @@
|
||
|
#include "qemu-common.h"
|
||
|
#include "cpu.h"
|
||
|
#include "qemu/timer.h"
|
||
|
+#include "sysemu/runstate.h"
|
||
|
#include "sysemu/kvm.h"
|
||
|
#include "kvm_arm.h"
|
||
|
#include "internals.h"
|
||
|
@@ -198,6 +199,8 @@ int kvm_arch_init_vcpu(CPUState *cs)
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
+ qemu_add_vm_change_state_handler(kvm_arm_vm_state_change, cs);
|
||
|
+
|
||
|
/* Determine init features for this CPU */
|
||
|
memset(cpu->kvm_init_features, 0, sizeof(cpu->kvm_init_features));
|
||
|
if (cpu->start_powered_off) {
|
||
|
diff --git a/target/arm/kvm64.c b/target/arm/kvm64.c
|
||
|
index 666a81a..d368189 100644
|
||
|
--- a/target/arm/kvm64.c
|
||
|
+++ b/target/arm/kvm64.c
|
||
|
@@ -23,6 +23,7 @@
|
||
|
#include "qemu/host-utils.h"
|
||
|
#include "qemu/main-loop.h"
|
||
|
#include "exec/gdbstub.h"
|
||
|
+#include "sysemu/runstate.h"
|
||
|
#include "sysemu/kvm.h"
|
||
|
#include "sysemu/kvm_int.h"
|
||
|
#include "kvm_arm.h"
|
||
|
@@ -735,6 +736,8 @@ int kvm_arch_init_vcpu(CPUState *cs)
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
+ qemu_add_vm_change_state_handler(kvm_arm_vm_state_change, cs);
|
||
|
+
|
||
|
/* Determine init features for this CPU */
|
||
|
memset(cpu->kvm_init_features, 0, sizeof(cpu->kvm_init_features));
|
||
|
if (cpu->start_powered_off) {
|
||
|
diff --git a/target/arm/kvm_arm.h b/target/arm/kvm_arm.h
|
||
|
index b48a9c9..01a9a18 100644
|
||
|
--- a/target/arm/kvm_arm.h
|
||
|
+++ b/target/arm/kvm_arm.h
|
||
|
@@ -128,6 +128,23 @@ bool write_list_to_kvmstate(ARMCPU *cpu, int level);
|
||
|
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:
|
||
|
* @cpu: ARMCPU
|
||
|
*
|
||
|
@@ -292,6 +309,24 @@ int kvm_arm_sync_mpstate_to_kvm(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);
|
||
|
|
||
|
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_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
|
||
|
|
||
|
static inline const char *gic_class_name(void)
|
||
|
diff --git a/target/arm/machine.c b/target/arm/machine.c
|
||
|
index eb28b23..241890a 100644
|
||
|
--- a/target/arm/machine.c
|
||
|
+++ b/target/arm/machine.c
|
||
|
@@ -642,6 +642,12 @@ static int cpu_pre_save(void *opaque)
|
||
|
/* This should never fail */
|
||
|
abort();
|
||
|
}
|
||
|
+
|
||
|
+ /*
|
||
|
+ * kvm_arm_cpu_pre_save() must be called after
|
||
|
+ * write_kvmstate_to_list()
|
||
|
+ */
|
||
|
+ kvm_arm_cpu_pre_save(cpu);
|
||
|
} else {
|
||
|
if (!write_cpustate_to_list(cpu, false)) {
|
||
|
/* This should never fail. */
|
||
|
@@ -744,6 +750,7 @@ static int cpu_post_load(void *opaque, int version_id)
|
||
|
* we're using it.
|
||
|
*/
|
||
|
write_list_to_cpustate(cpu);
|
||
|
+ kvm_arm_cpu_post_load(cpu);
|
||
|
} else {
|
||
|
if (!write_list_to_cpustate(cpu)) {
|
||
|
return -1;
|
||
|
--
|
||
|
1.8.3.1
|
||
|
|