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. Use a common helper for guest and userspace writes. Recreate events after HPMN, HPMD, or HLP changes. This preserves counter values. Defer event creation to the vCPU thread instead of 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 Reviewed-by: Fuad Tabba --- arch/arm64/kvm/pmu-emul.c | 14 ++++++++++++++ arch/arm64/kvm/sys_regs.c | 22 ++++++++++++++-------- include/kvm/arm_pmu.h | 2 ++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c index 8530cc907b56..953255111779 100644 --- a/arch/arm64/kvm/pmu-emul.c +++ b/arch/arm64/kvm/pmu-emul.c @@ -612,6 +612,20 @@ void kvm_pmu_handle_pmcr(struct kvm_vcpu *vcpu, u64 val) } } +void kvm_pmu_apply_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_HPMN | MDCR_EL2_HPMD | MDCR_EL2_HLP)) + kvm_pmu_request_recreate(vcpu); + else if (changed & MDCR_EL2_HPME) + 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); diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c index 1a57e07cec9a..bae1b69a5ab9 100644 --- a/arch/arm64/kvm/sys_regs.c +++ b/arch/arm64/kvm/sys_regs.c @@ -3114,17 +3114,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_apply_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_apply_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) @@ -3836,7 +3841,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 65cf9b49a0c0..f12c916c04d5 100644 --- a/include/kvm/arm_pmu.h +++ b/include/kvm/arm_pmu.h @@ -58,6 +58,7 @@ bool kvm_pmu_update_run(struct kvm_vcpu *vcpu); void kvm_pmu_software_increment(struct kvm_vcpu *vcpu, u64 val); void kvm_pmu_request_recreate(struct kvm_vcpu *vcpu); void kvm_pmu_handle_pmcr(struct kvm_vcpu *vcpu, u64 val); +void kvm_pmu_apply_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); @@ -139,6 +140,7 @@ 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_request_recreate(struct kvm_vcpu *vcpu) {} static inline void kvm_pmu_handle_pmcr(struct kvm_vcpu *vcpu, u64 val) {} +static inline void kvm_pmu_apply_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