KVM: arm64: timers: Convert per-vcpu virtual offset to a global value
Marc Zyngier
maintainerPATCH v2 1/1 KVM: arm64: timers: Convert per-vcpu virtual offset to a global value
Having a per-vcpu virtual offset is a pain. It needs to be synchronized on each update, and expands badly to a setup where different timers can have different offsets, or have composite offsets (as with NV).
So let's start by replacing the use of the CNTVOFF_EL2 shadow register (which we want to reclaim for NV anyway), and make the virtual timer carry a pointer to a VM-wide offset.
This simplifies the code significantly. It also addresses two terrible bugs:
- The use of CNTVOFF_EL2 leads to some nice offset corruption when the sysreg gets reset, as reported by Joey.
- The kvm mutex is taken from a vcpu ioctl, which goes against the locking rules...
--- arch/arm64/include/asm/kvm_host.h | 3 +++ arch/arm64/kvm/arch_timer.c | 45 +++++++------------------------ arch/arm64/kvm/hypercalls.c | 2 +- include/kvm/arm_arch_timer.h | 15 +++++++++++ 4 files changed, 29 insertions(+), 36 deletions(-)
arch/arm64/include/asm/kvm_host.h +3 -0
@@ -193,6 +193,9 @@ struct kvm_arch {/* Interrupt controller */ struct vgic_dist vgic; +/* Timers */ +struct arch_timer_vm_data timer_data; +/* Mandated version of PSCI */ u32 psci_version;
arch/arm64/kvm/arch_timer.c +10 -35
81 lines not on this page · raw message
arch/arm64/kvm/hypercalls.c +1 -1
@@ -44,7 +44,7 @@ static void kvm_ptp_get_time(struct kvm_vcpu *vcpu, u64 *val) feature =smccc_get_arg1 (vcpu);switch (feature) {case KVM_PTP_VIRT_COUNTER : - cycles = systime_snapshot.cycles -vcpu_read_sys_reg (vcpu,CNTVOFF_EL2 ); + cycles = systime_snapshot.cycles - vcpu->kvm->arch.timer_data.voffset;break ;case KVM_PTP_PHYS_COUNTER : cycles = systime_snapshot.cycles;
include/kvm/arm_arch_timer.h +15 -0
@@ -23,6 +23,19 @@ enum kvm_arch_timer_regs { TIMER_REG_CTL, }; +struct arch_timer_offset { +/* +* If set, pointer to one of the offsets in the kvm's offset +* structure. If NULL, assume a zero offset. +*/ +u64 *vm_offset; +}; + +struct arch_timer_vm_data { +/* Offset applied to the virtual timer/counter */ +u64 voffset; +}; +struct arch_timer_context {struct kvm_vcpu *vcpu;
@@ -32,6 +45,8 @@ struct arch_timer_context {/* Emulated Timer (may be unused) */ struct hrtimer hrtimer; +/* Offset for this counter/timer */ +struct arch_timer_offset offset;/* * We have multiple paths which can save/restore the timer state onto * the hardware, so we need some way of keeping track of where the
Signature · 1 line
2.34.1
Hi Marc,
Quoted 13 lines · Marc Zyngier
Having a per-vcpu virtual offset is a pain. It needs to be synchronized on each update, and expands badly to a setup where different timers can have different offsets, or have composite offsets (as with NV).
So let's start by replacing the use of the
CNTVOFF_EL2shadow register (which we want to reclaim for NV anyway), and make the virtual timer carry a pointer to a VM-wide offset.This simplifies the code significantly. It also addresses two terrible bugs:
- The use of
CNTVOFF_EL2leads to some nice offset corruption when the sysreg gets reset, as reported by Joey.
Quoted 7 lines
- The kvm mutex is taken from a vcpu ioctl, which goes against the locking rules...
Reported-by Joey Gouly <[email protected]>Reviewed-by Reiji Watanabe <[email protected]>Signed-off-by Marc Zyngier <[email protected]>
Fixes my mismatched timer offset issues.
Thanks,
Joey
Quoted 159 lines
--- arch/arm64/include/asm/kvm_host.h | 3 +++ arch/arm64/kvm/arch_timer.c | 45 +++++++------------------------ arch/arm64/kvm/hypercalls.c | 2 +- include/kvm/arm_arch_timer.h | 15 +++++++++++ 4 files changed, 29 insertions(+), 36 deletions(-)arch/arm64/include/asm/kvm_host.h +3
-0@@ -193,6 +193,9 @@ struct kvm_arch {/* Interrupt controller */ struct vgic_dist vgic; +/* Timers */ +struct arch_timer_vm_data timer_data; +/* Mandated version of PSCI */ u32 psci_version;arch/arm64/kvm/arch_timer.c +10
-35@@ -84,14 +84,10 @@ u64 timer_get_cval(struct arch_timer_context *ctxt)static u64 timer_get_offset (struct arch_timer_context *ctxt) { -struct kvm_vcpu *vcpu = ctxt->vcpu; +if (ctxt->offset.vm_offset) +return *ctxt->offset.vm_offset; -switch (arch_timer_ctx_index (ctxt)) { -case TIMER_VTIMER : -return __vcpu_sys_reg (vcpu,CNTVOFF_EL2 ); -default : -return 0 ; - } +return 0 ; }static void timer_set_ctl (struct arch_timer_context *ctxt,u32 ctl)@@ -128,15 +124,12 @@ static void timer_set_cval(struct arch_timer_context *ctxt, u64 cval)static void timer_set_offset (struct arch_timer_context *ctxt,u64 offset) { -struct kvm_vcpu *vcpu = ctxt->vcpu; - -switch (arch_timer_ctx_index (ctxt)) { -case TIMER_VTIMER : -__vcpu_sys_reg (vcpu,CNTVOFF_EL2 ) = offset; -break ; -default : +if (!ctxt->offset.vm_offset) {WARN (offset,"timer %ld\n" ,arch_timer_ctx_index (ctxt)); +return ; } + +WRITE_ONCE (*ctxt->offset.vm_offset, offset); }u64 kvm_phys_timer_read (void )@@ -765,25 +758,6 @@ int kvm_timer_vcpu_reset(struct kvm_vcpu *vcpu)return 0 ; } -/* Make the updates of cntvoff for all vtimer contexts atomic */ -static void update_vtimer_cntvoff (struct kvm_vcpu *vcpu,u64 cntvoff) -{ -unsigned long i; -struct kvm *kvm = vcpu->kvm; -struct kvm_vcpu *tmp; - -mutex_lock (&kvm->lock); -kvm_for_each_vcpu (i, tmp, kvm) -timer_set_offset (vcpu_vtimer (tmp), cntvoff); - -/* -* When called from the vcpu create path, the CPU being created is not -* included in the loop above, so we just set it here as well. -*/ -timer_set_offset (vcpu_vtimer (vcpu), cntvoff); -mutex_unlock (&kvm->lock); -} -void kvm_timer_vcpu_init (struct kvm_vcpu *vcpu) {struct arch_timer_cpu *timer =vcpu_timer (vcpu);@@ -791,10 +765,11 @@ void kvm_timer_vcpu_init(struct kvm_vcpu *vcpu)struct arch_timer_context *ptimer =vcpu_ptimer (vcpu); vtimer->vcpu = vcpu; + vtimer->offset.vm_offset = &vcpu->kvm->arch.timer_data.voffset; ptimer->vcpu = vcpu;/* Synchronize cntvoff across all vtimers of a VM. */ -update_vtimer_cntvoff (vcpu,kvm_phys_timer_read ()); +timer_set_offset (vtimer,kvm_phys_timer_read ());timer_set_offset (ptimer,0 );hrtimer_init (&timer->bg_timer,CLOCK_MONOTONIC ,HRTIMER_MODE_ABS_HARD );@@ -840,7 +815,7 @@ int kvm_arm_timer_set_reg(struct kvm_vcpu *vcpu, u64 regid, u64 value)break ;case KVM_REG_ARM_TIMER_CNT : timer =vcpu_vtimer (vcpu); -update_vtimer_cntvoff (vcpu,kvm_phys_timer_read () - value); +timer_set_offset (timer,kvm_phys_timer_read () - value);break ;case KVM_REG_ARM_TIMER_CVAL : timer =vcpu_vtimer (vcpu);arch/arm64/kvm/hypercalls.c +1
-1@@ -44,7 +44,7 @@ static void kvm_ptp_get_time(struct kvm_vcpu *vcpu, u64 *val) feature =smccc_get_arg1 (vcpu);switch (feature) {case KVM_PTP_VIRT_COUNTER : - cycles = systime_snapshot.cycles -vcpu_read_sys_reg (vcpu,CNTVOFF_EL2 ); + cycles = systime_snapshot.cycles - vcpu->kvm->arch.timer_data.voffset;break ;case KVM_PTP_PHYS_COUNTER : cycles = systime_snapshot.cycles;include/kvm/arm_arch_timer.h +15
-0@@ -23,6 +23,19 @@ enum kvm_arch_timer_regs { TIMER_REG_CTL, }; +struct arch_timer_offset { +/* +* If set, pointer to one of the offsets in the kvm's offset +* structure. If NULL, assume a zero offset. +*/ +u64 *vm_offset; +}; + +struct arch_timer_vm_data { +/* Offset applied to the virtual timer/counter */ +u64 voffset; +}; +struct arch_timer_context {struct kvm_vcpu *vcpu;@@ -32,6 +45,8 @@ struct arch_timer_context {/* Emulated Timer (may be unused) */ struct hrtimer hrtimer; +/* Offset for this counter/timer */ +struct arch_timer_offset offset;/* * We have multiple paths which can save/restore the timer state onto * the hardware, so we need some way of keeping track of where the Signature · 1 line
2.34.1
Marc Zyngier
maintainerQuoted 20 lines · Joey Gouly
Hi Marc,
Quoted 16 lines · Marc Zyngier
Having a per-vcpu virtual offset is a pain. It needs to be synchronized on each update, and expands badly to a setup where different timers can have different offsets, or have composite offsets (as with NV).
So let's start by replacing the use of the
CNTVOFF_EL2shadow register (which we want to reclaim for NV anyway), and make the virtual timer carry a pointer to a VM-wide offset.This simplifies the code significantly. It also addresses two terrible bugs:
- The use of
CNTVOFF_EL2leads to some nice offset corruption when the sysreg gets reset, as reported by Joey.- The kvm mutex is taken from a vcpu ioctl, which goes against the locking rules...
Quoted 8 lines
Quoted 4 lines
Reported-by Joey Gouly <[email protected]>Reviewed-by Reiji Watanabe <[email protected]>Signed-off-by Marc Zyngier <[email protected]>Fixes my mismatched timer offset issues.
Tested-by Joey Gouly <[email protected]>
Thanks for having given it a go. Hopefully Oliver will be able to send this to as a fix shortly.
Thanks,
M.
Signature · 1 line
Without deviation from the norm, progress is not possible.
Quoted 27 lines · Marc Zyngier
Quoted 25 lines · Joey Gouly
Hi Marc,
Quoted 20 lines · Marc Zyngier
Having a per-vcpu virtual offset is a pain. It needs to be synchronized on each update, and expands badly to a setup where different timers can have different offsets, or have composite offsets (as with NV).
So let's start by replacing the use of the
CNTVOFF_EL2shadow register (which we want to reclaim for NV anyway), and make the virtual timer carry a pointer to a VM-wide offset.This simplifies the code significantly. It also addresses two terrible bugs:
- The use of
CNTVOFF_EL2leads to some nice offset corruption when the sysreg gets reset, as reported by Joey.- The kvm mutex is taken from a vcpu ioctl, which goes against the locking rules...
Reported-by Joey Gouly <[email protected]>Reviewed-by Reiji Watanabe <[email protected]>Signed-off-by Marc Zyngier <[email protected]>
Quoted 6 lines
Quoted 3 lines
Fixes my mismatched timer offset issues.
Tested-by Joey Gouly <[email protected]>Thanks for having given it a go. Hopefully Oliver will be able to send this to as a fix shortly.
Absolutely, I have this queued up. Thanks for testing the patch Joey.
Want to see if I can get push access to the kvmarm repository in time, otherwise I'll send a pull to you Marc.
Signature · 2 lines
Thanks, Oliver
Oliver Upton
AppliedQuoted 9 lines · Marc Zyngier
Having a per-vcpu virtual offset is a pain. It needs to be synchronized on each update, and expands badly to a setup where different timers can have different offsets, or have composite offsets (as with NV).
So let's start by replacing the use of the
CNTVOFF_EL2shadow register (which we want to reclaim for NV anyway), and make the virtual timer carry a pointer to a VM-wide offset.[...]
Applied to kvmarm/fixes, thanks!
[1/1] KVM: arm64: timers: Convert per-vcpu virtual offset to a global value
https://git.kernel.org/kvmarm/kvmarm/c/47053904e182
--
Best,
Oliver