diff mbox series

[kvm-unit-tests] x86/pmu: Test PMU virtualization on emulated instructions

Message ID 20211112235652.1127814-1-jmattson@google.com (mailing list archive)
State New, archived
Headers show
Series [kvm-unit-tests] x86/pmu: Test PMU virtualization on emulated instructions | expand

Commit Message

Jim Mattson Nov. 12, 2021, 11:56 p.m. UTC
Add tests of "instructions retired" and "branch instructions retired,"
to ensure that these events count emulated instructions.

Signed-off-by: Eric Hankland <ehankland@google.com>
[jmattson:
  - Added command-line parameter to conditionally run the new tests.
  - Added pmu-emulation test to unittests.cfg
]
Signed-off-by: Jim Mattson <jmattson@google.com>
---
 x86/pmu.c         | 80 +++++++++++++++++++++++++++++++++++++++++++++++
 x86/unittests.cfg |  7 +++++
 2 files changed, 87 insertions(+)

Comments

Like Xu Nov. 16, 2021, 10:04 a.m. UTC | #1
On 13/11/2021 7:56 am, Jim Mattson wrote:
> Add tests of "instructions retired" and "branch instructions retired,"
> to ensure that these events count emulated instructions.
> 
> Signed-off-by: Eric Hankland <ehankland@google.com>
> [jmattson:
>    - Added command-line parameter to conditionally run the new tests.
>    - Added pmu-emulation test to unittests.cfg
> ]
> Signed-off-by: Jim Mattson <jmattson@google.com>
> ---
>   x86/pmu.c         | 80 +++++++++++++++++++++++++++++++++++++++++++++++
>   x86/unittests.cfg |  7 +++++
>   2 files changed, 87 insertions(+)
> 
> diff --git a/x86/pmu.c b/x86/pmu.c
> index ec61ac956a55..a159333b0c73 100644
> --- a/x86/pmu.c
> +++ b/x86/pmu.c
> @@ -33,6 +33,12 @@
>   
>   #define N 1000000
>   
> +#define KVM_FEP "ud2; .byte 'k', 'v', 'm';"
> +// These values match the number of instructions and branches in the
> +// assembly block in check_emulated_instr().
> +#define EXPECTED_INSTR 17
> +#define EXPECTED_BRNCH 5
> +
>   typedef struct {
>   	uint32_t ctr;
>   	uint32_t config;
> @@ -468,6 +474,77 @@ static void check_running_counter_wrmsr(void)
>   	report_prefix_pop();
>   }
>   
> +static void check_emulated_instr(void)
> +{
> +	uint64_t status, instr_start, brnch_start;
> +	pmu_counter_t brnch_cnt = {
> +		.ctr = MSR_IA32_PERFCTR0,
> +		/* branch instructions */
> +		.config = EVNTSEL_OS | EVNTSEL_USR | gp_events[5].unit_sel,
> +		.count = 0,
> +	};
> +	pmu_counter_t instr_cnt = {
> +		.ctr = MSR_IA32_PERFCTR0 + 1,
> +		/* instructions */
> +		.config = EVNTSEL_OS | EVNTSEL_USR | gp_events[1].unit_sel,
> +		.count = 0,
> +	};
> +	report_prefix_push("emulated instruction");
> +
> +	wrmsr(MSR_CORE_PERF_GLOBAL_OVF_CTRL,
> +	      rdmsr(MSR_CORE_PERF_GLOBAL_STATUS));
> +
> +	start_event(&brnch_cnt);
> +	start_event(&instr_cnt);
> +
> +	brnch_start = -EXPECTED_BRNCH;
> +	instr_start = -EXPECTED_INSTR;
> +	wrmsr(MSR_IA32_PERFCTR0, brnch_start);
> +	wrmsr(MSR_IA32_PERFCTR0 + 1, instr_start);
> +	// KVM_FEP is a magic prefix that forces emulation so
> +	// 'KVM_FEP "jne label\n"' just counts as a single instruction.
> +	asm volatile(
> +		"mov $0x0, %%eax\n"
> +		"cmp $0x0, %%eax\n"
> +		KVM_FEP "jne label\n"
> +		KVM_FEP "jne label\n"
> +		KVM_FEP "jne label\n"
> +		KVM_FEP "jne label\n"
> +		KVM_FEP "jne label\n"
> +		"mov $0xa, %%eax\n"
> +		"cpuid\n"
> +		"mov $0xa, %%eax\n"
> +		"cpuid\n"
> +		"mov $0xa, %%eax\n"
> +		"cpuid\n"
> +		"mov $0xa, %%eax\n"
> +		"cpuid\n"
> +		"mov $0xa, %%eax\n"
> +		"cpuid\n"
> +		"label:\n"
> +		:
> +		:
> +		: "eax", "ebx", "ecx", "edx");
> +
> +	wrmsr(MSR_CORE_PERF_GLOBAL_CTRL, 0);
> +
> +	stop_event(&brnch_cnt);
> +	stop_event(&instr_cnt);
> +
> +	// Check that the end count - start count is at least the expected
> +	// number of instructions and branches.
> +	report(instr_cnt.count - instr_start >= EXPECTED_INSTR,
> +	       "instruction count");
> +	report(brnch_cnt.count - brnch_start >= EXPECTED_BRNCH,
> +	       "branch count");
> +	// Additionally check that those counters overflowed properly.
> +	status = rdmsr(MSR_CORE_PERF_GLOBAL_STATUS);
> +	report(status & 1, "instruction counter overflow");
> +	report(status & 2, "branch counter overflow");
> +
> +	report_prefix_pop();
> +}
> +
>   static void check_counters(void)
>   {
>   	check_gp_counters();
> @@ -563,6 +640,9 @@ int main(int ac, char **av)
>   
>   	check_counters();
>   
> +	if (ac > 1 && !strcmp(av[1], "emulation"))
> +		check_emulated_instr();
> +
>   	if (rdmsr(MSR_IA32_PERF_CAPABILITIES) & PMU_CAP_FW_WRITES) {
>   		gp_counter_base = MSR_IA32_PMC0;
>   		report_prefix_push("full-width writes");
> diff --git a/x86/unittests.cfg b/x86/unittests.cfg
> index 3000e53c790f..2aedb24dc4ff 100644
> --- a/x86/unittests.cfg
> +++ b/x86/unittests.cfg
> @@ -185,6 +185,13 @@ extra_params = -cpu host,migratable=no
>   check = /sys/module/kvm/parameters/ignore_msrs=N
>   check = /proc/sys/kernel/nmi_watchdog=0
>   
> +[pmu_emulation]
> +file = pmu.flat
> +arch = x86_64
> +extra_params = -cpu max -append emulation
> +check = /sys/module/kvm_intel/parameters/force_emulation_prefix=Y

It's "/sys/module/kvm/parameters/force_emulation_prefix=Y",

If it's N, we need a output like "FAIL: check_emulated_instr"
rather than:

Unhandled exception 6 #UD at ip 0000000000401387
error_code=0000      rflags=00010046      cs=00000008
rax=0000000000000000 rcx=00000000000000c2 rdx=00000000ffffffff rbx=00000000009509f4
rbp=0000000000513730 rsi=0000000000000020 rdi=0000000000000034
  r8=0000000000000000  r9=0000000000000020 r10=000000000000000d r11=0000000000000000
r12=000000000000038e r13=0000000000000002 r14=0000000000513d80 r15=0000000000008603
cr0=0000000080010011 cr2=0000000000000000 cr3=0000000001007000 cr4=0000000000000020
cr8=0000000000000000
	STACK: @401387 400384

> +check = /proc/sys/kernel/nmi_watchdog=0
> +
>   [vmware_backdoors]
>   file = vmware_backdoors.flat
>   extra_params = -machine vmport=on -cpu max
>
Paolo Bonzini Nov. 16, 2021, 10:45 a.m. UTC | #2
On 11/16/21 11:04, Like Xu wrote:
>>
>> +check = /sys/module/kvm_intel/parameters/force_emulation_prefix=Y
> 
> It's "/sys/module/kvm/parameters/force_emulation_prefix=Y",
> 
> If it's N, we need a output like "FAIL: check_emulated_instr"
> rather than:
> 
> Unhandled exception 6 #UD at ip 0000000000401387
> error_code=0000      rflags=00010046      cs=00000008
> rax=0000000000000000 rcx=00000000000000c2 rdx=00000000ffffffff 
> rbx=00000000009509f4
> rbp=0000000000513730 rsi=0000000000000020 rdi=0000000000000034
>   r8=0000000000000000  r9=0000000000000020 r10=000000000000000d 
> r11=0000000000000000
> r12=000000000000038e r13=0000000000000002 r14=0000000000513d80 
> r15=0000000000008603
> cr0=0000000080010011 cr2=0000000000000000 cr3=0000000001007000 
> cr4=0000000000000020
> cr8=0000000000000000
>      STACK: @401387 400384
> 
>> +check = /proc/sys/kernel/nmi_watchdog=0 

Only one check is supported.  However, this test does not need all 
counters, so we can remove the NMI watchdog line.  I'll send a patch 
shortly.

Paolo
diff mbox series

Patch

diff --git a/x86/pmu.c b/x86/pmu.c
index ec61ac956a55..a159333b0c73 100644
--- a/x86/pmu.c
+++ b/x86/pmu.c
@@ -33,6 +33,12 @@ 
 
 #define N 1000000
 
+#define KVM_FEP "ud2; .byte 'k', 'v', 'm';"
+// These values match the number of instructions and branches in the
+// assembly block in check_emulated_instr().
+#define EXPECTED_INSTR 17
+#define EXPECTED_BRNCH 5
+
 typedef struct {
 	uint32_t ctr;
 	uint32_t config;
@@ -468,6 +474,77 @@  static void check_running_counter_wrmsr(void)
 	report_prefix_pop();
 }
 
+static void check_emulated_instr(void)
+{
+	uint64_t status, instr_start, brnch_start;
+	pmu_counter_t brnch_cnt = {
+		.ctr = MSR_IA32_PERFCTR0,
+		/* branch instructions */
+		.config = EVNTSEL_OS | EVNTSEL_USR | gp_events[5].unit_sel,
+		.count = 0,
+	};
+	pmu_counter_t instr_cnt = {
+		.ctr = MSR_IA32_PERFCTR0 + 1,
+		/* instructions */
+		.config = EVNTSEL_OS | EVNTSEL_USR | gp_events[1].unit_sel,
+		.count = 0,
+	};
+	report_prefix_push("emulated instruction");
+
+	wrmsr(MSR_CORE_PERF_GLOBAL_OVF_CTRL,
+	      rdmsr(MSR_CORE_PERF_GLOBAL_STATUS));
+
+	start_event(&brnch_cnt);
+	start_event(&instr_cnt);
+
+	brnch_start = -EXPECTED_BRNCH;
+	instr_start = -EXPECTED_INSTR;
+	wrmsr(MSR_IA32_PERFCTR0, brnch_start);
+	wrmsr(MSR_IA32_PERFCTR0 + 1, instr_start);
+	// KVM_FEP is a magic prefix that forces emulation so
+	// 'KVM_FEP "jne label\n"' just counts as a single instruction.
+	asm volatile(
+		"mov $0x0, %%eax\n"
+		"cmp $0x0, %%eax\n"
+		KVM_FEP "jne label\n"
+		KVM_FEP "jne label\n"
+		KVM_FEP "jne label\n"
+		KVM_FEP "jne label\n"
+		KVM_FEP "jne label\n"
+		"mov $0xa, %%eax\n"
+		"cpuid\n"
+		"mov $0xa, %%eax\n"
+		"cpuid\n"
+		"mov $0xa, %%eax\n"
+		"cpuid\n"
+		"mov $0xa, %%eax\n"
+		"cpuid\n"
+		"mov $0xa, %%eax\n"
+		"cpuid\n"
+		"label:\n"
+		:
+		:
+		: "eax", "ebx", "ecx", "edx");
+
+	wrmsr(MSR_CORE_PERF_GLOBAL_CTRL, 0);
+
+	stop_event(&brnch_cnt);
+	stop_event(&instr_cnt);
+
+	// Check that the end count - start count is at least the expected
+	// number of instructions and branches.
+	report(instr_cnt.count - instr_start >= EXPECTED_INSTR,
+	       "instruction count");
+	report(brnch_cnt.count - brnch_start >= EXPECTED_BRNCH,
+	       "branch count");
+	// Additionally check that those counters overflowed properly.
+	status = rdmsr(MSR_CORE_PERF_GLOBAL_STATUS);
+	report(status & 1, "instruction counter overflow");
+	report(status & 2, "branch counter overflow");
+
+	report_prefix_pop();
+}
+
 static void check_counters(void)
 {
 	check_gp_counters();
@@ -563,6 +640,9 @@  int main(int ac, char **av)
 
 	check_counters();
 
+	if (ac > 1 && !strcmp(av[1], "emulation"))
+		check_emulated_instr();
+
 	if (rdmsr(MSR_IA32_PERF_CAPABILITIES) & PMU_CAP_FW_WRITES) {
 		gp_counter_base = MSR_IA32_PMC0;
 		report_prefix_push("full-width writes");
diff --git a/x86/unittests.cfg b/x86/unittests.cfg
index 3000e53c790f..2aedb24dc4ff 100644
--- a/x86/unittests.cfg
+++ b/x86/unittests.cfg
@@ -185,6 +185,13 @@  extra_params = -cpu host,migratable=no
 check = /sys/module/kvm/parameters/ignore_msrs=N
 check = /proc/sys/kernel/nmi_watchdog=0
 
+[pmu_emulation]
+file = pmu.flat
+arch = x86_64
+extra_params = -cpu max -append emulation
+check = /sys/module/kvm_intel/parameters/force_emulation_prefix=Y
+check = /proc/sys/kernel/nmi_watchdog=0
+
 [vmware_backdoors]
 file = vmware_backdoors.flat
 extra_params = -machine vmport=on -cpu max