[PATCH v5 22/24] KVM: arm64: Add KVM_CAP to partition the PMU

Colton Lewis
2025-12-09T20:52:53Z

Add KVM_CAP_ARM_PARTITION_PMU to enable the partitioned PMU for a
given VM. This capability is allowed where PMUv3 and VHE are supported
and the host driver was configured with
arm_pmuv3.reserved_host_counters.

Ordering is enforced such that this must be called before the vCPUs
are created to avoid the possibility the guest is already using one
PMU configuration that gets switched out from under it.

The enabled capability is tracked by the new flag
KVM_ARCH_FLAG_PARTITIONED_PMU_ENABLED.

Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
Documentation/virt/kvm/api.rst | 24 +++++++++++++++++++++
arch/arm64/include/asm/kvm_host.h | 2 ++
arch/arm64/include/asm/kvm_pmu.h | 9 ++++++++
arch/arm64/kvm/arm.c | 15 +++++++++++++
arch/arm64/kvm/pmu-direct.c | 35 ++++++++++++++++++++++++++++---
include/uapi/linux/kvm.h | 1 +
6 files changed, 83 insertions(+), 3 deletions(-)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 57061fa29e6a0..ef1b22f20ee71 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -8703,6 +8703,30 @@ This capability indicate to the userspace whether a PFNMAP memory region
can be safely mapped as cacheable. This relies on the presence of
force write back (FWB) feature support on the hardware.

+
+7.245 KVM_CAP_ARM_PARTITION_PMU
+-------------------------------------
+
+:Architectures: arm64
+:Target: VM
+:Parameters: arg[0] is a boolean that enables or disables the capability
+:Returns: 0 on success
+ -EPERM if host doesn't support
+ -EBUSY if vPMU was already created
+
+This API controls the PMU implementation used for VMs. The capability
+is only available if the host PMUv3 driver was configured for
+partitioning via the module parameter
+``arm-pmuv3.reserved_guest_counters=[0-$NR_COUNTERS]``. When enabled,
+VMs are configured to have direct hardware access to the most
+frequently used registers for the counters configured by the
+aforementioned module parameters, bypassing the KVM traps in the
+standard emulated PMU implementation and reducing overhead of any
+guest software that uses PMU capabilities such as ``perf``.
+
+If the host driver was configured for partitioning but the partitioned
+PMU is disabled through this interface, the VM will use the legacy PMU
+
8. Other capabilities.
======================

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index f92027d8fdfd0..8431fdebcac43 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -349,6 +349,8 @@ struct kvm_arch {
#define KVM_ARCH_FLAG_GUEST_HAS_SVE 9
/* MIDR_EL1, REVIDR_EL1, and AIDR_EL1 are writable from userspace */
#define KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS 10
+ /* Partitioned PMU Enabled */
+#define KVM_ARCH_FLAG_PARTITION_PMU_ENABLED 11
unsigned long flags;

/* VM-wide vCPU feature set */
diff --git a/arch/arm64/include/asm/kvm_pmu.h b/arch/arm64/include/asm/kvm_pmu.h
index 47e6f2a14e381..6146120208e39 100644
--- a/arch/arm64/include/asm/kvm_pmu.h
+++ b/arch/arm64/include/asm/kvm_pmu.h
@@ -111,6 +111,8 @@ void kvm_pmu_load(struct kvm_vcpu *vcpu);
void kvm_pmu_put(struct kvm_vcpu *vcpu);

void kvm_pmu_set_physical_access(struct kvm_vcpu *vcpu);
+bool kvm_pmu_partition_ready(void);
+void kvm_pmu_partition_enable(struct kvm *kvm, bool enable);

#if !defined(__KVM_NVHE_HYPERVISOR__)
bool kvm_vcpu_pmu_is_partitioned(struct kvm_vcpu *vcpu);
@@ -327,6 +329,13 @@ static inline void kvm_pmu_host_counters_enable(void) {}
static inline void kvm_pmu_host_counters_disable(void) {}
static inline void kvm_pmu_handle_guest_irq(u64 govf) {}

+static inline bool kvm_pmu_partition_ready(void)
+{
+ return false;
+}
+
+static inline void kvm_pmu_partition_enable(struct kvm *kvm, bool enable) {}
+
#endif

#endif
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 1750df5944f6d..d09f272577277 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -20,6 +20,7 @@
#include <linux/irqbypass.h>
#include <linux/sched/stat.h>
#include <linux/psci.h>
+#include <linux/perf/arm_pmu.h>
#include <trace/events/kvm.h>

#define CREATE_TRACE_POINTS
@@ -37,6 +38,7 @@
#include <asm/kvm_emulate.h>
#include <asm/kvm_mmu.h>
#include <asm/kvm_nested.h>
+#include <asm/kvm_pmu.h>
#include <asm/kvm_pkvm.h>
#include <asm/kvm_pmu.h>
#include <asm/kvm_ptrauth.h>
@@ -132,6 +134,16 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
}
mutex_unlock(&kvm->lock);
break;
+ case KVM_CAP_ARM_PARTITION_PMU:
+ if (kvm->created_vcpus) {
+ r = -EBUSY;
+ } else if (!kvm_pmu_partition_ready()) {
+ r = -EPERM;
+ } else {
+ r = 0;
+ kvm_pmu_partition_enable(kvm, cap->args[0]);
+ }
+ break;
default:
break;
}
@@ -388,6 +400,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_ARM_PMU_V3:
r = kvm_supports_guest_pmuv3();
break;
+ case KVM_CAP_ARM_PARTITION_PMU:
+ r = kvm_pmu_partition_ready();
+ break;
case KVM_CAP_ARM_INJECT_SERROR_ESR:
r = cpus_have_final_cap(ARM64_HAS_RAS_EXTN);
break;
diff --git a/arch/arm64/kvm/pmu-direct.c b/arch/arm64/kvm/pmu-direct.c
index 2ee99d6d2b6c1..6cfba9caeea0e 100644
--- a/arch/arm64/kvm/pmu-direct.c
+++ b/arch/arm64/kvm/pmu-direct.c
@@ -45,8 +45,8 @@ bool kvm_pmu_is_partitioned(struct arm_pmu *pmu)
}

