Re: [PATCH 5/5] KVM: x86/pmu: Hide guest counter updates from the VMRUN instruction

Jim Mattson
2023-05-24T21:30:39Z

On Wed, May 24, 2023 at 2:23 PM Sean Christopherson <seanjc@google.com> wrote:
>
> On Fri, Mar 10, 2023, Like Xu wrote:
> > diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
> > index adb92fc4d7c9..d6fcbf233cb3 100644
> > --- a/arch/x86/include/asm/kvm_host.h
> > +++ b/arch/x86/include/asm/kvm_host.h
> > @@ -561,6 +561,10 @@ struct kvm_pmu {
> > */
> > u64 host_cross_mapped_mask;
> >
> > + /* Flags to track any HW quirks that need to be fixed by vPMU. */
> > + u64 quirk_flags;
> > + DECLARE_BITMAP(hide_vmrun_pmc_idx, X86_PMC_IDX_MAX);
>
> Since it sounds like AMD isn't changing the behavior, let's forego the quirk and
> just hardcode the fixup.
>
> > diff --git a/arch/x86/kvm/pmu.c b/arch/x86/kvm/pmu.c
> > index 2a0504732966..315dca021d57 100644
> > --- a/arch/x86/kvm/pmu.c
> > +++ b/arch/x86/kvm/pmu.c
> > @@ -254,6 +254,7 @@ static void pmc_pause_counter(struct kvm_pmc *pmc)
> > counter += perf_event_pause(pmc->perf_event, true);
> > pmc->counter = counter & pmc_bitmask(pmc);
> > pmc->is_paused = true;
> > + kvm_mark_pmc_is_quirky(pmc);
> > }
> >
> > static bool pmc_resume_counter(struct kvm_pmc *pmc)
> > @@ -822,6 +823,19 @@ int kvm_vm_ioctl_set_pmu_event_filter(struct kvm *kvm, void __user *argp)
> > return r;
> > }
> >
> > +static inline bool event_is_branch_instruction(struct kvm_pmc *pmc)
>
> How about pmc_is_counting_branches()? The "event" itself isn't a branch
> instruction.

Note that there's a bug in the original code for this that has
probably never been fixed: it ignores CMASK and INV in the PerfEvtSel.

