AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/06 14:56 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "KVM",
    "PERF_EVENTS"
  ],
  "FocusSymbols": [
    "kvm_arm_pmu_v3_set_pmu_fixed_counters_only",
    "kvm_vcpu_load_pmu",
    "kvm_supports_guest_pmuv3",
    "kvm_pmu_create_perf_event",
    "kvm_arm_pmu_v3_set_attr"
  ],
  "Reasoning": "The patch introduces a new KVM device attribute `KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY` for ARM64 PMUv3 emulation, which allows emulating a PMUv3 without programmable event counters. This modifies core KVM ARM64 PMU emulation logic and adds new ioctl handling, which is reachable from userspace and should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/07/06 14:56 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit d5ff889c9ed50d1b9ab8c1e51eceecfcb07904e5\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Jul 6 14:56:04 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst\nindex 66e714f2fcfa7f..14d11f9179d1f5 100644\n--- a/Documentation/virt/kvm/devices/vcpu.rst\n+++ b/Documentation/virt/kvm/devices/vcpu.rst\n@@ -74,7 +74,8 @@ irqchip.\n \t -ENODEV  PMUv3 not supported or GIC not initialized\n \t -ENXIO   PMUv3 not properly configured or in-kernel irqchip not\n \t \t  configured as required prior to calling this attribute\n-\t -EBUSY   PMUv3 already initialized or a VCPU has already run\n+\t -EBUSY   PMUv3 already initialized, a VCPU has already run or\n+\t\t  FIXED_COUNTERS_ONLY has already been set\n \t -EINVAL  Invalid filter range\n \t =======  ======================================================\n \n@@ -116,14 +117,14 @@ using event 0x11 (CPU_CYCLES).\n \n :Returns:\n \n-\t =======  ====================================================\n+\t =======  ===========================================================\n \t -EBUSY   PMUv3 already initialized, a VCPU has already run or\n-                  an event filter has already been set\n+                  an event filter or FIXED_COUNTERS_ONLY has already been set\n \t -EFAULT  Error accessing the PMU identifier\n \t -ENXIO   PMU not found\n \t -ENODEV  PMUv3 not supported or GIC not initialized\n \t -ENOMEM  Could not allocate memory\n-\t =======  ====================================================\n+\t =======  ===========================================================\n \n Request that the VCPU uses the specified hardware PMU when creating guest events\n for the purpose of PMU emulation. The PMU identifier can be read from the \"type\"\n@@ -165,6 +166,29 @@ explicitly selected, or the number of counters is out of range for the\n selected PMU. Selecting a new PMU cancels the effect of setting this\n attribute.\n \n+1.6 ATTRIBUTE: KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY\n+------------------------------------------------------\n+\n+:Parameters: no additional parameter in kvm_device_attr.addr\n+\n+:Returns:\n+\n+\t =======  ==================================================\n+\t -EBUSY   PMUv3 already initialized, a VCPU has already run,\n+\t\t  an event filter has already been set or\n+\t\t  a hardware PMU has already been specified\n+\t -ENXIO   Attempted to get before setting\n+\t -ENODEV  Attempted to set while PMUv3 not supported\n+\t =======  ==================================================\n+\n+If set, KVM emulates PMUv3 without programmable event counters. Only fixed\n+counters are exposed to the guest: the cycle counter today, and the instruction\n+counter if support for FEAT_PMUv3_ICNTR is added.\n+\n+With programmable counters disabled, the VCPU can run on any physical CPU.\n+This is particularly useful on heterogeneous systems where different hardware\n+PMUs cover different physical CPUs. All VCPUs in a VM share this attribute.\n+\n 2. GROUP: KVM_ARM_VCPU_TIMER_CTRL\n =================================\n \ndiff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h\nindex 2faa60df847d20..f66459f81cd6c4 100644\n--- a/arch/arm64/include/asm/kvm_host.h\n+++ b/arch/arm64/include/asm/kvm_host.h\n@@ -367,6 +367,10 @@ struct kvm_arch {\n #define KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS\t\t10\n \t/* Unhandled SEAs are taken to userspace */\n #define KVM_ARCH_FLAG_EXIT_SEA\t\t\t\t11\n+\t/* PMUv3 is emulated with an explicitly specified hardware PMU */\n+#define KVM_ARCH_FLAG_PMU_V3_EXPLICIT\t\t\t12\n+\t/* PMUv3 is emulated without progammable event counters */\n+#define KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY\t13\n \tunsigned long flags;\n \n \t/* VM-wide vCPU feature set */\ndiff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h\nindex 1c13bfa2d38aad..39a1a1e412e66f 100644\n--- a/arch/arm64/include/uapi/asm/kvm.h\n+++ b/arch/arm64/include/uapi/asm/kvm.h\n@@ -437,6 +437,7 @@ enum {\n #define   KVM_ARM_VCPU_PMU_V3_FILTER\t\t2\n #define   KVM_ARM_VCPU_PMU_V3_SET_PMU\t\t3\n #define   KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS\t4\n+#define   KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY\t5\n #define KVM_ARM_VCPU_TIMER_CTRL\t\t1\n #define   KVM_ARM_VCPU_TIMER_IRQ_VTIMER\t\t0\n #define   KVM_ARM_VCPU_TIMER_IRQ_PTIMER\t\t1\ndiff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c\nindex 3732ee9eb0d4e7..1c0bce27c71bf5 100644\n--- a/arch/arm64/kvm/arm.c\n+++ b/arch/arm64/kvm/arm.c\n@@ -698,6 +698,7 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)\n \tif (has_vhe())\n \t\tkvm_vcpu_load_vhe(vcpu);\n \tkvm_arch_vcpu_load_fp(vcpu);\n+\tkvm_vcpu_load_pmu(vcpu);\n \tkvm_vcpu_pmu_restore_guest(vcpu);\n \tif (kvm_arm_is_pvtime_enabled(\u0026vcpu-\u003earch))\n \t\tkvm_make_request(KVM_REQ_RECORD_STEAL, vcpu);\n@@ -1554,14 +1555,19 @@ int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irq_level,\n \treturn -EINVAL;\n }\n \n-static unsigned long system_supported_vcpu_features(void)\n+static long system_supported_vcpu_features(void)\n {\n \tunsigned long features = KVM_VCPU_VALID_FEATURES;\n+\tint r;\n \n \tif (!cpus_have_final_cap(ARM64_HAS_32BIT_EL1))\n \t\tclear_bit(KVM_ARM_VCPU_EL1_32BIT, \u0026features);\n \n-\tif (!kvm_supports_guest_pmuv3())\n+\tr = kvm_supports_guest_pmuv3();\n+\tif (r \u003c 0)\n+\t\treturn r;\n+\n+\tif (!r)\n \t\tclear_bit(KVM_ARM_VCPU_PMU_V3, \u0026features);\n \n \tif (!system_supports_sve())\n@@ -1582,6 +1588,7 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu,\n \t\t\t\t\tconst struct kvm_vcpu_init *init)\n {\n \tunsigned long features = init-\u003efeatures[0];\n+\tlong r;\n \tint i;\n \n \tif (features \u0026 ~KVM_VCPU_VALID_FEATURES)\n@@ -1592,7 +1599,11 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu,\n \t\t\treturn -ENOENT;\n \t}\n \n-\tif (features \u0026 ~system_supported_vcpu_features())\n+\tr = system_supported_vcpu_features();\n+\tif (r \u003c 0)\n+\t\treturn r;\n+\n+\tif (features \u0026 ~r)\n \t\treturn -EINVAL;\n \n \t/*\ndiff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c\nindex edb21239478a95..52812607b76d99 100644\n--- a/arch/arm64/kvm/pmu-emul.c\n+++ b/arch/arm64/kvm/pmu-emul.c\n@@ -7,9 +7,9 @@\n #include \u003clinux/cpu.h\u003e\n #include \u003clinux/kvm.h\u003e\n #include \u003clinux/kvm_host.h\u003e\n-#include \u003clinux/list.h\u003e\n #include \u003clinux/perf_event.h\u003e\n #include \u003clinux/perf/arm_pmu.h\u003e\n+#include \u003clinux/rculist.h\u003e\n #include \u003clinux/uaccess.h\u003e\n #include \u003casm/kvm_emulate.h\u003e\n #include \u003ckvm/arm_pmu.h\u003e\n@@ -24,10 +24,26 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc);\n static void kvm_pmu_release_perf_event(struct kvm_pmc *pmc);\n static bool kvm_pmu_counter_is_enabled(struct kvm_pmc *pmc);\n \n-bool kvm_supports_guest_pmuv3(void)\n+int kvm_supports_guest_pmuv3(void)\n {\n-\tguard(mutex)(\u0026arm_pmus_lock);\n-\treturn !list_empty(\u0026arm_pmus);\n+\tcpumask_var_t cpus __free(free_cpumask_var) = CPUMASK_VAR_NULL;\n+\tstruct arm_pmu_entry *entry;\n+\n+\tif (!alloc_cpumask_var(\u0026cpus, GFP_KERNEL))\n+\t\treturn -ENOMEM;\n+\n+\tcpumask_copy(cpus, cpu_possible_mask);\n+\n+\tguard(rcu)();\n+\n+\tlist_for_each_entry_rcu(entry, \u0026arm_pmus, entry) {\n+\t\tstruct arm_pmu *pmu = entry-\u003earm_pmu;\n+\n+\t\tif (!cpumask_andnot(cpus, cpus, \u0026pmu-\u003esupported_cpus))\n+\t\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n }\n \n static struct kvm_vcpu *kvm_pmc_to_vcpu(const struct kvm_pmc *pmc)\n@@ -80,6 +96,11 @@ u64 kvm_pmu_evtyper_mask(struct kvm *kvm)\n \treturn mask;\n }\n \n+static bool kvm_pmu_fixed_counters_only(struct kvm *kvm)\n+{\n+\treturn test_bit(KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY, \u0026kvm-\u003earch.flags);\n+}\n+\n /**\n  * kvm_pmc_is_64bit - determine if counter is 64bit\n  * @pmc: counter context\n@@ -327,7 +348,11 @@ u64 kvm_pmu_implemented_counter_mask(struct kvm_vcpu *vcpu)\n \n static void kvm_pmc_enable_perf_event(struct kvm_pmc *pmc)\n {\n-\tif (!pmc-\u003eperf_event) {\n+\tstruct kvm_vcpu *vcpu = kvm_pmc_to_vcpu(pmc);\n+\n+\tif (!pmc-\u003eperf_event ||\n+\t    (kvm_pmu_fixed_counters_only(vcpu-\u003ekvm) \u0026\u0026\n+\t     !cpumask_test_cpu(vcpu-\u003ecpu, \u0026to_arm_pmu(pmc-\u003eperf_event-\u003epmu)-\u003esupported_cpus))) {\n \t\tkvm_pmu_create_perf_event(pmc);\n \t\treturn;\n \t}\n@@ -649,10 +674,8 @@ static bool kvm_pmc_counts_at_el2(struct kvm_pmc *pmc)\n \treturn kvm_pmc_read_evtreg(pmc) \u0026 ARMV8_PMU_INCLUDE_EL2;\n }\n \n-static int kvm_map_pmu_event(struct kvm *kvm, unsigned int eventsel)\n+static int kvm_map_pmu_event(struct arm_pmu *pmu, unsigned int eventsel)\n {\n-\tstruct arm_pmu *pmu = kvm-\u003earch.arm_pmu;\n-\n \t/*\n \t * The CPU PMU likely isn't PMUv3; let the driver provide a mapping\n \t * for the guest's PMUv3 event ID.\n@@ -663,6 +686,23 @@ static int kvm_map_pmu_event(struct kvm *kvm, unsigned int eventsel)\n \treturn eventsel;\n }\n \n+static struct arm_pmu *kvm_pmu_probe_armpmu(int cpu)\n+{\n+\tstruct arm_pmu_entry *entry;\n+\tstruct arm_pmu *pmu;\n+\n+\tguard(rcu)();\n+\n+\tlist_for_each_entry_rcu(entry, \u0026arm_pmus, entry) {\n+\t\tpmu = entry-\u003earm_pmu;\n+\n+\t\tif (cpumask_test_cpu(cpu, \u0026pmu-\u003esupported_cpus))\n+\t\t\treturn pmu;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n /**\n  * kvm_pmu_create_perf_event - create a perf event for a counter\n  * @pmc: Counter context\n@@ -676,6 +716,12 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc)\n \tint eventsel;\n \tu64 evtreg;\n \n+\tif (kvm_pmu_fixed_counters_only(vcpu-\u003ekvm)) {\n+\t\tarm_pmu = kvm_pmu_probe_armpmu(vcpu-\u003ecpu);\n+\t\tif (WARN_ON_ONCE(!arm_pmu))\n+\t\t\treturn;\n+\t}\n+\n \tevtreg = kvm_pmc_read_evtreg(pmc);\n \n \tkvm_pmu_stop_counter(pmc);\n@@ -704,7 +750,7 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc)\n \t * Don't create an event if we're running on hardware that requires\n \t * PMUv3 event translation and we couldn't find a valid mapping.\n \t */\n-\teventsel = kvm_map_pmu_event(vcpu-\u003ekvm, eventsel);\n+\teventsel = kvm_map_pmu_event(arm_pmu, eventsel);\n \tif (eventsel \u003c 0)\n \t\treturn;\n \n@@ -789,43 +835,7 @@ void kvm_host_pmu_init(struct arm_pmu *pmu)\n \t\treturn;\n \n \tentry-\u003earm_pmu = pmu;\n-\tlist_add_tail(\u0026entry-\u003eentry, \u0026arm_pmus);\n-}\n-\n-static struct arm_pmu *kvm_pmu_probe_armpmu(void)\n-{\n-\tstruct arm_pmu_entry *entry;\n-\tstruct arm_pmu *pmu;\n-\tint cpu;\n-\n-\tguard(mutex)(\u0026arm_pmus_lock);\n-\n-\t/*\n-\t * It is safe to use a stale cpu to iterate the list of PMUs so long as\n-\t * the same value is used for the entirety of the loop. Given this, and\n-\t * the fact that no percpu data is used for the lookup there is no need\n-\t * to disable preemption.\n-\t *\n-\t * It is still necessary to get a valid cpu, though, to probe for the\n-\t * default PMU instance as userspace is not required to specify a PMU\n-\t * type. In order to uphold the preexisting behavior KVM selects the\n-\t * PMU instance for the core during vcpu init. A dependent use\n-\t * case would be a user with disdain of all things big.LITTLE that\n-\t * affines the VMM to a particular cluster of cores.\n-\t *\n-\t * In any case, userspace should just do the sane thing and use the UAPI\n-\t * to select a PMU type directly. But, be wary of the baggage being\n-\t * carried here.\n-\t */\n-\tcpu = raw_smp_processor_id();\n-\tlist_for_each_entry(entry, \u0026arm_pmus, entry) {\n-\t\tpmu = entry-\u003earm_pmu;\n-\n-\t\tif (cpumask_test_cpu(cpu, \u0026pmu-\u003esupported_cpus))\n-\t\t\treturn pmu;\n-\t}\n-\n-\treturn NULL;\n+\tlist_add_tail_rcu(\u0026entry-\u003eentry, \u0026arm_pmus);\n }\n \n static u64 __compute_pmceid(struct arm_pmu *pmu, bool pmceid1)\n@@ -870,6 +880,9 @@ u64 kvm_pmu_get_pmceid(struct kvm_vcpu *vcpu, bool pmceid1)\n \tu64 val, mask = 0;\n \tint base, i, nr_events;\n \n+\tif (kvm_pmu_fixed_counters_only(vcpu-\u003ekvm))\n+\t\treturn 0;\n+\n \tif (!pmceid1) {\n \t\tval = compute_pmceid0(cpu_pmu);\n \t\tbase = 0;\n@@ -897,6 +910,24 @@ u64 kvm_pmu_get_pmceid(struct kvm_vcpu *vcpu, bool pmceid1)\n \treturn val \u0026 mask;\n }\n \n+void kvm_vcpu_load_pmu(struct kvm_vcpu *vcpu)\n+{\n+\t/*\n+\t * ARMV8_PMU_INSTR_IDX will need the same check once\n+\t * FEAT_PMUv3_ICNTR is supported.\n+\t */\n+\tstruct kvm_pmc *pmc = kvm_vcpu_idx_to_pmc(vcpu, ARMV8_PMU_CYCLE_IDX);\n+\tstruct arm_pmu *cpu_pmu;\n+\n+\tif (!kvm_pmu_fixed_counters_only(vcpu-\u003ekvm) ||\n+\t    !kvm_pmu_counter_is_enabled(pmc) || !pmc-\u003eperf_event)\n+\t\treturn;\n+\n+\tcpu_pmu = to_arm_pmu(pmc-\u003eperf_event-\u003epmu);\n+\tif (!cpumask_test_cpu(vcpu-\u003ecpu, \u0026cpu_pmu-\u003esupported_cpus))\n+\t\tkvm_make_request(KVM_REQ_RELOAD_PMU, vcpu);\n+}\n+\n void kvm_vcpu_reload_pmu(struct kvm_vcpu *vcpu)\n {\n \tu64 mask = kvm_pmu_implemented_counter_mask(vcpu);\n@@ -1008,6 +1039,9 @@ u8 kvm_arm_pmu_get_max_counters(struct kvm *kvm)\n {\n \tstruct arm_pmu *arm_pmu = kvm-\u003earch.arm_pmu;\n \n+\tif (kvm_pmu_fixed_counters_only(kvm))\n+\t\treturn 0;\n+\n \t/*\n \t * PMUv3 requires that all event counters are capable of counting any\n \t * event, though the same may not be true of non-PMUv3 hardware.\n@@ -1062,7 +1096,24 @@ static void kvm_arm_set_pmu(struct kvm *kvm, struct arm_pmu *arm_pmu)\n  */\n int kvm_arm_set_default_pmu(struct kvm *kvm)\n {\n-\tstruct arm_pmu *arm_pmu = kvm_pmu_probe_armpmu();\n+\t/*\n+\t * It is safe to use a stale cpu to iterate the list of PMUs so long as\n+\t * the same value is used for the entirety of the loop. Given this, and\n+\t * the fact that no percpu data is used for the lookup there is no need\n+\t * to disable preemption.\n+\t *\n+\t * It is still necessary to get a valid cpu, though, to probe for the\n+\t * default PMU instance as userspace is not required to specify a PMU\n+\t * type. In order to uphold the preexisting behavior KVM selects the\n+\t * PMU instance for the core during vcpu init. A dependent use\n+\t * case would be a user with disdain of all things big.LITTLE that\n+\t * affines the VMM to a particular cluster of cores.\n+\t *\n+\t * In any case, userspace should just do the sane thing and use the UAPI\n+\t * to select a PMU type directly. But, be wary of the baggage being\n+\t * carried here.\n+\t */\n+\tstruct arm_pmu *arm_pmu = kvm_pmu_probe_armpmu(raw_smp_processor_id());\n \n \tif (!arm_pmu)\n \t\treturn -ENODEV;\n@@ -1079,17 +1130,19 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)\n \tint ret = -ENXIO;\n \n \tlockdep_assert_held(\u0026kvm-\u003earch.config_lock);\n-\tmutex_lock(\u0026arm_pmus_lock);\n+\tguard(rcu)();\n \n-\tlist_for_each_entry(entry, \u0026arm_pmus, entry) {\n+\tlist_for_each_entry_rcu(entry, \u0026arm_pmus, entry) {\n \t\tarm_pmu = entry-\u003earm_pmu;\n \t\tif (arm_pmu-\u003epmu.type == pmu_id) {\n \t\t\tif (kvm_vm_has_ran_once(kvm) ||\n+\t\t\t    kvm_pmu_fixed_counters_only(kvm) ||\n \t\t\t    (kvm-\u003earch.pmu_filter \u0026\u0026 kvm-\u003earch.arm_pmu != arm_pmu)) {\n \t\t\t\tret = -EBUSY;\n \t\t\t\tbreak;\n \t\t\t}\n \n+\t\t\tset_bit(KVM_ARCH_FLAG_PMU_V3_EXPLICIT, \u0026kvm-\u003earch.flags);\n \t\t\tkvm_arm_set_pmu(kvm, arm_pmu);\n \t\t\tcpumask_copy(kvm-\u003earch.supported_cpus, \u0026arm_pmu-\u003esupported_cpus);\n \t\t\tret = 0;\n@@ -1097,10 +1150,25 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)\n \t\t}\n \t}\n \n-\tmutex_unlock(\u0026arm_pmus_lock);\n \treturn ret;\n }\n \n+static int kvm_arm_pmu_v3_set_pmu_fixed_counters_only(struct kvm_vcpu *vcpu)\n+{\n+\tstruct kvm *kvm = vcpu-\u003ekvm;\n+\n+\tlockdep_assert_held(\u0026kvm-\u003earch.config_lock);\n+\n+\tif (kvm_vm_has_ran_once(kvm) || kvm-\u003earch.pmu_filter ||\n+\t    test_bit(KVM_ARCH_FLAG_PMU_V3_EXPLICIT, \u0026kvm-\u003earch.flags))\n+\t\treturn -EBUSY;\n+\n+\tset_bit(KVM_ARCH_FLAG_PMU_V3_FIXED_COUNTERS_ONLY, \u0026kvm-\u003earch.flags);\n+\tkvm_arm_set_nr_counters(kvm, 0);\n+\n+\treturn 0;\n+}\n+\n static int kvm_arm_pmu_v3_set_nr_counters(struct kvm_vcpu *vcpu, unsigned int n)\n {\n \tstruct kvm *kvm = vcpu-\u003ekvm;\n@@ -1175,7 +1243,7 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)\n \t\t     filter.action != KVM_PMU_EVENT_DENY))\n \t\t\treturn -EINVAL;\n \n-\t\tif (kvm_vm_has_ran_once(kvm))\n+\t\tif (kvm_vm_has_ran_once(kvm) || kvm_pmu_fixed_counters_only(kvm))\n \t\t\treturn -EBUSY;\n \n \t\tif (!kvm-\u003earch.pmu_filter) {\n@@ -1220,6 +1288,8 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)\n \n \t\treturn kvm_arm_pmu_v3_set_nr_counters(vcpu, n);\n \t}\n+\tcase KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY:\n+\t\treturn kvm_arm_pmu_v3_set_pmu_fixed_counters_only(vcpu);\n \tcase KVM_ARM_VCPU_PMU_V3_INIT:\n \t\treturn kvm_arm_pmu_v3_init(vcpu);\n \t}\n@@ -1246,6 +1316,9 @@ int kvm_arm_pmu_v3_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)\n \t\tirq = vcpu-\u003earch.pmu.irq_num;\n \t\treturn put_user(irq, uaddr);\n \t}\n+\tcase KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY:\n+\t\tif (kvm_pmu_fixed_counters_only(vcpu-\u003ekvm))\n+\t\t\treturn 0;\n \t}\n \n \treturn -ENXIO;\n@@ -1259,6 +1332,7 @@ int kvm_arm_pmu_v3_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)\n \tcase KVM_ARM_VCPU_PMU_V3_FILTER:\n \tcase KVM_ARM_VCPU_PMU_V3_SET_PMU:\n \tcase KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS:\n+\tcase KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY:\n \t\tif (kvm_vcpu_has_pmu(vcpu))\n \t\t\treturn 0;\n \t}\ndiff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h\nindex b5e5942204fc69..02d73386a6d0e4 100644\n--- a/include/kvm/arm_pmu.h\n+++ b/include/kvm/arm_pmu.h\n@@ -39,7 +39,7 @@ struct arm_pmu_entry {\n \tstruct arm_pmu *arm_pmu;\n };\n \n-bool kvm_supports_guest_pmuv3(void);\n+int kvm_supports_guest_pmuv3(void);\n #define kvm_arm_pmu_irq_initialized(v)\t((v)-\u003earch.pmu.irq_num != 0)\n u64 kvm_pmu_get_counter_value(struct kvm_vcpu *vcpu, u64 select_idx);\n void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu, u64 select_idx, u64 val);\n@@ -58,6 +58,7 @@ void kvm_pmu_software_increment(struct kvm_vcpu *vcpu, u64 val);\n void kvm_pmu_handle_pmcr(struct kvm_vcpu *vcpu, u64 val);\n void kvm_pmu_set_counter_event_type(struct kvm_vcpu *vcpu, u64 data,\n \t\t\t\t    u64 select_idx);\n+void kvm_vcpu_load_pmu(struct kvm_vcpu *vcpu);\n void kvm_vcpu_reload_pmu(struct kvm_vcpu *vcpu);\n int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu,\n \t\t\t    struct kvm_device_attr *attr);\n@@ -98,9 +99,9 @@ void kvm_pmu_nested_transition(struct kvm_vcpu *vcpu);\n struct kvm_pmu {\n };\n \n-static inline bool kvm_supports_guest_pmuv3(void)\n+static inline int kvm_supports_guest_pmuv3(void)\n {\n-\treturn false;\n+\treturn 0;\n }\n \n #define kvm_arm_pmu_irq_initialized(v)\t(false)\n@@ -163,6 +164,7 @@ static inline u64 kvm_pmu_get_pmceid(struct kvm_vcpu *vcpu, bool pmceid1)\n static inline void kvm_pmu_update_vcpu_events(struct kvm_vcpu *vcpu) {}\n static inline void kvm_vcpu_pmu_restore_guest(struct kvm_vcpu *vcpu) {}\n static inline void kvm_vcpu_pmu_restore_host(struct kvm_vcpu *vcpu) {}\n+static inline void kvm_vcpu_load_pmu(struct kvm_vcpu *vcpu) {}\n static inline void kvm_vcpu_reload_pmu(struct kvm_vcpu *vcpu) {}\n static inline u8 kvm_arm_pmu_get_pmuver_limit(void)\n {\ndiff --git a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c\nindex 22223395969e0f..eb8352a6e2f626 100644\n--- a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c\n+++ b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c\n@@ -403,12 +403,7 @@ static void create_vpmu_vm(void *guest_code)\n {\n \tstruct kvm_vcpu_init init;\n \tu8 pmuver, ec;\n-\tu64 dfr0, irq = 23;\n-\tstruct kvm_device_attr irq_attr = {\n-\t\t.group = KVM_ARM_VCPU_PMU_V3_CTRL,\n-\t\t.attr = KVM_ARM_VCPU_PMU_V3_IRQ,\n-\t\t.addr = (u64)\u0026irq,\n-\t};\n+\tu64 dfr0;\n \n \t/* The test creates the vpmu_vm multiple times. Ensure a clean state */\n \tmemset(\u0026vpmu_vm, 0, sizeof(vpmu_vm));\n@@ -434,8 +429,6 @@ static void create_vpmu_vm(void *guest_code)\n \tTEST_ASSERT(pmuver != ID_AA64DFR0_EL1_PMUVer_IMP_DEF \u0026\u0026\n \t\t    pmuver \u003e= ID_AA64DFR0_EL1_PMUVer_IMP,\n \t\t    \"Unexpected PMUVER (0x%x) on the vCPU with PMUv3\", pmuver);\n-\n-\tvcpu_ioctl(vpmu_vm.vcpu, KVM_SET_DEVICE_ATTR, \u0026irq_attr);\n }\n \n static void destroy_vpmu_vm(void)\n@@ -461,15 +454,30 @@ static void run_vcpu(struct kvm_vcpu *vcpu, u64 pmcr_n)\n \t}\n }\n \n-static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool expect_fail)\n+static void guest_code_done(void)\n+{\n+\tGUEST_DONE();\n+}\n+\n+static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters,\n+\t\t\t\t\t\t bool fixed_counters_only,\n+\t\t\t\t\t\t bool expect_fail)\n {\n \tstruct kvm_vcpu *vcpu;\n \tunsigned int prev;\n \tint ret;\n+\tu64 irq = 23;\n \n \tcreate_vpmu_vm(guest_code);\n \tvcpu = vpmu_vm.vcpu;\n \n+\tif (fixed_counters_only)\n+\t\tvcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\n+\n+\tvcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t     KVM_ARM_VCPU_PMU_V3_IRQ, \u0026irq);\n+\n \tprev = get_pmcr_n(vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_PMCR_EL0)));\n \n \tret = __vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n@@ -489,15 +497,15 @@ static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool\n  * Create a guest with one vCPU, set the PMCR_EL0.N for the vCPU to @pmcr_n,\n  * and run the test.\n  */\n-static void run_access_test(u64 pmcr_n)\n+static void run_access_test(u64 pmcr_n, bool fixed_counters_only)\n {\n \tu64 sp;\n \tstruct kvm_vcpu *vcpu;\n \tstruct kvm_vcpu_init init;\n \n-\tpr_debug(\"Test with pmcr_n %lu\\n\", pmcr_n);\n+\tpr_debug(\"Test with pmcr_n %lu, fixed_counters_only %d\\n\", pmcr_n, fixed_counters_only);\n \n-\ttest_create_vpmu_vm_with_nr_counters(pmcr_n, false);\n+\ttest_create_vpmu_vm_with_nr_counters(pmcr_n, fixed_counters_only, false);\n \tvcpu = vpmu_vm.vcpu;\n \n \t/* Save the initial sp to restore them later to run the guest again */\n@@ -531,14 +539,14 @@ static struct pmreg_sets validity_check_reg_sets[] = {\n  * Create a VM, and check if KVM handles the userspace accesses of\n  * the PMU register sets in @validity_check_reg_sets[] correctly.\n  */\n-static void run_pmregs_validity_test(u64 pmcr_n)\n+static void run_pmregs_validity_test(u64 pmcr_n, bool fixed_counters_only)\n {\n \tint i;\n \tstruct kvm_vcpu *vcpu;\n \tu64 set_reg_id, clr_reg_id, reg_val;\n \tu64 valid_counters_mask, max_counters_mask;\n \n-\ttest_create_vpmu_vm_with_nr_counters(pmcr_n, false);\n+\ttest_create_vpmu_vm_with_nr_counters(pmcr_n, fixed_counters_only, false);\n \tvcpu = vpmu_vm.vcpu;\n \n \tvalid_counters_mask = get_counters_mask(pmcr_n);\n@@ -588,11 +596,11 @@ static void run_pmregs_validity_test(u64 pmcr_n)\n  * the vCPU to @pmcr_n, which is larger than the host value.\n  * The attempt should fail as @pmcr_n is too big to set for the vCPU.\n  */\n-static void run_error_test(u64 pmcr_n)\n+static void run_error_test(u64 pmcr_n, bool fixed_counters_only)\n {\n \tpr_debug(\"Error test with pmcr_n %lu (larger than the host)\\n\", pmcr_n);\n \n-\ttest_create_vpmu_vm_with_nr_counters(pmcr_n, true);\n+\ttest_create_vpmu_vm_with_nr_counters(pmcr_n, fixed_counters_only, true);\n \tdestroy_vpmu_vm();\n }\n \n@@ -622,22 +630,114 @@ static bool kvm_supports_nr_counters_attr(void)\n \treturn supported;\n }\n \n-int main(void)\n+static void test_config(u64 pmcr_n, bool fixed_counters_only)\n {\n-\tu64 i, pmcr_n;\n-\n-\tTEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3));\n-\tTEST_REQUIRE(kvm_supports_vgic_v3());\n-\tTEST_REQUIRE(kvm_supports_nr_counters_attr());\n+\tu64 i;\n \n-\tpmcr_n = get_pmcr_n_limit();\n \tfor (i = 0; i \u003c= pmcr_n; i++) {\n-\t\trun_access_test(i);\n-\t\trun_pmregs_validity_test(i);\n+\t\trun_access_test(i, fixed_counters_only);\n+\t\trun_pmregs_validity_test(i, fixed_counters_only);\n \t}\n \n \tfor (i = pmcr_n + 1; i \u003c ARMV8_PMU_MAX_COUNTERS; i++)\n-\t\trun_error_test(i);\n+\t\trun_error_test(i, fixed_counters_only);\n+}\n+\n+static void test_fixed_counters_only(void)\n+{\n+\tstruct kvm_pmu_event_filter filter = { .nevents = 0 };\n+\tstruct kvm_vm *vm;\n+\tstruct kvm_vcpu *running_vcpu;\n+\tstruct kvm_vcpu *stopped_vcpu;\n+\tstruct kvm_vcpu_init init;\n+\tint ret;\n+\tu64 irq = 23;\n+\n+\tcreate_vpmu_vm(guest_code);\n+\tret = __vcpu_has_device_attr(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY);\n+\tif (ret) {\n+\t\tTEST_ASSERT(ret == -1 \u0026\u0026 errno == ENXIO,\n+\t\t\t    KVM_IOCTL_ERROR(KVM_HAS_DEVICE_ATTR, ret));\n+\t\tdestroy_vpmu_vm();\n+\t\treturn;\n+\t}\n+\n+\t/* Assert that FIXED_COUNTERS_ONLY is unset at initialization. */\n+\tret = __vcpu_device_attr_get(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\n+\tTEST_ASSERT(ret == -1 \u0026\u0026 errno == ENXIO,\n+\t\t    KVM_IOCTL_ERROR(KVM_GET_DEVICE_ATTR, ret));\n+\n+\t/* Assert that setting FIXED_COUNTERS_ONLY succeeds. */\n+\tvcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\n+\n+\t/* Assert that FIXED_COUNTERS_ONLY is set. */\n+\tvcpu_device_attr_get(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\n+\n+\t/*\n+\t * Setting an event filter when FIXED_COUNTERS_ONLY has already been set\n+\t * results in EBUSY.\n+\t */\n+\tret = __vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t\t     KVM_ARM_VCPU_PMU_V3_FILTER, \u0026filter);\n+\tTEST_ASSERT(ret == -1 \u0026\u0026 errno == EBUSY,\n+\t\t    KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret));\n+\n+\tdestroy_vpmu_vm();\n+\n+\tcreate_vpmu_vm(guest_code);\n+\n+\t/*\n+\t * Assert that setting FIXED_COUNTERS_ONLY when an event filter has\n+\t * already been set results in EBUSY.\n+\t */\n+\tvcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t     KVM_ARM_VCPU_PMU_V3_FILTER, \u0026filter);\n+\n+\tret = __vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\n+\tTEST_ASSERT(ret == -1 \u0026\u0026 errno == EBUSY,\n+\t\t    KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret));\n+\n+\tdestroy_vpmu_vm();\n+\n+\t/*\n+\t * Assert that setting FIXED_COUNTERS_ONLY when a VCPU has already run\n+\t * results in EBUSY.\n+\t */\n+\tvm = vm_create(2);\n+\tvm_ioctl(vm, KVM_ARM_PREFERRED_TARGET, \u0026init);\n+\tinit.features[0] |= (1 \u003c\u003c KVM_ARM_VCPU_PMU_V3);\n+\trunning_vcpu = aarch64_vcpu_add(vm, 0, \u0026init, guest_code_done);\n+\tstopped_vcpu = aarch64_vcpu_add(vm, 1, \u0026init, guest_code_done);\n+\tkvm_arch_vm_finalize_vcpus(vm);\n+\tvcpu_device_attr_set(running_vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t     KVM_ARM_VCPU_PMU_V3_IRQ, \u0026irq);\n+\tvcpu_device_attr_set(running_vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t     KVM_ARM_VCPU_PMU_V3_INIT, NULL);\n+\tvcpu_run(running_vcpu);\n+\n+\tret = __vcpu_device_attr_set(stopped_vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\n+\t\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\n+\tTEST_ASSERT(ret == -1 \u0026\u0026 errno == EBUSY,\n+\t\t    KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret));\n+\n+\tkvm_vm_free(vm);\n+\n+\ttest_config(0, true);\n+}\n+\n+int main(void)\n+{\n+\tTEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3));\n+\tTEST_REQUIRE(kvm_supports_vgic_v3());\n+\tTEST_REQUIRE(kvm_supports_nr_counters_attr());\n+\n+\ttest_config(get_pmcr_n_limit(), false);\n+\ttest_fixed_counters_only();\n \n \treturn 0;\n }\n"
}

