diff mbox series

[3/6] KVM: x86: selftests: Set up AMD VM in pmu_counters_test

Message ID 20240813164244.751597-4-coltonlewis@google.com
State New
Headers show
Series Extend pmu_counters_test to AMD CPUs | expand

Commit Message

Colton Lewis Aug. 13, 2024, 4:42 p.m. UTC
Branch in main() depending on if the CPU is Intel or AMD. They are
subject to vastly different requirements because the AMD PMU lacks
many properties defined by the Intel PMU including the entire CPUID
0xa function where Intel stores all the PMU properties. AMD lacks this
as well as any consistent notion of PMU versions as Intel does. Every
feature is a separate flag and they aren't the same features as Intel.

Set up a VM for testing core AMD counters and ensure proper CPUID
features are set.

Signed-off-by: Colton Lewis <coltonlewis@google.com>
---
 .../selftests/kvm/x86_64/pmu_counters_test.c  | 80 ++++++++++++++++---
 1 file changed, 68 insertions(+), 12 deletions(-)

Comments

Mingwei Zhang Aug. 26, 2024, 10:43 p.m. UTC | #1
On Tue, Aug 13, 2024, Colton Lewis wrote:
> Branch in main() depending on if the CPU is Intel or AMD. They are
> subject to vastly different requirements because the AMD PMU lacks
> many properties defined by the Intel PMU including the entire CPUID
> 0xa function where Intel stores all the PMU properties. AMD lacks this
> as well as any consistent notion of PMU versions as Intel does. Every
> feature is a separate flag and they aren't the same features as Intel.
> 
> Set up a VM for testing core AMD counters and ensure proper CPUID
> features are set.
> 
> Signed-off-by: Colton Lewis <coltonlewis@google.com>
> ---
>  .../selftests/kvm/x86_64/pmu_counters_test.c  | 80 ++++++++++++++++---
>  1 file changed, 68 insertions(+), 12 deletions(-)
> 
> diff --git a/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c b/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c
> index 0e305e43a93b..a11df073331a 100644
> --- a/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c
> +++ b/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c
> @@ -33,7 +33,7 @@
>  static uint8_t kvm_pmu_version;
>  static bool kvm_has_perf_caps;
>  
> -static struct kvm_vm *pmu_vm_create_with_one_vcpu(struct kvm_vcpu **vcpu,
> +static struct kvm_vm *intel_pmu_vm_create(struct kvm_vcpu **vcpu,
>  						  void *guest_code,
>  						  uint8_t pmu_version,
>  						  uint64_t perf_capabilities)
> @@ -303,7 +303,7 @@ static void test_arch_events(uint8_t pmu_version, uint64_t perf_capabilities,
>  	if (!pmu_version)
>  		return;
>  
> -	vm = pmu_vm_create_with_one_vcpu(&vcpu, guest_test_arch_events,
> +	vm = intel_pmu_vm_create(&vcpu, guest_test_arch_events,
>  					 pmu_version, perf_capabilities);
>  
>  	vcpu_set_cpuid_property(vcpu, X86_PROPERTY_PMU_EBX_BIT_VECTOR_LENGTH,
> @@ -463,7 +463,7 @@ static void test_gp_counters(uint8_t pmu_version, uint64_t perf_capabilities,
>  	struct kvm_vcpu *vcpu;
>  	struct kvm_vm *vm;
>  
> -	vm = pmu_vm_create_with_one_vcpu(&vcpu, guest_test_gp_counters,
> +	vm = intel_pmu_vm_create(&vcpu, guest_test_gp_counters,
>  					 pmu_version, perf_capabilities);
>  
>  	vcpu_set_cpuid_property(vcpu, X86_PROPERTY_PMU_NR_GP_COUNTERS,
> @@ -530,7 +530,7 @@ static void test_fixed_counters(uint8_t pmu_version, uint64_t perf_capabilities,
>  	struct kvm_vcpu *vcpu;
>  	struct kvm_vm *vm;
>  
> -	vm = pmu_vm_create_with_one_vcpu(&vcpu, guest_test_fixed_counters,
> +	vm = intel_pmu_vm_create(&vcpu, guest_test_fixed_counters,
>  					 pmu_version, perf_capabilities);
>  
>  	vcpu_set_cpuid_property(vcpu, X86_PROPERTY_PMU_FIXED_COUNTERS_BITMASK,
> @@ -627,18 +627,74 @@ static void test_intel_counters(void)
>  	}
>  }
>  
> -int main(int argc, char *argv[])
> +static uint8_t nr_core_counters(void)
>  {
> -	TEST_REQUIRE(kvm_is_pmu_enabled());
> +	const uint8_t nr_counters = kvm_cpu_property(X86_PROPERTY_NUM_PERF_CTR_CORE);
> +	const bool core_ext = kvm_cpu_has(X86_FEATURE_PERF_CTR_EXT_CORE);
> +	/* The default numbers promised if the property is 0 */
> +	const uint8_t amd_nr_core_ext_counters = 6;
> +	const uint8_t amd_nr_core_counters = 4;
> +
> +	if (nr_counters != 0)
> +		return nr_counters;
> +
> +	if (core_ext)
> +		return amd_nr_core_ext_counters;
> +
> +	return amd_nr_core_counters;
> +}
> +
> +static void guest_test_core_counters(void)
> +{
> +	GUEST_DONE();
> +}
>  
> -	TEST_REQUIRE(host_cpu_is_intel);
> -	TEST_REQUIRE(kvm_cpu_has_p(X86_PROPERTY_PMU_VERSION));
> -	TEST_REQUIRE(kvm_cpu_property(X86_PROPERTY_PMU_VERSION) > 0);
> +static void test_core_counters(void)
> +{
> +	uint8_t nr_counters = nr_core_counters();
> +	bool core_ext = kvm_cpu_has(X86_FEATURE_PERF_CTR_EXT_CORE);
> +	bool perf_mon_v2 = kvm_cpu_has(X86_FEATURE_PERF_MON_V2);
> +	struct kvm_vcpu *vcpu;
> +	struct kvm_vm *vm;
>  
> -	kvm_pmu_version = kvm_cpu_property(X86_PROPERTY_PMU_VERSION);
> -	kvm_has_perf_caps = kvm_cpu_has(X86_FEATURE_PDCM);
> +	vm = vm_create_with_one_vcpu(&vcpu, guest_test_core_counters);
>  
> -	test_intel_counters();
> +	/* This property may not be there in older underlying CPUs,
> +	 * but it simplifies the test code for it to be set
> +	 * unconditionally.
> +	 */
> +	vcpu_set_cpuid_property(vcpu, X86_PROPERTY_NUM_PERF_CTR_CORE, nr_counters);
> +	if (core_ext)
> +		vcpu_set_cpuid_feature(vcpu, X86_FEATURE_PERF_CTR_EXT_CORE);
> +	if (perf_mon_v2)
> +		vcpu_set_cpuid_feature(vcpu, X86_FEATURE_PERF_MON_V2);

hmm, I think this might not be enough. So, when the baremetal machine
supports Perfmon v2, this code is just testing v2. But we should be able
to test anything below v2, ie., v1, v1 without core_ext. So, three
cases need to be tested here: v1 with 4 counters; v1 with core_ext (6
counters); v2.

If, the machine running this selftest does not support v2 but it does
support core extension, then we fall back to test v1 with 4 counters and
v1 with 6 counters.
> +
> +	pr_info("Testing core counters: CoreExt = %u, PerfMonV2 = %u, NumCounters = %u\n",
> +		core_ext, perf_mon_v2, nr_counters);
> +	run_vcpu(vcpu);
> +
> +	kvm_vm_free(vm);
> +}
> +
> +static void test_amd_counters(void)
> +{
> +	test_core_counters();
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	TEST_REQUIRE(kvm_is_pmu_enabled());
> +
> +	if (host_cpu_is_intel) {
> +		TEST_REQUIRE(kvm_cpu_has_p(X86_PROPERTY_PMU_VERSION));
> +		TEST_REQUIRE(kvm_cpu_property(X86_PROPERTY_PMU_VERSION) > 0);
> +		kvm_pmu_version = kvm_cpu_property(X86_PROPERTY_PMU_VERSION);
> +		kvm_has_perf_caps = kvm_cpu_has(X86_FEATURE_PDCM);
> +		test_intel_counters();
> +	} else if (host_cpu_is_amd) {
> +		/* AMD CPUs don't have the same properties to look at. */
> +		test_amd_counters();
> +	}
>  
>  	return 0;
>  }
> -- 
> 2.46.0.76.ge559c4bf1a-goog
>
Mingwei Zhang Aug. 28, 2024, 11:18 p.m. UTC | #2
On Wed, Aug 28, 2024 at 3:39 PM Colton Lewis <coltonlewis@google.com> wrote:
>
> Hi Mingwei
>
> Mingwei Zhang <mizhang@google.com> writes:
>
> > On Tue, Aug 13, 2024, Colton Lewis wrote:
> >> Branch in main() depending on if the CPU is Intel or AMD. They are
> >> subject to vastly different requirements because the AMD PMU lacks
> >> many properties defined by the Intel PMU including the entire CPUID
> >> 0xa function where Intel stores all the PMU properties. AMD lacks this
> >> as well as any consistent notion of PMU versions as Intel does. Every
> >> feature is a separate flag and they aren't the same features as Intel.
>
> >> Set up a VM for testing core AMD counters and ensure proper CPUID
> >> features are set.
>
> >> Signed-off-by: Colton Lewis <coltonlewis@google.com>
> >> ---
> >>   .../selftests/kvm/x86_64/pmu_counters_test.c  | 80 ++++++++++++++++---
> >>   1 file changed, 68 insertions(+), 12 deletions(-)
>
> >> diff --git a/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c
> >> b/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c
> >> index 0e305e43a93b..a11df073331a 100644
> >> --- a/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c
> >> +++ b/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c
> >> @@ -33,7 +33,7 @@
> >>   static uint8_t kvm_pmu_version;
> >>   static bool kvm_has_perf_caps;
>
> >> -static struct kvm_vm *pmu_vm_create_with_one_vcpu(struct kvm_vcpu
> >> **vcpu,
> >> +static struct kvm_vm *intel_pmu_vm_create(struct kvm_vcpu **vcpu,
> >>                                                void *guest_code,
> >>                                                uint8_t pmu_version,
> >>                                                uint64_t perf_capabilities)
> >> @@ -303,7 +303,7 @@ static void test_arch_events(uint8_t pmu_version,
> >> uint64_t perf_capabilities,
> >>      if (!pmu_version)
> >>              return;
>
> >> -    vm = pmu_vm_create_with_one_vcpu(&vcpu, guest_test_arch_events,
> >> +    vm = intel_pmu_vm_create(&vcpu, guest_test_arch_events,
> >>                                       pmu_version, perf_capabilities);
>
> >>      vcpu_set_cpuid_property(vcpu, X86_PROPERTY_PMU_EBX_BIT_VECTOR_LENGTH,
> >> @@ -463,7 +463,7 @@ static void test_gp_counters(uint8_t pmu_version,
> >> uint64_t perf_capabilities,
> >>      struct kvm_vcpu *vcpu;
> >>      struct kvm_vm *vm;
>
> >> -    vm = pmu_vm_create_with_one_vcpu(&vcpu, guest_test_gp_counters,
> >> +    vm = intel_pmu_vm_create(&vcpu, guest_test_gp_counters,
> >>                                       pmu_version, perf_capabilities);
>
> >>      vcpu_set_cpuid_property(vcpu, X86_PROPERTY_PMU_NR_GP_COUNTERS,
> >> @@ -530,7 +530,7 @@ static void test_fixed_counters(uint8_t pmu_version,
> >> uint64_t perf_capabilities,
> >>      struct kvm_vcpu *vcpu;
> >>      struct kvm_vm *vm;
>
> >> -    vm = pmu_vm_create_with_one_vcpu(&vcpu, guest_test_fixed_counters,
> >> +    vm = intel_pmu_vm_create(&vcpu, guest_test_fixed_counters,
> >>                                       pmu_version, perf_capabilities);
>
> >>      vcpu_set_cpuid_property(vcpu, X86_PROPERTY_PMU_FIXED_COUNTERS_BITMASK,
> >> @@ -627,18 +627,74 @@ static void test_intel_counters(void)
> >>      }
> >>   }
>
> >> -int main(int argc, char *argv[])
> >> +static uint8_t nr_core_counters(void)
> >>   {
> >> -    TEST_REQUIRE(kvm_is_pmu_enabled());
> >> +    const uint8_t nr_counters =
> >> kvm_cpu_property(X86_PROPERTY_NUM_PERF_CTR_CORE);
> >> +    const bool core_ext = kvm_cpu_has(X86_FEATURE_PERF_CTR_EXT_CORE);
> >> +    /* The default numbers promised if the property is 0 */
> >> +    const uint8_t amd_nr_core_ext_counters = 6;
> >> +    const uint8_t amd_nr_core_counters = 4;
> >> +
> >> +    if (nr_counters != 0)
> >> +            return nr_counters;
> >> +
> >> +    if (core_ext)
> >> +            return amd_nr_core_ext_counters;
> >> +
> >> +    return amd_nr_core_counters;
> >> +}
> >> +
> >> +static void guest_test_core_counters(void)
> >> +{
> >> +    GUEST_DONE();
> >> +}
>
> >> -    TEST_REQUIRE(host_cpu_is_intel);
> >> -    TEST_REQUIRE(kvm_cpu_has_p(X86_PROPERTY_PMU_VERSION));
> >> -    TEST_REQUIRE(kvm_cpu_property(X86_PROPERTY_PMU_VERSION) > 0);
> >> +static void test_core_counters(void)
> >> +{
> >> +    uint8_t nr_counters = nr_core_counters();
> >> +    bool core_ext = kvm_cpu_has(X86_FEATURE_PERF_CTR_EXT_CORE);
> >> +    bool perf_mon_v2 = kvm_cpu_has(X86_FEATURE_PERF_MON_V2);
> >> +    struct kvm_vcpu *vcpu;
> >> +    struct kvm_vm *vm;
>
> >> -    kvm_pmu_version = kvm_cpu_property(X86_PROPERTY_PMU_VERSION);
> >> -    kvm_has_perf_caps = kvm_cpu_has(X86_FEATURE_PDCM);
> >> +    vm = vm_create_with_one_vcpu(&vcpu, guest_test_core_counters);
>
> >> -    test_intel_counters();
> >> +    /* This property may not be there in older underlying CPUs,
> >> +     * but it simplifies the test code for it to be set
> >> +     * unconditionally.
> >> +     */
> >> +    vcpu_set_cpuid_property(vcpu, X86_PROPERTY_NUM_PERF_CTR_CORE,
> >> nr_counters);
> >> +    if (core_ext)
> >> +            vcpu_set_cpuid_feature(vcpu, X86_FEATURE_PERF_CTR_EXT_CORE);
> >> +    if (perf_mon_v2)
> >> +            vcpu_set_cpuid_feature(vcpu, X86_FEATURE_PERF_MON_V2);
>
> > hmm, I think this might not be enough. So, when the baremetal machine
> > supports Perfmon v2, this code is just testing v2. But we should be able
> > to test anything below v2, ie., v1, v1 without core_ext. So, three
> > cases need to be tested here: v1 with 4 counters; v1 with core_ext (6
> > counters); v2.
>
> > If, the machine running this selftest does not support v2 but it does
> > support core extension, then we fall back to test v1 with 4 counters and
> > v1 with 6 counters.
>
> This should cover all cases the way I wrote it. I detect the number of
> counters in nr_core_counters(). That tells me if I am dealing with 4 or
> 6 and then I set the cpuid property based on that so I can read that
> number in later code instead of duplicating the logic.

right. in the current code, you set up the counters properly according
to the hw capability. But the test can do more on a hw with perfmon
v2, right? Because it can test multiple combinations of setup for a
VM: say v1 + 4 counters and v1 + 6 counters etc. I am just following
the style of this selftest on Intel side, in which they do a similar
kind of enumeration of PMU version + PDCM capabilities. In each
configuration, it will invoke a VM and do the test.

>
> I could always inline nr_core_counters() to make this more obvious since
> it is only called in this function. It was one of the first bits of code
> I wrote working on this series and I assumed I would need to call it a
> bunch before I decided I could just set the cpuid property after calling
> it once.
>
> >> +
> >> +    pr_info("Testing core counters: CoreExt = %u, PerfMonV2 = %u,
> >> NumCounters = %u\n",
> >> +            core_ext, perf_mon_v2, nr_counters);
> >> +    run_vcpu(vcpu);
> >> +
> >> +    kvm_vm_free(vm);
> >> +}
> >> +
> >> +static void test_amd_counters(void)
> >> +{
> >> +    test_core_counters();
> >> +}
> >> +
> >> +int main(int argc, char *argv[])
> >> +{
> >> +    TEST_REQUIRE(kvm_is_pmu_enabled());
> >> +
> >> +    if (host_cpu_is_intel) {
> >> +            TEST_REQUIRE(kvm_cpu_has_p(X86_PROPERTY_PMU_VERSION));
> >> +            TEST_REQUIRE(kvm_cpu_property(X86_PROPERTY_PMU_VERSION) > 0);
> >> +            kvm_pmu_version = kvm_cpu_property(X86_PROPERTY_PMU_VERSION);
> >> +            kvm_has_perf_caps = kvm_cpu_has(X86_FEATURE_PDCM);
> >> +            test_intel_counters();
> >> +    } else if (host_cpu_is_amd) {
> >> +            /* AMD CPUs don't have the same properties to look at. */
> >> +            test_amd_counters();
> >> +    }
>
> >>      return 0;
> >>   }
> >> --
> >> 2.46.0.76.ge559c4bf1a-goog
>
Sean Christopherson Aug. 29, 2024, 8:45 p.m. UTC | #3
On Wed, Aug 28, 2024, Mingwei Zhang wrote:
> > >> +static void test_core_counters(void)
> > >> +{
> > >> +    uint8_t nr_counters = nr_core_counters();
> > >> +    bool core_ext = kvm_cpu_has(X86_FEATURE_PERF_CTR_EXT_CORE);
> > >> +    bool perf_mon_v2 = kvm_cpu_has(X86_FEATURE_PERF_MON_V2);
> > >> +    struct kvm_vcpu *vcpu;
> > >> +    struct kvm_vm *vm;
> >
> > >> -    kvm_pmu_version = kvm_cpu_property(X86_PROPERTY_PMU_VERSION);
> > >> -    kvm_has_perf_caps = kvm_cpu_has(X86_FEATURE_PDCM);
> > >> +    vm = vm_create_with_one_vcpu(&vcpu, guest_test_core_counters);
> >
> > >> -    test_intel_counters();
> > >> +    /* This property may not be there in older underlying CPUs,
> > >> +     * but it simplifies the test code for it to be set
> > >> +     * unconditionally.

But then the test isn't verifying that KVM is honoring the architecture.  I.e.
backdooring information to the guest risks getting false passes because KVM
incorrectly peeks at the same information, which shouldn't exist.

> > >> +     */

	/*
	 * Multi-line function comments should start on the line after the
	 * opening slash-asterisk, like so.
	 */

> > >> +    vcpu_set_cpuid_property(vcpu, X86_PROPERTY_NUM_PERF_CTR_CORE,
> > >> nr_counters);
> > >> +    if (core_ext)
> > >> +            vcpu_set_cpuid_feature(vcpu, X86_FEATURE_PERF_CTR_EXT_CORE);
> > >> +    if (perf_mon_v2)
> > >> +            vcpu_set_cpuid_feature(vcpu, X86_FEATURE_PERF_MON_V2);
> >
> > > hmm, I think this might not be enough. So, when the baremetal machine
> > > supports Perfmon v2, this code is just testing v2. But we should be able
> > > to test anything below v2, ie., v1, v1 without core_ext. So, three
> > > cases need to be tested here: v1 with 4 counters; v1 with core_ext (6
> > > counters); v2.
> >
> > > If, the machine running this selftest does not support v2 but it does
> > > support core extension, then we fall back to test v1 with 4 counters and
> > > v1 with 6 counters.
> >
> > This should cover all cases the way I wrote it. I detect the number of
> > counters in nr_core_counters(). That tells me if I am dealing with 4 or
> > 6 and then I set the cpuid property based on that so I can read that
> > number in later code instead of duplicating the logic.
> 
> right. in the current code, you set up the counters properly according
> to the hw capability. But the test can do more on a hw with perfmon
> v2, right? Because it can test multiple combinations of setup for a
> VM: say v1 + 4 counters and v1 + 6 counters etc. I am just following
> the style of this selftest on Intel side, in which they do a similar
> kind of enumeration of PMU version + PDCM capabilities. In each
> configuration, it will invoke a VM and do the test.

Ya.  This is similar my comments on setting NUM_PER_CTR_CORE when the field
shouldn't exist.  One of the main goals of this test is to verify the KVM honors
the architecture based on userspace's defined virtual CPU model, i.e. guest
CPUID.  That means testing all (or at least, within reason) possible combinations
that can feasibly be supported by KVM given the underlying hardware.

As written, this essentially just tests the maximal configuration that can be
exposed to a guest, which isn't _that_ interesting because KVM tends to get plenty
of coverage for such setups, e.g. by running "real" VMs.
diff mbox series

Patch

diff --git a/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c b/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c
index 0e305e43a93b..a11df073331a 100644
--- a/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c
+++ b/tools/testing/selftests/kvm/x86_64/pmu_counters_test.c
@@ -33,7 +33,7 @@ 
 static uint8_t kvm_pmu_version;
 static bool kvm_has_perf_caps;
 
-static struct kvm_vm *pmu_vm_create_with_one_vcpu(struct kvm_vcpu **vcpu,
+static struct kvm_vm *intel_pmu_vm_create(struct kvm_vcpu **vcpu,
 						  void *guest_code,
 						  uint8_t pmu_version,
 						  uint64_t perf_capabilities)
@@ -303,7 +303,7 @@  static void test_arch_events(uint8_t pmu_version, uint64_t perf_capabilities,
 	if (!pmu_version)
 		return;
 
-	vm = pmu_vm_create_with_one_vcpu(&vcpu, guest_test_arch_events,
+	vm = intel_pmu_vm_create(&vcpu, guest_test_arch_events,
 					 pmu_version, perf_capabilities);
 
 	vcpu_set_cpuid_property(vcpu, X86_PROPERTY_PMU_EBX_BIT_VECTOR_LENGTH,
@@ -463,7 +463,7 @@  static void test_gp_counters(uint8_t pmu_version, uint64_t perf_capabilities,
 	struct kvm_vcpu *vcpu;
 	struct kvm_vm *vm;
 
-	vm = pmu_vm_create_with_one_vcpu(&vcpu, guest_test_gp_counters,
+	vm = intel_pmu_vm_create(&vcpu, guest_test_gp_counters,
 					 pmu_version, perf_capabilities);
 
 	vcpu_set_cpuid_property(vcpu, X86_PROPERTY_PMU_NR_GP_COUNTERS,
@@ -530,7 +530,7 @@  static void test_fixed_counters(uint8_t pmu_version, uint64_t perf_capabilities,
 	struct kvm_vcpu *vcpu;
 	struct kvm_vm *vm;
 
-	vm = pmu_vm_create_with_one_vcpu(&vcpu, guest_test_fixed_counters,
+	vm = intel_pmu_vm_create(&vcpu, guest_test_fixed_counters,
 					 pmu_version, perf_capabilities);
 
 	vcpu_set_cpuid_property(vcpu, X86_PROPERTY_PMU_FIXED_COUNTERS_BITMASK,
@@ -627,18 +627,74 @@  static void test_intel_counters(void)
 	}
 }
 
-int main(int argc, char *argv[])
+static uint8_t nr_core_counters(void)
 {
-	TEST_REQUIRE(kvm_is_pmu_enabled());
+	const uint8_t nr_counters = kvm_cpu_property(X86_PROPERTY_NUM_PERF_CTR_CORE);
+	const bool core_ext = kvm_cpu_has(X86_FEATURE_PERF_CTR_EXT_CORE);
+	/* The default numbers promised if the property is 0 */
+	const uint8_t amd_nr_core_ext_counters = 6;
+	const uint8_t amd_nr_core_counters = 4;
+
+	if (nr_counters != 0)
+		return nr_counters;
+
+	if (core_ext)
+		return amd_nr_core_ext_counters;
+
+	return amd_nr_core_counters;
+}
+
+static void guest_test_core_counters(void)
+{
+	GUEST_DONE();
+}
 
-	TEST_REQUIRE(host_cpu_is_intel);
-	TEST_REQUIRE(kvm_cpu_has_p(X86_PROPERTY_PMU_VERSION));
-	TEST_REQUIRE(kvm_cpu_property(X86_PROPERTY_PMU_VERSION) > 0);
+static void test_core_counters(void)
+{
+	uint8_t nr_counters = nr_core_counters();
+	bool core_ext = kvm_cpu_has(X86_FEATURE_PERF_CTR_EXT_CORE);
+	bool perf_mon_v2 = kvm_cpu_has(X86_FEATURE_PERF_MON_V2);
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
 
-	kvm_pmu_version = kvm_cpu_property(X86_PROPERTY_PMU_VERSION);
-	kvm_has_perf_caps = kvm_cpu_has(X86_FEATURE_PDCM);
+	vm = vm_create_with_one_vcpu(&vcpu, guest_test_core_counters);
 
-	test_intel_counters();
+	/* This property may not be there in older underlying CPUs,
+	 * but it simplifies the test code for it to be set
+	 * unconditionally.
+	 */
+	vcpu_set_cpuid_property(vcpu, X86_PROPERTY_NUM_PERF_CTR_CORE, nr_counters);
+	if (core_ext)
+		vcpu_set_cpuid_feature(vcpu, X86_FEATURE_PERF_CTR_EXT_CORE);
+	if (perf_mon_v2)
+		vcpu_set_cpuid_feature(vcpu, X86_FEATURE_PERF_MON_V2);
+
+	pr_info("Testing core counters: CoreExt = %u, PerfMonV2 = %u, NumCounters = %u\n",
+		core_ext, perf_mon_v2, nr_counters);
+	run_vcpu(vcpu);
+
+	kvm_vm_free(vm);
+}
+
+static void test_amd_counters(void)
+{
+	test_core_counters();
+}
+
+int main(int argc, char *argv[])
+{
+	TEST_REQUIRE(kvm_is_pmu_enabled());
+
+	if (host_cpu_is_intel) {
+		TEST_REQUIRE(kvm_cpu_has_p(X86_PROPERTY_PMU_VERSION));
+		TEST_REQUIRE(kvm_cpu_property(X86_PROPERTY_PMU_VERSION) > 0);
+		kvm_pmu_version = kvm_cpu_property(X86_PROPERTY_PMU_VERSION);
+		kvm_has_perf_caps = kvm_cpu_has(X86_FEATURE_PDCM);
+		test_intel_counters();
+	} else if (host_cpu_is_amd) {
+		/* AMD CPUs don't have the same properties to look at. */
+		test_amd_counters();
+	}
 
 	return 0;
 }