| 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 |
| |
| |
| @@ -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 |
| |
| |
| @@ -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 |
| |
| |
| @@ -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 |
| |
| |
| @@ -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 |
| |
| |
| @@ -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 |
| |
| |
| @@ -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 |
| |