2/1 2026/07/06 14:56 agent patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "KVM",
    "PERF_EVENTS"
  ],
  "FocusSymbols": [
    "kvm_arm_pmu_v3_set_pmu_fixed_counters_only",
    "kvm_vcpu_load_pmu",
    "kvm_supports_guest_pmuv3",
    "kvm_pmu_create_perf_event",
    "kvm_arm_pmu_v3_set_attr"
  ],
  "Reasoning": "The patch introduces a new KVM device attribute `KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY` for ARM64 PMUv3 emulation, which allows emulating a PMUv3 without programmable event counters. This modifies core KVM ARM64 PMU emulation logic and adds new ioctl handling, which is reachable from userspace and should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit d5ff889c9ed50d1b9ab8c1e51eceecfcb07904e5
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Jul 6 14:56:04 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
index 66e714f2fcfa7f..14d11f9179d1f5 100644
--- a/Documentation/virt/kvm/devices/vcpu.rst
+++ b/Documentation/virt/kvm/devices/vcpu.rst
@@ -74,7 +74,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
 	 =======  ======================================================
 
@@ -116,14 +117,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"
@@ -165,6 +166,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/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 2faa60df847d20..f66459f81cd6c4 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -367,6 +367,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/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 1c13bfa2d38aad..39a1a1e412e66f 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/arm.c b/arch/arm64/kvm/arm.c
index 3732ee9eb0d4e7..1c0bce27c71bf5 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -698,6 +698,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);
@@ -1554,14 +1555,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())
@@ -1582,6 +1588,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)
@@ -1592,7 +1599,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 edb21239478a95..52812607b76d99 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>
@@ -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)
 {
-	guard(mutex)(&arm_pmus_lock);
-	return !list_empty(&arm_pmus);
+	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(rcu)();
+
+	list_for_each_entry_rcu(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)
@@ -80,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
@@ -327,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;
 	}
