Re: [PATCH v9 06/16] KVM: arm64: PMU: Recreate events after MDCR_EL2 changes

From: Fuad Tabba

Date: Wed Jul 29 2026 - 12:28:54 EST


On Mon, 20 Jul 2026 at 06:44, Akihiko Odaki
<odaki@xxxxxxxxxxxxxxxxxxxxxx> wrote:
>
> 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 <odaki@xxxxxxxxxxxxxxxxxxxxxx>

Sashiko's comment seems to be worth pursuing, but that's a separate fix.

Reviewed-by: Fuad Tabba <fuad.tabba@xxxxxxxxx>

Cheers,
/fuad


> ---
> 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
>
>