MDCR_EL2.HPMN changes which counters are reserved for EL2 and thus which
enable control, event filter, and overflow width apply. HPMD changes EL2
filtering, while HLP changes the overflow width and sample period.

The existing guest MDCR_EL2 handling only requests a PMU reload for
HPME. Reloading enables or disables existing perf events, but does not
rebuild events whose attributes have become stale. Generic userspace
writes through KVM_SET_ONE_REG do not request a reload at all.

Route guest writes, userspace writes, and reset through a common helper.
Mark the vCPU's perf events for recreation when HPMN, HPMD, or HLP
changes. Consume the marker on the vCPU thread before reprogramming the
counters, which also preserves counter values and avoids creating events
on an arbitrary ioctl thread.

Use a setter-only accessor so register restore gains these side effects
without changing generic reads or rejecting register values.

Fixes: fe827f916662 ("KVM: arm64: nv: Honor MDCR_EL2.HPME")
Fixes: 8a34979030f6 ("KVM: arm64: nv: Apply EL2 event filtering when in hyp 
context")
Fixes: 16535d55e91f ("KVM: arm64: nv: Honor MDCR_EL2.HLP")
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Akihiko Odaki <[email protected]>
---
 arch/arm64/kvm/pmu-emul.c | 27 +++++++++++++++++++++++++++
 arch/arm64/kvm/sys_regs.c | 31 +++++++++++++++++++++----------
 include/kvm/arm_pmu.h     |  3 +++
 3 files changed, 51 insertions(+), 10 deletions(-)

diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index 5281d37634a0..9ac5f307ea31 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -17,6 +17,9 @@
 
 #define PERF_ATTR_CFG1_COUNTER_64BIT   BIT(0)
 
+#define MDCR_EL2_PMU_RECREATE_MASK     (MDCR_EL2_HPMN | MDCR_EL2_HPMD | \
+                                        MDCR_EL2_HLP)
+
 static LIST_HEAD(arm_pmus);
 static DEFINE_MUTEX(arm_pmus_lock);
 
@@ -606,6 +609,21 @@ void kvm_pmu_handle_pmcr(struct kvm_vcpu *vcpu, u64 val)
        }
 }
 
+void kvm_pmu_handle_mdcr(struct kvm_vcpu *vcpu, u64 old, u64 val)
+{
+       u64 changed = old ^ val;
+
+       /*
+        * HPMN determines which counters HPMD and HLP apply to. Changes to
+        * these fields require new perf event filters and sample periods.
+        */
+       if (changed & MDCR_EL2_PMU_RECREATE_MASK)
+               vcpu->arch.pmu.events_need_recreate = true;
+
+       if (changed & (MDCR_EL2_HPME | MDCR_EL2_PMU_RECREATE_MASK))
+               kvm_make_request(KVM_REQ_RELOAD_PMU, vcpu);
+}
+
 static bool kvm_pmu_counter_is_enabled(struct kvm_pmc *pmc)
 {
        struct kvm_vcpu *vcpu = kvm_pmc_to_vcpu(pmc);
@@ -899,7 +917,16 @@ u64 kvm_pmu_get_pmceid(struct kvm_vcpu *vcpu, bool pmceid1)
 
 void kvm_vcpu_reload_pmu(struct kvm_vcpu *vcpu)
 {
+       struct kvm_pmu *pmu = &vcpu->arch.pmu;
        u64 mask = kvm_pmu_implemented_counter_mask(vcpu);
+       int i;
+
+       if (pmu->events_need_recreate) {
+               for (i = 0; i < KVM_ARMV8_PMU_MAX_COUNTERS; i++)
+                       kvm_pmu_stop_counter(kvm_vcpu_idx_to_pmc(vcpu, i));
+
+               pmu->events_need_recreate = false;
+       }
 
        __vcpu_rmw_sys_reg(vcpu, PMOVSSET_EL0, &=, mask);
        __vcpu_rmw_sys_reg(vcpu, PMINTENSET_EL1, &=, mask);
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 5d5c579d4579..9e905d9be9f6 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -3020,17 +3020,22 @@ static bool access_mdcr(struct kvm_vcpu *vcpu,
        }
 
        __vcpu_assign_sys_reg(vcpu, MDCR_EL2, val);
-
-       /*
-        * Request a reload of the PMU to enable/disable the counters
-        * affected by HPME.
-        */
-       if ((old ^ val) & MDCR_EL2_HPME)
-               kvm_make_request(KVM_REQ_RELOAD_PMU, vcpu);
+       kvm_pmu_handle_mdcr(vcpu, old, val);
 
        return true;
 }
 
+static int set_mdcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *rd,
+                   u64 val)
+{
+       u64 old = __vcpu_sys_reg(vcpu, MDCR_EL2);
+
+       __vcpu_assign_sys_reg(vcpu, MDCR_EL2, val);
+       kvm_pmu_handle_mdcr(vcpu, old, val);
+
+       return 0;
+}
+
 static bool access_ras(struct kvm_vcpu *vcpu,
                       struct sys_reg_params *p,
                       const struct sys_reg_desc *r)
