[PATCH 11/15] KVM: x86/pmu: Emulate streamlined Freeze-LBR-on-PMI
From: Zide Chen
Date: Tue Jul 07 2026 - 14:45:26 EST
PerfMon v4 streamlined the Freeze-LBR-on-PMI mechanism. When
DEBUGCTLMSR_FREEZE_LBRS_ON_PMI is set and a PMI fires, hardware sets
IA32_PERF_GLOBAL_STATUS.LBR_Frz instead of clearing DEBUGCTLMSR_LBR.
Guest PerfMon v4+ is supported under mediated vPMU only. When KVM
relays a guest-induced PMI, the mediated vPMU framework already
propagates IA32_PERF_GLOBAL_STATUS.LBR_Frz, and no additional handling
is required.
For PMIs generated by KVM-emulated PMU events, however, KVM must
emulate IA32_PERF_GLOBAL_STATUS.LBR_Frz so that guest LBR recording
is frozen as required by the architectural behavior.
Signed-off-by: Zide Chen <zide.chen@xxxxxxxxx>
---
arch/x86/kvm/vmx/pmu_intel.c | 47 ++++++++++++++++++++++--------------
1 file changed, 29 insertions(+), 18 deletions(-)
diff --git a/arch/x86/kvm/vmx/pmu_intel.c b/arch/x86/kvm/vmx/pmu_intel.c
index cb6f9c272e03..556c119d5e91 100644
--- a/arch/x86/kvm/vmx/pmu_intel.c
+++ b/arch/x86/kvm/vmx/pmu_intel.c
@@ -660,6 +660,8 @@ static void intel_pmu_refresh(struct kvm_vcpu *vcpu)
pmu->global_status_rsvd = pmu->global_ctrl_rsvd
& ~(GLOBAL_STATUS_BUFFER_OVF | GLOBAL_STATUS_COND_CHG);
+ if (pmu->version > 3)
+ pmu->global_status_rsvd &= ~GLOBAL_STATUS_LBRS_FROZEN;
if (vmx_pt_mode_is_host_guest())
pmu->global_status_rsvd &= ~GLOBAL_STATUS_TRACE_TOPAPMI;
@@ -718,32 +720,41 @@ static void intel_pmu_reset(struct kvm_vcpu *vcpu)
}
/*
- * Emulate LBR_On_PMI behavior for 1 < pmu.version < 4.
- *
- * If Freeze_LBR_On_PMI = 1, the LBR is frozen on PMI and
- * the KVM emulates to clear the LBR bit (bit 0) in IA32_DEBUGCTL.
- *
- * Guest needs to re-enable LBR to resume branches recording.
+ * Emulate legacy and streamlined Freeze_LBR_On_PMI behavior.
+ * In either case, guest needs to re-enable LBR to resume branches recording.
*/
-static void intel_pmu_legacy_freezing_lbrs_on_pmi(struct kvm_vcpu *vcpu)
+static void intel_pmu_freeze_lbr_on_pmi(struct kvm_vcpu *vcpu)
{
- u64 data = vmx_guest_debugctl_read();
+ u8 version;
+ u64 data;
- if (data & DEBUGCTLMSR_FREEZE_LBRS_ON_PMI) {
- data &= ~DEBUGCTLMSR_LBR;
- vmx_guest_debugctl_write(vcpu, data);
+ if (!intel_pmu_lbr_is_enabled(vcpu))
+ return;
+
+ data = vmx_guest_debugctl_read();
+ version = vcpu_to_pmu(vcpu)->version;
+
+ if (!(data & DEBUGCTLMSR_FREEZE_LBRS_ON_PMI))
+ return;
+
+ if (version > 1 && version < 4) {
+ if (data & DEBUGCTLMSR_LBR) {
+ data &= ~DEBUGCTLMSR_LBR;
+ vmx_guest_debugctl_write(vcpu, data);
+ }
+ } else if (vcpu_to_lbr_desc(vcpu)->msr_passthrough &&
+ kvm_vcpu_has_mediated_pmu(vcpu)) {
+ /*
+ * This will be restored to guest before VM-Entry, setting
+ * LBR_Frz to freeze LBR recording until the guest clears it.
+ */
+ vcpu_to_pmu(vcpu)->global_status |= GLOBAL_STATUS_LBRS_FROZEN;
}
}
static void intel_pmu_deliver_pmi(struct kvm_vcpu *vcpu)
{
- u8 version = vcpu_to_pmu(vcpu)->version;
-
- if (!intel_pmu_lbr_is_enabled(vcpu))
- return;
-
- if (version > 1 && version < 4)
- intel_pmu_legacy_freezing_lbrs_on_pmi(vcpu);
+ intel_pmu_freeze_lbr_on_pmi(vcpu);
}
static void vmx_update_intercept_for_lbr_msrs(struct kvm_vcpu *vcpu, bool set)
--
2.54.0