@@ -649,10 +674,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.
@@ -663,6 +686,23 @@ static int kvm_map_pmu_event(struct kvm *kvm, 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
@@ -676,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);
@@ -704,7 +750,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(arm_pmu, eventsel);
 	if (eventsel < 0)
 		return;
 
@@ -789,43 +835,7 @@ void kvm_host_pmu_init(struct arm_pmu *pmu)
 		return;
 
 	entry->arm_pmu = pmu;
-	list_add_tail(&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(mutex)(&arm_pmus_lock);
-
-	/*
-	 * 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(entry, &arm_pmus, entry) {
-		pmu = entry->arm_pmu;
-
-		if (cpumask_test_cpu(cpu, &pmu->supported_cpus))
-			return pmu;
-	}
-
-	return NULL;
+	list_add_tail_rcu(&entry->entry, &arm_pmus);
 }
 
 static u64 __compute_pmceid(struct arm_pmu *pmu, bool pmceid1)
@@ -870,6 +880,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;
@@ -897,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;
+
+	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);
@@ -1008,6 +1039,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.
@@ -1062,7 +1096,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;
@@ -1079,17 +1130,19 @@ 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) ||
+			    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;
@@ -1097,10 +1150,25 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
 		}
 	}
 
-	mutex_unlock(&arm_pmus_lock);
 	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;
@@ -1175,7 +1243,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) {
@@ -1220,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);
 	case KVM_ARM_VCPU_PMU_V3_INIT:
 		return kvm_arm_pmu_v3_init(vcpu);
 	}
@@ -1246,6 +1316,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;
@@ -1259,6 +1332,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;
 	}
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index b5e5942204fc69..02d73386a6d0e4 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -39,7 +39,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);
@@ -58,6 +58,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);
@@ -98,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)
@@ -163,6 +164,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)
 {
diff --git a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
index 22223395969e0f..eb8352a6e2f626 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;
 }

3/2 2026/07/06 14:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12992
							output: 35
							thoughts: 217
						

Thoughts:
**Assessing the Impact of the New KVM ARM64 PMUv3 Attribute**

Okay, so I've got this patch series to review, and the first thing I need to do is figure out if it introduces any new kernel functionality that warrants fuzzing. Looks like this patch is adding a new KVM device attribute, specifically `KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY`, for ARM64's PMUv3 emulation. Interesting... It seems like the core idea is to allow PMUv3 emulation even without the programmable event counters, sticking solely to the fixed counters. That's definitely a functional change in the KVM ARM64 PMU emulation logic. This changes how we model performance counters in the hypervisor and that, by definition, must be looked at with scrutiny.

Now, let's dive into the code. The patch touches `arch/arm64/kvm/pmu-emul.c`, `arch/arm64/kvm/arm.c`, and, importantly, adds a new ioctl attribute. I'll need to understand how the new attribute works. The key file seems to be `arch/arm64/kvm/pmu-emul.c`, which is the heart of the PMU emulation.

Since this is KVM on ARM64, this code will be compiled when `CONFIG_KVM` is enabled and we are on an ARM64 architecture, so I'll need to verify that those configurations are properly set to test this new functionality. Let's make sure that's correct, and verify that the build configuration is as expected.



4/2 2026/07/06 14:56 tool grepper
0m Args:
{
  "Expression": "KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY"
}

Results:
{
  "Output": "Documentation/virt/kvm/devices/vcpu.rst=167=attribute.\nDocumentation/virt/kvm/devices/vcpu.rst-168-\nDocumentation/virt/kvm/devices/vcpu.rst:169:1.6 ATTRIBUTE: KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY\nDocumentation/virt/kvm/devices/vcpu.rst-170-------------------------------------------------------\n--\narch/arm64/include/uapi/asm/kvm.h=389=enum {\n--\narch/arm64/include/uapi/asm/kvm.h-439-#define   KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS\t4\narch/arm64/include/uapi/asm/kvm.h:440:#define   KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY\t5\narch/arm64/include/uapi/asm/kvm.h-441-#define KVM_ARM_VCPU_TIMER_CTRL\t\t1\n--\narch/arm64/kvm/pmu-emul.c=1186=int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)\n--\narch/arm64/kvm/pmu-emul.c-1290-\t}\narch/arm64/kvm/pmu-emul.c:1291:\tcase KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY:\narch/arm64/kvm/pmu-emul.c-1292-\t\treturn kvm_arm_pmu_v3_set_pmu_fixed_counters_only(vcpu);\n--\narch/arm64/kvm/pmu-emul.c=1300=int kvm_arm_pmu_v3_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)\n--\narch/arm64/kvm/pmu-emul.c-1318-\t}\narch/arm64/kvm/pmu-emul.c:1319:\tcase KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY:\narch/arm64/kvm/pmu-emul.c-1320-\t\tif (kvm_pmu_fixed_counters_only(vcpu-\u003ekvm))\n--\narch/arm64/kvm/pmu-emul.c=1327=int kvm_arm_pmu_v3_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)\n--\narch/arm64/kvm/pmu-emul.c-1334-\tcase KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS:\narch/arm64/kvm/pmu-emul.c:1335:\tcase KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY:\narch/arm64/kvm/pmu-emul.c-1336-\t\tif (kvm_vcpu_has_pmu(vcpu))\n--\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c=462=static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters,\n--\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-475-\t\tvcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c:476:\t\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-477-\n--\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c=646=static void test_fixed_counters_only(void)\n--\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-657-\tret = __vcpu_has_device_attr(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c:658:\t\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY);\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-659-\tif (ret) {\n--\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-667-\tret = __vcpu_device_attr_get(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c:668:\t\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-669-\tTEST_ASSERT(ret == -1 \u0026\u0026 errno == ENXIO,\n--\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-673-\tvcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c:674:\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-675-\n--\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-677-\tvcpu_device_attr_get(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c:678:\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-679-\n--\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-700-\tret = __vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c:701:\t\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-702-\tTEST_ASSERT(ret == -1 \u0026\u0026 errno == EBUSY,\n--\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-723-\tret = __vcpu_device_attr_set(stopped_vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c:724:\t\t\t\t     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);\ntools/testing/selftests/kvm/arm64/vpmu_counter_access.c-725-\tTEST_ASSERT(ret == -1 \u0026\u0026 errno == EBUSY,\n"
}

5/2 2026/07/06 14:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15637
							output: 22
							thoughts: 122
						

Thoughts:
**Analyzing the KVM ARM64 Patch**

Okay, so this patch is introducing `KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY` to KVM for ARM64. Interesting. I immediately see the code modifications are in `arch/arm64/kvm/pmu-emul.c` and `arch/arm64/kvm/arm.c`. This strongly suggests a functional change within the KVM ARM64 architecture, specifically related to Performance Monitoring Units, potentially focusing on fixed counters. This type of modification screams "fuzz me"! Definitely warrants a thorough fuzzing campaign to identify any potential vulnerabilities that may have been introduced.

Let's quickly check the relevant configuration options. It's safe to assume `KVM` is required, being the base KVM config. Now, I should investigate if there are any PMUv3 specific configurations that this patch may interact with or depend on. I need to understand the configuration impact to design appropriate fuzzing inputs and targets. Time to dive deeper into the KVM and ARM64 configuration landscape.



6/2 2026/07/06 14:56 tool grepper
0m Args:
{
  "Expression": "CONFIG_HW_PERF_EVENTS"
}

Results:
{
  "Output": "arch/arm/mach-exynos/suspend.c=451=static void exynos5420_prepare_pm_resume(void)\n--\narch/arm/mach-exynos/suspend.c-460-\narch/arm/mach-exynos/suspend.c:461:\tif (IS_ENABLED(CONFIG_HW_PERF_EVENTS) \u0026\u0026 cluster != 0) {\narch/arm/mach-exynos/suspend.c-462-\t\t/*\n--\narch/arm64/include/asm/cpucaps.h=14=cpucap_is_possible(const unsigned int cap)\n--\narch/arm64/include/asm/cpucaps.h-74-\tcase ARM64_HAS_PMUV3:\narch/arm64/include/asm/cpucaps.h:75:\t\treturn IS_ENABLED(CONFIG_HW_PERF_EVENTS);\narch/arm64/include/asm/cpucaps.h-76-\tcase ARM64_HAS_LSUI:\n--\narch/arm64/kernel/cpufeature.c=1923=static bool has_lpa2(const struct arm64_cpu_capabilities *entry, int scope)\n--\narch/arm64/kernel/cpufeature.c-1928-\narch/arm64/kernel/cpufeature.c:1929:#ifdef CONFIG_HW_PERF_EVENTS\narch/arm64/kernel/cpufeature.c-1930-static bool has_pmuv3(const struct arm64_cpu_capabilities *entry, int scope)\n--\narch/arm64/kernel/cpufeature.c=2532=static const struct arm64_cpu_capabilities arm64_features[] = {\n--\narch/arm64/kernel/cpufeature.c-3131-#endif\narch/arm64/kernel/cpufeature.c:3132:#ifdef CONFIG_HW_PERF_EVENTS\narch/arm64/kernel/cpufeature.c-3133-\t{\n--\narch/arm64/kvm/Makefile=16=kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \\\n--\narch/arm64/kvm/Makefile-28-\narch/arm64/kvm/Makefile:29:kvm-$(CONFIG_HW_PERF_EVENTS)  += pmu-emul.o pmu.o\narch/arm64/kvm/Makefile-30-kvm-$(CONFIG_ARM64_PTR_AUTH)  += pauth.o\n--\narch/arm64/kvm/hyp/nvhe/switch.c=129=static void __hyp_vgic_restore_state(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/hyp/nvhe/switch.c-145- */\narch/arm64/kvm/hyp/nvhe/switch.c:146:#ifdef CONFIG_HW_PERF_EVENTS\narch/arm64/kvm/hyp/nvhe/switch.c-147-static bool __pmu_switch_to_guest(struct kvm_vcpu *vcpu)\n--\narch/mips/kernel/Makefile=102=obj-$(CONFIG_PERF_EVENTS)\t+= perf_event.o perf_regs.o\narch/mips/kernel/Makefile:103:obj-$(CONFIG_HW_PERF_EVENTS)\t+= perf_event_mipsxx.o\narch/mips/kernel/Makefile-104-\n--\ninclude/kvm/arm_pmu.h-17-\ninclude/kvm/arm_pmu.h:18:#if IS_ENABLED(CONFIG_HW_PERF_EVENTS) \u0026\u0026 IS_ENABLED(CONFIG_KVM)\ninclude/kvm/arm_pmu.h-19-struct kvm_pmc {\n"
}

