Merge branch kvm-arm64/misc-7.4 into kvmarm-master/next

* kvm-arm64/misc-7.4:
  : \
  : Misc KVM/arm64 fixes for 7.4:
  :
  : - Correctly report SMCCC_VERSION and SMCCC_ARCH_FEATURES as implemented
  :
  : - Don't register a page table dumper for pKVM VMs, as S2 is not
  :   accessible when in protected mode
  :
  : - Drop PAGE_SIZE alignment requirement for hyp trace event sections
  :
  : - Tighten PMUv3 being disabled for the guest even if vcpus were created
  :   with one.
  :
  : - Fix timer emulation when the HW doesn't support offsetting of the
  :   counter.
  : /
  KVM: arm64: selftests: Test a timer set past the counter's wrap
  KVM: arm64: nv: Read a guest hypervisor's CNTV_CVAL_EL0 from memory on x1e
  KVM: arm64: timers: Compute an offset-applied CVAL from the current count
  KVM: arm64: Check ID_AA64DFR0_EL1.PMUVer in pmu_visibility()
  KVM: arm64: Handle ID_AA64DFR0_EL1.PMUVer == NI in __kvm_pmu_event_mask()
  KVM: arm64: Remove PAGE_SIZE alignment for hyp event ELF sections
  KVM: arm64: Disable stage-2 ptdump of pKVM
  KVM: arm64: selftests: Check the mandatory SMCCC_ARCH_FEATURES queries
  KVM: arm64: Report SMCCC_VERSION and SMCCC_ARCH_FEATURES as implemented

Signed-off-by: Marc Zyngier <maz@kernel.org>
This commit is contained in:
Marc Zyngier 2026-10-11 10:44:26 +01:00
commit fddb672002
11 changed files with 105 additions and 32 deletions

View File

@ -15,7 +15,7 @@
#ifdef CONFIG_NVHE_EL2_TRACING
#define HYPERVISOR_EVENT_IDS \
. = ALIGN(PAGE_SIZE); \
. = ALIGN(SZ_8); \
__hyp_event_ids_start = .; \
*(HYP_SECTION_NAME(.event_ids)) \
__hyp_event_ids_end = .;

View File

