From: Colton Lewis <coltonlewis@google.com>
To: kvm@vger.kernel.org
Cc: Alexandru Elisei <alexandru.elisei@arm.com>,
Paolo Bonzini <pbonzini@redhat.com>,
Jonathan Corbet <corbet@lwn.net>,
Russell King <linux@armlinux.org.uk>,
Catalin Marinas <catalin.marinas@arm.com>,
Will Deacon <will@kernel.org>, Marc Zyngier <maz@kernel.org>,
Oliver Upton <oliver.upton@linux.dev>,
Mingwei Zhang <mizhang@google.com>,
Joey Gouly <joey.gouly@arm.com>,
Suzuki K Poulose <suzuki.poulose@arm.com>,
Zenghui Yu <yuzenghui@huawei.com>,
Mark Rutland <mark.rutland@arm.com>,
Shuah Khan <shuah@kernel.org>,
Ganapatrao Kulkarni <gankulkarni@os.amperecomputing.com>,
James Clark <james.clark@linaro.org>,
linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
linux-perf-users@vger.kernel.org,
linux-kselftest@vger.kernel.org,
Colton Lewis <coltonlewis@google.com>
Subject: [PATCH 15/21] KVM: arm64: Implement lazy PMU context swaps
Date: Fri, 12 Jun 2026 19:29:03 +0000 [thread overview]
Message-ID: <20260612192909.1153907-16-coltonlewis@google.com> (raw)
In-Reply-To: <20260612192909.1153907-1-coltonlewis@google.com>
Since many guests will never touch the PMU, they need not pay the cost
of context swapping those registers.
Use an enum to implement a simple state machine for PMU register
access. The PMU is either free or guest owned. We only need to context
swap if the PMU registers are guest owned. The PMU initially starts as
free and only transitions to guest owned if a guest has touched the
PMU registers.
Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
arch/arm64/include/asm/kvm_host.h | 1 +
arch/arm64/include/asm/kvm_types.h | 6 +++++-
arch/arm64/kvm/debug.c | 5 +++--
arch/arm64/kvm/pmu-direct.c | 21 +++++++++++++++++++--
arch/arm64/kvm/sys_regs.c | 29 ++++++++++++++++-------------
include/kvm/arm_pmu.h | 8 ++++++++
6 files changed, 52 insertions(+), 18 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 9c7e9b92dfbd3..32573b10d9c5b 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -1445,6 +1445,7 @@ static inline bool kvm_system_needs_idmapped_vectors(void)
return cpus_have_final_cap(ARM64_SPECTRE_V3A);
}
+void kvm_arm_setup_mdcr_el2(struct kvm_vcpu *vcpu);
void kvm_init_host_debug_data(void);
void kvm_debug_init_vhe(void);
void kvm_vcpu_load_debug(struct kvm_vcpu *vcpu);
diff --git a/arch/arm64/include/asm/kvm_types.h b/arch/arm64/include/asm/kvm_types.h
index 9a126b9e2d7c9..4e39cbc80aa0b 100644
--- a/arch/arm64/include/asm/kvm_types.h
+++ b/arch/arm64/include/asm/kvm_types.h
@@ -4,5 +4,9 @@
#define KVM_ARCH_NR_OBJS_PER_MEMORY_CACHE 40
-#endif /* _ASM_ARM64_KVM_TYPES_H */
+enum vcpu_pmu_register_access {
+ VCPU_PMU_ACCESS_FREE,
+ VCPU_PMU_ACCESS_GUEST_OWNED,
+};
+#endif /* _ASM_ARM64_KVM_TYPES_H */
diff --git a/arch/arm64/kvm/debug.c b/arch/arm64/kvm/debug.c
index c84321277d893..ab80325e67c5c 100644
--- a/arch/arm64/kvm/debug.c
+++ b/arch/arm64/kvm/debug.c
@@ -35,7 +35,7 @@ static int cpu_has_spe(u64 dfr0)
* - Self-hosted Trace Filter controls (MDCR_EL2_TTRF)
* - Self-hosted Trace (MDCR_EL2_TTRF/MDCR_EL2_E2TB)
*/
-static void kvm_arm_setup_mdcr_el2(struct kvm_vcpu *vcpu)
+void kvm_arm_setup_mdcr_el2(struct kvm_vcpu *vcpu)
{
preempt_disable();
@@ -63,7 +63,8 @@ static void kvm_arm_setup_mdcr_el2(struct kvm_vcpu *vcpu)
* fine grain traps and enforce counter access with
* HPMN.
*/
- if (!vcpu_on_unsupported_cpu(vcpu) &&
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED &&
+ !vcpu_on_unsupported_cpu(vcpu) &&
cpus_have_final_cap(ARM64_HAS_FGT) &&
(cpus_have_final_cap(ARM64_HAS_HPMN0) || nr_guest_cntr > 0)) {
vcpu->arch.mdcr_el2 &= ~(MDCR_EL2_TPM | MDCR_EL2_TPMCR | MDCR_EL2_HPMN);
diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
index 044f011c9c84b..bb1f3dca03869 100644
--- a/arch/arm64/kvm/pmu-direct.c
+++ b/arch/arm64/kvm/pmu-direct.c
@@ -269,7 +269,7 @@ void kvm_pmu_load(struct kvm_vcpu *vcpu)
* If we aren't guest-owned then we know the guest isn't using
* the PMU anyway, so no need to bother with the swap.
*/
- if (!kvm_pmu_is_partitioned(vcpu->kvm))
+ if (vcpu->arch.pmu.access != VCPU_PMU_ACCESS_GUEST_OWNED)
return;
preempt_disable();
@@ -343,7 +343,7 @@ void kvm_pmu_put(struct kvm_vcpu *vcpu)
* accessing the PMU anyway, so no need to bother with the
* swap.
*/
- if (!kvm_pmu_is_partitioned(vcpu->kvm))
+ if (vcpu->arch.pmu.access != VCPU_PMU_ACCESS_GUEST_OWNED)
return;
preempt_disable();
@@ -388,3 +388,20 @@ void kvm_pmu_put(struct kvm_vcpu *vcpu)
kvm_pmu_set_guest_counters(pmu, 0);
preempt_enable();
}
+
+/**
+ * kvm_pmu_set_guest_owned() - Give PMU ownership to guest
+ * @vcpu: Pointer to vcpu struct
+ *
+ * Reconfigure the guest for physical access of PMU hardware if
+ * allowed. This means reconfiguring mdcr_el2.
+ *
+ */
+void kvm_pmu_set_guest_owned(struct kvm_vcpu *vcpu)
+{
+ if (kvm_pmu_is_partitioned(vcpu->kvm) &&
+ vcpu->arch.pmu.access == VCPU_PMU_ACCESS_FREE) {
+ vcpu->arch.pmu.access = VCPU_PMU_ACCESS_GUEST_OWNED;
+ kvm_arm_setup_mdcr_el2(vcpu);
+ }
+}
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 94572bc52c32a..f0eebeeb5ed96 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -1085,15 +1085,17 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
u64 mask;
int idx;
+ kvm_pmu_set_guest_owned(vcpu);
+
switch (reg) {
case PMCR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
kvm_pmu_direct_pmcr_write(vcpu, val);
else
kvm_pmu_handle_pmcr(vcpu, val);
break;
case PMSELR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
write_sysreg(val, pmselr_el0);
else
__vcpu_assign_sys_reg(vcpu, reg, val);
@@ -1101,7 +1103,7 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
case PMEVCNTR0_EL0 ... PMCCNTR_EL0:
idx = reg - PMEVCNTR0_EL0;
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
if (idx == ARMV8_PMU_CYCLE_IDX)
write_sysreg(val, pmccntr_el0);
else
@@ -1122,7 +1124,7 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
}
break;
case PMCNTENSET_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
if (set)
write_sysreg(val, pmcntenset_el0);
else
@@ -1139,7 +1141,7 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
}
break;
case PMINTENSET_EL1:
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
if (set)
write_sysreg(val, pmintenset_el1);
else
@@ -1166,7 +1168,7 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
local_irq_restore(flags);
break;
case PMUSERENR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
write_sysreg(val, pmuserenr_el0);
else
__vcpu_assign_sys_reg(vcpu, reg, val);
@@ -1175,7 +1177,6 @@ static void pmu_reg_write(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg, u64 val,
WARN_ON(1);
break;
}
-
}
/**
@@ -1192,15 +1193,17 @@ static u64 pmu_reg_read(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
u64 val = 0;
int idx;
+ kvm_pmu_set_guest_owned(vcpu);
+
switch (reg) {
case PMCR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
val = kvm_pmu_direct_pmcr_read(vcpu);
else
val = kvm_vcpu_read_pmcr(vcpu);
break;
case PMSELR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
val = read_sysreg(pmselr_el0);
else
val = __vcpu_sys_reg(vcpu, reg);
@@ -1208,7 +1211,7 @@ static u64 pmu_reg_read(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
case PMEVCNTR0_EL0 ... PMCCNTR_EL0:
idx = reg - PMEVCNTR0_EL0;
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
if (idx == ARMV8_PMU_CYCLE_IDX)
val = read_sysreg(pmccntr_el0);
else
@@ -1221,7 +1224,7 @@ static u64 pmu_reg_read(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
val = __vcpu_sys_reg(vcpu, reg);
break;
case PMCNTENSET_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
val = read_sysreg(pmcntenset_el0);
val &= kvm_pmu_guest_counter_mask(vcpu->kvm->arch.arm_pmu);
} else {
@@ -1229,7 +1232,7 @@ static u64 pmu_reg_read(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
}
break;
case PMINTENSET_EL1:
- if (kvm_pmu_is_partitioned(vcpu->kvm)) {
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED) {
val = read_sysreg(pmintenset_el1);
val &= kvm_pmu_guest_counter_mask(vcpu->kvm->arch.arm_pmu);
} else {
@@ -1240,7 +1243,7 @@ static u64 pmu_reg_read(struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
val = __vcpu_sys_reg(vcpu, reg);
break;
case PMUSERENR_EL0:
- if (kvm_pmu_is_partitioned(vcpu->kvm))
+ if (kvm_pmu_get_access(vcpu) == VCPU_PMU_ACCESS_GUEST_OWNED)
val = read_sysreg(pmuserenr_el0);
else
val = __vcpu_sys_reg(vcpu, reg);
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index 61f8d4ed35e10..b77ddb94dc99b 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -7,6 +7,7 @@
#ifndef __ASM_ARM_KVM_PMU_H
#define __ASM_ARM_KVM_PMU_H
+#include <linux/kvm_types.h>
#include <linux/perf_event.h>
#include <linux/perf/arm_pmuv3.h>
#include <linux/perf/arm_pmu.h>
@@ -43,6 +44,7 @@ struct kvm_pmu {
int irq_num;
bool created;
bool irq_level;
+ enum vcpu_pmu_register_access access;
};
struct arm_pmu_entry {
@@ -103,6 +105,9 @@ u64 kvm_pmu_host_counter_mask(struct arm_pmu *pmu);
u64 kvm_pmu_guest_counter_mask(struct arm_pmu *pmu);
void kvm_pmu_load(struct kvm_vcpu *vcpu);
void kvm_pmu_put(struct kvm_vcpu *vcpu);
+void kvm_pmu_set_guest_owned(struct kvm_vcpu *vcpu);
+
+#define kvm_pmu_get_access(vcpu) ((vcpu)->arch.pmu.access)
/*
* Updates the vcpu's view of the pmu events for this cpu.
@@ -147,6 +152,8 @@ static inline bool kvm_pmu_is_partitioned(struct kvm *kvm)
{
return false;
}
+
+#define kvm_pmu_get_access(vcpu) (VCPU_PMU_ACCESS_FREE)
static inline void kvm_pmu_direct_pmcr_write(struct kvm_vcpu *vcpu, u64 val) {}
static inline u64 kvm_pmu_direct_pmcr_read(struct kvm_vcpu *vcpu)
{
@@ -154,6 +161,7 @@ static inline u64 kvm_pmu_direct_pmcr_read(struct kvm_vcpu *vcpu)
}
static inline void kvm_pmu_load(struct kvm_vcpu *vcpu) {}
static inline void kvm_pmu_put(struct kvm_vcpu *vcpu) {}
+static inline void kvm_pmu_set_guest_owned(struct kvm_vcpu *vcpu) {}
static inline void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu,
u64 select_idx, u64 val) {}
static inline void kvm_pmu_set_counter_value_user(struct kvm_vcpu *vcpu,
--
2.54.0.1136.gdb2ca164c4-goog
next prev parent reply other threads:[~2026-06-12 19:48 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-12 19:28 [PATCH v8 00/21] ARM64 PMU Partitioning Colton Lewis
2026-06-12 19:28 ` [PATCH 01/21] arm64: cpufeature: Add cpucap for HPMN0 Colton Lewis
2026-06-12 19:28 ` [PATCH 02/21] KVM: arm64: Reorganize PMU includes Colton Lewis
2026-06-12 19:28 ` [PATCH 03/21] KVM: arm64: Reorganize PMU functions Colton Lewis
2026-06-12 19:28 ` [PATCH 04/21] perf: arm_pmuv3: Generalize counter bitmasks Colton Lewis
2026-06-12 19:28 ` [PATCH 05/21] perf: arm_pmuv3: Check cntr_mask before using pmccntr Colton Lewis
2026-06-12 19:28 ` [PATCH 06/21] perf: arm_pmuv3: Allocate counter indices from high to low Colton Lewis
2026-06-12 19:28 ` [PATCH 07/21] perf: arm_pmuv3: Add method to partition the PMU Colton Lewis
2026-06-12 19:28 ` [PATCH 08/21] KVM: arm64: Set up FGT for Partitioned PMU Colton Lewis
2026-06-12 19:28 ` [PATCH 09/21] KVM: arm64: Add Partitioned PMU register trap handlers Colton Lewis
2026-06-12 19:28 ` [PATCH 10/21] KVM: arm64: Set up MDCR_EL2 to handle a Partitioned PMU Colton Lewis
2026-06-12 19:28 ` [PATCH 11/21] KVM: arm64: Context swap Partitioned PMU guest registers Colton Lewis
2026-06-12 19:29 ` [PATCH 12/21] KVM: arm64: Enforce PMU event filter at vcpu_load() Colton Lewis
2026-06-12 19:29 ` [PATCH 13/21] perf: Add perf_pmu_resched_update() Colton Lewis
2026-06-12 19:29 ` [PATCH 14/21] KVM: arm64: Apply dynamic guest counter reservations Colton Lewis
2026-06-12 19:29 ` Colton Lewis [this message]
2026-06-12 19:29 ` [PATCH 16/21] perf: arm_pmuv3: Handle IRQs for Partitioned PMU guest counters Colton Lewis
2026-06-12 19:29 ` [PATCH 17/21] KVM: arm64: Detect overflows for the Partitioned PMU Colton Lewis
2026-06-12 19:29 ` [PATCH 18/21] KVM: arm64: Add vCPU device attr to partition the PMU Colton Lewis
2026-06-12 19:29 ` [PATCH 19/21] KVM: selftests: Add find_bit to KVM library Colton Lewis
2026-06-12 19:29 ` [PATCH 20/21] KVM: arm64: selftests: Add test case for Partitioned PMU Colton Lewis
2026-06-12 19:29 ` [PATCH 21/21] KVM: arm64: selftests: Relax testing for exceptions when partitioned Colton Lewis
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260612192909.1153907-16-coltonlewis@google.com \
--to=coltonlewis@google.com \
--cc=alexandru.elisei@arm.com \
--cc=catalin.marinas@arm.com \
--cc=corbet@lwn.net \
--cc=gankulkarni@os.amperecomputing.com \
--cc=james.clark@linaro.org \
--cc=joey.gouly@arm.com \
--cc=kvm@vger.kernel.org \
--cc=kvmarm@lists.linux.dev \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=mark.rutland@arm.com \
--cc=maz@kernel.org \
--cc=mizhang@google.com \
--cc=oliver.upton@linux.dev \
--cc=pbonzini@redhat.com \
--cc=shuah@kernel.org \
--cc=suzuki.poulose@arm.com \
--cc=will@kernel.org \
--cc=yuzenghui@huawei.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox