mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-28 08:59:50 +00:00

It appears that on Qualcomm's x1e CPU, CNTVOFF_EL2 doesn't really work, specially with HCR_EL2.E2H=1. A non-zero offset results in a screaming virtual timer interrupt, to the tune of a few 100k interrupts per second on a 4 vcpu VM. This is also evidenced by this CPU's inability to correctly run any of the timer selftests. The only case this doesn't break is when this register is set to 0, which breaks VM migration. When HCR_EL2.E2H=0, the timer seems to behave normally, and does not result in an interrupt storm. As a workaround, use the fact that this CPU implements FEAT_ECV, and trap all accesses to the virtual timer and counter, keeping CNTVOFF_EL2 set to zero, and emulate accesses to CVAL/TVAL/CTL and the counter itself, fixing up the timer to account for the missing offset. And if you think this is disgusting, you'd probably be right. Acked-by: Oliver Upton <oliver.upton@linux.dev> Link: https://lore.kernel.org/r/20241217142321.763801-12-maz@kernel.org Signed-off-by: Marc Zyngier <maz@kernel.org>
71 lines
1.6 KiB
C
71 lines
1.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2012-2015 - ARM Ltd
|
|
* Author: Marc Zyngier <marc.zyngier@arm.com>
|
|
*/
|
|
|
|
#include <clocksource/arm_arch_timer.h>
|
|
#include <linux/compiler.h>
|
|
#include <linux/kvm_host.h>
|
|
|
|
#include <asm/kvm_hyp.h>
|
|
#include <asm/kvm_mmu.h>
|
|
|
|
void __kvm_timer_set_cntvoff(u64 cntvoff)
|
|
{
|
|
write_sysreg(cntvoff, cntvoff_el2);
|
|
}
|
|
|
|
/*
|
|
* Should only be called on non-VHE or hVHE setups.
|
|
* VHE systems use EL2 timers and configure EL1 timers in kvm_timer_init_vhe().
|
|
*/
|
|
void __timer_disable_traps(struct kvm_vcpu *vcpu)
|
|
{
|
|
u64 set, clr, shift = 0;
|
|
|
|
if (has_hvhe())
|
|
shift = 10;
|
|
|
|
/* Allow physical timer/counter access for the host */
|
|
set = (CNTHCTL_EL1PCTEN | CNTHCTL_EL1PCEN) << shift;
|
|
clr = CNTHCTL_EL1TVT | CNTHCTL_EL1TVCT;
|
|
|
|
sysreg_clear_set(cnthctl_el2, clr, set);
|
|
}
|
|
|
|
/*
|
|
* Should only be called on non-VHE or hVHE setups.
|
|
* VHE systems use EL2 timers and configure EL1 timers in kvm_timer_init_vhe().
|
|
*/
|
|
void __timer_enable_traps(struct kvm_vcpu *vcpu)
|
|
{
|
|
u64 clr = 0, set = 0;
|
|
|
|
/*
|
|
* Disallow physical timer access for the guest
|
|
* Physical counter access is allowed if no offset is enforced
|
|
* or running protected (we don't offset anything in this case).
|
|
*/
|
|
clr = CNTHCTL_EL1PCEN;
|
|
if (is_protected_kvm_enabled() ||
|
|
!kern_hyp_va(vcpu->kvm)->arch.timer_data.poffset)
|
|
set |= CNTHCTL_EL1PCTEN;
|
|
else
|
|
clr |= CNTHCTL_EL1PCTEN;
|
|
|
|
if (has_hvhe()) {
|
|
clr <<= 10;
|
|
set <<= 10;
|
|
}
|
|
|
|
/*
|
|
* Trap the virtual counter/timer if we have a broken cntvoff
|
|
* implementation.
|
|
*/
|
|
if (has_broken_cntvoff())
|
|
set |= CNTHCTL_EL1TVT | CNTHCTL_EL1TVCT;
|
|
|
|
sysreg_clear_set(cnthctl_el2, clr, set);
|
|
}
|