@ -528,17 +528,11 @@ static void timer_save_state(struct arch_timer_context *ctx)
goto out;
switch (index) {
u64 cval;
case TIMER_VTIMER:
case TIMER_HVTIMER:
timer_set_ctl(ctx, read_sysreg_el0(SYS_CNTV_CTL));
cval = read_sysreg_el0(SYS_CNTV_CVAL);
if (has_broken_cntvoff())
cval -= timer_get_offset(ctx);
timer_set_cval(ctx, cval);
if (!has_broken_cntvoff() || !timer_get_offset(ctx))
timer_set_cval(ctx, read_sysreg_el0(SYS_CNTV_CVAL));
/* Disable the timer */
write_sysreg_el0(0, SYS_CNTV_CTL);
@ -564,11 +558,12 @@ static void timer_save_state(struct arch_timer_context *ctx)
case TIMER_PTIMER:
case TIMER_HPTIMER:
timer_set_ctl(ctx, read_sysreg_el0(SYS_CNTP_CTL));
cval = read_sysreg_el0(SYS_CNTP_CVAL);
cval -= timer_get_offset(ctx);
timer_set_cval(ctx, cval);
/*
* With an offset, memory already holds the guest's CVAL (the
* trap handler or __deactivate_traps() wrote it).
*/
if (!timer_get_offset(ctx))
timer_set_cval(ctx, read_sysreg_el0(SYS_CNTP_CVAL));
/* Disable the timer */
write_sysreg_el0(0, SYS_CNTP_CTL);
@ -647,7 +642,8 @@ static void timer_restore_state(struct arch_timer_context *ctx)
offset = timer_get_offset(ctx);
if (has_broken_cntvoff()) {
set_cntvoff(0);
cval += offset;
if (offset)
cval = timer_apply_offset(cval, offset, kvm_phys_timer_read());
} else {
set_cntvoff(offset);
}
@ -660,7 +656,8 @@ static void timer_restore_state(struct arch_timer_context *ctx)
cval = timer_get_cval(ctx);
offset = timer_get_offset(ctx);
set_cntpoff(offset);
cval += offset;
if (offset)
cval = timer_apply_offset(cval, offset, kvm_phys_timer_read());
write_sysreg_el0(cval, SYS_CNTP_CVAL);
isb();
write_sysreg_el0(timer_get_ctl(ctx), SYS_CNTP_CTL);

View File

@ -17,7 +17,6 @@ SECTIONS {
HYP_SECTION(.data..ro_after_init)
HYP_SECTION(.rodata)
#ifdef CONFIG_NVHE_EL2_TRACING
. = ALIGN(PAGE_SIZE);
BEGIN_HYP_SECTION(.event_ids)
*(SORT(.hyp.event_ids.*))
END_HYP_SECTION

View File

@ -175,7 +175,8 @@ static void __deactivate_traps(struct kvm_vcpu *vcpu)
offset = read_sysreg_s(SYS_CNTPOFF_EL2);
if (map.direct_ptimer && offset) {
write_sysreg_el0(val + offset, SYS_CNTP_CVAL);
val = timer_apply_offset(val, offset, arch_timer_read_cntpct_el0());
write_sysreg_el0(val, SYS_CNTP_CVAL);
isb();
}
}
@ -296,10 +297,10 @@ static bool kvm_hyp_handle_timer(struct kvm_vcpu *vcpu, u64 *exit_code)
break;
case SYS_CNTP_CVAL_EL0:
if (vcpu_el2_e2h_is_set(vcpu)) {
val = read_sysreg_el0(SYS_CNTP_CVAL);
if (!has_cntpoff())
val -= timer_get_offset(vcpu_hptimer(vcpu));
if (!has_cntpoff() && timer_get_offset(vcpu_hptimer(vcpu)))
val = __vcpu_sys_reg(vcpu, CNTHP_CVAL_EL2);
else
val = read_sysreg_el0(SYS_CNTP_CVAL);
} else {
val = __vcpu_sys_reg(vcpu, CNTP_CVAL_EL0);
}
@ -321,10 +322,14 @@ static bool kvm_hyp_handle_timer(struct kvm_vcpu *vcpu, u64 *exit_code)
val = __vcpu_sys_reg(vcpu, CNTV_CVAL_EL0);
break;
case SYS_CNTV_CVAL_EL0:
if (vcpu_el2_e2h_is_set(vcpu))
val = read_sysreg_el0(SYS_CNTV_CVAL);
else
if (vcpu_el2_e2h_is_set(vcpu)) {
if (has_broken_cntvoff() && timer_get_offset(vcpu_hvtimer(vcpu)))
val = __vcpu_sys_reg(vcpu, CNTHV_CVAL_EL2);
else
val = read_sysreg_el0(SYS_CNTV_CVAL);
} else {
val = __vcpu_sys_reg(vcpu, CNTV_CVAL_EL0);
}
break;
case SYS_CNTVCT_EL0:
case SYS_CNTVCTSS_EL0:

View File

@ -294,6 +294,10 @@ int kvm_smccc_call_handler(struct kvm_vcpu *vcpu)
case ARM_SMCCC_ARCH_FEATURES_FUNC_ID:
feature = smccc_get_arg1(vcpu);
switch (feature) {
case ARM_SMCCC_VERSION_FUNC_ID:
case ARM_SMCCC_ARCH_FEATURES_FUNC_ID:
val[0] = SMCCC_RET_SUCCESS;
break;
case ARM_SMCCC_ARCH_WORKAROUND_1:
switch (arm64_get_spectre_v2_state()) {
case SPECTRE_VULNERABLE:

View File

@ -50,8 +50,11 @@ static u32 __kvm_pmu_event_mask(unsigned int pmuver)
case ID_AA64DFR0_EL1_PMUVer_V3P5:
case ID_AA64DFR0_EL1_PMUVer_V3P7:
return GENMASK(15, 0);
default: /* Shouldn't be here, just for sanity */
default:
/* Shouldn't be here, just for sanity */
WARN_ONCE(1, "Unknown PMU version %d\n", pmuver);
fallthrough;
case ID_AA64DFR0_EL1_PMUVer_NI:
return 0;
}
}

View File

@ -321,6 +321,10 @@ void kvm_nested_s2_ptdump_create_debugfs(struct kvm_s2_mmu *mmu, int idx)
void kvm_s2_ptdump_create_debugfs(struct kvm *kvm)
{
/* pKVM handles the page-tables in the hypervisor */
if (is_protected_kvm_enabled())
return;
debugfs_create_file("stage2_page_tables", 0400, kvm->debugfs_dentry,
&kvm->arch.mmu, &kvm_ptdump_guest_fops);
debugfs_create_file("ipa_range", 0400, kvm->debugfs_dentry,

View File

@ -1029,7 +1029,7 @@ static unsigned int hidden_visibility(const struct kvm_vcpu *vcpu,
static unsigned int pmu_visibility(const struct kvm_vcpu *vcpu,
const struct sys_reg_desc *r)
{
if (kvm_vcpu_has_pmu(vcpu))
if (kvm_has_feat(vcpu->kvm, ID_AA64DFR0_EL1, PMUVer, IMP))
return 0;
return REG_HIDDEN;
@ -1059,10 +1059,6 @@ static u64 reset_pmevcntr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
static u64 reset_pmevtyper(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
{
/* This thing will UNDEF, who cares about the reset value? */
if (!kvm_vcpu_has_pmu(vcpu))
return 0;
reset_unknown(vcpu, r);
__vcpu_rmw_sys_reg(vcpu, r->reg, &=, kvm_pmu_evtyper_mask(vcpu->kvm));

View File

@ -195,4 +195,19 @@ static inline void timer_set_offset(struct arch_timer_context *ctxt, u64 offset)
WRITE_ONCE(*ctxt->offset.vm_offset, offset);
}
/*
* CVAL to program when the hardware won't apply the timer's offset, so that
* its compare against the raw counter matches the guest's at 'now'. An
* expired timer stays expired; one past the wrap never fires.
*/
static inline u64 timer_apply_offset(u64 cval, u64 offset, u64 now)
{
u64 hw = cval + offset;
if (now - offset >= cval)
return 0;
return hw > now ? hw : U64_MAX;
}
#endif

View File

@ -787,6 +787,30 @@ static void test_timers_in_the_past(enum arch_timer timer)
}
}
/*
* A cval within long_wait_ms of the counter's wrap while the counter sits at
* wait_ms: a deadline further away than the counter can reach. Unmasked, so
* an early ISTATUS shows up as an IRQ.
*/
static void test_timers_past_counter_wrap(enum arch_timer timer)
{
u64 cval = -msec_to_cycles(test_args.long_wait_ms);
int i;
for (i = 0; i < ARRAY_SIZE(sleep_method); i++) {
sleep_method_t sm = sleep_method[i];
local_irq_disable();
set_counter(timer, msec_to_cycles(test_args.wait_ms));
set_cval_irq(timer, cval, CTL_ENABLE);
sm(timer, msecs_to_usecs(test_args.wait_ms) + TIMEOUT_NO_IRQ_US);
local_irq_enable();
isb();
assert_irqs_handled(0);
timer_set_ctl(timer, CTL_IMASK);
}
}
static void test_long_timer_delays(enum arch_timer timer)
{
s32 tval = (s32)msec_to_cycles(test_args.long_wait_ms);
@ -806,6 +830,7 @@ static void guest_run_iteration(enum arch_timer timer)
test_basic_functionality(timer);
test_timers_sanity_checks(timer);
test_timers_past_counter_wrap(timer);
test_timers_above_tval_max(timer);
test_timers_in_the_past(timer);

View File

@ -6,7 +6,8 @@
* via the pseudo-firmware bitmap register. This includes the registers'
* read/write behavior before and after the VM has started, and if the
* hypercalls are properly masked or unmasked to the guest when disabled or
* enabled from the KVM userspace, respectively.
* enabled from the KVM userspace, respectively. It also checks the two
* SMCCC_ARCH_FEATURES queries whose answer SMCCC pins to SUCCESS.
*/
#include <errno.h>
#include <linux/arm-smccc.h>
@ -97,6 +98,12 @@ static const struct test_hvc_info false_hvc_info[] = {
TEST_HVC_INFO(ARM_SMCCC_HV_PV_TIME_FEATURES, ARM_SMCCC_TRNG_RND64),
};
/* SMCCC pins the answer to SUCCESS, whatever the bitmaps hold. */
static const struct test_hvc_info always_hvc_info[] = {
TEST_HVC_INFO(ARM_SMCCC_ARCH_FEATURES_FUNC_ID, ARM_SMCCC_VERSION_FUNC_ID),
TEST_HVC_INFO(ARM_SMCCC_ARCH_FEATURES_FUNC_ID, ARM_SMCCC_ARCH_FEATURES_FUNC_ID),
};
static void guest_test_hvc(const struct test_hvc_info *hc_info)
{
unsigned int i;
@ -128,6 +135,23 @@ static void guest_test_hvc(const struct test_hvc_info *hc_info)
}
}
static void guest_test_always_hvc(void)
{
struct arm_smccc_res res;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(always_hvc_info); i++) {
memset(&res, 0, sizeof(res));
do_smccc(always_hvc_info[i].func_id, always_hvc_info[i].arg1,
0, 0, 0, 0, 0, 0, &res);
__GUEST_ASSERT(res.a0 == SMCCC_RET_SUCCESS,
"a0 = 0x%lx, func_id = 0x%x, arg1 = 0x%lx, stage = %u",
res.a0, always_hvc_info[i].func_id,
always_hvc_info[i].arg1, stage);
}
}
static void guest_code(void)
{
while (stage != TEST_STAGE_END) {
@ -137,6 +161,7 @@ static void guest_code(void)
case TEST_STAGE_HVC_IFACE_FEAT_DISABLED:
case TEST_STAGE_HVC_IFACE_FEAT_ENABLED:
guest_test_hvc(hvc_info);
guest_test_always_hvc();
break;
case TEST_STAGE_HVC_IFACE_FALSE_INFO:
guest_test_hvc(false_hvc_info);