Commit b1f778a2 authored by Marc Zyngier's avatar Marc Zyngier
Browse files

KVM: arm64: pmu: Resync EL0 state on counter rotation



Huang Shijie reports that, when profiling a guest from the host
with a number of events that exceeds the number of available
counters, the reported counts are wildly inaccurate. Without
the counter oversubscription, the reported counts are correct.

Their investigation indicates that upon counter rotation (which
takes place on the back of a timer interrupt), we fail to
re-apply the guest EL0 enabling, leading to the counting of host
events instead of guest events.

In order to solve this, add yet another hook between the host PMU
driver and KVM, re-applying the guest EL0 configuration if the
right conditions apply (the host is VHE, we are in interrupt
context, and we interrupted a running vcpu). This triggers a new
vcpu request which will apply the correct configuration on guest
reentry.

With this, we have the correct counts, even when the counters are
oversubscribed.

Reported-by: default avatarHuang Shijie <shijie@os.amperecomputing.com>
Suggested-by: default avatarOliver Upton <oliver.upton@linux.dev>
Tested_by: Huang Shijie <shijie@os.amperecomputing.com>
Signed-off-by: default avatarMarc Zyngier <maz@kernel.org>
Cc: Leo Yan <leo.yan@linaro.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Will Deacon <will@kernel.org>
Link: https://lore.kernel.org/r/20230809013953.7692-1-shijie@os.amperecomputing.com


Acked-by: default avatarMark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20230820090108.177817-1-maz@kernel.org
parent 64b81000
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -227,6 +227,8 @@ static inline bool kvm_set_pmuserenr(u64 val)
	return false;
}

static inline void kvm_vcpu_pmu_resync_el0(void) {}

/* PMU Version in DFR Register */
#define ARMV8_PMU_DFR_VER_NI        0
#define ARMV8_PMU_DFR_VER_V3P4      0x5
+1 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@
#define KVM_REQ_RELOAD_GICv4	KVM_ARCH_REQ(4)
#define KVM_REQ_RELOAD_PMU	KVM_ARCH_REQ(5)
#define KVM_REQ_SUSPEND		KVM_ARCH_REQ(6)
#define KVM_REQ_RESYNC_PMU_EL0	KVM_ARCH_REQ(7)

#define KVM_DIRTY_LOG_MANUAL_CAPS   (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE | \
				     KVM_DIRTY_LOG_INITIALLY_SET)
+3 −0
Original line number Diff line number Diff line
@@ -803,6 +803,9 @@ static int check_vcpu_requests(struct kvm_vcpu *vcpu)
			kvm_pmu_handle_pmcr(vcpu,
					    __vcpu_sys_reg(vcpu, PMCR_EL0));

		if (kvm_check_request(KVM_REQ_RESYNC_PMU_EL0, vcpu))
			kvm_vcpu_pmu_restore_guest(vcpu);

		if (kvm_check_request(KVM_REQ_SUSPEND, vcpu))
			return kvm_vcpu_suspend(vcpu);

+18 −0
Original line number Diff line number Diff line
@@ -236,3 +236,21 @@ bool kvm_set_pmuserenr(u64 val)
	ctxt_sys_reg(hctxt, PMUSERENR_EL0) = val;
	return true;
}

/*
 * If we interrupted the guest to update the host PMU context, make
 * sure we re-apply the guest EL0 state.
 */
void kvm_vcpu_pmu_resync_el0(void)
{
	struct kvm_vcpu *vcpu;

	if (!has_vhe() || !in_interrupt())
		return;

	vcpu = kvm_get_running_vcpu();
	if (!vcpu)
		return;

	kvm_make_request(KVM_REQ_RESYNC_PMU_EL0, vcpu);
}
+2 −0
Original line number Diff line number Diff line
@@ -772,6 +772,8 @@ static void armv8pmu_start(struct arm_pmu *cpu_pmu)

	/* Enable all counters */
	armv8pmu_pmcr_write(armv8pmu_pmcr_read() | ARMV8_PMU_PMCR_E);

	kvm_vcpu_pmu_resync_el0();
}

static void armv8pmu_stop(struct arm_pmu *cpu_pmu)
Loading