7/2 2026/07/06 14:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16989
							output: 213
							thoughts: 1261
						

Thoughts:
**Analyzing the KVM PMU v3 Patch**

Okay, so I'm looking at this patch and my initial thought is, I need to understand how these new PMU features are integrated. First, I see `CONFIG_HW_PERF_EVENTS` is essential for compiling `pmu-emul.c` – that's a key dependency. Obviously, `CONFIG_KVM` is another requirement, that's a given. Now, I need to zero in on the core functions this patch is introducing or modifying. I'm focusing on these:

*   `kvm_arm_pmu_v3_set_pmu_fixed_counters_only`
*   `kvm_vcpu_load_pmu`
*   `kvm_supports_guest_pmuv3`
*   `kvm_pmu_create_perf_event`
*   `kvm_arm_pmu_v3_set_attr`

I'm particularly interested in this new function `kvm_arm_pmu_v3_set_pmu_fixed_counters_only`. I'm going to look at the code:

```c
+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;
+}
```

It seems to be setting a flag and limiting the number of counters when the VM hasn't run yet and when not already configured in another way.

I am also examining `kvm_supports_guest_pmuv3`:

```c
-bool kvm_supports_guest_pmuv3(void)
+int kvm_supports_guest_pmuv3(void)
 {
-	guard(mutex)(&arm_pmus_lock);
-	return !list_empty(&arm_pmus);
+	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(rcu)();
+
+	list_for_each_entry_rcu(entry, &arm_pmus, entry) {
+		struct arm_pmu *pmu = entry->arm_pmu;
+
+		if (!cpumask_andnot(cpus, cpus, &pmu->supported_cpus))
+			return 1;
+	}
+
+	return 0;
 }
```

