[kvm-unit-tests PATCH] x86: pmu: Test full-width counter writes support

From: Like Xu
Date: Fri May 29 2020 - 03:44:37 EST


When the full-width writes capability is set, use the alternative MSR
range to write larger sign counter values (up to GP counter width).

Signed-off-by: Like Xu <like.xu@xxxxxxxxxxxxxxx>
---
lib/x86/msr.h | 1 +
x86/pmu.c | 125 ++++++++++++++++++++++++++++++++++++++++----------
2 files changed, 102 insertions(+), 24 deletions(-)

diff --git a/lib/x86/msr.h b/lib/x86/msr.h
index 8dca964..6ef5502 100644
--- a/lib/x86/msr.h
+++ b/lib/x86/msr.h
@@ -35,6 +35,7 @@
#define MSR_IA32_SPEC_CTRL 0x00000048
#define MSR_IA32_PRED_CMD 0x00000049

+#define MSR_IA32_PMC0 0x000004c1
#define MSR_IA32_PERFCTR0 0x000000c1
#define MSR_IA32_PERFCTR1 0x000000c2
#define MSR_FSB_FREQ 0x000000cd
diff --git a/x86/pmu.c b/x86/pmu.c
index f45621a..fb9bf0a 100644
--- a/x86/pmu.c
+++ b/x86/pmu.c
@@ -91,6 +91,9 @@ struct pmu_event {
{"fixed 3", MSR_CORE_PERF_FIXED_CTR0 + 2, 0.1*N, 30*N}
};

+#define PMU_CAP_FW_WRITES (1ULL << 13)
+static u64 gp_counter_base = MSR_IA32_PERFCTR0;
+
static int num_counters;

char *buf;
@@ -125,12 +128,13 @@ static bool check_irq(void)

static bool is_gp(pmu_counter_t *evt)
{
- return evt->ctr < MSR_CORE_PERF_FIXED_CTR0;
+ return evt->ctr < MSR_CORE_PERF_FIXED_CTR0 ||
+ evt->ctr >= MSR_IA32_PMC0;
}

