@@ -1703,6 +1703,12 @@ int kvm_arm_pmu_v3_has_attr(struct kvm_vcpu *vcpu,
struct kvm_device_attr *attr);
int kvm_arm_pmu_v3_enable(struct kvm_vcpu *vcpu);
+bool kvm_vcpu_pmu_is_partitioned(struct kvm_vcpu *vcpu);
+
+#if defined(__KVM_NVHE_HYPERVISOR__)
+#define kvm_vcpu_pmu_is_partitioned(_) false
+#endif
+
struct kvm_pmu_events *kvm_get_pmu_events(void);
void kvm_vcpu_pmu_restore_guest(struct kvm_vcpu *vcpu);
void kvm_vcpu_pmu_restore_host(struct kvm_vcpu *vcpu);
@@ -1819,6 +1825,11 @@ static inline bool kvm_pmu_counter_is_hyp(struct kvm_vcpu *vcpu, unsigned int id
static inline void kvm_pmu_nested_transition(struct kvm_vcpu *vcpu) {}
+static inline bool kvm_vcpu_pmu_is_partitioned(struct kvm_vcpu *vcpu)
+{
+ return false;
+}
+
#endif
#endif /* __ARM64_KVM_HOST_H__ */
@@ -42,13 +42,14 @@ static void kvm_arm_setup_mdcr_el2(struct kvm_vcpu *vcpu)
*/
vcpu->arch.mdcr_el2 = FIELD_PREP(MDCR_EL2_HPMN, hpmn);
vcpu->arch.mdcr_el2 |= (MDCR_EL2_HPMD |
- MDCR_EL2_TPM |
MDCR_EL2_TPMS |
MDCR_EL2_TTRF |
- MDCR_EL2_TPMCR |
MDCR_EL2_TDRA |
MDCR_EL2_TDOSA);
+ if (!kvm_vcpu_pmu_is_partitioned(vcpu))
+ vcpu->arch.mdcr_el2 |= MDCR_EL2_TPM | MDCR_EL2_TPMCR;
+
/* Is the VM being debugged by userspace? */
if (vcpu->guest_debug)
/* Route all software debug exceptions to EL2 */
@@ -133,6 +133,10 @@ static inline void __activate_traps_fpsimd32(struct kvm_vcpu *vcpu)
case HDFGWTR_EL2: \
id = HDFGRTR_GROUP; \
break; \
+ case HDFGRTR2_EL2: \
+ case HDFGWTR2_EL2: \
+ id = HDFGRTR2_GROUP; \
+ break; \
case HAFGRTR_EL2: \
id = HAFGRTR_GROUP; \
break; \
@@ -143,10 +147,6 @@ static inline void __activate_traps_fpsimd32(struct kvm_vcpu *vcpu)
case HFGITR2_EL2: \
id = HFGITR2_GROUP; \
break; \
- case HDFGRTR2_EL2: \
- case HDFGWTR2_EL2: \
- id = HDFGRTR2_GROUP; \
- break; \
default: \
BUILD_BUG_ON(1); \
} \
@@ -191,6 +191,59 @@ static inline bool cpu_has_amu(void)
ID_AA64PFR0_EL1_AMU_SHIFT);
}
+/**
+ * __activate_pmu_fgt() - Activate fine grain traps for partitioned PMU
+ * @vcpu: Pointer to struct kvm_vcpu
+ *
+ * Clear the most commonly accessed registers for a partitioned
+ * PMU. Trap the rest.
+ */
+static inline void __activate_pmu_fgt(struct kvm_vcpu *vcpu)
+{
+ struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
+ struct kvm *kvm = kern_hyp_va(vcpu->kvm);
+ u64 set;
+ u64 clr;
+
+ set = HDFGRTR_EL2_PMOVS
+ | HDFGRTR_EL2_PMCCFILTR_EL0
+ | HDFGRTR_EL2_PMEVTYPERn_EL0;
+ clr = HDFGRTR_EL2_PMUSERENR_EL0
+ | HDFGRTR_EL2_PMSELR_EL0
+ | HDFGRTR_EL2_PMINTEN
+ | HDFGRTR_EL2_PMCNTEN
+ | HDFGRTR_EL2_PMCCNTR_EL0
+ | HDFGRTR_EL2_PMEVCNTRn_EL0;
+
+ update_fgt_traps_cs(hctxt, vcpu, kvm, HDFGRTR_EL2, clr, set);
+
+ set = HDFGWTR_EL2_PMOVS
+ | HDFGWTR_EL2_PMCCFILTR_EL0
+ | HDFGWTR_EL2_PMEVTYPERn_EL0;
+ clr = HDFGWTR_EL2_PMUSERENR_EL0
+ | HDFGWTR_EL2_PMCR_EL0
+ | HDFGWTR_EL2_PMSELR_EL0
+ | HDFGWTR_EL2_PMINTEN
+ | HDFGWTR_EL2_PMCNTEN
+ | HDFGWTR_EL2_PMCCNTR_EL0
+ | HDFGWTR_EL2_PMEVCNTRn_EL0;
+
+ update_fgt_traps_cs(hctxt, vcpu, kvm, HDFGWTR_EL2, clr, set);
+
+ if (!cpus_have_final_cap(ARM64_HAS_FGT2))
+ return;
+
+ set = HDFGRTR2_EL2_nPMICFILTR_EL0;
+ clr = HDFGRTR2_EL2_nPMICNTR_EL0;
+
+ update_fgt_traps_cs(hctxt, vcpu, kvm, HDFGRTR2_EL2, clr, set);
+
+ set = HDFGWTR2_EL2_nPMICFILTR_EL0;
+ clr = HDFGWTR2_EL2_nPMICNTR_EL0;
+
+ update_fgt_traps_cs(hctxt, vcpu, kvm, HDFGWTR2_EL2, clr, set);
+}
+
static inline void __activate_traps_hfgxtr(struct kvm_vcpu *vcpu)
{
struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
@@ -210,6 +263,9 @@ static inline void __activate_traps_hfgxtr(struct kvm_vcpu *vcpu)
if (cpu_has_amu())
update_fgt_traps(hctxt, vcpu, kvm, HAFGRTR_EL2);
+ if (kvm_vcpu_pmu_is_partitioned(vcpu))
+ __activate_pmu_fgt(vcpu);
+
if (!cpus_have_final_cap(ARM64_HAS_FGT2))
return;
@@ -131,6 +131,20 @@ bool kvm_pmu_is_partitioned(struct arm_pmu *pmu)
return pmu->hpmn < *host_data_ptr(nr_event_counters);
}
+/**
+ * kvm_vcpu_pmu_is_partitioned() - Determine if given VCPU has a partitioned PMU
+ * @vcpu: Pointer to kvm_vcpu struct
+ *
+ * Determine if given VCPU has a partitioned PMU by extracting that
+ * field and passing it to :c:func:`kvm_pmu_is_partitioned`
+ *
+ * Return: True if the VCPU PMU is partitioned, false otherwise
+ */
+bool kvm_vcpu_pmu_is_partitioned(struct kvm_vcpu *vcpu)
+{
+ return kvm_pmu_is_partitioned(vcpu->kvm->arch.arm_pmu);
+}
+
/**
* kvm_pmu_host_counter_mask() - Compute bitmask of host-reserved counters
* @pmu: Pointer to arm_pmu struct
In order to gain a real performance benefit from partitioning the PMU, utilize fine grain traps (FEAT_FGT and FEAT_FGT2) to avoid trapping common PMU register accesses by the guest to remove that overhead. There should be no information leaks between guests as all these registers are context switched by a later patch in this series. Untrapped: * PMCR_EL0 * PMUSERENR_EL0 * PMSELR_EL0 * PMCCNTR_EL0 * PMINTEN_EL0 * PMEVCNTRn_EL0 * PMICNTR_EL0 Trapped: * PMOVS_EL0 * PMEVTYPERn_EL0 * PMICFILTR_EL0 * PMCCFILTR_EL0 PMOVS remains trapped so KVM can track overflow IRQs that will need to be injected into the guest. PMEVTYPERn remains trapped so KVM can limit which events guests can count, such as disallowing counting at EL2. PMCCFILTR and PMCIFILTR are the same Signed-off-by: Colton Lewis <coltonlewis@google.com> --- arch/arm64/include/asm/kvm_host.h | 11 +++++ arch/arm64/kvm/debug.c | 5 +- arch/arm64/kvm/hyp/include/hyp/switch.h | 64 +++++++++++++++++++++++-- arch/arm64/kvm/pmu-part.c | 14 ++++++ 4 files changed, 88 insertions(+), 6 deletions(-)