This now returns an integer which can represent a failure.

The other functions listed are likely related to PMU setup and event handling.

A quick check confirms that `HW_PERF_EVENTS` is the correct config name. And `KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY` is a macro, `KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY`.

My conclusion is this looks like a functional patch. I should be able to incorporate this into my fuzzing strategy. I'll use the `set-results` tool for this.



8/2 2026/07/06 14:56 tool set-results
0m Error:
the following configs do not exist in the kernel tree: HW_PERF_EVENTS

Args:
{
  "EnableConfigs": [
    "KVM",
    "HW_PERF_EVENTS"
  ],
  "FocusSymbols": [
    "kvm_arm_pmu_v3_set_pmu_fixed_counters_only",
    "kvm_vcpu_load_pmu",
    "kvm_supports_guest_pmuv3",
    "kvm_pmu_create_perf_event",
    "kvm_arm_pmu_v3_set_attr"
  ],
  "Reasoning": "The patch introduces a new KVM device attribute `KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY` for ARM64 PMUv3 emulation, which allows emulating a PMUv3 without programmable event counters. This modifies core KVM ARM64 PMU emulation logic and adds new ioctl handling, which is reachable from userspace and should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "error": "the following configs do not exist in the kernel tree: HW_PERF_EVENTS"
}

