@@ -16,6 +16,17 @@
/* Guest payload for any performance counter counting */
#define NUM_BRANCHES 10
+/*
+ * KVM implements the first two non-existent counters (MSR_P6_PERFCTRx)
+ * via kvm_pr_unimpl_wrmsr() instead of #GP.
+ */
+#define MSR_INTEL_ARCH_PMU_GPCTR (MSR_IA32_PERFCTR0 + 2)
+
+static const uint64_t perf_caps[] = {
+ 0,
+ PMU_CAP_FW_WRITES,
+};
+
static struct kvm_vm *pmu_vm_create_with_one_vcpu(struct kvm_vcpu **vcpu,
void *guest_code)
{
@@ -169,9 +180,86 @@ static void intel_test_arch_events(void)
}
}
+static void guest_wr_and_rd_msrs(uint32_t base, uint64_t value,
+ uint8_t begin, uint8_t offset)
+{
+ unsigned int i;
+ uint8_t wr_vector, rd_vector;
+ uint64_t msr_val;
+
+ for (i = begin; i < begin + offset; i++) {
+ wr_vector = wrmsr_safe(base + i, value);
+ rd_vector = rdmsr_safe(base + i, &msr_val);
+ if (wr_vector == GP_VECTOR || rd_vector == GP_VECTOR)
+ GUEST_SYNC(GP_VECTOR);
+ else
+ GUEST_SYNC(msr_val);
+ }
+
+ GUEST_DONE();
+}
+
+/* Access the first out-of-range counter register to trigger #GP */
+static void test_oob_gp_counter(uint8_t eax_gp_num, uint8_t offset,
+ uint64_t perf_cap, uint64_t exported)
+{
+ struct kvm_vm *vm;
+ struct kvm_vcpu *vcpu;
+ struct kvm_cpuid_entry2 *entry;
+ uint32_t ctr_msr = MSR_IA32_PERFCTR0;
+ uint64_t msr_val;
+
+ vm = pmu_vm_create_with_one_vcpu(&vcpu, guest_wr_and_rd_msrs);
+
+ entry = vcpu_get_cpuid_entry(vcpu, 0xa);
+ entry->eax = (entry->eax & ~GP_CTR_NUM_MASK) |
+ (eax_gp_num << GP_CTR_NUM_OFS_BIT);
+ vcpu_set_cpuid(vcpu);
+
+ if (perf_cap & PMU_CAP_FW_WRITES)
+ ctr_msr = MSR_IA32_PMC0;
+
+ vcpu_set_msr(vcpu, MSR_IA32_PERF_CAPABILITIES, perf_cap);
+ vcpu_args_set(vcpu, 4, ctr_msr, 0xffff, eax_gp_num, offset);
+ while (run_vcpu(vcpu, &msr_val) != UCALL_DONE)
+ TEST_ASSERT(msr_val == exported,
+ "Unexpected when testing gp counter num.");
+
+ kvm_vm_free(vm);
+}
+
+static void intel_test_counters_num(void)
+{
+ unsigned int i;
+ uint8_t kvm_gp_num = X86_INTEL_MAX_GP_CTR_NUM;
+
+ TEST_REQUIRE(kvm_gp_num > 2);
+
+ for (i = 0; i < ARRAY_SIZE(perf_caps); i++) {
+ /*
+ * For compatibility reasons, KVM does not emulate #GP
+ * when MSR_P6_PERFCTR[0|1] is not present, but it doesn't
+ * affect checking the presence of MSR_IA32_PMCx with #GP.
+ */
+ if (perf_caps[i] & PMU_CAP_FW_WRITES)
+ test_oob_gp_counter(0, 1, perf_caps[i], GP_VECTOR);
+
+ test_oob_gp_counter(2, 1, perf_caps[i], GP_VECTOR);
+ test_oob_gp_counter(kvm_gp_num, 1, perf_caps[i], GP_VECTOR);
+
+ /* KVM doesn't emulate more counters than it can support. */
+ test_oob_gp_counter(kvm_gp_num + 1, 1, perf_caps[i], GP_VECTOR);
+
+ /* Test that KVM drops writes to MSR_P6_PERFCTR[0|1]. */
+ if (perf_caps[i] == 0)
+ test_oob_gp_counter(0, 2, perf_caps[i], 0);
+ }
+}
+
static void intel_test_pmu_cpuid(void)
{
intel_test_arch_events();
+ intel_test_counters_num();
}
int main(int argc, char *argv[])