/**
- * kvm_vcpu_pmu_is_partitioned() - Determine if given VCPU has a partitioned PMU
- * @vcpu: Pointer to kvm_vcpu struct
+ * kvm_pmu_is_partitioned() - Determine if given VCPU has a partitioned PMU
+ * @kvm: Pointer to kvm_vcpu struct
*
* Determine if given VCPU has a partitioned PMU by extracting that
* field and passing it to :c:func:`kvm_pmu_is_partitioned`
@@ -56,7 +56,36 @@ bool kvm_pmu_is_partitioned(struct arm_pmu *pmu)
bool kvm_vcpu_pmu_is_partitioned(struct kvm_vcpu *vcpu)
{
return kvm_pmu_is_partitioned(vcpu->kvm->arch.arm_pmu) &&
- false;
+ test_bit(KVM_ARCH_FLAG_PARTITION_PMU_ENABLED, &vcpu->kvm->arch.flags);
+}
+
+/**
+ * kvm_pmu_partition_ready() - If we can enable/disable partition
+ *
+ * Return: true if allowed, false otherwise.
+ */
+bool kvm_pmu_partition_ready(void)
+{
+ return kvm_pmu_partition_supported() &&
+ kvm_supports_guest_pmuv3() &&
+ armv8pmu_hpmn_max > -1;
+}
+
+/**
+ * kvm_pmu_partition_enable() - Enable/disable partition flag
+ * @kvm: Pointer to vcpu
+ * @enable: Whether to enable or disable
+ *
+ * If we want to enable the partition, the guest is free to grab
+ * hardware by accessing PMU registers. Otherwise, the host maintains
+ * control.
+ */
+void kvm_pmu_partition_enable(struct kvm *kvm, bool enable)
+{
+ if (enable)
+ set_bit(KVM_ARCH_FLAG_PARTITION_PMU_ENABLED, &kvm->arch.flags);
+ else
+ clear_bit(KVM_ARCH_FLAG_PARTITION_PMU_ENABLED, &kvm->arch.flags);
}

/**
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 52f6000ab0208..2bb2f234df0e6 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -963,6 +963,7 @@ struct kvm_enable_cap {
#define KVM_CAP_RISCV_MP_STATE_RESET 242
#define KVM_CAP_ARM_CACHEABLE_PFNMAP_SUPPORTED 243
#define KVM_CAP_GUEST_MEMFD_FLAGS 244
+#define KVM_CAP_ARM_PARTITION_PMU 245

struct kvm_irq_routing_irqchip {
__u32 irqchip;
--
2.52.0.239.gd5f0c6e74e-goog