@@ -3177,8 +3182,13 @@ static int set_imp_id_reg(struct kvm_vcpu *vcpu, const 
struct sys_reg_desc *r,
 
 static u64 reset_mdcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
 {
-       __vcpu_assign_sys_reg(vcpu, r->reg, vcpu->kvm->arch.nr_pmu_counters);
-       return vcpu->kvm->arch.nr_pmu_counters;
+       u64 old = __vcpu_sys_reg(vcpu, r->reg);
+       u64 val = vcpu->kvm->arch.nr_pmu_counters;
+
+       __vcpu_assign_sys_reg(vcpu, r->reg, val);
+       kvm_pmu_handle_mdcr(vcpu, old, val);
+
+       return val;
 }
 
 /*
@@ -3741,7 +3751,8 @@ static const struct sys_reg_desc sys_reg_descs[] = {
        EL2_REG_FILTERED(SCTLR2_EL2, access_vm_reg, reset_val, 0,
                         sctlr2_el2_visibility),
        EL2_REG_VNCR(HCR_EL2, reset_hcr, 0),
-       EL2_REG(MDCR_EL2, access_mdcr, reset_mdcr, 0),
+       SYS_REG_USER_FILTER(MDCR_EL2, access_mdcr, reset_mdcr, 0,
+                           NULL, set_mdcr, el2_visibility),
        EL2_REG(CPTR_EL2, access_rw, reset_val, CPTR_NVHE_EL2_RES1),
        EL2_REG_VNCR(HSTR_EL2, reset_val, 0),
        EL2_REG_VNCR_FILT(HFGRTR_EL2, fgt_visibility),
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index b5e5942204fc..1971f8794c9d 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -32,6 +32,7 @@ struct kvm_pmu {
        struct kvm_pmc pmc[KVM_ARMV8_PMU_MAX_COUNTERS];
        int irq_num;
        bool created;
+       bool events_need_recreate;
 };
 
 struct arm_pmu_entry {
@@ -56,6 +57,7 @@ bool kvm_pmu_should_notify_user(struct kvm_vcpu *vcpu);
 bool kvm_pmu_update_run(struct kvm_vcpu *vcpu);
 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_handle_mdcr(struct kvm_vcpu *vcpu, u64 old, u64 val);
 void kvm_pmu_set_counter_event_type(struct kvm_vcpu *vcpu, u64 data,
                                    u64 select_idx);
 void kvm_vcpu_reload_pmu(struct kvm_vcpu *vcpu);
@@ -133,6 +135,7 @@ static inline bool kvm_pmu_should_notify_user(struct 
kvm_vcpu *vcpu)
 static inline bool kvm_pmu_update_run(struct kvm_vcpu *vcpu) { return false; }
 static inline void kvm_pmu_software_increment(struct kvm_vcpu *vcpu, u64 val) 
{}
 static inline void kvm_pmu_handle_pmcr(struct kvm_vcpu *vcpu, u64 val) {}
+static inline void kvm_pmu_handle_mdcr(struct kvm_vcpu *vcpu, u64 old, u64 
val) {}
 static inline void kvm_pmu_set_counter_event_type(struct kvm_vcpu *vcpu,
                                                  u64 data, u64 select_idx) {}
 static inline int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu,

-- 
2.55.0


Reply via email to