[PATCH 16/23] KVM: x86/pmu: Handle FIXED_CTR_CTRL under PerfMon masking

From: Zide Chen

Date: Fri Aug 21 2026 - 18:33:51 EST


PerfMon masking affects reads from and writes to this MSR. The effective
mask is defined as bits 4n+3:4n and bits 4n+35:4n+32 being set if and
only if bit 32+n of the PerfMon mask is set (for 0 <= n <= 7).

KVM doesn't support some fixed-counter features, and
pmu->fixed_ctr_ctrl_rsvd is a subset of the effective mask above, so the
existing gating in intel_pmu_set_msr() is sufficient even under PerfMon
masking.

Upon guest PMU context load, OR the guest-owned bits with whatever the
hardware currently holds for every fixed-counter index the guest does
not own, before writing the result to the MSR.

During PMU context put, host-owned fixed counters may still be scheduled
for system-wide host events, so the host-owned bits should remain
unchanged.

Signed-off-by: Zide Chen <zide.chen@xxxxxxxxx>
---
arch/x86/kvm/vmx/pmu_intel.c | 40 +++++++++++++++++++++++++++++++++---
1 file changed, 37 insertions(+), 3 deletions(-)

diff --git a/arch/x86/kvm/vmx/pmu_intel.c b/arch/x86/kvm/vmx/pmu_intel.c
index 19ccc7cd319c..9236bfa15c41 100644
--- a/arch/x86/kvm/vmx/pmu_intel.c
+++ b/arch/x86/kvm/vmx/pmu_intel.c
@@ -920,6 +920,33 @@ static void intel_pmu_write_global_ctrl(u64 global_ctrl)
vmcs_write64(GUEST_IA32_PERF_GLOBAL_CTRL, global_ctrl);
}

+static u64 intel_fixed_ctrl_host_bits(struct kvm_pmu *pmu)
+{
+ unsigned long fixed_mask;
+ u64 fixed_ctl;
+ int i;
+
+ if (!kvm_vcpu_has_perfmon_mask(pmu_to_vcpu(pmu)))
+ return 0;
+
+ fixed_mask = kvm_fixed_pmc_mask(pmu);
+
+ rdmsrq(MSR_CORE_PERF_FIXED_CTR_CTRL, fixed_ctl);
+
+ /*
+ * Use the full per-counter nibbles (bits 4n+3:4n and 4n+35:4n+32) to
+ * strip all bits belonging to guest-owned counters.
+ *
+ * pmu->fixed_ctr_ctrl_rsvd can't be used here since it can't gate bits
+ * that are not supported by KVM.
+ */
+ kvm_for_each_fixed_counter(i, fixed_mask)
+ fixed_ctl &= ~intel_fixed_bits_by_idx(i, GENMASK_ULL(3, 0) |
+ GENMASK_ULL(35, 32));
+
+ return fixed_ctl;
+}
+
static void intel_mediated_pmu_load(struct kvm_vcpu *vcpu)
{
struct kvm_pmu *pmu = vcpu_to_pmu(vcpu);
@@ -935,7 +962,8 @@ static void intel_mediated_pmu_load(struct kvm_vcpu *vcpu)
if (pmu->global_status & toggle)
wrmsrq(MSR_CORE_PERF_GLOBAL_STATUS_SET, pmu->global_status & toggle);

- wrmsrq(MSR_CORE_PERF_FIXED_CTR_CTRL, pmu->fixed_ctr_ctrl_hw);
+ wrmsrq(MSR_CORE_PERF_FIXED_CTR_CTRL,
+ pmu->fixed_ctr_ctrl_hw | intel_fixed_ctrl_host_bits(pmu));
}

static void intel_mediated_pmu_put(struct kvm_vcpu *vcpu)
@@ -953,9 +981,15 @@ static void intel_mediated_pmu_put(struct kvm_vcpu *vcpu)
* Clear hardware FIXED_CTR_CTRL MSR to avoid information leakage and
* also to avoid accidentally enabling fixed counters (based on guest
* state) while running in the host, e.g. when setting global ctrl.
+ *
+ * Keep the host-owned counters unchanged.
*/
- if (pmu->fixed_ctr_ctrl_hw)
- wrmsrq(MSR_CORE_PERF_FIXED_CTR_CTRL, 0);
+ if (pmu->fixed_ctr_ctrl_hw) {
+ u64 fixed_ctl = intel_fixed_ctrl_host_bits(pmu);
+
+ fixed_ctl &= ~pmu->fixed_ctr_ctrl_hw;
+ wrmsrq(MSR_CORE_PERF_FIXED_CTR_CTRL, fixed_ctl);
+ }

if (kvm_vcpu_has_perf_metrics(vcpu)) {
pmu->perf_metrics = rdpmc(INTEL_PMC_FIXED_RDPMC_METRICS);
--
2.55.0