> > +{
> > + return eventsel_match_perf_hw_id(pmc, PERF_COUNT_HW_INSTRUCTIONS) ||
> > + eventsel_match_perf_hw_id(pmc,
> > + PERF_COUNT_HW_BRANCH_INSTRUCTIONS);
>
> Let this poke out.
>
> > +}
> > +
> > +static inline bool quirky_pmc_will_count_vmrun(struct kvm_pmc *pmc)
> > +{
> > + return event_is_branch_instruction(pmc) && event_is_allowed(pmc) &&
> > + !static_call(kvm_x86_get_cpl)(pmc->vcpu);
> > +}
> > +
> > void kvm_pmu_handle_event(struct kvm_vcpu *vcpu)
> > {
> > struct kvm_pmu *pmu = vcpu_to_pmu(vcpu);
> > @@ -837,6 +851,10 @@ void kvm_pmu_handle_event(struct kvm_vcpu *vcpu)
> >
> > reprogram_counter(pmc);
> > kvm_pmu_handle_pmc_overflow(pmc);
> > +
> > + if (vcpu_has_pmu_quirks(vcpu) &&
> > + quirky_pmc_will_count_vmrun(pmc))
> > + set_bit(pmc->idx, pmu->hide_vmrun_pmc_idx);
>
> Doesn't this need to adjust the count _before_ handling overflow? I.e. isn't it
> possible for the bogus counts to cause bogus overflow?
>
> > diff --git a/arch/x86/kvm/pmu.h b/arch/x86/kvm/pmu.h
> > index a47b579667c6..30f6f58f4c38 100644
> > --- a/arch/x86/kvm/pmu.h
> > +++ b/arch/x86/kvm/pmu.h
> > @@ -18,6 +18,9 @@
> > #define VMWARE_BACKDOOR_PMC_REAL_TIME 0x10001
> > #define VMWARE_BACKDOOR_PMC_APPARENT_TIME 0x10002
> >
> > +#define X86_PMU_COUNT_VMRUN BIT_ULL(0)
> > +#define X86_PMU_QUIRKS_MASK X86_PMU_COUNT_VMRUN
> > +
> > struct kvm_pmu_ops {
> > bool (*hw_event_available)(struct kvm_pmc *pmc);
> > bool (*pmc_is_enabled)(struct kvm_pmc *pmc);
> > @@ -54,14 +57,33 @@ static inline void kvm_pmu_request_counter_reprogram(struct kvm_pmc *pmc)
> > kvm_make_request(KVM_REQ_PMU, pmc->vcpu);
> > }
> >
> > +static inline bool vcpu_has_pmu_quirks(struct kvm_vcpu *vcpu)
> > +{
> > + return vcpu_to_pmu(vcpu)->quirk_flags & X86_PMU_QUIRKS_MASK;
> > +}
> > +
> > +/*
> > + * The time to mark pmc is when the accumulation value returned
> > + * by perf API based on a HW counter has just taken effect.
> > + */
> > +static inline void kvm_mark_pmc_is_quirky(struct kvm_pmc *pmc)
> > +{
> > + if (!vcpu_has_pmu_quirks(pmc->vcpu))
> > + return;
> > +
> > + kvm_pmu_request_counter_reprogram(pmc);
> > +}
> > +
> > static inline u64 pmc_read_counter(struct kvm_pmc *pmc)
> > {
> > u64 counter, enabled, running;
> >
> > counter = pmc->counter;
> > - if (pmc->perf_event && !pmc->is_paused)
> > + if (pmc->perf_event && !pmc->is_paused) {
> > counter += perf_event_read_value(pmc->perf_event,
> > &enabled, &running);
> > + kvm_mark_pmc_is_quirky(pmc);
> > + }
> > /* FIXME: Scaling needed? */
> > return counter & pmc_bitmask(pmc);
> > }
> > diff --git a/arch/x86/kvm/svm/pmu.c b/arch/x86/kvm/svm/pmu.c
> > index 5fa939e411d8..130991a97f22 100644
> > --- a/arch/x86/kvm/svm/pmu.c
> > +++ b/arch/x86/kvm/svm/pmu.c
> > @@ -187,6 +187,7 @@ static void amd_pmu_refresh(struct kvm_vcpu *vcpu)
> > pmu->nr_arch_fixed_counters = 0;
> > pmu->global_status = 0;
> > bitmap_set(pmu->all_valid_pmc_idx, 0, pmu->nr_arch_gp_counters);
> > + pmu->quirk_flags |= X86_PMU_COUNT_VMRUN;
> > }
> >
> > static void amd_pmu_init(struct kvm_vcpu *vcpu)
> > diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
> > index f41d96e638ef..f6b33d172481 100644
> > --- a/arch/x86/kvm/svm/svm.c
> > +++ b/arch/x86/kvm/svm/svm.c
> > @@ -3919,6 +3919,31 @@ static fastpath_t svm_exit_handlers_fastpath(struct kvm_vcpu *vcpu)
> > return EXIT_FASTPATH_NONE;
> > }
> >
> > +static void pmu_hide_vmrun(struct kvm_vcpu *vcpu)
>
> This needs to be noinstr.
>
> > +{
> > + struct kvm_pmu *pmu = vcpu_to_pmu(vcpu);
> > + struct kvm_pmc *pmc;
> > + unsigned int i;
> > +
> > + for_each_set_bit(i, pmu->hide_vmrun_pmc_idx, X86_PMC_IDX_MAX) {
> > + clear_bit(i, pmu->hide_vmrun_pmc_idx);
>
> Clearing the bit will hide only the first VMRUN after the guest attempts to read
> the counter, no? The fixup needs to apply to every VMRUN that is executed after
> the PMC is programmed. Or am I misreading the patch?
>
> > +
> > + /* AMD doesn't have fixed counters at now. */
> > + if (i >= pmu->nr_arch_gp_counters)
> > + continue;
> > +
> > + /*
> > + * The prerequisite for fixing HW quirks is that there is indeed
> > + * HW working and perf has no chance to retrieve the counter.
>
> I don't follow the "perf has no chance to retrieve the counter" part.
>
> > + */
> > + pmc = &pmu->gp_counters[i];
> > + if (!pmc->perf_event || pmc->perf_event->hw.idx < 0)
> > + continue;
> > +
> > + pmc->counter--;
> > + }
> > +}
> > +
> > static noinstr void svm_vcpu_enter_exit(struct kvm_vcpu *vcpu, bool spec_ctrl_intercepted)
> > {
> > struct vcpu_svm *svm = to_svm(vcpu);
> > @@ -3986,6 +4011,9 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu)
> >
> > kvm_wait_lapic_expire(vcpu);
> >
> > + if (vcpu->kvm->arch.enable_pmu && vcpu_has_pmu_quirks(vcpu))
> > + pmu_hide_vmrun(vcpu);
> > +
> > /*
> > * If this vCPU has touched SPEC_CTRL, restore the guest's value if
> > * it's non-zero. Since vmentry is serialising on affected CPUs, there
> > --
> > 2.39.2
> >