9/2 2026/07/06 14:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19430
							output: 21
							thoughts: 45
						

Thoughts:
Okay, here's my take on those thoughts, as if they were my own:

**Investigating the Missing `HW_PERF_EVENTS`**

Hmm, it seems like `HW_PERF_EVENTS` isn't defined. That's unusual, I was expecting to find that. Right, so I need to track down where this configuration option *should* be defined. Given its nature, it's most likely related to hardware performance counters, so it should be defined within the architecture-specific Kconfig files or the general initialization Kconfig. Let's start by checking `arch/arm64/Kconfig` and then maybe `init/Kconfig`. That's where I'll most likely find what I need to understand why it's missing or what's going on with this config option.



10/2 2026/07/06 14:56 tool grepper
0m Args:
{
  "Expression": "config HW_PERF_EVENTS"
}

Results:
{
  "Output": "arch/arm/Kconfig=1251=config CPU_TTBR0_PAN\n--\narch/arm/Kconfig-1257-\narch/arm/Kconfig:1258:config HW_PERF_EVENTS\narch/arm/Kconfig-1259-\tdef_bool y\n--\narch/arm64/Kconfig=1549=config ARCH_SPARSEMEM_ENABLE\n--\narch/arm64/Kconfig-1552-\narch/arm64/Kconfig:1553:config HW_PERF_EVENTS\narch/arm64/Kconfig-1554-\tdef_bool y\n--\narch/mips/Kconfig=2690=config NODES_SHIFT\n--\narch/mips/Kconfig-2694-\narch/mips/Kconfig:2695:config HW_PERF_EVENTS\narch/mips/Kconfig-2696-\tbool \"Enable hardware performance counter support for perf events\"\n--\narch/sh/Kconfig=626=config GUSA_RB\n--\narch/sh/Kconfig-635-\narch/sh/Kconfig:636:config HW_PERF_EVENTS\narch/sh/Kconfig-637-\tbool \"Enable hardware performance counter support for perf events\"\n"
}

