2023-09-27 03:44:22

by Raghavendra Rao Ananta

[permalink] [raw]
Subject: [PATCH v6 00/11] KVM: arm64: PMU: Allow userspace to limit the number of PMCs on vCPU

Hello,

With permission from Reiji Watanabe <[email protected]>, the original
author of the series, I'm posting the v6 with necessary alterations.

The goal of this series is to allow userspace to limit the number
of PMU event counters on the vCPU. We need this to support migration
across systems that implement different numbers of counters.

The number of PMU event counters is indicated in PMCR_EL0.N.
For a vCPU with PMUv3 configured, its value will be the same as
the current PE by default. Userspace can set PMCR_EL0.N for the
vCPU to any value even with the current KVM using KVM_SET_ONE_REG.
However, it is practically unsupported, as KVM resets PMCR_EL0.N
to the host value on vCPU reset and some KVM code uses the host
value to identify (un)implemented event counters on the vCPU.

This series will ensure that the PMCR_EL0.N value is preserved
on vCPU reset and that KVM doesn't use the host value
to identify (un)implemented event counters on the vCPU.
This allows userspace to limit the number of the PMU event
counters on the vCPU.

The series is based on v6.6-rc3.

Patch 1 adds helper functions to set a PMU for the guest. This
helper will make it easier for the following patches to add
modify codes for that process.

Patch 2 makes the default PMU for the guest set on the first
vCPU reset.

Patch 3 fixes reset_pmu_reg() to ensure that (RAZ) bits of
PMCNTEN{SET,CLR}_EL0, PMINTEN{SET,CLR}_EL1, and
PMOVS{SET,CLR}_EL1 corresponding to unimplemented event
counters on the vCPU are reset to zero.

Patch 4 is a minor refactoring to use the default PMU register reset
function for PMUSERENR_EL0 and PMCCFILTR_EL0.

Patch 5 adds a helper to read vCPU's PMCR_EL0.

Patch 6 changes the code to use the guest's PMCR_EL0.N, instead
of the PE's PMCR_EL0.N.

Patch 7 adds support userspace modifying PMCR_EL0.N.

Patch 8-11 adds a selftest to verify reading and writing PMU registers
for implemented or unimplemented PMU event counters on the vCPU.

v6: Thanks, Oliver and Shaoqin for the suggestions
- Split the previously defined kvm_arm_set_vm_pmu() into separate
functions: default arm_pmu and a caller requested arm_pmu.
- Send -EINVAL from kvm_reset_vcpu(), instead of -ENODEV for the
case where KVM fails to set a default arm_pmu, to remain consistent
with the existing behavior.
- Drop the v5 patch-5/12 that removes ARMV8_PMU_PMCR_N_MASK and adds
ARMV8_PMU_PMCR_N. Make corresponding changes to v5 patch-6/12.
- Disregard introducing 'pmcr_n_limit' in kvm->arch as a member to
be accessed later in 'set_pmcr()'. Instead, directly obtain the
value by accessing the saved 'arm_pmu'.
- 'set_pmcr()' ignores the error when userspace tries to set PMCR.N
greater than the hardware limit to keep the existing API behavior.
- 'set_pmcr()' ignores modifications to the register after the VM has
started and returns a success to userspace.
- Introduce [get|set]_pmcr_n() helpers in the selftest to make
modifications to the field easier.
- Define the 'vpmu_vm' globally in the selftest, instead of allocating
it every time a VM is created.
- Use the new printf style __GUEST_ASSERT()s in the selftest.

