* [PATCH v8 1/7] KVM: arm64: Disallow vPMU when pPMUs do not cover all CPUs
2026-07-06 10:03 [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs Akihiko Odaki
@ 2026-07-06 10:03 ` Akihiko Odaki
2026-07-06 17:04 ` Oliver Upton
2026-07-06 10:03 ` [PATCH v8 2/7] KVM: arm64: PMU: Protect the list of PMUs with RCU Akihiko Odaki
` (6 subsequent siblings)
7 siblings, 1 reply; 20+ messages in thread
From: Akihiko Odaki @ 2026-07-06 10:03 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Kees Cook,
Gustavo A. R. Silva, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Shuah Khan
Cc: linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest, Akihiko Odaki
Commit ec3eb9ed6081 ("KVM: arm64: PMU: Disallow vPMU on non-uniform
PMUVer") made KVM reject vPMU unless the system-wide PMUVer is usable.
That covers systems where PMUv3 is absent or non-uniform, as well as
systems where IMPDEF PMUv3 sysreg traps are unavailable.
However, KVM can still accept vPMU when all CPUs uniformly trap PMUv3
sysregs, but the pPMUs registered with KVM only cover a subset of
possible CPUs.
Reject vPMU unless the registered pPMUs cover every possible CPU.
This avoids carrying support for partial pPMU coverage into the
fixed-counters-only UAPI introduced later in the series.
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
---
arch/arm64/kvm/arm.c | 16 +++++++++++++---
arch/arm64/kvm/pmu-emul.c | 20 ++++++++++++++++++--
include/kvm/arm_pmu.h | 6 +++---
3 files changed, 34 insertions(+), 8 deletions(-)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 29f48f2c63ec..68767bb08285 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1527,14 +1527,19 @@ int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irq_level,
return -EINVAL;
}
-static unsigned long system_supported_vcpu_features(void)
+static long system_supported_vcpu_features(void)
{
unsigned long features = KVM_VCPU_VALID_FEATURES;
+ int r;
if (!cpus_have_final_cap(ARM64_HAS_32BIT_EL1))
clear_bit(KVM_ARM_VCPU_EL1_32BIT, &features);
- if (!kvm_supports_guest_pmuv3())
+ r = kvm_supports_guest_pmuv3();
+ if (r < 0)
+ return r;
+
+ if (!r)
clear_bit(KVM_ARM_VCPU_PMU_V3, &features);
if (!system_supports_sve())
@@ -1555,6 +1560,7 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu,
const struct kvm_vcpu_init *init)
{
unsigned long features = init->features[0];
+ long r;
int i;
if (features & ~KVM_VCPU_VALID_FEATURES)
@@ -1565,7 +1571,11 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu,
return -ENOENT;
}
- if (features & ~system_supported_vcpu_features())
+ r = system_supported_vcpu_features();
+ if (r < 0)
+ return r;
+
+ if (features & ~r)
return -EINVAL;
/*
diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index c816db5d6761..f50bb9d9a1e7 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -24,10 +24,26 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc);
static void kvm_pmu_release_perf_event(struct kvm_pmc *pmc);
static bool kvm_pmu_counter_is_enabled(struct kvm_pmc *pmc);
-bool kvm_supports_guest_pmuv3(void)
+int kvm_supports_guest_pmuv3(void)
{
+ cpumask_var_t cpus __free(free_cpumask_var) = CPUMASK_VAR_NULL;
+ struct arm_pmu_entry *entry;
+
+ if (!alloc_cpumask_var(&cpus, GFP_KERNEL))
+ return -ENOMEM;
+
+ cpumask_copy(cpus, cpu_possible_mask);
+
guard(mutex)(&arm_pmus_lock);
- return !list_empty(&arm_pmus);
+
+ list_for_each_entry(entry, &arm_pmus, entry) {
+ struct arm_pmu *pmu = entry->arm_pmu;
+
+ if (!cpumask_andnot(cpus, cpus, &pmu->supported_cpus))
+ return 1;
+ }
+
+ return 0;
}
static struct kvm_vcpu *kvm_pmc_to_vcpu(const struct kvm_pmc *pmc)
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index 0a36a3d5c894..354f06edfdee 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -40,7 +40,7 @@ struct arm_pmu_entry {
struct arm_pmu *arm_pmu;
};
-bool kvm_supports_guest_pmuv3(void);
+int kvm_supports_guest_pmuv3(void);
#define kvm_arm_pmu_irq_initialized(v) ((v)->arch.pmu.irq_num != 0)
u64 kvm_pmu_get_counter_value(struct kvm_vcpu *vcpu, u64 select_idx);
void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu, u64 select_idx, u64 val);
@@ -99,9 +99,9 @@ void kvm_pmu_nested_transition(struct kvm_vcpu *vcpu);
struct kvm_pmu {
};
-static inline bool kvm_supports_guest_pmuv3(void)
+static inline int kvm_supports_guest_pmuv3(void)
{
- return false;
+ return 0;
}
#define kvm_arm_pmu_irq_initialized(v) (false)
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v8 1/7] KVM: arm64: Disallow vPMU when pPMUs do not cover all CPUs
2026-07-06 10:03 ` [PATCH v8 1/7] KVM: arm64: Disallow vPMU when pPMUs do not cover all CPUs Akihiko Odaki
@ 2026-07-06 17:04 ` Oliver Upton
2026-07-07 11:08 ` Akihiko Odaki
0 siblings, 1 reply; 20+ messages in thread
From: Oliver Upton @ 2026-07-06 17:04 UTC (permalink / raw)
To: Akihiko Odaki
Cc: Marc Zyngier, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Catalin Marinas, Will Deacon, Kees Cook, Gustavo A. R. Silva,
Paolo Bonzini, Jonathan Corbet, Shuah Khan, Shuah Khan,
linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest
Hi,
On Mon, Jul 06, 2026 at 07:03:24PM +0900, Akihiko Odaki wrote:
> Commit ec3eb9ed6081 ("KVM: arm64: PMU: Disallow vPMU on non-uniform
> PMUVer") made KVM reject vPMU unless the system-wide PMUVer is usable.
> That covers systems where PMUv3 is absent or non-uniform, as well as
> systems where IMPDEF PMUv3 sysreg traps are unavailable.
>
> However, KVM can still accept vPMU when all CPUs uniformly trap PMUv3
> sysregs, but the pPMUs registered with KVM only cover a subset of
> possible CPUs.
>
> Reject vPMU unless the registered pPMUs cover every possible CPU.
> This avoids carrying support for partial pPMU coverage into the
> fixed-counters-only UAPI introduced later in the series.
Doesn't CPU hotplug screw this up? I could online a CPU that doesn't
have a PMU after creating the VM.
I'd rather just change ARM64_WORKAROUND_PMUV3_IMPDEF_TRAPS to become a
system feature. That way any CPU which breaks the system-wide assumption
cannot be onlined.
Thanks,
Oliver
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v8 1/7] KVM: arm64: Disallow vPMU when pPMUs do not cover all CPUs
2026-07-06 17:04 ` Oliver Upton
@ 2026-07-07 11:08 ` Akihiko Odaki
0 siblings, 0 replies; 20+ messages in thread
From: Akihiko Odaki @ 2026-07-07 11:08 UTC (permalink / raw)
To: Oliver Upton
Cc: Marc Zyngier, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Catalin Marinas, Will Deacon, Kees Cook, Gustavo A. R. Silva,
Paolo Bonzini, Jonathan Corbet, Shuah Khan, Shuah Khan,
linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest
On 2026/07/07 2:04, Oliver Upton wrote:
> Hi,
>
> On Mon, Jul 06, 2026 at 07:03:24PM +0900, Akihiko Odaki wrote:
>> Commit ec3eb9ed6081 ("KVM: arm64: PMU: Disallow vPMU on non-uniform
>> PMUVer") made KVM reject vPMU unless the system-wide PMUVer is usable.
>> That covers systems where PMUv3 is absent or non-uniform, as well as
>> systems where IMPDEF PMUv3 sysreg traps are unavailable.
>>
>> However, KVM can still accept vPMU when all CPUs uniformly trap PMUv3
>> sysregs, but the pPMUs registered with KVM only cover a subset of
>> possible CPUs.
>>
>> Reject vPMU unless the registered pPMUs cover every possible CPU.
>> This avoids carrying support for partial pPMU coverage into the
>> fixed-counters-only UAPI introduced later in the series.
>
> Doesn't CPU hotplug screw this up? I could online a CPU that doesn't
> have a PMU after creating the VM.>
> I'd rather just change ARM64_WORKAROUND_PMUV3_IMPDEF_TRAPS to become a
> system feature. That way any CPU which breaks the system-wide assumption
> cannot be onlined.
ARM64_WORKAROUND_PMUV3_IMPDEF_TRAPS only says that IMPDEF PMUv3 sysregs
are trapped. It does not say that KVM has a driver-backed PMU usable for
PMUv3 emulation. This patch checks that extra requirement.
I re-checked CPU hotplug. Onlining a CPU without a PMU later does not
make an accepted VM unsafe, since the check is against
cpu_possible_mask. The problem is the reverse case on ACPI: this check
can disable vPMU when a possible CPU is offline. DT populates
supported_cpus at boot, while ACPI initially populates it only from
online CPUs and grows it as matching CPUs come online.
That makes this patch too conservative. In practice, I do not expect
systems to mix CPUs with and without a usable PMU. A better approach is
probably to treat such a host as out of spec and add
TAINT_CPU_OUT_OF_SPEC. We already do that for architectural PMUv3 by
detecting mismatches in ID_AA64DFR0_EL1.PMUVer; we can do the same for
ARM64_WORKAROUND_PMUV3_IMPDEF_TRAPS with non-standard PMUs.
Regards,
Akihiko Odaki
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v8 2/7] KVM: arm64: PMU: Protect the list of PMUs with RCU
2026-07-06 10:03 [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs Akihiko Odaki
2026-07-06 10:03 ` [PATCH v8 1/7] KVM: arm64: Disallow vPMU when pPMUs do not cover all CPUs Akihiko Odaki
@ 2026-07-06 10:03 ` Akihiko Odaki
2026-07-06 10:03 ` [PATCH v8 3/7] KVM: arm64: PMU: Pass the pPMU to kvm_map_pmu_event() Akihiko Odaki
` (5 subsequent siblings)
7 siblings, 0 replies; 20+ messages in thread
From: Akihiko Odaki @ 2026-07-06 10:03 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Kees Cook,
Gustavo A. R. Silva, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Shuah Khan
Cc: linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest, Akihiko Odaki
Convert the list of PMUs to a RCU-protected list that has primitives to
avoid read-side contention.
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
---
arch/arm64/kvm/pmu-emul.c | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index f50bb9d9a1e7..4e8c872b3e20 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -7,9 +7,9 @@
#include <linux/cpu.h>
#include <linux/kvm.h>
#include <linux/kvm_host.h>
-#include <linux/list.h>
#include <linux/perf_event.h>
#include <linux/perf/arm_pmu.h>
+#include <linux/rculist.h>
#include <linux/uaccess.h>
#include <asm/kvm_emulate.h>
#include <kvm/arm_pmu.h>
@@ -34,9 +34,9 @@ int kvm_supports_guest_pmuv3(void)
cpumask_copy(cpus, cpu_possible_mask);
- guard(mutex)(&arm_pmus_lock);
+ guard(rcu)();
- list_for_each_entry(entry, &arm_pmus, entry) {
+ list_for_each_entry_rcu(entry, &arm_pmus, entry) {
struct arm_pmu *pmu = entry->arm_pmu;
if (!cpumask_andnot(cpus, cpus, &pmu->supported_cpus))
@@ -818,7 +818,7 @@ void kvm_host_pmu_init(struct arm_pmu *pmu)
return;
entry->arm_pmu = pmu;
- list_add_tail(&entry->entry, &arm_pmus);
+ list_add_tail_rcu(&entry->entry, &arm_pmus);
}
static struct arm_pmu *kvm_pmu_probe_armpmu(void)
@@ -827,7 +827,7 @@ static struct arm_pmu *kvm_pmu_probe_armpmu(void)
struct arm_pmu *pmu;
int cpu;
- guard(mutex)(&arm_pmus_lock);
+ guard(rcu)();
/*
* It is safe to use a stale cpu to iterate the list of PMUs so long as
@@ -847,7 +847,7 @@ static struct arm_pmu *kvm_pmu_probe_armpmu(void)
* carried here.
*/
cpu = raw_smp_processor_id();
- list_for_each_entry(entry, &arm_pmus, entry) {
+ list_for_each_entry_rcu(entry, &arm_pmus, entry) {
pmu = entry->arm_pmu;
if (cpumask_test_cpu(cpu, &pmu->supported_cpus))
@@ -1108,9 +1108,9 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
int ret = -ENXIO;
lockdep_assert_held(&kvm->arch.config_lock);
- mutex_lock(&arm_pmus_lock);
+ guard(rcu)();
- list_for_each_entry(entry, &arm_pmus, entry) {
+ list_for_each_entry_rcu(entry, &arm_pmus, entry) {
arm_pmu = entry->arm_pmu;
if (arm_pmu->pmu.type == pmu_id) {
if (kvm_vm_has_ran_once(kvm) ||
@@ -1126,7 +1126,6 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
}
}
- mutex_unlock(&arm_pmus_lock);
return ret;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v8 3/7] KVM: arm64: PMU: Pass the pPMU to kvm_map_pmu_event()
2026-07-06 10:03 [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs Akihiko Odaki
2026-07-06 10:03 ` [PATCH v8 1/7] KVM: arm64: Disallow vPMU when pPMUs do not cover all CPUs Akihiko Odaki
2026-07-06 10:03 ` [PATCH v8 2/7] KVM: arm64: PMU: Protect the list of PMUs with RCU Akihiko Odaki
@ 2026-07-06 10:03 ` Akihiko Odaki
2026-07-06 10:03 ` [PATCH v8 4/7] KVM: arm64: PMU: Pass the target CPU to kvm_pmu_probe_armpmu() Akihiko Odaki
` (4 subsequent siblings)
7 siblings, 0 replies; 20+ messages in thread
From: Akihiko Odaki @ 2026-07-06 10:03 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Kees Cook,
Gustavo A. R. Silva, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Shuah Khan
Cc: linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest, Akihiko Odaki
Replace the VM argument with the pPMU used for event creation. The
current caller still passes the VM's default pPMU, but this removes the
implicit lookup from kvm_map_pmu_event() so later changes can map events
against the pPMU selected for an individual vCPU.
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
---
arch/arm64/kvm/pmu-emul.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index 4e8c872b3e20..185ea3631eee 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -678,10 +678,8 @@ static bool kvm_pmc_counts_at_el2(struct kvm_pmc *pmc)
return kvm_pmc_read_evtreg(pmc) & ARMV8_PMU_INCLUDE_EL2;
}
-static int kvm_map_pmu_event(struct kvm *kvm, unsigned int eventsel)
+static int kvm_map_pmu_event(struct arm_pmu *pmu, unsigned int eventsel)
{
- struct arm_pmu *pmu = kvm->arch.arm_pmu;
-
/*
* The CPU PMU likely isn't PMUv3; let the driver provide a mapping
* for the guest's PMUv3 event ID.
@@ -733,7 +731,7 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc)
* Don't create an event if we're running on hardware that requires
* PMUv3 event translation and we couldn't find a valid mapping.
*/
- eventsel = kvm_map_pmu_event(vcpu->kvm, eventsel);
+ eventsel = kvm_map_pmu_event(vcpu->kvm->arch.arm_pmu, eventsel);
if (eventsel < 0)
return;
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v8 4/7] KVM: arm64: PMU: Pass the target CPU to kvm_pmu_probe_armpmu()
2026-07-06 10:03 [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs Akihiko Odaki
` (2 preceding siblings ...)
2026-07-06 10:03 ` [PATCH v8 3/7] KVM: arm64: PMU: Pass the pPMU to kvm_map_pmu_event() Akihiko Odaki
@ 2026-07-06 10:03 ` Akihiko Odaki
2026-07-06 10:03 ` [PATCH v8 5/7] KVM: arm64: PMU: Implement fixed-counters-only emulation Akihiko Odaki
` (3 subsequent siblings)
7 siblings, 0 replies; 20+ messages in thread
From: Akihiko Odaki @ 2026-07-06 10:03 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Kees Cook,
Gustavo A. R. Silva, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Shuah Khan
Cc: linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest, Akihiko Odaki
kvm_pmu_probe_armpmu() currently samples the current CPU internally,
which ties the helper to default PMU selection.
Move that policy to kvm_arm_set_default_pmu() by passing
raw_smp_processor_id() from the caller, and make the helper search for
the pPMU covering an explicit CPU. Move the helper above
kvm_pmu_create_perf_event() so later code can reuse it when creating
PMU events for a VCPU's current pCPU.
This preserves the existing default PMU selection behavior while
preparing fixed-counters-only mode to select a pPMU at runtime.
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
---
arch/arm64/kvm/pmu-emul.c | 72 +++++++++++++++++++++++------------------------
1 file changed, 35 insertions(+), 37 deletions(-)
diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index 185ea3631eee..e70628653e4b 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -690,6 +690,23 @@ static int kvm_map_pmu_event(struct arm_pmu *pmu, unsigned int eventsel)
return eventsel;
}
+static struct arm_pmu *kvm_pmu_probe_armpmu(int cpu)
+{
+ struct arm_pmu_entry *entry;
+ struct arm_pmu *pmu;
+
+ guard(rcu)();
+
+ list_for_each_entry_rcu(entry, &arm_pmus, entry) {
+ pmu = entry->arm_pmu;
+
+ if (cpumask_test_cpu(cpu, &pmu->supported_cpus))
+ return pmu;
+ }
+
+ return NULL;
+}
+
/**
* kvm_pmu_create_perf_event - create a perf event for a counter
* @pmc: Counter context
@@ -819,42 +836,6 @@ void kvm_host_pmu_init(struct arm_pmu *pmu)
list_add_tail_rcu(&entry->entry, &arm_pmus);
}
-static struct arm_pmu *kvm_pmu_probe_armpmu(void)
-{
- struct arm_pmu_entry *entry;
- struct arm_pmu *pmu;
- int cpu;
-
- guard(rcu)();
-
- /*
- * It is safe to use a stale cpu to iterate the list of PMUs so long as
- * the same value is used for the entirety of the loop. Given this, and
- * the fact that no percpu data is used for the lookup there is no need
- * to disable preemption.
- *
- * It is still necessary to get a valid cpu, though, to probe for the
- * default PMU instance as userspace is not required to specify a PMU
- * type. In order to uphold the preexisting behavior KVM selects the
- * PMU instance for the core during vcpu init. A dependent use
- * case would be a user with disdain of all things big.LITTLE that
- * affines the VMM to a particular cluster of cores.
- *
- * In any case, userspace should just do the sane thing and use the UAPI
- * to select a PMU type directly. But, be wary of the baggage being
- * carried here.
- */
- cpu = raw_smp_processor_id();
- list_for_each_entry_rcu(entry, &arm_pmus, entry) {
- pmu = entry->arm_pmu;
-
- if (cpumask_test_cpu(cpu, &pmu->supported_cpus))
- return pmu;
- }
-
- return NULL;
-}
-
static u64 __compute_pmceid(struct arm_pmu *pmu, bool pmceid1)
{
u32 hi[2], lo[2];
@@ -1089,7 +1070,24 @@ static void kvm_arm_set_pmu(struct kvm *kvm, struct arm_pmu *arm_pmu)
*/
int kvm_arm_set_default_pmu(struct kvm *kvm)
{
- struct arm_pmu *arm_pmu = kvm_pmu_probe_armpmu();
+ /*
+ * It is safe to use a stale cpu to iterate the list of PMUs so long as
+ * the same value is used for the entirety of the loop. Given this, and
+ * the fact that no percpu data is used for the lookup there is no need
+ * to disable preemption.
+ *
+ * It is still necessary to get a valid cpu, though, to probe for the
+ * default PMU instance as userspace is not required to specify a PMU
+ * type. In order to uphold the preexisting behavior KVM selects the
+ * PMU instance for the core during vcpu init. A dependent use
+ * case would be a user with disdain of all things big.LITTLE that
+ * affines the VMM to a particular cluster of cores.
+ *
+ * In any case, userspace should just do the sane thing and use the UAPI
+ * to select a PMU type directly. But, be wary of the baggage being
+ * carried here.
+ */
+ struct arm_pmu *arm_pmu = kvm_pmu_probe_armpmu(raw_smp_processor_id());
if (!arm_pmu)
return -ENODEV;
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v8 5/7] KVM: arm64: PMU: Implement fixed-counters-only emulation
2026-07-06 10:03 [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs Akihiko Odaki
` (3 preceding siblings ...)
2026-07-06 10:03 ` [PATCH v8 4/7] KVM: arm64: PMU: Pass the target CPU to kvm_pmu_probe_armpmu() Akihiko Odaki
@ 2026-07-06 10:03 ` Akihiko Odaki
2026-07-06 10:53 ` sashiko-bot
2026-07-06 18:23 ` Oliver Upton
2026-07-06 10:03 ` [PATCH v8 6/7] KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY Akihiko Odaki
` (2 subsequent siblings)
7 siblings, 2 replies; 20+ messages in thread
From: Akihiko Odaki @ 2026-07-06 10:03 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Kees Cook,
Gustavo A. R. Silva, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Shuah Khan
Cc: linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest, Akihiko Odaki
Add internal state for PMUv3 emulation without programmable event
counters. When fixed-counters-only mode is active, KVM reports no
programmable counters and hides PMCEID, avoiding event-counter state
whose behavior can depend on the selected hardware PMU.
The cycle counter still uses a host perf event. Unlike the normal PMU
path, fixed-counters-only mode may create that event from the hardware
PMU attached to the VCPU's current pCPU. If the VCPU later loads on a
pCPU that is not covered by the existing event's PMU, request a PMU
reload so the cycle counter can be recreated against the new pCPU's PMU.
Keep this affinity check limited to fixed-counters-only VMs; the normal
programmable-counter mode continues to use the VM-wide PMU and does not
need per-load reload decisions.
Add a separate internal flag for explicit userspace PMU selection. The
UAPI wiring added later will use it to keep explicit PMU selection and
fixed-counters-only mode mutually exclusive while still allowing
fixed-counters-only mode to replace the default PMU selected during
KVM_ARM_VCPU_INIT.
The UAPI wiring that sets the fixed-counters-only flag and records
explicit PMU selection is added later in the series.
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
---
arch/arm64/include/asm/kvm_host.h | 4 ++++
arch/arm64/kvm/arm.c | 1 +
arch/arm64/kvm/pmu-emul.c | 43 +++++++++++++++++++++++++++++++++++++--
include/kvm/arm_pmu.h | 2 ++
4 files changed, 48 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 0c39d9db7d57..aa07b05b8231 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -353,6 +353,10 @@ struct kvm_arch {
#define KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS 10
/* Unhandled SEAs are taken to userspace */
#define KVM_ARCH_FLAG_EXIT_SEA 11
+ /* PMUv3 is emulated with an explicitly specified hardware PMU */
+#define KVM_ARCH_FLAG_PMU_V3_EXPLICIT 12
+ /* PMUv3 is emulated without progammable event counters */
+#define KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY 13
unsigned long flags;
/* VM-wide vCPU feature set */
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 68767bb08285..1cc7754d5ace 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -687,6 +687,7 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
if (has_vhe())
kvm_vcpu_load_vhe(vcpu);
kvm_arch_vcpu_load_fp(vcpu);
+ kvm_vcpu_load_pmu(vcpu);
kvm_vcpu_pmu_restore_guest(vcpu);
if (kvm_arm_is_pvtime_enabled(&vcpu->arch))
kvm_make_request(KVM_REQ_RECORD_STEAL, vcpu);
diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index e70628653e4b..40cad183376c 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -96,6 +96,11 @@ u64 kvm_pmu_evtyper_mask(struct kvm *kvm)
return mask;
}
+static bool kvm_pmu_fixed_counters_only(struct kvm *kvm)
+{
+ return test_bit(KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY, &kvm->arch.flags);
+}
+
/**
* kvm_pmc_is_64bit - determine if counter is 64bit
* @pmc: counter context
@@ -343,7 +348,11 @@ u64 kvm_pmu_implemented_counter_mask(struct kvm_vcpu *vcpu)
static void kvm_pmc_enable_perf_event(struct kvm_pmc *pmc)
{
- if (!pmc->perf_event) {
+ struct kvm_vcpu *vcpu = kvm_pmc_to_vcpu(pmc);
+
+ if (!pmc->perf_event ||
+ (kvm_pmu_fixed_counters_only(vcpu->kvm) &&
+ !cpumask_test_cpu(vcpu->cpu, &to_arm_pmu(pmc->perf_event->pmu)->supported_cpus))) {
kvm_pmu_create_perf_event(pmc);
return;
}
@@ -720,6 +729,12 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc)
int eventsel;
u64 evtreg;
+ if (kvm_pmu_fixed_counters_only(vcpu->kvm)) {
+ arm_pmu = kvm_pmu_probe_armpmu(vcpu->cpu);
+ if (WARN_ON_ONCE(!arm_pmu))
+ return;
+ }
+
evtreg = kvm_pmc_read_evtreg(pmc);
kvm_pmu_stop_counter(pmc);
@@ -748,7 +763,7 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc)
* Don't create an event if we're running on hardware that requires
* PMUv3 event translation and we couldn't find a valid mapping.
*/
- eventsel = kvm_map_pmu_event(vcpu->kvm->arch.arm_pmu, eventsel);
+ eventsel = kvm_map_pmu_event(arm_pmu, eventsel);
if (eventsel < 0)
return;
@@ -878,6 +893,9 @@ u64 kvm_pmu_get_pmceid(struct kvm_vcpu *vcpu, bool pmceid1)
u64 val, mask = 0;
int base, i, nr_events;
+ if (kvm_pmu_fixed_counters_only(vcpu->kvm))
+ return 0;
+
if (!pmceid1) {
val = compute_pmceid0(cpu_pmu);
base = 0;
@@ -905,6 +923,24 @@ u64 kvm_pmu_get_pmceid(struct kvm_vcpu *vcpu, bool pmceid1)
return val & mask;
}
+void kvm_vcpu_load_pmu(struct kvm_vcpu *vcpu)
+{
+ /*
+ * ARMV8_PMU_INSTR_IDX will need the same check once
+ * FEAT_PMUv3_ICNTR is supported.
+ */
+ struct kvm_pmc *pmc = kvm_vcpu_idx_to_pmc(vcpu, ARMV8_PMU_CYCLE_IDX);
+ struct arm_pmu *cpu_pmu;
+
+ if (!kvm_pmu_fixed_counters_only(vcpu->kvm) ||
+ !kvm_pmu_counter_is_enabled(pmc) || !pmc->perf_event)
+ return;
+
+ cpu_pmu = to_arm_pmu(pmc->perf_event->pmu);
+ if (!cpumask_test_cpu(vcpu->cpu, &cpu_pmu->supported_cpus))
+ kvm_make_request(KVM_REQ_RELOAD_PMU, vcpu);
+}
+
void kvm_vcpu_reload_pmu(struct kvm_vcpu *vcpu)
{
u64 mask = kvm_pmu_implemented_counter_mask(vcpu);
@@ -1016,6 +1052,9 @@ u8 kvm_arm_pmu_get_max_counters(struct kvm *kvm)
{
struct arm_pmu *arm_pmu = kvm->arch.arm_pmu;
+ if (kvm_pmu_fixed_counters_only(kvm))
+ return 0;
+
/*
* PMUv3 requires that all event counters are capable of counting any
* event, though the same may not be true of non-PMUv3 hardware.
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index 354f06edfdee..f01c68a79d22 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -59,6 +59,7 @@ void kvm_pmu_software_increment(struct kvm_vcpu *vcpu, u64 val);
void kvm_pmu_handle_pmcr(struct kvm_vcpu *vcpu, u64 val);
void kvm_pmu_set_counter_event_type(struct kvm_vcpu *vcpu, u64 data,
u64 select_idx);
+void kvm_vcpu_load_pmu(struct kvm_vcpu *vcpu);
void kvm_vcpu_reload_pmu(struct kvm_vcpu *vcpu);
int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu,
struct kvm_device_attr *attr);
@@ -164,6 +165,7 @@ static inline u64 kvm_pmu_get_pmceid(struct kvm_vcpu *vcpu, bool pmceid1)
static inline void kvm_pmu_update_vcpu_events(struct kvm_vcpu *vcpu) {}
static inline void kvm_vcpu_pmu_restore_guest(struct kvm_vcpu *vcpu) {}
static inline void kvm_vcpu_pmu_restore_host(struct kvm_vcpu *vcpu) {}
+static inline void kvm_vcpu_load_pmu(struct kvm_vcpu *vcpu) {}
static inline void kvm_vcpu_reload_pmu(struct kvm_vcpu *vcpu) {}
static inline u8 kvm_arm_pmu_get_pmuver_limit(void)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v8 5/7] KVM: arm64: PMU: Implement fixed-counters-only emulation
2026-07-06 10:03 ` [PATCH v8 5/7] KVM: arm64: PMU: Implement fixed-counters-only emulation Akihiko Odaki
@ 2026-07-06 10:53 ` sashiko-bot
2026-07-06 18:23 ` Oliver Upton
1 sibling, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-07-06 10:53 UTC (permalink / raw)
To: Akihiko Odaki; +Cc: kvmarm, kvm, Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Time-of-check to time-of-use (TOCTOU) race condition in `kvm_pmu_create_perf_event()` allows the PMU cycle counter event to be created against the wrong physical CPU's PMU if the vCPU thread migrates during event creation, silently breaking the guest cycle counter.
--
commit 20c92d3c8fbdbec7346087339db116c12fe2ba15
Author: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
KVM: arm64: PMU: Implement fixed-counters-only emulation
This commit adds internal state for PMUv3 emulation without programmable
event counters. When fixed-counters-only mode is active, it reports no
programmable counters, hides PMCEID, and manages per-load PMU reloading
for the cycle counter across asymmetric PMUs.
> diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
> index 465e40bd7353e..de4daaab3ae5e 100644
> --- a/arch/arm64/kvm/pmu-emul.c
> +++ b/arch/arm64/kvm/pmu-emul.c
> @@ -707,6 +716,12 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc)
> int eventsel;
> u64 evtreg;
>
> + if (kvm_pmu_fixed_counters_only(vcpu->kvm)) {
> + arm_pmu = kvm_pmu_probe_armpmu(vcpu->cpu);
> + if (WARN_ON_ONCE(!arm_pmu))
> + return;
> + }
> +
> evtreg = kvm_pmc_read_evtreg(pmc);
>
> kvm_pmu_stop_counter(pmc);
[ ... ]
> @@ -892,6 +910,24 @@ u64 kvm_pmu_get_pmceid(struct kvm_vcpu *vcpu, bool pmceid1)
> return val & mask;
> }
>
> +void kvm_vcpu_load_pmu(struct kvm_vcpu *vcpu)
> +{
> + /*
> + * ARMV8_PMU_INSTR_IDX will need the same check once
> + * FEAT_PMUv3_ICNTR is supported.
> + */
> + struct kvm_pmc *pmc = kvm_vcpu_idx_to_pmc(vcpu, ARMV8_PMU_CYCLE_IDX);
> + struct arm_pmu *cpu_pmu;
> +
> + if (!kvm_pmu_fixed_counters_only(vcpu->kvm) ||
> + !kvm_pmu_counter_is_enabled(pmc) || !pmc->perf_event)
> + return;
[Severity: Medium]
Is there a TOCTOU race condition if the vCPU thread migrates during event
creation?
In kvm_pmu_create_perf_event(), arm_pmu is cached based on vcpu->cpu, and
then pmc->perf_event is cleared by kvm_pmu_stop_counter():
if (kvm_pmu_fixed_counters_only(vcpu->kvm)) {
arm_pmu = kvm_pmu_probe_armpmu(vcpu->cpu);
...
}
...
kvm_pmu_stop_counter(pmc);
If the thread is preempted and migrates to a different physical CPU before
the new event is assigned, the preempt notifier calls kvm_arch_vcpu_load(),
updating vcpu->cpu and calling kvm_vcpu_load_pmu().
Because pmc->perf_event is temporarily NULL during this window, this check
returns early and skips setting KVM_REQ_RELOAD_PMU.
When the thread resumes, it completes event creation using the originally
cached arm_pmu. On asymmetric systems, could this result in the cycle
counter being instantiated against the wrong hardware PMU and silently
failing to count?
> +
> + cpu_pmu = to_arm_pmu(pmc->perf_event->pmu);
> + if (!cpumask_test_cpu(vcpu->cpu, &cpu_pmu->supported_cpus))
> + kvm_make_request(KVM_REQ_RELOAD_PMU, vcpu);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260706-hybrid-v8-0-de459617b59d@rsg.ci.i.u-tokyo.ac.jp?part=5
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v8 5/7] KVM: arm64: PMU: Implement fixed-counters-only emulation
2026-07-06 10:03 ` [PATCH v8 5/7] KVM: arm64: PMU: Implement fixed-counters-only emulation Akihiko Odaki
2026-07-06 10:53 ` sashiko-bot
@ 2026-07-06 18:23 ` Oliver Upton
2026-07-07 11:23 ` Akihiko Odaki
1 sibling, 1 reply; 20+ messages in thread
From: Oliver Upton @ 2026-07-06 18:23 UTC (permalink / raw)
To: Akihiko Odaki
Cc: Marc Zyngier, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Catalin Marinas, Will Deacon, Kees Cook, Gustavo A. R. Silva,
Paolo Bonzini, Jonathan Corbet, Shuah Khan, Shuah Khan,
linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest
On Mon, Jul 06, 2026 at 07:03:28PM +0900, Akihiko Odaki wrote:
> Add internal state for PMUv3 emulation without programmable event
> counters. When fixed-counters-only mode is active, KVM reports no
> programmable counters and hides PMCEID, avoiding event-counter state
> whose behavior can depend on the selected hardware PMU.
>
> The cycle counter still uses a host perf event. Unlike the normal PMU
> path, fixed-counters-only mode may create that event from the hardware
> PMU attached to the VCPU's current pCPU. If the VCPU later loads on a
> pCPU that is not covered by the existing event's PMU, request a PMU
> reload so the cycle counter can be recreated against the new pCPU's PMU.
> Keep this affinity check limited to fixed-counters-only VMs; the normal
> programmable-counter mode continues to use the VM-wide PMU and does not
> need per-load reload decisions.
>
> Add a separate internal flag for explicit userspace PMU selection. The
> UAPI wiring added later will use it to keep explicit PMU selection and
> fixed-counters-only mode mutually exclusive while still allowing
> fixed-counters-only mode to replace the default PMU selected during
> KVM_ARM_VCPU_INIT.
>
> The UAPI wiring that sets the fixed-counters-only flag and records
> explicit PMU selection is added later in the series.
>
> Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
> ---
> arch/arm64/include/asm/kvm_host.h | 4 ++++
> arch/arm64/kvm/arm.c | 1 +
> arch/arm64/kvm/pmu-emul.c | 43 +++++++++++++++++++++++++++++++++++++--
> include/kvm/arm_pmu.h | 2 ++
> 4 files changed, 48 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
> index 0c39d9db7d57..aa07b05b8231 100644
> --- a/arch/arm64/include/asm/kvm_host.h
> +++ b/arch/arm64/include/asm/kvm_host.h
> @@ -353,6 +353,10 @@ struct kvm_arch {
> #define KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS 10
> /* Unhandled SEAs are taken to userspace */
> #define KVM_ARCH_FLAG_EXIT_SEA 11
> + /* PMUv3 is emulated with an explicitly specified hardware PMU */
> +#define KVM_ARCH_FLAG_PMU_V3_EXPLICIT 12
> + /* PMUv3 is emulated without progammable event counters */
> +#define KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY 13
> unsigned long flags;
>
> /* VM-wide vCPU feature set */
> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index 68767bb08285..1cc7754d5ace 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -687,6 +687,7 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
> if (has_vhe())
> kvm_vcpu_load_vhe(vcpu);
> kvm_arch_vcpu_load_fp(vcpu);
> + kvm_vcpu_load_pmu(vcpu);
> kvm_vcpu_pmu_restore_guest(vcpu);
> if (kvm_arm_is_pvtime_enabled(&vcpu->arch))
> kvm_make_request(KVM_REQ_RECORD_STEAL, vcpu);
> diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
> index e70628653e4b..40cad183376c 100644
> --- a/arch/arm64/kvm/pmu-emul.c
> +++ b/arch/arm64/kvm/pmu-emul.c
> @@ -96,6 +96,11 @@ u64 kvm_pmu_evtyper_mask(struct kvm *kvm)
> return mask;
> }
>
> +static bool kvm_pmu_fixed_counters_only(struct kvm *kvm)
> +{
> + return test_bit(KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY, &kvm->arch.flags);
> +}
> +
> /**
> * kvm_pmc_is_64bit - determine if counter is 64bit
> * @pmc: counter context
> @@ -343,7 +348,11 @@ u64 kvm_pmu_implemented_counter_mask(struct kvm_vcpu *vcpu)
>
> static void kvm_pmc_enable_perf_event(struct kvm_pmc *pmc)
> {
> - if (!pmc->perf_event) {
> + struct kvm_vcpu *vcpu = kvm_pmc_to_vcpu(pmc);
> +
> + if (!pmc->perf_event ||
> + (kvm_pmu_fixed_counters_only(vcpu->kvm) &&
> + !cpumask_test_cpu(vcpu->cpu, &to_arm_pmu(pmc->perf_event->pmu)->supported_cpus))) {
> kvm_pmu_create_perf_event(pmc);
> return;
> }
> @@ -720,6 +729,12 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc)
> int eventsel;
> u64 evtreg;
>
> + if (kvm_pmu_fixed_counters_only(vcpu->kvm)) {
> + arm_pmu = kvm_pmu_probe_armpmu(vcpu->cpu);
> + if (WARN_ON_ONCE(!arm_pmu))
> + return;
> + }
> +
> evtreg = kvm_pmc_read_evtreg(pmc);
>
> kvm_pmu_stop_counter(pmc);
> @@ -748,7 +763,7 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc)
> * Don't create an event if we're running on hardware that requires
> * PMUv3 event translation and we couldn't find a valid mapping.
> */
> - eventsel = kvm_map_pmu_event(vcpu->kvm->arch.arm_pmu, eventsel);
> + eventsel = kvm_map_pmu_event(arm_pmu, eventsel);
> if (eventsel < 0)
> return;
>
> @@ -878,6 +893,9 @@ u64 kvm_pmu_get_pmceid(struct kvm_vcpu *vcpu, bool pmceid1)
> u64 val, mask = 0;
> int base, i, nr_events;
>
> + if (kvm_pmu_fixed_counters_only(vcpu->kvm))
> + return 0;
> +
Even if we advertise bits in PMCEID, does it matter? There's no PMC that
the guest could use to count it.
I understand it isn't aesthetic but I really want to minimize the
special-casing that has to be done for this feature.
> +void kvm_vcpu_load_pmu(struct kvm_vcpu *vcpu)
> +{
> + /*
> + * ARMV8_PMU_INSTR_IDX will need the same check once
> + * FEAT_PMUv3_ICNTR is supported.
> + */
> + struct kvm_pmc *pmc = kvm_vcpu_idx_to_pmc(vcpu, ARMV8_PMU_CYCLE_IDX);
> + struct arm_pmu *cpu_pmu;
> +
> + if (!kvm_pmu_fixed_counters_only(vcpu->kvm) ||
> + !kvm_pmu_counter_is_enabled(pmc) || !pmc->perf_event)
> + return;
> +
> + cpu_pmu = to_arm_pmu(pmc->perf_event->pmu);
> + if (!cpumask_test_cpu(vcpu->cpu, &cpu_pmu->supported_cpus))
> + kvm_make_request(KVM_REQ_RELOAD_PMU, vcpu);
Just detect the changing PMU implementation here, KVM_REQ_RELOAD_PMU
will need to detect the PMCs that require an update anyway. Stash the
last cpu in kvm_arch_vcpu_load() and pass it to this:
void kvm_vcpu_load_pmu(struct kvm_vcpu *vcpu, int last_cpu)
{
if (!kvm_pmu_fixed_counters_only(vcpu->kvm) || vcpu->cpu == last_cpu)
return;
if (kvm_pmu_probe_armpmu(vcpu->cpu) != kvm_pmu_probe_armpmu(last_cpu))
kvm_make_request(KVM_REQ_RELOAD_PMU);
}
Thanks,
Oliver
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v8 5/7] KVM: arm64: PMU: Implement fixed-counters-only emulation
2026-07-06 18:23 ` Oliver Upton
@ 2026-07-07 11:23 ` Akihiko Odaki
0 siblings, 0 replies; 20+ messages in thread
From: Akihiko Odaki @ 2026-07-07 11:23 UTC (permalink / raw)
To: Oliver Upton
Cc: Marc Zyngier, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Catalin Marinas, Will Deacon, Kees Cook, Gustavo A. R. Silva,
Paolo Bonzini, Jonathan Corbet, Shuah Khan, Shuah Khan,
linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest
On 2026/07/07 3:23, Oliver Upton wrote:
> On Mon, Jul 06, 2026 at 07:03:28PM +0900, Akihiko Odaki wrote:
>> Add internal state for PMUv3 emulation without programmable event
>> counters. When fixed-counters-only mode is active, KVM reports no
>> programmable counters and hides PMCEID, avoiding event-counter state
>> whose behavior can depend on the selected hardware PMU.
>>
>> The cycle counter still uses a host perf event. Unlike the normal PMU
>> path, fixed-counters-only mode may create that event from the hardware
>> PMU attached to the VCPU's current pCPU. If the VCPU later loads on a
>> pCPU that is not covered by the existing event's PMU, request a PMU
>> reload so the cycle counter can be recreated against the new pCPU's PMU.
>> Keep this affinity check limited to fixed-counters-only VMs; the normal
>> programmable-counter mode continues to use the VM-wide PMU and does not
>> need per-load reload decisions.
>>
>> Add a separate internal flag for explicit userspace PMU selection. The
>> UAPI wiring added later will use it to keep explicit PMU selection and
>> fixed-counters-only mode mutually exclusive while still allowing
>> fixed-counters-only mode to replace the default PMU selected during
>> KVM_ARM_VCPU_INIT.
>>
>> The UAPI wiring that sets the fixed-counters-only flag and records
>> explicit PMU selection is added later in the series.
>>
>> Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
>> ---
>> arch/arm64/include/asm/kvm_host.h | 4 ++++
>> arch/arm64/kvm/arm.c | 1 +
>> arch/arm64/kvm/pmu-emul.c | 43 +++++++++++++++++++++++++++++++++++++--
>> include/kvm/arm_pmu.h | 2 ++
>> 4 files changed, 48 insertions(+), 2 deletions(-)
>>
>> diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
>> index 0c39d9db7d57..aa07b05b8231 100644
>> --- a/arch/arm64/include/asm/kvm_host.h
>> +++ b/arch/arm64/include/asm/kvm_host.h
>> @@ -353,6 +353,10 @@ struct kvm_arch {
>> #define KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS 10
>> /* Unhandled SEAs are taken to userspace */
>> #define KVM_ARCH_FLAG_EXIT_SEA 11
>> + /* PMUv3 is emulated with an explicitly specified hardware PMU */
>> +#define KVM_ARCH_FLAG_PMU_V3_EXPLICIT 12
>> + /* PMUv3 is emulated without progammable event counters */
>> +#define KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY 13
>> unsigned long flags;
>>
>> /* VM-wide vCPU feature set */
>> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
>> index 68767bb08285..1cc7754d5ace 100644
>> --- a/arch/arm64/kvm/arm.c
>> +++ b/arch/arm64/kvm/arm.c
>> @@ -687,6 +687,7 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
>> if (has_vhe())
>> kvm_vcpu_load_vhe(vcpu);
>> kvm_arch_vcpu_load_fp(vcpu);
>> + kvm_vcpu_load_pmu(vcpu);
>> kvm_vcpu_pmu_restore_guest(vcpu);
>> if (kvm_arm_is_pvtime_enabled(&vcpu->arch))
>> kvm_make_request(KVM_REQ_RECORD_STEAL, vcpu);
>> diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
>> index e70628653e4b..40cad183376c 100644
>> --- a/arch/arm64/kvm/pmu-emul.c
>> +++ b/arch/arm64/kvm/pmu-emul.c
>> @@ -96,6 +96,11 @@ u64 kvm_pmu_evtyper_mask(struct kvm *kvm)
>> return mask;
>> }
>>
>> +static bool kvm_pmu_fixed_counters_only(struct kvm *kvm)
>> +{
>> + return test_bit(KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY, &kvm->arch.flags);
>> +}
>> +
>> /**
>> * kvm_pmc_is_64bit - determine if counter is 64bit
>> * @pmc: counter context
>> @@ -343,7 +348,11 @@ u64 kvm_pmu_implemented_counter_mask(struct kvm_vcpu *vcpu)
>>
>> static void kvm_pmc_enable_perf_event(struct kvm_pmc *pmc)
>> {
>> - if (!pmc->perf_event) {
>> + struct kvm_vcpu *vcpu = kvm_pmc_to_vcpu(pmc);
>> +
>> + if (!pmc->perf_event ||
>> + (kvm_pmu_fixed_counters_only(vcpu->kvm) &&
>> + !cpumask_test_cpu(vcpu->cpu, &to_arm_pmu(pmc->perf_event->pmu)->supported_cpus))) {
>> kvm_pmu_create_perf_event(pmc);
>> return;
>> }
>> @@ -720,6 +729,12 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc)
>> int eventsel;
>> u64 evtreg;
>>
>> + if (kvm_pmu_fixed_counters_only(vcpu->kvm)) {
>> + arm_pmu = kvm_pmu_probe_armpmu(vcpu->cpu);
>> + if (WARN_ON_ONCE(!arm_pmu))
>> + return;
>> + }
>> +
>> evtreg = kvm_pmc_read_evtreg(pmc);
>>
>> kvm_pmu_stop_counter(pmc);
>> @@ -748,7 +763,7 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc)
>> * Don't create an event if we're running on hardware that requires
>> * PMUv3 event translation and we couldn't find a valid mapping.
>> */
>> - eventsel = kvm_map_pmu_event(vcpu->kvm->arch.arm_pmu, eventsel);
>> + eventsel = kvm_map_pmu_event(arm_pmu, eventsel);
>> if (eventsel < 0)
>> return;
>>
>> @@ -878,6 +893,9 @@ u64 kvm_pmu_get_pmceid(struct kvm_vcpu *vcpu, bool pmceid1)
>> u64 val, mask = 0;
>> int base, i, nr_events;
>>
>> + if (kvm_pmu_fixed_counters_only(vcpu->kvm))
>> + return 0;
>> +
>
> Even if we advertise bits in PMCEID, does it matter? There's no PMC that
> the guest could use to count it.
>
> I understand it isn't aesthetic but I really want to minimize the
> special-casing that has to be done for this feature.
This strictly ensures that sysregs are stable after migrating across
physical CPUs, which may have different PMCEID values.
>
>> +void kvm_vcpu_load_pmu(struct kvm_vcpu *vcpu)
>> +{
>> + /*
>> + * ARMV8_PMU_INSTR_IDX will need the same check once
>> + * FEAT_PMUv3_ICNTR is supported.
>> + */
>> + struct kvm_pmc *pmc = kvm_vcpu_idx_to_pmc(vcpu, ARMV8_PMU_CYCLE_IDX);
>> + struct arm_pmu *cpu_pmu;
>> +
>> + if (!kvm_pmu_fixed_counters_only(vcpu->kvm) ||
>> + !kvm_pmu_counter_is_enabled(pmc) || !pmc->perf_event)
>> + return;
>> +
>> + cpu_pmu = to_arm_pmu(pmc->perf_event->pmu);
>> + if (!cpumask_test_cpu(vcpu->cpu, &cpu_pmu->supported_cpus))
>> + kvm_make_request(KVM_REQ_RELOAD_PMU, vcpu);
>
> Just detect the changing PMU implementation here, KVM_REQ_RELOAD_PMU
> will need to detect the PMCs that require an update anyway. Stash the
> last cpu in kvm_arch_vcpu_load() and pass it to this:
>
> void kvm_vcpu_load_pmu(struct kvm_vcpu *vcpu, int last_cpu)
> {
> if (!kvm_pmu_fixed_counters_only(vcpu->kvm) || vcpu->cpu == last_cpu)
> return;
>
> if (kvm_pmu_probe_armpmu(vcpu->cpu) != kvm_pmu_probe_armpmu(last_cpu))
> kvm_make_request(KVM_REQ_RELOAD_PMU);
> }
It is a nice way to simplify the code and to avoid hardcoding
ARMV8_PMU_INSTR_IDX. I'll use the code for the next version.
Regards,
Akihiko Odaki
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v8 6/7] KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY
2026-07-06 10:03 [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs Akihiko Odaki
` (4 preceding siblings ...)
2026-07-06 10:03 ` [PATCH v8 5/7] KVM: arm64: PMU: Implement fixed-counters-only emulation Akihiko Odaki
@ 2026-07-06 10:03 ` Akihiko Odaki
2026-07-06 11:13 ` sashiko-bot
2026-07-06 17:58 ` Oliver Upton
2026-07-06 10:03 ` [PATCH v8 7/7] KVM: arm64: selftests: Test PMU_V3_FIXED_COUNTERS_ONLY Akihiko Odaki
2026-07-06 18:28 ` [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs Oliver Upton
7 siblings, 2 replies; 20+ messages in thread
From: Akihiko Odaki @ 2026-07-06 10:03 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Kees Cook,
Gustavo A. R. Silva, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Shuah Khan
Cc: linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest, Akihiko Odaki
Introduce the KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY attribute to
create a "fixed-counters-only" PMU.
Much like KVM_ARM_VCPU_PMU_V3_IRQ and other read-write attributes, this
attribute provides a getter that facilitates kernel and userspace
debugging/testing.
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
---
Documentation/virt/kvm/devices/vcpu.rst | 32 ++++++++++++++++++++++++++++----
arch/arm64/include/uapi/asm/kvm.h | 1 +
arch/arm64/kvm/pmu-emul.c | 26 +++++++++++++++++++++++++-
3 files changed, 54 insertions(+), 5 deletions(-)
diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
index 5e3805820010..58a6639c0196 100644
--- a/Documentation/virt/kvm/devices/vcpu.rst
+++ b/Documentation/virt/kvm/devices/vcpu.rst
@@ -71,7 +71,8 @@ irqchip.
-ENODEV PMUv3 not supported or GIC not initialized
-ENXIO PMUv3 not properly configured or in-kernel irqchip not
configured as required prior to calling this attribute
- -EBUSY PMUv3 already initialized or a VCPU has already run
+ -EBUSY PMUv3 already initialized, a VCPU has already run or
+ FIXED_COUNTERS_ONLY has already been set
-EINVAL Invalid filter range
======= ======================================================
@@ -113,14 +114,14 @@ using event 0x11 (CPU_CYCLES).
:Returns:
- ======= ====================================================
+ ======= ===========================================================
-EBUSY PMUv3 already initialized, a VCPU has already run or
- an event filter has already been set
+ an event filter or FIXED_COUNTERS_ONLY has already been set
-EFAULT Error accessing the PMU identifier
-ENXIO PMU not found
-ENODEV PMUv3 not supported or GIC not initialized
-ENOMEM Could not allocate memory
- ======= ====================================================
+ ======= ===========================================================
Request that the VCPU uses the specified hardware PMU when creating guest events
for the purpose of PMU emulation. The PMU identifier can be read from the "type"
@@ -162,6 +163,29 @@ explicitly selected, or the number of counters is out of range for the
selected PMU. Selecting a new PMU cancels the effect of setting this
attribute.
+1.6 ATTRIBUTE: KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY
+------------------------------------------------------
+
+:Parameters: no additional parameter in kvm_device_attr.addr
+
+:Returns:
+
+ ======= ==================================================
+ -EBUSY PMUv3 already initialized, a VCPU has already run,
+ an event filter has already been set or
+ a hardware PMU has already been specified
+ -ENXIO Attempted to get before setting
+ -ENODEV Attempted to set while PMUv3 not supported
+ ======= ==================================================
+
+If set, KVM emulates PMUv3 without programmable event counters. Only fixed
+counters are exposed to the guest: the cycle counter today, and the instruction
+counter if support for FEAT_PMUv3_ICNTR is added.
+
+With programmable counters disabled, the VCPU can run on any physical CPU.
+This is particularly useful on heterogeneous systems where different hardware
+PMUs cover different physical CPUs. All VCPUs in a VM share this attribute.
+
2. GROUP: KVM_ARM_VCPU_TIMER_CTRL
=================================
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 1c13bfa2d38a..39a1a1e412e6 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -437,6 +437,7 @@ enum {
#define KVM_ARM_VCPU_PMU_V3_FILTER 2
#define KVM_ARM_VCPU_PMU_V3_SET_PMU 3
#define KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS 4
+#define KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY 5
#define KVM_ARM_VCPU_TIMER_CTRL 1
#define KVM_ARM_VCPU_TIMER_IRQ_VTIMER 0
#define KVM_ARM_VCPU_TIMER_IRQ_PTIMER 1
diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index 40cad183376c..ad9ff4d0d89c 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -1149,11 +1149,13 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
arm_pmu = entry->arm_pmu;
if (arm_pmu->pmu.type == pmu_id) {
if (kvm_vm_has_ran_once(kvm) ||
+ kvm_pmu_fixed_counters_only(kvm) ||
(kvm->arch.pmu_filter && kvm->arch.arm_pmu != arm_pmu)) {
ret = -EBUSY;
break;
}
+ set_bit(KVM_ARCH_FLAG_PMU_V3_EXPLICIT, &kvm->arch.flags);
kvm_arm_set_pmu(kvm, arm_pmu);
cpumask_copy(kvm->arch.supported_cpus, &arm_pmu->supported_cpus);
ret = 0;
@@ -1164,6 +1166,22 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
return ret;
}
+static int kvm_arm_pmu_v3_set_pmu_fixed_counters_only(struct kvm_vcpu *vcpu)
+{
+ struct kvm *kvm = vcpu->kvm;
+
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ if (kvm_vm_has_ran_once(kvm) || kvm->arch.pmu_filter ||
+ test_bit(KVM_ARCH_FLAG_PMU_V3_EXPLICIT, &kvm->arch.flags))
+ return -EBUSY;
+
+ set_bit(KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY, &kvm->arch.flags);
+ kvm_arm_set_nr_counters(kvm, 0);
+
+ return 0;
+}
+
static int kvm_arm_pmu_v3_set_nr_counters(struct kvm_vcpu *vcpu, unsigned int n)
{
struct kvm *kvm = vcpu->kvm;
@@ -1238,7 +1256,7 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
filter.action != KVM_PMU_EVENT_DENY))
return -EINVAL;
- if (kvm_vm_has_ran_once(kvm))
+ if (kvm_vm_has_ran_once(kvm) || kvm_pmu_fixed_counters_only(kvm))
return -EBUSY;
if (!kvm->arch.pmu_filter) {
@@ -1283,6 +1301,8 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
return kvm_arm_pmu_v3_set_nr_counters(vcpu, n);
}
+ case KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY:
+ return kvm_arm_pmu_v3_set_pmu_fixed_counters_only(vcpu);
case KVM_ARM_VCPU_PMU_V3_INIT:
return kvm_arm_pmu_v3_init(vcpu);
}
@@ -1309,6 +1329,9 @@ int kvm_arm_pmu_v3_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
irq = vcpu->arch.pmu.irq_num;
return put_user(irq, uaddr);
}
+ case KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY:
+ if (kvm_pmu_fixed_counters_only(vcpu->kvm))
+ return 0;
}
return -ENXIO;
@@ -1322,6 +1345,7 @@ int kvm_arm_pmu_v3_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
case KVM_ARM_VCPU_PMU_V3_FILTER:
case KVM_ARM_VCPU_PMU_V3_SET_PMU:
case KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS:
+ case KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY:
if (kvm_vcpu_has_pmu(vcpu))
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v8 6/7] KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY
2026-07-06 10:03 ` [PATCH v8 6/7] KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY Akihiko Odaki
@ 2026-07-06 11:13 ` sashiko-bot
2026-07-06 17:58 ` Oliver Upton
1 sibling, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-07-06 11:13 UTC (permalink / raw)
To: Akihiko Odaki; +Cc: kvm, kvmarm, Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] Host-userspace can trigger a WARN_ON_ONCE in KVM by scheduling a VCPU on a physical CPU without a PMU.
- [Medium] KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS can silently override the KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY restriction.
Pre-existing issues:
- [High] kvm_arm_set_nr_counters() modifies MDCR_EL2 for all vCPUs without acquiring their vcpu->mutex, causing a data race with KVM_SET_ONE_REG.
--
commit 18166de56dc2a0622e0ddd3b21dbd03b3b4d6bd7
Author: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY
Introduce the KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY attribute to
create a "fixed-counters-only" PMU.
> diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
> --- a/arch/arm64/kvm/pmu-emul.c
> +++ b/arch/arm64/kvm/pmu-emul.c
[ ... ]
> @@ -1151,6 +1153,22 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
> return ret;
> }
>
> +static int kvm_arm_pmu_v3_set_pmu_fixed_counters_only(struct kvm_vcpu *vcpu)
> +{
> + struct kvm *kvm = vcpu->kvm;
> +
> + lockdep_assert_held(&kvm->arch.config_lock);
> +
> + if (kvm_vm_has_ran_once(kvm) || kvm->arch.pmu_filter ||
> + test_bit(KVM_ARCH_FLAG_PMU_V3_EXPLICIT, &kvm->arch.flags))
> + return -EBUSY;
> +
> + set_bit(KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY, &kvm->arch.flags);
> + kvm_arm_set_nr_counters(kvm, 0);
[Severity: High]
Can an unprivileged host userspace trigger a WARN_ON_ONCE if they bind the
VCPU thread to a physical CPU without a PMU and run the VM?
If KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY is set, it appears
kvm_pmu_create_perf_event() will attempt to find a PMU using
kvm_pmu_probe_armpmu(vcpu->cpu):
arch/arm64/kvm/pmu-emul.c:kvm_pmu_create_perf_event() {
...
if (kvm_pmu_fixed_counters_only(vcpu->kvm)) {
arm_pmu = kvm_pmu_probe_armpmu(vcpu->cpu);
if (WARN_ON_ONCE(!arm_pmu))
return;
}
...
}
If the CPU lacks a PMU, wouldn't this hit the WARN_ON_ONCE and act as a host
denial of service if panic_on_warn is set?
[Severity: High]
This is a pre-existing issue, but can this call to kvm_arm_set_nr_counters()
cause a data race with KVM_SET_ONE_REG?
Looking at kvm_arm_set_nr_counters(), it iterates over all vCPUs and modifies
__vcpu_sys_reg(vcpu, MDCR_EL2) "behind their back" without acquiring their
vcpu->mutex:
arch/arm64/kvm/pmu-emul.c:kvm_arm_set_nr_counters() {
...
kvm_for_each_vcpu(i, vcpu, kvm) {
u64 val = __vcpu_sys_reg(vcpu, MDCR_EL2);
val &= ~MDCR_EL2_HPMN;
val |= FIELD_PREP(MDCR_EL2_HPMN, kvm->arch.nr_pmu_counters);
__vcpu_assign_sys_reg(vcpu, MDCR_EL2, val);
}
...
}
Concurrently, userspace could call KVM_SET_ONE_REG on another vCPU to modify
MDCR_EL2 bits, leading to a read-modify-write corruption since the global
config_lock is not taken by KVM_SET_ONE_REG.
> +
> + return 0;
> +}
> +
> static int kvm_arm_pmu_v3_set_nr_counters(struct kvm_vcpu *vcpu, unsigned int n)
> {
[ ... ]
> @@ -1270,6 +1288,8 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>
> return kvm_arm_pmu_v3_set_nr_counters(vcpu, n);
> }
> + case KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY:
> + return kvm_arm_pmu_v3_set_pmu_fixed_counters_only(vcpu);
[Severity: Medium]
If userspace configures the PMU for fixed-counters-only, can this state be
silently overwritten if userspace subsequently calls
KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS?
The kvm_arm_pmu_v3_set_nr_counters() function does not check if
KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY is active:
arch/arm64/kvm/pmu-emul.c:kvm_arm_pmu_v3_set_nr_counters() {
...
if (n > kvm_arm_pmu_get_max_counters(kvm))
return -EINVAL;
kvm_arm_set_nr_counters(kvm, n);
return 0;
}
Could this allow exposing programmable counters to the guest despite being
configured for fixed-counters-only?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260706-hybrid-v8-0-de459617b59d@rsg.ci.i.u-tokyo.ac.jp?part=6
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v8 6/7] KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY
2026-07-06 10:03 ` [PATCH v8 6/7] KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY Akihiko Odaki
2026-07-06 11:13 ` sashiko-bot
@ 2026-07-06 17:58 ` Oliver Upton
2026-07-07 11:36 ` Akihiko Odaki
1 sibling, 1 reply; 20+ messages in thread
From: Oliver Upton @ 2026-07-06 17:58 UTC (permalink / raw)
To: Akihiko Odaki
Cc: Marc Zyngier, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Catalin Marinas, Will Deacon, Kees Cook, Gustavo A. R. Silva,
Paolo Bonzini, Jonathan Corbet, Shuah Khan, Shuah Khan,
linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest
On Mon, Jul 06, 2026 at 07:03:29PM +0900, Akihiko Odaki wrote:
> +1.6 ATTRIBUTE: KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY
> +------------------------------------------------------
> +
> +:Parameters: no additional parameter in kvm_device_attr.addr
> +
> +:Returns:
> +
> + ======= ==================================================
> + -EBUSY PMUv3 already initialized, a VCPU has already run,
> + an event filter has already been set or
> + a hardware PMU has already been specified
> + -ENXIO Attempted to get before setting
> + -ENODEV Attempted to set while PMUv3 not supported
> + ======= ==================================================
> +
> +If set, KVM emulates PMUv3 without programmable event counters. Only fixed
> +counters are exposed to the guest: the cycle counter today, and the instruction
> +counter if support for FEAT_PMUv3_ICNTR is added.
Drop the mention of cycle counter and FEAT_PMUv3_ICNTR, I want to avoid
the argument later down the line when ICNTR support is added to this
mode.
The expectation is that the VMM discovers the feature set using the ID
registers just like every other CPU feature. KVM documentation doesn't
need to describe the architecture.
> +With programmable counters disabled, the VCPU can run on any physical CPU.
> +This is particularly useful on heterogeneous systems where different hardware
> +PMUs cover different physical CPUs. All VCPUs in a VM share this attribute.
When this attribute is enabled, the vCPU can run on any physical CPU
that has a PMU, regardless of the underlying implementation. This
attribute is VM-scoped.
The documentation is focused on unambiguously representing the UAPI, not
providing application recommendations.
> 2. GROUP: KVM_ARM_VCPU_TIMER_CTRL
> =================================
>
> diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
> index 1c13bfa2d38a..39a1a1e412e6 100644
> --- a/arch/arm64/include/uapi/asm/kvm.h
> +++ b/arch/arm64/include/uapi/asm/kvm.h
> @@ -437,6 +437,7 @@ enum {
> #define KVM_ARM_VCPU_PMU_V3_FILTER 2
> #define KVM_ARM_VCPU_PMU_V3_SET_PMU 3
> #define KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS 4
> +#define KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY 5
> #define KVM_ARM_VCPU_TIMER_CTRL 1
> #define KVM_ARM_VCPU_TIMER_IRQ_VTIMER 0
> #define KVM_ARM_VCPU_TIMER_IRQ_PTIMER 1
> diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
> index 40cad183376c..ad9ff4d0d89c 100644
> --- a/arch/arm64/kvm/pmu-emul.c
> +++ b/arch/arm64/kvm/pmu-emul.c
> @@ -1149,11 +1149,13 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
> arm_pmu = entry->arm_pmu;
> if (arm_pmu->pmu.type == pmu_id) {
> if (kvm_vm_has_ran_once(kvm) ||
> + kvm_pmu_fixed_counters_only(kvm) ||
> (kvm->arch.pmu_filter && kvm->arch.arm_pmu != arm_pmu)) {
> ret = -EBUSY;
> break;
> }
>
> + set_bit(KVM_ARCH_FLAG_PMU_V3_EXPLICIT, &kvm->arch.flags);
Why is this flag necessary? kvm->arch.arm_pmu is NULL if no PMU
implementation was selected by userspace.
> kvm_arm_set_pmu(kvm, arm_pmu);
> cpumask_copy(kvm->arch.supported_cpus, &arm_pmu->supported_cpus);
> ret = 0;
> @@ -1164,6 +1166,22 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
> return ret;
> }
>
> +static int kvm_arm_pmu_v3_set_pmu_fixed_counters_only(struct kvm_vcpu *vcpu)
> +{
> + struct kvm *kvm = vcpu->kvm;
> +
> + lockdep_assert_held(&kvm->arch.config_lock);
> +
> + if (kvm_vm_has_ran_once(kvm) || kvm->arch.pmu_filter ||
> + test_bit(KVM_ARCH_FLAG_PMU_V3_EXPLICIT, &kvm->arch.flags))
> + return -EBUSY;
> +
> + set_bit(KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY, &kvm->arch.flags);
> + kvm_arm_set_nr_counters(kvm, 0);
> +
> + return 0;
> +}
> +
> static int kvm_arm_pmu_v3_set_nr_counters(struct kvm_vcpu *vcpu, unsigned int n)
> {
> struct kvm *kvm = vcpu->kvm;
> @@ -1238,7 +1256,7 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
> filter.action != KVM_PMU_EVENT_DENY))
> return -EINVAL;
>
> - if (kvm_vm_has_ran_once(kvm))
> + if (kvm_vm_has_ran_once(kvm) || kvm_pmu_fixed_counters_only(kvm))
There's no reason for doing this, just let userspace create an event
filter. Even with the current PMU implementation it's possible that the
fixed cycle counter gets filtered.
Thanks,
Oliver
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v8 6/7] KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY
2026-07-06 17:58 ` Oliver Upton
@ 2026-07-07 11:36 ` Akihiko Odaki
0 siblings, 0 replies; 20+ messages in thread
From: Akihiko Odaki @ 2026-07-07 11:36 UTC (permalink / raw)
To: Oliver Upton
Cc: Marc Zyngier, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Catalin Marinas, Will Deacon, Kees Cook, Gustavo A. R. Silva,
Paolo Bonzini, Jonathan Corbet, Shuah Khan, Shuah Khan,
linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest
On 2026/07/07 2:58, Oliver Upton wrote:
> On Mon, Jul 06, 2026 at 07:03:29PM +0900, Akihiko Odaki wrote:
>> +1.6 ATTRIBUTE: KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY
>> +------------------------------------------------------
>> +
>> +:Parameters: no additional parameter in kvm_device_attr.addr
>> +
>> +:Returns:
>> +
>> + ======= ==================================================
>> + -EBUSY PMUv3 already initialized, a VCPU has already run,
>> + an event filter has already been set or
>> + a hardware PMU has already been specified
>> + -ENXIO Attempted to get before setting
>> + -ENODEV Attempted to set while PMUv3 not supported
>> + ======= ==================================================
>> +
>> +If set, KVM emulates PMUv3 without programmable event counters. Only fixed
>> +counters are exposed to the guest: the cycle counter today, and the instruction
>> +counter if support for FEAT_PMUv3_ICNTR is added.
>
> Drop the mention of cycle counter and FEAT_PMUv3_ICNTR, I want to avoid
> the argument later down the line when ICNTR support is added to this
> mode.
>
> The expectation is that the VMM discovers the feature set using the ID
> registers just like every other CPU feature. KVM documentation doesn't
> need to describe the architecture.
I'll drop it with the next version.
>
>> +With programmable counters disabled, the VCPU can run on any physical CPU.
>> +This is particularly useful on heterogeneous systems where different hardware
>> +PMUs cover different physical CPUs. All VCPUs in a VM share this attribute.
>
> When this attribute is enabled, the vCPU can run on any physical CPU
> that has a PMU, regardless of the underlying implementation. This
> attribute is VM-scoped.
>
> The documentation is focused on unambiguously representing the UAPI, not
> providing application recommendations.
I agree. I'll use your wording for the next version.
>
>> 2. GROUP: KVM_ARM_VCPU_TIMER_CTRL
>> =================================
>>
>> diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
>> index 1c13bfa2d38a..39a1a1e412e6 100644
>> --- a/arch/arm64/include/uapi/asm/kvm.h
>> +++ b/arch/arm64/include/uapi/asm/kvm.h
>> @@ -437,6 +437,7 @@ enum {
>> #define KVM_ARM_VCPU_PMU_V3_FILTER 2
>> #define KVM_ARM_VCPU_PMU_V3_SET_PMU 3
>> #define KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS 4
>> +#define KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY 5
>> #define KVM_ARM_VCPU_TIMER_CTRL 1
>> #define KVM_ARM_VCPU_TIMER_IRQ_VTIMER 0
>> #define KVM_ARM_VCPU_TIMER_IRQ_PTIMER 1
>> diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
>> index 40cad183376c..ad9ff4d0d89c 100644
>> --- a/arch/arm64/kvm/pmu-emul.c
>> +++ b/arch/arm64/kvm/pmu-emul.c
>> @@ -1149,11 +1149,13 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
>> arm_pmu = entry->arm_pmu;
>> if (arm_pmu->pmu.type == pmu_id) {
>> if (kvm_vm_has_ran_once(kvm) ||
>> + kvm_pmu_fixed_counters_only(kvm) ||
>> (kvm->arch.pmu_filter && kvm->arch.arm_pmu != arm_pmu)) {
>> ret = -EBUSY;
>> break;
>> }
>>
>> + set_bit(KVM_ARCH_FLAG_PMU_V3_EXPLICIT, &kvm->arch.flags);
>
> Why is this flag necessary? kvm->arch.arm_pmu is NULL if no PMU
> implementation was selected by userspace.
The explicit flag is there because kvm->arch.arm_pmu is also populated
by KVM_ARM_VCPU_INIT through kvm_arm_set_default_pmu(). I wanted to keep
allowing userspace to enable FIXED_COUNTERS_ONLY after that default PMU
selection, as long as PMUv3 has not been initialized and the VM has not
run, while still rejecting it after an explicit SET_PMU.
>
>> kvm_arm_set_pmu(kvm, arm_pmu);
>> cpumask_copy(kvm->arch.supported_cpus, &arm_pmu->supported_cpus);
>> ret = 0;
>> @@ -1164,6 +1166,22 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
>> return ret;
>> }
>>
>> +static int kvm_arm_pmu_v3_set_pmu_fixed_counters_only(struct kvm_vcpu *vcpu)
>> +{
>> + struct kvm *kvm = vcpu->kvm;
>> +
>> + lockdep_assert_held(&kvm->arch.config_lock);
>> +
>> + if (kvm_vm_has_ran_once(kvm) || kvm->arch.pmu_filter ||
>> + test_bit(KVM_ARCH_FLAG_PMU_V3_EXPLICIT, &kvm->arch.flags))
>> + return -EBUSY;
>> +
>> + set_bit(KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY, &kvm->arch.flags);
>> + kvm_arm_set_nr_counters(kvm, 0);
>> +
>> + return 0;
>> +}
>> +
>> static int kvm_arm_pmu_v3_set_nr_counters(struct kvm_vcpu *vcpu, unsigned int n)
>> {
>> struct kvm *kvm = vcpu->kvm;
>> @@ -1238,7 +1256,7 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>> filter.action != KVM_PMU_EVENT_DENY))
>> return -EINVAL;
>>
>> - if (kvm_vm_has_ran_once(kvm))
>> + if (kvm_vm_has_ran_once(kvm) || kvm_pmu_fixed_counters_only(kvm))
>
> There's no reason for doing this, just let userspace create an event
> filter. Even with the current PMU implementation it's possible that the
> fixed cycle counter gets filtered.
This check was added as the consequence of the discussion in the
following thread:
https://lore.kernel.org/all/327f74e4-78b8-4629-a418-d1d3ba78859f@rsg.ci.i.u-tokyo.ac.jp/
Regards,
Akihiko Odaki
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v8 7/7] KVM: arm64: selftests: Test PMU_V3_FIXED_COUNTERS_ONLY
2026-07-06 10:03 [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs Akihiko Odaki
` (5 preceding siblings ...)
2026-07-06 10:03 ` [PATCH v8 6/7] KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY Akihiko Odaki
@ 2026-07-06 10:03 ` Akihiko Odaki
2026-07-06 18:28 ` [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs Oliver Upton
7 siblings, 0 replies; 20+ messages in thread
From: Akihiko Odaki @ 2026-07-06 10:03 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Kees Cook,
Gustavo A. R. Silva, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
Shuah Khan
Cc: linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest, Akihiko Odaki
Assert the following:
- FIXED_COUNTERS_ONLY is unset at initialization.
- FIXED_COUNTERS_ONLY can be set.
- Setting an event filter when FIXED_COUNTERS_ONLY has already been set
results in EBUSY.
- Setting FIXED_COUNTERS_ONLY when an event filter has already been set
results in EBUSY.
- Setting FIXED_COUNTERS_ONLY when a VCPU has already run results in
EBUSY.
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
---
.../selftests/kvm/arm64/vpmu_counter_access.c | 152 +++++++++++++++++----
1 file changed, 126 insertions(+), 26 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
index 22223395969e..eb8352a6e2f6 100644
--- a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
+++ b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
@@ -403,12 +403,7 @@ static void create_vpmu_vm(void *guest_code)
{
struct kvm_vcpu_init init;
u8 pmuver, ec;
- u64 dfr0, irq = 23;
- struct kvm_device_attr irq_attr = {
- .group = KVM_ARM_VCPU_PMU_V3_CTRL,
- .attr = KVM_ARM_VCPU_PMU_V3_IRQ,
- .addr = (u64)&irq,
- };
+ u64 dfr0;
/* The test creates the vpmu_vm multiple times. Ensure a clean state */
memset(&vpmu_vm, 0, sizeof(vpmu_vm));
@@ -434,8 +429,6 @@ static void create_vpmu_vm(void *guest_code)
TEST_ASSERT(pmuver != ID_AA64DFR0_EL1_PMUVer_IMP_DEF &&
pmuver >= ID_AA64DFR0_EL1_PMUVer_IMP,
"Unexpected PMUVER (0x%x) on the vCPU with PMUv3", pmuver);
-
- vcpu_ioctl(vpmu_vm.vcpu, KVM_SET_DEVICE_ATTR, &irq_attr);
}
static void destroy_vpmu_vm(void)
@@ -461,15 +454,30 @@ static void run_vcpu(struct kvm_vcpu *vcpu, u64 pmcr_n)
}
}
-static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool expect_fail)
+static void guest_code_done(void)
+{
+ GUEST_DONE();
+}
+
+static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters,
+ bool fixed_counters_only,
+ bool expect_fail)
{
struct kvm_vcpu *vcpu;
unsigned int prev;
int ret;
+ u64 irq = 23;
create_vpmu_vm(guest_code);
vcpu = vpmu_vm.vcpu;
+ if (fixed_counters_only)
+ vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);
+
+ vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_IRQ, &irq);
+
prev = get_pmcr_n(vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_PMCR_EL0)));
ret = __vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
@@ -489,15 +497,15 @@ static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool
* Create a guest with one vCPU, set the PMCR_EL0.N for the vCPU to @pmcr_n,
* and run the test.
*/
-static void run_access_test(u64 pmcr_n)
+static void run_access_test(u64 pmcr_n, bool fixed_counters_only)
{
u64 sp;
struct kvm_vcpu *vcpu;
struct kvm_vcpu_init init;
- pr_debug("Test with pmcr_n %lu\n", pmcr_n);
+ pr_debug("Test with pmcr_n %lu, fixed_counters_only %d\n", pmcr_n, fixed_counters_only);
- test_create_vpmu_vm_with_nr_counters(pmcr_n, false);
+ test_create_vpmu_vm_with_nr_counters(pmcr_n, fixed_counters_only, false);
vcpu = vpmu_vm.vcpu;
/* Save the initial sp to restore them later to run the guest again */
@@ -531,14 +539,14 @@ static struct pmreg_sets validity_check_reg_sets[] = {
* Create a VM, and check if KVM handles the userspace accesses of
* the PMU register sets in @validity_check_reg_sets[] correctly.
*/
-static void run_pmregs_validity_test(u64 pmcr_n)
+static void run_pmregs_validity_test(u64 pmcr_n, bool fixed_counters_only)
{
int i;
struct kvm_vcpu *vcpu;
u64 set_reg_id, clr_reg_id, reg_val;
u64 valid_counters_mask, max_counters_mask;
- test_create_vpmu_vm_with_nr_counters(pmcr_n, false);
+ test_create_vpmu_vm_with_nr_counters(pmcr_n, fixed_counters_only, false);
vcpu = vpmu_vm.vcpu;
valid_counters_mask = get_counters_mask(pmcr_n);
@@ -588,11 +596,11 @@ static void run_pmregs_validity_test(u64 pmcr_n)
* the vCPU to @pmcr_n, which is larger than the host value.
* The attempt should fail as @pmcr_n is too big to set for the vCPU.
*/
-static void run_error_test(u64 pmcr_n)
+static void run_error_test(u64 pmcr_n, bool fixed_counters_only)
{
pr_debug("Error test with pmcr_n %lu (larger than the host)\n", pmcr_n);
- test_create_vpmu_vm_with_nr_counters(pmcr_n, true);
+ test_create_vpmu_vm_with_nr_counters(pmcr_n, fixed_counters_only, true);
destroy_vpmu_vm();
}
@@ -622,22 +630,114 @@ static bool kvm_supports_nr_counters_attr(void)
return supported;
}
-int main(void)
+static void test_config(u64 pmcr_n, bool fixed_counters_only)
{
- u64 i, pmcr_n;
-
- TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3));
- TEST_REQUIRE(kvm_supports_vgic_v3());
- TEST_REQUIRE(kvm_supports_nr_counters_attr());
+ u64 i;
- pmcr_n = get_pmcr_n_limit();
for (i = 0; i <= pmcr_n; i++) {
- run_access_test(i);
- run_pmregs_validity_test(i);
+ run_access_test(i, fixed_counters_only);
+ run_pmregs_validity_test(i, fixed_counters_only);
}
for (i = pmcr_n + 1; i < ARMV8_PMU_MAX_COUNTERS; i++)
- run_error_test(i);
+ run_error_test(i, fixed_counters_only);
+}
+
+static void test_fixed_counters_only(void)
+{
+ struct kvm_pmu_event_filter filter = { .nevents = 0 };
+ struct kvm_vm *vm;
+ struct kvm_vcpu *running_vcpu;
+ struct kvm_vcpu *stopped_vcpu;
+ struct kvm_vcpu_init init;
+ int ret;
+ u64 irq = 23;
+
+ create_vpmu_vm(guest_code);
+ ret = __vcpu_has_device_attr(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY);
+ if (ret) {
+ TEST_ASSERT(ret == -1 && errno == ENXIO,
+ KVM_IOCTL_ERROR(KVM_HAS_DEVICE_ATTR, ret));
+ destroy_vpmu_vm();
+ return;
+ }
+
+ /* Assert that FIXED_COUNTERS_ONLY is unset at initialization. */
+ ret = __vcpu_device_attr_get(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);
+ TEST_ASSERT(ret == -1 && errno == ENXIO,
+ KVM_IOCTL_ERROR(KVM_GET_DEVICE_ATTR, ret));
+
+ /* Assert that setting FIXED_COUNTERS_ONLY succeeds. */
+ vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);
+
+ /* Assert that FIXED_COUNTERS_ONLY is set. */
+ vcpu_device_attr_get(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);
+
+ /*
+ * Setting an event filter when FIXED_COUNTERS_ONLY has already been set
+ * results in EBUSY.
+ */
+ ret = __vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_FILTER, &filter);
+ TEST_ASSERT(ret == -1 && errno == EBUSY,
+ KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret));
+
+ destroy_vpmu_vm();
+
+ create_vpmu_vm(guest_code);
+
+ /*
+ * Assert that setting FIXED_COUNTERS_ONLY when an event filter has
+ * already been set results in EBUSY.
+ */
+ vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_FILTER, &filter);
+
+ ret = __vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);
+ TEST_ASSERT(ret == -1 && errno == EBUSY,
+ KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret));
+
+ destroy_vpmu_vm();
+
+ /*
+ * Assert that setting FIXED_COUNTERS_ONLY when a VCPU has already run
+ * results in EBUSY.
+ */
+ vm = vm_create(2);
+ vm_ioctl(vm, KVM_ARM_PREFERRED_TARGET, &init);
+ init.features[0] |= (1 << KVM_ARM_VCPU_PMU_V3);
+ running_vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code_done);
+ stopped_vcpu = aarch64_vcpu_add(vm, 1, &init, guest_code_done);
+ kvm_arch_vm_finalize_vcpus(vm);
+ vcpu_device_attr_set(running_vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_IRQ, &irq);
+ vcpu_device_attr_set(running_vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_INIT, NULL);
+ vcpu_run(running_vcpu);
+
+ ret = __vcpu_device_attr_set(stopped_vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
+ KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);
+ TEST_ASSERT(ret == -1 && errno == EBUSY,
+ KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret));
+
+ kvm_vm_free(vm);
+
+ test_config(0, true);
+}
+
+int main(void)
+{
+ TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3));
+ TEST_REQUIRE(kvm_supports_vgic_v3());
+ TEST_REQUIRE(kvm_supports_nr_counters_attr());
+
+ test_config(get_pmcr_n_limit(), false);
+ test_fixed_counters_only();
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs
2026-07-06 10:03 [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs Akihiko Odaki
` (6 preceding siblings ...)
2026-07-06 10:03 ` [PATCH v8 7/7] KVM: arm64: selftests: Test PMU_V3_FIXED_COUNTERS_ONLY Akihiko Odaki
@ 2026-07-06 18:28 ` Oliver Upton
2026-07-07 7:58 ` Marc Zyngier
7 siblings, 1 reply; 20+ messages in thread
From: Oliver Upton @ 2026-07-06 18:28 UTC (permalink / raw)
To: Akihiko Odaki
Cc: Marc Zyngier, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Catalin Marinas, Will Deacon, Kees Cook, Gustavo A. R. Silva,
Paolo Bonzini, Jonathan Corbet, Shuah Khan, Shuah Khan,
linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest
On Mon, Jul 06, 2026 at 07:03:23PM +0900, Akihiko Odaki wrote:
> Akihiko Odaki (7):
> KVM: arm64: Disallow vPMU when pPMUs do not cover all CPUs
> KVM: arm64: PMU: Protect the list of PMUs with RCU
> KVM: arm64: PMU: Pass the pPMU to kvm_map_pmu_event()
> KVM: arm64: PMU: Pass the target CPU to kvm_pmu_probe_armpmu()
> KVM: arm64: PMU: Implement fixed-counters-only emulation
> KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY
> KVM: arm64: selftests: Test PMU_V3_FIXED_COUNTERS_ONLY
Thanks for respinning the series, I left some comments. I'd really like
to get this feature picked up, hope you have cycles to work on it soon.
FWIW, we're considering adding a new vCPU feature flag to deprecate all
the ugliness of the old PMUv3 UAPI. I'm tempted to say that the
FIXED_COUNTERS_ONLY feature is conditioned on the new feature flag...
https://lore.kernel.org/kvmarm/20260702190421.420992-1-congkai@amazon.com/
Thanks,
Oliver
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs
2026-07-06 18:28 ` [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs Oliver Upton
@ 2026-07-07 7:58 ` Marc Zyngier
2026-07-07 8:10 ` Oliver Upton
0 siblings, 1 reply; 20+ messages in thread
From: Marc Zyngier @ 2026-07-07 7:58 UTC (permalink / raw)
To: Oliver Upton
Cc: Akihiko Odaki, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Catalin Marinas, Will Deacon, Kees Cook, Gustavo A. R. Silva,
Paolo Bonzini, Jonathan Corbet, Shuah Khan, Shuah Khan,
linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest
On Mon, 06 Jul 2026 19:28:20 +0100,
Oliver Upton <oupton@kernel.org> wrote:
>
> On Mon, Jul 06, 2026 at 07:03:23PM +0900, Akihiko Odaki wrote:
> > Akihiko Odaki (7):
> > KVM: arm64: Disallow vPMU when pPMUs do not cover all CPUs
> > KVM: arm64: PMU: Protect the list of PMUs with RCU
> > KVM: arm64: PMU: Pass the pPMU to kvm_map_pmu_event()
> > KVM: arm64: PMU: Pass the target CPU to kvm_pmu_probe_armpmu()
> > KVM: arm64: PMU: Implement fixed-counters-only emulation
> > KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY
> > KVM: arm64: selftests: Test PMU_V3_FIXED_COUNTERS_ONLY
>
> Thanks for respinning the series, I left some comments. I'd really like
> to get this feature picked up, hope you have cycles to work on it soon.
>
> FWIW, we're considering adding a new vCPU feature flag to deprecate all
> the ugliness of the old PMUv3 UAPI. I'm tempted to say that the
> FIXED_COUNTERS_ONLY feature is conditioned on the new feature flag...
It'd be good to have a strawman proposal on the list if we are going
to change add a new API, specially if this is likely to gate new code.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs
2026-07-07 7:58 ` Marc Zyngier
@ 2026-07-07 8:10 ` Oliver Upton
2026-07-07 9:41 ` Marc Zyngier
0 siblings, 1 reply; 20+ messages in thread
From: Oliver Upton @ 2026-07-07 8:10 UTC (permalink / raw)
To: Marc Zyngier
Cc: Akihiko Odaki, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Catalin Marinas, Will Deacon, Kees Cook, Gustavo A. R. Silva,
Paolo Bonzini, Jonathan Corbet, Shuah Khan, Shuah Khan,
linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest
On Tue, Jul 07, 2026 at 08:58:50AM +0100, Marc Zyngier wrote:
> On Mon, 06 Jul 2026 19:28:20 +0100,
> Oliver Upton <oupton@kernel.org> wrote:
> >
> > On Mon, Jul 06, 2026 at 07:03:23PM +0900, Akihiko Odaki wrote:
> > > Akihiko Odaki (7):
> > > KVM: arm64: Disallow vPMU when pPMUs do not cover all CPUs
> > > KVM: arm64: PMU: Protect the list of PMUs with RCU
> > > KVM: arm64: PMU: Pass the pPMU to kvm_map_pmu_event()
> > > KVM: arm64: PMU: Pass the target CPU to kvm_pmu_probe_armpmu()
> > > KVM: arm64: PMU: Implement fixed-counters-only emulation
> > > KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY
> > > KVM: arm64: selftests: Test PMU_V3_FIXED_COUNTERS_ONLY
> >
> > Thanks for respinning the series, I left some comments. I'd really like
> > to get this feature picked up, hope you have cycles to work on it soon.
> >
> > FWIW, we're considering adding a new vCPU feature flag to deprecate all
> > the ugliness of the old PMUv3 UAPI. I'm tempted to say that the
> > FIXED_COUNTERS_ONLY feature is conditioned on the new feature flag...
>
> It'd be good to have a strawman proposal on the list if we are going
> to change add a new API, specially if this is likely to gate new code.
>
Patch 1 [*] of the series I linked does exactly this, no? Needs some
attention like a KVM_CAP to make the bit discoverable by the VMM but
gets the general idea across.
[*]: https://lore.kernel.org/kvmarm/20260702190421.420992-2-congkai@amazon.com/
Thanks,
Oliver
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v8 0/7] KVM: arm64: PMU: Use multiple host PMUs
2026-07-07 8:10 ` Oliver Upton
@ 2026-07-07 9:41 ` Marc Zyngier
0 siblings, 0 replies; 20+ messages in thread
From: Marc Zyngier @ 2026-07-07 9:41 UTC (permalink / raw)
To: Oliver Upton
Cc: Akihiko Odaki, Joey Gouly, Suzuki K Poulose, Zenghui Yu,
Catalin Marinas, Will Deacon, Kees Cook, Gustavo A. R. Silva,
Paolo Bonzini, Jonathan Corbet, Shuah Khan, Shuah Khan,
linux-arm-kernel, kvmarm, linux-kernel, linux-hardening, devel,
kvm, linux-doc, linux-kselftest
On Tue, 07 Jul 2026 09:10:17 +0100,
Oliver Upton <oupton@kernel.org> wrote:
>
> On Tue, Jul 07, 2026 at 08:58:50AM +0100, Marc Zyngier wrote:
> > On Mon, 06 Jul 2026 19:28:20 +0100,
> > Oliver Upton <oupton@kernel.org> wrote:
> > >
> > > On Mon, Jul 06, 2026 at 07:03:23PM +0900, Akihiko Odaki wrote:
> > > > Akihiko Odaki (7):
> > > > KVM: arm64: Disallow vPMU when pPMUs do not cover all CPUs
> > > > KVM: arm64: PMU: Protect the list of PMUs with RCU
> > > > KVM: arm64: PMU: Pass the pPMU to kvm_map_pmu_event()
> > > > KVM: arm64: PMU: Pass the target CPU to kvm_pmu_probe_armpmu()
> > > > KVM: arm64: PMU: Implement fixed-counters-only emulation
> > > > KVM: arm64: PMU: Introduce FIXED_COUNTERS_ONLY
> > > > KVM: arm64: selftests: Test PMU_V3_FIXED_COUNTERS_ONLY
> > >
> > > Thanks for respinning the series, I left some comments. I'd really like
> > > to get this feature picked up, hope you have cycles to work on it soon.
> > >
> > > FWIW, we're considering adding a new vCPU feature flag to deprecate all
> > > the ugliness of the old PMUv3 UAPI. I'm tempted to say that the
> > > FIXED_COUNTERS_ONLY feature is conditioned on the new feature flag...
> >
> > It'd be good to have a strawman proposal on the list if we are going
> > to change add a new API, specially if this is likely to gate new code.
> >
>
> Patch 1 [*] of the series I linked does exactly this, no? Needs some
Ah, I overlooked that, apologies.
> attention like a KVM_CAP to make the bit discoverable by the VMM but
> gets the general idea across.
Not sure that's the ideal approach, specially in the light of this
"multiple PMU" series. This "strict" API mandates the selection of a
PMU (singular), while this stuff is the exact opposite (grab all the
possible PMUs).
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 20+ messages in thread