11/2 2026/07/06 14:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20001
							output: 19
							thoughts: 116
						

Thoughts:
**Investigating HW_PERF_EVENTS Configuration**

Okay, so I'm dealing with `HW_PERF_EVENTS` and, on arm64, it's defined as a boolean `y`, meaning it's always enabled if `PERF_EVENTS` is enabled. Seems straightforward enough. But then I saw this error message: "the following configs do not exist in the kernel tree: HW_PERF_EVENTS." That's throwing me off.

Wait a second... Perhaps the issue isn't that it *literally* doesn't exist, but that it's not a user-configurable option directly through something like `make menuconfig`. It's a dependency, and the system is getting tripped up on that distinction. It doesn't show up in those menus, so it's not "existing" in that sense. I need to be sure that `PERF_EVENTS` is the right thing to check.



12/2 2026/07/06 14:57 tool grepper
0m Args:
{
  "Expression": "config PERF_EVENTS"
}

Results:
{
  "Output": "arch/x86/events/Kconfig=2=menu \"Performance monitoring\"\narch/x86/events/Kconfig-3-\narch/x86/events/Kconfig:4:config PERF_EVENTS_INTEL_UNCORE\narch/x86/events/Kconfig-5-\ttristate \"Intel uncore performance events\"\n--\narch/x86/events/Kconfig-11-\narch/x86/events/Kconfig:12:config PERF_EVENTS_INTEL_RAPL\narch/x86/events/Kconfig-13-\ttristate \"Intel/AMD rapl performance events\"\n--\narch/x86/events/Kconfig-19-\narch/x86/events/Kconfig:20:config PERF_EVENTS_INTEL_CSTATE\narch/x86/events/Kconfig-21-\ttristate \"Intel cstate performance events\"\n--\narch/x86/events/Kconfig-27-\narch/x86/events/Kconfig:28:config PERF_EVENTS_AMD_POWER\narch/x86/events/Kconfig-29-\tdepends on PERF_EVENTS \u0026\u0026 CPU_SUP_AMD\n--\narch/x86/events/Kconfig-36-\narch/x86/events/Kconfig:37:config PERF_EVENTS_AMD_UNCORE\narch/x86/events/Kconfig-38-\ttristate \"AMD Uncore performance events\"\n--\narch/x86/events/Kconfig-47-\narch/x86/events/Kconfig:48:config PERF_EVENTS_AMD_BRS\narch/x86/events/Kconfig-49-\tdepends on PERF_EVENTS \u0026\u0026 CPU_SUP_AMD\n--\ninit/Kconfig=2121=menu \"Kernel Performance Events And Counters\"\ninit/Kconfig-2122-\ninit/Kconfig:2123:config PERF_EVENTS\ninit/Kconfig-2124-\tbool \"Kernel performance events and counters\"\n"
}