v5:
https://lore.kernel.org/all/[email protected]/
- Drop the patches (v4 3,4) related to PMU version fixes as it's
now being handled in a separate series [1].
- Switch to config_lock, instead of kvm->lock, while configuring
the guest PMU.
- Instead of continuing after a WARN_ON() for the return value of
kvm_arm_set_vm_pmu() in kvm_arm_pmu_v3_set_pmu(), patch-1 now
returns from the function immediately with the error code.
- Fix WARN_ON() logic in kvm_host_pmu_init() (patch v4 9/14).
- Instead of returning 0, return -ENODEV from the
kvm_arm_set_vm_pmu() stub function.
- Do not define the PMEVN_CASE() and PMEVN_SWITCH() macros in
the selftest code as they are now included in the imported
arm_pmuv3.h header.
- Since the (initial) purpose of the selftest is to test the
accessibility of the counter registers, remove the functional
test at the end of test_access_pmc_regs(). It'll be added
later in a separate series.
- Introduce additional helper functions (destroy_vpmu_vm(),
PMC_ACC_TO_IDX()) in the selftest for ease of maintenance
and debugging.

v4:
https://lore.kernel.org/all/[email protected]/
- Fix the selftest bug in patch 13 (Have test_access_pmc_regs() to
specify pmc index for test_bitmap_pmu_regs() instead of bit-shifted
value (Thank you Raghavendra for the reporting the issue!).

v3:
https://lore.kernel.org/all/[email protected]/
- Remove reset_pmu_reg(), and use reset_val() instead. [Marc]
- Fixed the initial value of PMCR_EL0.N on heterogeneous
PMU systems. [Oliver]
- Fixed PMUVer issues on heterogeneous PMU systems.
- Fixed typos [Shaoqin]

v2:
https://lore.kernel.org/all/[email protected]/
- Added the sys_reg's set_user() handler for the PMCR_EL0 to
disallow userspace to set PMCR_EL0.N for the vCPU to a value
that is greater than the host value (and added a new test
case for this behavior). [Oliver]
- Added to the commit log of the patch 2 that PMUSERENR_EL0 and
PMCCFILTR_EL0 have UNKNOWN reset values.

v1:
https://lore.kernel.org/all/[email protected]/

Thank you.
Raghavendra

[1]:
https://lore.kernel.org/all/[email protected]/

Raghavendra Rao Ananta (1):
tools: Import arm_pmuv3.h

Reiji Watanabe (10):
KVM: arm64: PMU: Introduce helpers to set the guest's PMU
KVM: arm64: PMU: Set the default PMU for the guest on vCPU reset
KVM: arm64: PMU: Clear PM{C,I}NTEN{SET,CLR} and PMOVS{SET,CLR} on vCPU
reset
KVM: arm64: PMU: Don't define the sysreg reset() for
PM{USERENR,CCFILTR}_EL0
KVM: arm64: PMU: Add a helper to read a vCPU's PMCR_EL0
KVM: arm64: PMU: Set PMCR_EL0.N for vCPU based on the associated PMU
KVM: arm64: PMU: Allow userspace to limit PMCR_EL0.N for the guest
KVM: selftests: aarch64: Introduce vpmu_counter_access test
KVM: selftests: aarch64: vPMU register test for implemented counters
KVM: selftests: aarch64: vPMU register test for unimplemented counters

arch/arm64/include/asm/kvm_host.h | 3 +
arch/arm64/kvm/arm.c | 3 +-
arch/arm64/kvm/pmu-emul.c | 91 ++-
arch/arm64/kvm/reset.c | 18 +-
arch/arm64/kvm/sys_regs.c | 102 ++-
include/kvm/arm_pmu.h | 12 +
tools/include/perf/arm_pmuv3.h | 308 +++++++++
tools/testing/selftests/kvm/Makefile | 1 +
.../kvm/aarch64/vpmu_counter_access.c | 590 ++++++++++++++++++
.../selftests/kvm/include/aarch64/processor.h | 1 +
10 files changed, 1064 insertions(+), 65 deletions(-)
create mode 100644 tools/include/perf/arm_pmuv3.h
create mode 100644 tools/testing/selftests/kvm/aarch64/vpmu_counter_access.c


base-commit: 6465e260f48790807eef06b583b38ca9789b6072
--
2.42.0.582.g8ccd20d70d-goog


2023-09-27 03:47:17

by Raghavendra Rao Ananta

[permalink] [raw]
Subject: [PATCH v6 04/11] KVM: arm64: PMU: Don't define the sysreg reset() for PM{USERENR,CCFILTR}_EL0

From: Reiji Watanabe <[email protected]>

The default reset function for PMU registers (defined by PMU_SYS_REG)
now simply clears a specified register. Use the default one for
PMUSERENR_EL0 and PMCCFILTR_EL0, as KVM currently clears those
registers on vCPU reset (NOTE: All non-RES0 fields of those
registers have UNKNOWN reset values, and the same fields of
their AArch32 registers have 0 reset values).

No functional change intended.

Signed-off-by: Reiji Watanabe <[email protected]>
Signed-off-by: Raghavendra Rao Ananta <[email protected]>
---
arch/arm64/kvm/sys_regs.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index ffd123fae30d6..66b9e1de54230 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -2180,7 +2180,7 @@ static const struct sys_reg_desc sys_reg_descs[] = {
* in 32bit mode. Here we choose to reset it as zero for consistency.
*/
{ PMU_SYS_REG(PMUSERENR_EL0), .access = access_pmuserenr,
- .reset = reset_val, .reg = PMUSERENR_EL0, .val = 0 },
+ .reg = PMUSERENR_EL0, },
{ PMU_SYS_REG(PMOVSSET_EL0),
.access = access_pmovs, .reg = PMOVSSET_EL0 },

@@ -2338,7 +2338,7 @@ static const struct sys_reg_desc sys_reg_descs[] = {
* in 32bit mode. Here we choose to reset it as zero for consistency.
*/
{ PMU_SYS_REG(PMCCFILTR_EL0), .access = access_pmu_evtyper,
- .reset = reset_val, .reg = PMCCFILTR_EL0, .val = 0 },
+ .reg = PMCCFILTR_EL0, },

EL2_REG(VPIDR_EL2, access_rw, reset_unknown, 0),
EL2_REG(VMPIDR_EL2, access_rw, reset_unknown, 0),
--
2.42.0.582.g8ccd20d70d-goog

2023-09-27 06:43:06

by Raghavendra Rao Ananta

[permalink] [raw]
Subject: [PATCH v6 06/11] KVM: arm64: PMU: Set PMCR_EL0.N for vCPU based on the associated PMU

From: Reiji Watanabe <[email protected]>

The number of PMU event counters is indicated in PMCR_EL0.N.
For a vCPU with PMUv3 configured, the value is set to the same
value as the current PE on every vCPU reset. Unless the vCPU is
pinned to PEs that has the PMU associated to the guest from the
initial vCPU reset, the value might be different from the PMU's
PMCR_EL0.N on heterogeneous PMU systems.

Fix this by setting the vCPU's PMCR_EL0.N to the PMU's PMCR_EL0.N
value. Track the PMCR_EL0.N per guest, as only one PMU can be set
for the guest (PMCR_EL0.N must be the same for all vCPUs of the
guest), and it is convenient for updating the value.

KVM does not yet support userspace modifying PMCR_EL0.N.
The following patch will add support for that.

Signed-off-by: Reiji Watanabe <[email protected]>
Signed-off-by: Raghavendra Rao Ananta <[email protected]>
---
arch/arm64/include/asm/kvm_host.h | 3 +++
arch/arm64/kvm/pmu-emul.c | 20 +++++++++++++++++++-
arch/arm64/kvm/sys_regs.c | 15 +++++++++------
3 files changed, 31 insertions(+), 7 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index af06ccb7ee343..0fe9cc9dc8368 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -257,6 +257,9 @@ struct kvm_arch {

cpumask_var_t supported_cpus;

+ /* PMCR_EL0.N value for the guest */
+ u8 pmcr_n;
+
/* Hypercall features firmware registers' descriptor */
struct kvm_smccc_features smccc_feat;
struct maple_tree smccc_filter;
diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index d89438956f7d3..121cfd161959a 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -690,6 +690,9 @@ void kvm_host_pmu_init(struct arm_pmu *pmu)
if (!entry)
goto out_unlock;

+ WARN_ON((pmu->num_events <= 0) ||
+ (pmu->num_events > ARMV8_PMU_MAX_COUNTERS));
+
entry->arm_pmu = pmu;
list_add_tail(&entry->entry, &arm_pmus);

@@ -878,6 +881,13 @@ static void kvm_arm_set_pmu(struct kvm *kvm, struct arm_pmu *arm_pmu)
lockdep_assert_held(&kvm->arch.config_lock);

kvm->arch.arm_pmu = arm_pmu;
+
+ /*
+ * Both the num_events and PMCR_EL0.N indicates the number of
+ * PMU event counters, but the former includes the cycle counter
+ * while the latter does not.
+ */
+ kvm->arch.pmcr_n = arm_pmu->num_events - 1;
}

/**
@@ -1088,8 +1098,16 @@ u8 kvm_arm_pmu_get_pmuver_limit(void)
/**
* kvm_vcpu_read_pmcr - Read PMCR_EL0 register for the vCPU
* @vcpu: The vcpu pointer
+ *
+ * The function returns the value of PMCR.N based on the per-VM tracked
+ * value (kvm->arch.pmcr_n). This is to ensure that the register field
+ * remains consistent for the VM, even on heterogeneous systems where
+ * the value may vary when read from different CPUs (during vCPU reset).
*/
u64 kvm_vcpu_read_pmcr(struct kvm_vcpu *vcpu)
{
- return __vcpu_sys_reg(vcpu, PMCR_EL0);
+ u64 pmcr = __vcpu_sys_reg(vcpu, PMCR_EL0) &
+ ~(ARMV8_PMU_PMCR_N_MASK << ARMV8_PMU_PMCR_N_SHIFT);
+
+ return pmcr | ((u64)vcpu->kvm->arch.pmcr_n << ARMV8_PMU_PMCR_N_SHIFT);
}
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 5ae7399f2b822..d1db1f292645e 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -745,12 +745,8 @@ static u64 reset_pmcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
{
u64 pmcr;

- /* No PMU available, PMCR_EL0 may UNDEF... */
- if (!kvm_arm_support_pmu_v3())
- return 0;
-
/* Only preserve PMCR_EL0.N, and reset the rest to 0 */
- pmcr = read_sysreg(pmcr_el0) & (ARMV8_PMU_PMCR_N_MASK << ARMV8_PMU_PMCR_N_SHIFT);
+ pmcr = kvm_vcpu_read_pmcr(vcpu) & (ARMV8_PMU_PMCR_N_MASK << ARMV8_PMU_PMCR_N_SHIFT);
if (!kvm_supports_32bit_el0())
pmcr |= ARMV8_PMU_PMCR_LC;

@@ -1084,6 +1080,13 @@ static bool access_pmuserenr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
return true;
}

+static int get_pmcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
+ u64 *val)
+{
+ *val = kvm_vcpu_read_pmcr(vcpu);
+ return 0;
+}
+
/* Silly macro to expand the DBG{BCR,BVR,WVR,WCR}n_EL1 registers in one go */
#define DBG_BCR_BVR_WCR_WVR_EL1(n) \
{ SYS_DESC(SYS_DBGBVRn_EL1(n)), \
@@ -2148,7 +2151,7 @@ static const struct sys_reg_desc sys_reg_descs[] = {
{ SYS_DESC(SYS_SVCR), undef_access },

{ PMU_SYS_REG(PMCR_EL0), .access = access_pmcr,
- .reset = reset_pmcr, .reg = PMCR_EL0 },
+ .reset = reset_pmcr, .reg = PMCR_EL0, .get_user = get_pmcr },
{ PMU_SYS_REG(PMCNTENSET_EL0),
.access = access_pmcnten, .reg = PMCNTENSET_EL0 },
{ PMU_SYS_REG(PMCNTENCLR_EL0),
--
2.42.0.582.g8ccd20d70d-goog

2023-09-27 09:02:08

by Raghavendra Rao Ananta

[permalink] [raw]
Subject: [PATCH v6 03/11] KVM: arm64: PMU: Clear PM{C,I}NTEN{SET,CLR} and PMOVS{SET,CLR} on vCPU reset

From: Reiji Watanabe <[email protected]>

On vCPU reset, PMCNTEN{SET,CLR}_EL0, PMINTEN{SET,CLR}_EL1, and
PMOVS{SET,CLR}_EL1 for a vCPU are reset by reset_pmu_reg().
This function clears RAZ bits of those registers corresponding
to unimplemented event counters on the vCPU, and sets bits
corresponding to implemented event counters to a predefined
pseudo UNKNOWN value (some bits are set to 1).

The function identifies (un)implemented event counters on the
vCPU based on the PMCR_EL0.N value on the host. Using the host
value for this would be problematic when KVM supports letting
userspace set PMCR_EL0.N to a value different from the host value
(some of the RAZ bits of those registers could end up being set to 1).

Fix this by clearing the registers so that it can ensure
that all the RAZ bits are cleared even when the PMCR_EL0.N value
for the vCPU is different from the host value. Use reset_val() to
do this instead of fixing reset_pmu_reg(), and remove
reset_pmu_reg(), as it is no longer used.

Signed-off-by: Reiji Watanabe <[email protected]>
Signed-off-by: Raghavendra Rao Ananta <[email protected]>
---
arch/arm64/kvm/sys_regs.c | 21 +--------------------
1 file changed, 1 insertion(+), 20 deletions(-)

diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index e92ec810d4494..ffd123fae30d6 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -717,25 +717,6 @@ static unsigned int pmu_visibility(const struct kvm_vcpu *vcpu,
return REG_HIDDEN;
}

-static u64 reset_pmu_reg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
-{
- u64 n, mask = BIT(ARMV8_PMU_CYCLE_IDX);
-
- /* No PMU available, any PMU reg may UNDEF... */
- if (!kvm_arm_support_pmu_v3())
- return 0;
-
- n = read_sysreg(pmcr_el0) >> ARMV8_PMU_PMCR_N_SHIFT;
- n &= ARMV8_PMU_PMCR_N_MASK;
- if (n)
- mask |= GENMASK(n - 1, 0);
-
- reset_unknown(vcpu, r);
- __vcpu_sys_reg(vcpu, r->reg) &= mask;
-
- return __vcpu_sys_reg(vcpu, r->reg);
-}
-
static u64 reset_pmevcntr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
{
reset_unknown(vcpu, r);
@@ -1115,7 +1096,7 @@ static bool access_pmuserenr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
trap_wcr, reset_wcr, 0, 0, get_wcr, set_wcr }

#define PMU_SYS_REG(name) \
- SYS_DESC(SYS_##name), .reset = reset_pmu_reg, \
+ SYS_DESC(SYS_##name), .reset = reset_val, \
.visibility = pmu_visibility

/* Macro to expand the PMEVCNTRn_EL0 register */
--
2.42.0.582.g8ccd20d70d-goog

2023-09-27 10:38:11

by Raghavendra Rao Ananta

[permalink] [raw]
Subject: [PATCH v6 11/11] KVM: selftests: aarch64: vPMU register test for unimplemented counters

From: Reiji Watanabe <[email protected]>

Add a new test case to the vpmu_counter_access test to check
if PMU registers or their bits for unimplemented counters are not
accessible or are RAZ, as expected.

Signed-off-by: Reiji Watanabe <[email protected]>
Signed-off-by: Raghavendra Rao Ananta <[email protected]>
---
.../kvm/aarch64/vpmu_counter_access.c | 95 +++++++++++++++++--
.../selftests/kvm/include/aarch64/processor.h | 1 +
2 files changed, 87 insertions(+), 9 deletions(-)

diff --git a/tools/testing/selftests/kvm/aarch64/vpmu_counter_access.c b/tools/testing/selftests/kvm/aarch64/vpmu_counter_access.c
index e92af3c0db039..788386ac08940 100644
--- a/tools/testing/selftests/kvm/aarch64/vpmu_counter_access.c
+++ b/tools/testing/selftests/kvm/aarch64/vpmu_counter_access.c
@@ -5,8 +5,8 @@
* Copyright (c) 2022 Google LLC.
*
* This test checks if the guest can see the same number of the PMU event
- * counters (PMCR_EL0.N) that userspace sets, and if the guest can access
- * those counters.
+ * counters (PMCR_EL0.N) that userspace sets, if the guest can access
+ * those counters, and if the guest cannot access any other counters.
* This test runs only when KVM_CAP_ARM_PMU_V3 is supported on the host.
*/
#include <kvm_util.h>
@@ -131,9 +131,9 @@ static void write_pmevtypern(int n, unsigned long val)
}

/*
- * The pmc_accessor structure has pointers to PMEVT{CNTR,TYPER}<n>_EL0
+ * The pmc_accessor structure has pointers to PMEV{CNTR,TYPER}<n>_EL0
* accessors that test cases will use. Each of the accessors will
- * either directly reads/writes PMEVT{CNTR,TYPER}<n>_EL0
+ * either directly reads/writes PMEV{CNTR,TYPER}<n>_EL0
* (i.e. {read,write}_pmev{cnt,type}rn()), or reads/writes them through
* PMXEV{CNTR,TYPER}_EL0 (i.e. {read,write}_sel_ev{cnt,type}r()).
*
@@ -291,25 +291,85 @@ static void test_access_pmc_regs(struct pmc_accessor *acc, int pmc_idx)
pmc_idx, PMC_ACC_TO_IDX(acc), read_data, write_data);
}

+#define INVALID_EC (-1ul)
+uint64_t expected_ec = INVALID_EC;
+uint64_t op_end_addr;
+
static void guest_sync_handler(struct ex_regs *regs)
{
uint64_t esr, ec;

esr = read_sysreg(esr_el1);
ec = (esr >> ESR_EC_SHIFT) & ESR_EC_MASK;
- __GUEST_ASSERT(0, "PC: 0x%lx; ESR: 0x%lx; EC: 0x%lx", regs->pc, esr, ec);
+
+ __GUEST_ASSERT(op_end_addr && (expected_ec == ec),
+ "PC: 0x%lx; ESR: 0x%lx; EC: 0x%lx; EC expected: 0x%lx",
+ regs->pc, esr, ec, expected_ec);
+
+ /* Will go back to op_end_addr after the handler exits */
+ regs->pc = op_end_addr;
+
+ /*
+ * Clear op_end_addr, and setting expected_ec to INVALID_EC
+ * as a sign that an exception has occurred.
+ */
+ op_end_addr = 0;
+ expected_ec = INVALID_EC;
+}
+
+/*
+ * Run the given operation that should trigger an exception with the
+ * given exception class. The exception handler (guest_sync_handler)
+ * will reset op_end_addr to 0, and expected_ec to INVALID_EC, and
+ * will come back to the instruction at the @done_label.
+ * The @done_label must be a unique label in this test program.
+ */
+#define TEST_EXCEPTION(ec, ops, done_label) \
+{ \
+ extern int done_label; \
+ \
+ WRITE_ONCE(op_end_addr, (uint64_t)&done_label); \
+ GUEST_ASSERT(ec != INVALID_EC); \
+ WRITE_ONCE(expected_ec, ec); \
+ dsb(ish); \
+ ops; \
+ asm volatile(#done_label":"); \
+ GUEST_ASSERT(!op_end_addr); \
+ GUEST_ASSERT(expected_ec == INVALID_EC); \
+}
+
+/*
+ * Tests for reading/writing registers for the unimplemented event counter
+ * specified by @pmc_idx (>= PMCR_EL0.N).
+ */
+static void test_access_invalid_pmc_regs(struct pmc_accessor *acc, int pmc_idx)
+{
+ /*
+ * Reading/writing the event count/type registers should cause
+ * an UNDEFINED exception.
+ */
+ TEST_EXCEPTION(ESR_EC_UNKNOWN, acc->read_cntr(pmc_idx), inv_rd_cntr);
+ TEST_EXCEPTION(ESR_EC_UNKNOWN, acc->write_cntr(pmc_idx, 0), inv_wr_cntr);
+ TEST_EXCEPTION(ESR_EC_UNKNOWN, acc->read_typer(pmc_idx), inv_rd_typer);
+ TEST_EXCEPTION(ESR_EC_UNKNOWN, acc->write_typer(pmc_idx, 0), inv_wr_typer);
+ /*
+ * The bit corresponding to the (unimplemented) counter in
+ * {PMCNTEN,PMOVS}{SET,CLR}_EL1 registers should be RAZ.
+ */
+ test_bitmap_pmu_regs(pmc_idx, 1);
+ test_bitmap_pmu_regs(pmc_idx, 0);
}

/*
* The guest is configured with PMUv3 with @expected_pmcr_n number of
* event counters.
* Check if @expected_pmcr_n is consistent with PMCR_EL0.N, and
- * if reading/writing PMU registers for implemented counters can work
- * as expected.
+ * if reading/writing PMU registers for implemented or unimplemented
+ * counters can work as expected.
*/
static void guest_code(uint64_t expected_pmcr_n)
{
- uint64_t pmcr, pmcr_n;
+ uint64_t pmcr, pmcr_n, unimp_mask;
int i, pmc;

__GUEST_ASSERT(expected_pmcr_n <= ARMV8_PMU_MAX_GENERAL_COUNTERS,
@@ -324,15 +384,32 @@ static void guest_code(uint64_t expected_pmcr_n)
"Expected PMCR.N: 0x%lx, PMCR.N: 0x%lx",
pmcr_n, expected_pmcr_n);

+ /*
+ * Make sure that (RAZ) bits corresponding to unimplemented event
+ * counters in {PMCNTEN,PMOVS}{SET,CLR}_EL1 registers are reset to zero.
+ * (NOTE: bits for implemented event counters are reset to UNKNOWN)
+ */
+ unimp_mask = GENMASK_ULL(ARMV8_PMU_MAX_GENERAL_COUNTERS - 1, pmcr_n);
+ check_bitmap_pmu_regs(unimp_mask, false);
+
/*
* Tests for reading/writing PMU registers for implemented counters.
- * Use each combination of PMEVT{CNTR,TYPER}<n>_EL0 accessor functions.
+ * Use each combination of PMEV{CNTR,TYPER}<n>_EL0 accessor functions.
*/
for (i = 0; i < ARRAY_SIZE(pmc_accessors); i++) {
for (pmc = 0; pmc < pmcr_n; pmc++)
test_access_pmc_regs(&pmc_accessors[i], pmc);
}

+ /*
+ * Tests for reading/writing PMU registers for unimplemented counters.
+ * Use each combination of PMEV{CNTR,TYPER}<n>_EL0 accessor functions.
+ */
+ for (i = 0; i < ARRAY_SIZE(pmc_accessors); i++) {
+ for (pmc = pmcr_n; pmc < ARMV8_PMU_MAX_GENERAL_COUNTERS; pmc++)
+ test_access_invalid_pmc_regs(&pmc_accessors[i], pmc);
+ }
+
GUEST_DONE();
}

diff --git a/tools/testing/selftests/kvm/include/aarch64/processor.h b/tools/testing/selftests/kvm/include/aarch64/processor.h
index cb537253a6b9c..c42d683102c7a 100644
--- a/tools/testing/selftests/kvm/include/aarch64/processor.h
+++ b/tools/testing/selftests/kvm/include/aarch64/processor.h
@@ -104,6 +104,7 @@ enum {
#define ESR_EC_SHIFT 26
#define ESR_EC_MASK (ESR_EC_NUM - 1)

+#define ESR_EC_UNKNOWN 0x0
#define ESR_EC_SVC64 0x15
#define ESR_EC_IABT 0x21
#define ESR_EC_DABT 0x25
--
2.42.0.582.g8ccd20d70d-goog