static int event_to_global_idx(pmu_counter_t *cnt)
{
- return cnt->ctr - (is_gp(cnt) ? MSR_IA32_PERFCTR0 :
+ return cnt->ctr - (is_gp(cnt) ? gp_counter_base :
(MSR_CORE_PERF_FIXED_CTR0 - FIXED_CNT_INDEX));
}

@@ -226,7 +230,7 @@ static bool verify_counter(pmu_counter_t *cnt)
static void check_gp_counter(struct pmu_event *evt)
{
pmu_counter_t cnt = {
- .ctr = MSR_IA32_PERFCTR0,
+ .ctr = gp_counter_base,
.config = EVNTSEL_OS | EVNTSEL_USR | evt->unit_sel,
};
int i;
@@ -276,7 +280,7 @@ static void check_counters_many(void)
continue;

cnt[n].count = 0;
- cnt[n].ctr = MSR_IA32_PERFCTR0 + n;
+ cnt[n].ctr = gp_counter_base + n;
cnt[n].config = EVNTSEL_OS | EVNTSEL_USR |
gp_events[i % ARRAY_SIZE(gp_events)].unit_sel;
n++;
@@ -302,7 +306,7 @@ static void check_counter_overflow(void)
uint64_t count;
int i;
pmu_counter_t cnt = {
- .ctr = MSR_IA32_PERFCTR0,
+ .ctr = gp_counter_base,
.config = EVNTSEL_OS | EVNTSEL_USR | gp_events[1].unit_sel /* instructions */,
.count = 0,
};
@@ -319,6 +323,8 @@ static void check_counter_overflow(void)
int idx;

cnt.count = 1 - count;
+ if (gp_counter_base == MSR_IA32_PMC0)
+ cnt.count &= (1ul << eax.split.bit_width) - 1;

if (i == num_counters) {
cnt.ctr = fixed_events[0].unit_sel;
@@ -346,7 +352,7 @@ static void check_counter_overflow(void)
static void check_gp_counter_cmask(void)
{
pmu_counter_t cnt = {
- .ctr = MSR_IA32_PERFCTR0,
+ .ctr = gp_counter_base,
.config = EVNTSEL_OS | EVNTSEL_USR | gp_events[1].unit_sel /* instructions */,
.count = 0,
};
@@ -369,7 +375,7 @@ static void do_rdpmc_fast(void *ptr)

static void check_rdpmc(void)
{
- uint64_t val = 0x1f3456789ull;
+ uint64_t val = 0xff0123456789ull;
bool exc;
int i;

@@ -378,20 +384,23 @@ static void check_rdpmc(void)
for (i = 0; i < num_counters; i++) {
uint64_t x;
pmu_counter_t cnt = {
- .ctr = MSR_IA32_PERFCTR0 + i,
+ .ctr = gp_counter_base + i,
.idx = i
};

- /*
- * Only the low 32 bits are writable, and the value is
- * sign-extended.
- */
- x = (uint64_t)(int64_t)(int32_t)val;
+ /*
+ * Without full-width writes, only the low 32 bits are writable,
+ * and the value is sign-extended.
+ */
+ if (gp_counter_base == MSR_IA32_PERFCTR0)
+ x = (uint64_t)(int64_t)(int32_t)val;
+ else
+ x = (uint64_t)(int64_t)val;

/* Mask according to the number of supported bits */
x &= (1ull << eax.split.bit_width) - 1;

- wrmsr(MSR_IA32_PERFCTR0 + i, val);
+ wrmsr(gp_counter_base + i, val);
report(rdpmc(i) == x, "cntr-%d", i);

exc = test_for_exception(GP_VECTOR, do_rdpmc_fast, &cnt);
@@ -423,8 +432,9 @@ static void check_rdpmc(void)
static void check_running_counter_wrmsr(void)
{
uint64_t status;
+ uint64_t count;
pmu_counter_t evt = {
- .ctr = MSR_IA32_PERFCTR0,
+ .ctr = gp_counter_base,
.config = EVNTSEL_OS | EVNTSEL_USR | gp_events[1].unit_sel,
.count = 0,
};
@@ -433,7 +443,7 @@ static void check_running_counter_wrmsr(void)

start_event(&evt);
loop();
- wrmsr(MSR_IA32_PERFCTR0, 0);
+ wrmsr(gp_counter_base, 0);
stop_event(&evt);
report(evt.count < gp_events[1].min, "cntr");

@@ -443,7 +453,13 @@ static void check_running_counter_wrmsr(void)

evt.count = 0;
start_event(&evt);
- wrmsr(MSR_IA32_PERFCTR0, -1);
+
+ count = -1;
+ if (gp_counter_base == MSR_IA32_PMC0)
+ count &= (1ul << eax.split.bit_width) - 1;
+
+ wrmsr(gp_counter_base, count);
+
loop();
stop_event(&evt);
status = rdmsr(MSR_CORE_PERF_GLOBAL_STATUS);
@@ -452,6 +468,66 @@ static void check_running_counter_wrmsr(void)
report_prefix_pop();
}

+static void check_counters(void)
+{
+ check_gp_counters();
+ check_fixed_counters();
+ check_rdpmc();
+ check_counters_many();
+ check_counter_overflow();
+ check_gp_counter_cmask();
+ check_running_counter_wrmsr();
+}
+
+static void do_unsupported_width_counter_write(void *index)
+{
+ wrmsr(MSR_IA32_PMC0 + *((int *) index), 0xffffff0123456789ull);
+}
+
+static void check_gp_counters_write_width(void)
+{
+ u64 val_64 = 0xffffff0123456789ull;
+ u64 val_32 = val_64 & ((1ul << 32) - 1);
+ u64 val_max_width = val_64 & ((1ul << eax.split.bit_width) - 1);
+ int i;
+
+ /*
+ * MSR_IA32_PERFCTRn supports 64-bit writes,
+ * but only the lowest 32 bits are valid.
+ */
+ for (i = 0; i < num_counters; i++) {
+ wrmsr(MSR_IA32_PERFCTR0 + i, val_32);
+ assert(rdmsr(MSR_IA32_PERFCTR0 + i) == val_32);
+ assert(rdmsr(MSR_IA32_PMC0 + i) == val_32);
+
+ wrmsr(MSR_IA32_PERFCTR0 + i, val_max_width);
+ assert(rdmsr(MSR_IA32_PERFCTR0 + i) == val_32);
+ assert(rdmsr(MSR_IA32_PMC0 + i) == val_32);
+
+ wrmsr(MSR_IA32_PERFCTR0 + i, val_64);
+ assert(rdmsr(MSR_IA32_PERFCTR0 + i) == val_32);
+ assert(rdmsr(MSR_IA32_PMC0 + i) == val_32);
+ }
+
+ /*
+ * MSR_IA32_PMCn supports writing values ÃââÃââup to GP counter width,
+ * and only the lowest bits of GP counter width are valid.
+ */
+ for (i = 0; i < num_counters; i++) {
+ wrmsr(MSR_IA32_PMC0 + i, val_32);
+ assert(rdmsr(MSR_IA32_PMC0 + i) == val_32);
+ assert(rdmsr(MSR_IA32_PERFCTR0 + i) == val_32);
+
+ wrmsr(MSR_IA32_PMC0 + i, val_max_width);
+ assert(rdmsr(MSR_IA32_PMC0 + i) == val_max_width);
+ assert(rdmsr(MSR_IA32_PERFCTR0 + i) == val_max_width);
+
+ report(test_for_exception(GP_VECTOR,
+ do_unsupported_width_counter_write, &i),
+ "writing unsupported width to MSR_IA32_PMC%d raises #GP", i);
+ }
+}
+
int main(int ac, char **av)
{
struct cpuid id = cpuid(10);
@@ -480,13 +556,14 @@ int main(int ac, char **av)

apic_write(APIC_LVTPC, PC_VECTOR);

- check_gp_counters();
- check_fixed_counters();
- check_rdpmc();
- check_counters_many();
- check_counter_overflow();
- check_gp_counter_cmask();
- check_running_counter_wrmsr();
+ check_counters();
+
+ if (rdmsr(MSR_IA32_PERF_CAPABILITIES) & PMU_CAP_FW_WRITES) {
+ gp_counter_base = MSR_IA32_PMC0;
+ report_prefix_push("full-width writes");
+ check_counters();
+ check_gp_counters_write_width();
+ }

return report_summary();
}
--
2.21.3