13/2 2026/07/06 14:57 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20845
							output: 211
							thoughts: 28
						

Thoughts:
Here's your summary:

**The Right Tools for the Job**

Okay, so I've been considering the best way to approach this particular performance analysis, and it's become clear that `PERF_EVENTS` is the correct avenue to explore. I've worked with a lot of different profiling tools over the years, and `PERF_EVENTS` seems like the most versatile and powerful option for what I need to achieve here. I'm thinking, given the scope of this project, that leveraging `KVM` alongside `PERF_EVENTS` will give me the granularity and access to low-level data I require. This combination should allow me to pinpoint the precise bottlenecks and behavior I'm after.



14/2 2026/07/06 14:57 tool set-results
0m Args:
{
  "EnableConfigs": [
    "KVM",
    "PERF_EVENTS"
  ],
  "FocusSymbols": [
    "kvm_arm_pmu_v3_set_pmu_fixed_counters_only",
    "kvm_vcpu_load_pmu",
    "kvm_supports_guest_pmuv3",
    "kvm_pmu_create_perf_event",
    "kvm_arm_pmu_v3_set_attr"
  ],
  "Reasoning": "The patch introduces a new KVM device attribute `KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY` for ARM64 PMUv3 emulation, which allows emulating a PMUv3 without programmable event counters. This modifies core KVM ARM64 PMU emulation logic and adds new ioctl handling, which is reachable from userspace and should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "KVM",
    "PERF_EVENTS"
  ],
  "FocusSymbols": [
    "kvm_arm_pmu_v3_set_pmu_fixed_counters_only",
    "kvm_vcpu_load_pmu",
    "kvm_supports_guest_pmuv3",
    "kvm_pmu_create_perf_event",
    "kvm_arm_pmu_v3_set_attr"
  ],
  "Reasoning": "The patch introduces a new KVM device attribute `KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY` for ARM64 PMUv3 emulation, which allows emulating a PMUv3 without programmable event counters. This modifies core KVM ARM64 PMU emulation logic and adds new ioctl handling, which is reachable from userspace and should be fuzzed.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)