#include <kvm_util.h>
#include <processor.h>
#include <test_util.h>
#include <vgic.h>
#include <perf/arm_pmuv3.h>
#include <linux/bitfield.h>
#define ARMV8_PMU_MAX_GENERAL_COUNTERS (ARMV8_PMU_MAX_COUNTERS - 1)
#define ARMV8_PMU_CYCLE_IDX 31
struct vpmu_vm {
struct kvm_vm *vm;
struct kvm_vcpu *vcpu;
};
static struct vpmu_vm vpmu_vm;
struct pmreg_sets {
uint64_t set_reg_id;
uint64_t clr_reg_id;
};
#define PMREG_SET(set, clr) {.set_reg_id = set, .clr_reg_id = clr}
static uint64_t get_pmcr_n(uint64_t pmcr)
{
return FIELD_GET(ARMV8_PMU_PMCR_N, pmcr);
}
static uint64_t get_counters_mask(uint64_t n)
{
uint64_t mask = BIT(ARMV8_PMU_CYCLE_IDX);
if (n)
mask |= GENMASK(n - 1, 0);
return mask;
}
static inline unsigned long read_sel_evcntr(int sel)
{
write_sysreg(sel, pmselr_el0);
isb();
return read_sysreg(pmxevcntr_el0);
}
static inline void write_sel_evcntr(int sel, unsigned long val)
{
write_sysreg(sel, pmselr_el0);
isb();
write_sysreg(val, pmxevcntr_el0);
isb();
}
static inline unsigned long read_sel_evtyper(int sel)
{
write_sysreg(sel, pmselr_el0);
isb();
return read_sysreg(pmxevtyper_el0);
}
static inline void write_sel_evtyper(int sel, unsigned long val)
{
write_sysreg(sel, pmselr_el0);
isb();
write_sysreg(val, pmxevtyper_el0);
isb();
}
static void pmu_disable_reset(void)
{
uint64_t pmcr = read_sysreg(pmcr_el0);
pmcr &= ~ARMV8_PMU_PMCR_E;
write_sysreg(pmcr | ARMV8_PMU_PMCR_P, pmcr_el0);
isb();
}
#define RETURN_READ_PMEVCNTRN(n) \
return read_sysreg(pmevcntr##n##_el0)
static unsigned long read_pmevcntrn(int n)
{
PMEVN_SWITCH(n, RETURN_READ_PMEVCNTRN);
return 0;
}
#define WRITE_PMEVCNTRN(n) \
write_sysreg(val, pmevcntr##n##_el0)
static void write_pmevcntrn(int n, unsigned long val)
{
PMEVN_SWITCH(n, WRITE_PMEVCNTRN);
isb();
}
#define READ_PMEVTYPERN(n) \
return read_sysreg(pmevtyper##n##_el0)
static unsigned long read_pmevtypern(int n)
{
PMEVN_SWITCH(n, READ_PMEVTYPERN);
return 0;
}
#define WRITE_PMEVTYPERN(n) \
write_sysreg(val, pmevtyper##n##_el0)
static void write_pmevtypern(int n, unsigned long val)
{
PMEVN_SWITCH(n, WRITE_PMEVTYPERN);
isb();
}
struct pmc_accessor {
unsigned long (*read_cntr)(int idx);
void (*write_cntr)(int idx, unsigned long val);
unsigned long (*read_typer)(int idx);
void (*write_typer)(int idx, unsigned long val);
};
struct pmc_accessor pmc_accessors[] = {
{ read_pmevcntrn, write_pmevcntrn, read_pmevtypern, write_pmevtypern },
{ read_sel_evcntr, write_sel_evcntr, read_sel_evtyper, write_sel_evtyper },
{ read_pmevcntrn, write_sel_evcntr, read_pmevtypern, write_sel_evtyper },
{ read_sel_evcntr, write_pmevcntrn, read_sel_evtyper, write_pmevtypern },
};
#define PMC_ACC_TO_IDX(acc) (acc - &pmc_accessors[0])
#define GUEST_ASSERT_BITMAP_REG(regname, mask, set_expected) \
{ \
uint64_t _tval = read_sysreg(regname); \
\
if (set_expected) \
__GUEST_ASSERT((_tval & mask), \
"tval: 0x%lx; mask: 0x%lx; set_expected: %u", \
_tval, mask, set_expected); \
else \
__GUEST_ASSERT(!(_tval & mask), \
"tval: 0x%lx; mask: 0x%lx; set_expected: %u", \
_tval, mask, set_expected); \
}
static void check_bitmap_pmu_regs(uint64_t mask, bool set_expected)
{
GUEST_ASSERT_BITMAP_REG(pmcntenset_el0, mask, set_expected);
GUEST_ASSERT_BITMAP_REG(pmcntenclr_el0, mask, set_expected);
GUEST_ASSERT_BITMAP_REG(pmintenset_el1, mask, set_expected);
GUEST_ASSERT_BITMAP_REG(pmintenclr_el1, mask, set_expected);
GUEST_ASSERT_BITMAP_REG(pmovsset_el0, mask, set_expected);
GUEST_ASSERT_BITMAP_REG(pmovsclr_el0, mask, set_expected);
}
static void test_bitmap_pmu_regs(int pmc_idx, bool set_op)
{
uint64_t pmcr_n, test_bit = BIT(pmc_idx);
bool set_expected = false;
if (set_op) {
write_sysreg(test_bit, pmcntenset_el0);
write_sysreg(test_bit, pmintenset_el1);
write_sysreg(test_bit, pmovsset_el0);
pmcr_n = get_pmcr_n(read_sysreg(pmcr_el0));
set_expected = (pmc_idx < pmcr_n) ? true : false;
} else {
write_sysreg(test_bit, pmcntenclr_el0);
write_sysreg(test_bit, pmintenclr_el1);
write_sysreg(test_bit, pmovsclr_el0);
}
check_bitmap_pmu_regs(test_bit, set_expected);
}
static void test_access_pmc_regs(struct pmc_accessor *acc, int pmc_idx)
{
uint64_t write_data, read_data;
pmu_disable_reset();
test_bitmap_pmu_regs(pmc_idx, false);
test_bitmap_pmu_regs(pmc_idx, true);
test_bitmap_pmu_regs(pmc_idx, false);
write_data = (ARMV8_PMU_EXCLUDE_EL1 | ARMV8_PMUV3_PERFCTR_INST_RETIRED);
acc->write_typer(pmc_idx, write_data);
read_data = acc->read_typer(pmc_idx);
__GUEST_ASSERT(read_data == write_data,
"pmc_idx: 0x%x; acc_idx: 0x%lx; read_data: 0x%lx; write_data: 0x%lx",
pmc_idx, PMC_ACC_TO_IDX(acc), read_data, write_data);
read_data = acc->read_cntr(pmc_idx);
__GUEST_ASSERT(read_data == 0,
"pmc_idx: 0x%x; acc_idx: 0x%lx; read_data: 0x%lx",
pmc_idx, PMC_ACC_TO_IDX(acc), read_data);
write_data = read_data + pmc_idx + 0x12345;
acc->write_cntr(pmc_idx, write_data);
read_data = acc->read_cntr(pmc_idx);
__GUEST_ASSERT(read_data == write_data,
"pmc_idx: 0x%x; acc_idx: 0x%lx; read_data: 0x%lx; write_data: 0x%lx",
pmc_idx, PMC_ACC_TO_IDX(acc), read_data, write_data);
}
#define INVALID_EC (-1ul)
uint64_t expected_ec = INVALID_EC;
static void guest_sync_handler(struct ex_regs *regs)
{
uint64_t esr, ec;
esr = read_sysreg(esr_el1);
ec = ESR_ELx_EC(esr);
__GUEST_ASSERT(expected_ec == ec,
"PC: 0x%lx; ESR: 0x%lx; EC: 0x%lx; EC expected: 0x%lx",
regs->pc, esr, ec, expected_ec);
regs->pc += 4;
expected_ec = INVALID_EC;
}
#define TEST_EXCEPTION(ec, ops) \
({ \
GUEST_ASSERT(ec != INVALID_EC); \
WRITE_ONCE(expected_ec, ec); \
dsb(ish); \
ops; \
GUEST_ASSERT(expected_ec == INVALID_EC); \
})
static void test_access_invalid_pmc_regs(struct pmc_accessor *acc, int pmc_idx)
{
TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->read_cntr(pmc_idx));
TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->write_cntr(pmc_idx, 0));
TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->read_typer(pmc_idx));
TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->write_typer(pmc_idx, 0));
test_bitmap_pmu_regs(pmc_idx, 1);
test_bitmap_pmu_regs(pmc_idx, 0);
}
static void guest_code(uint64_t expected_pmcr_n)
{
uint64_t pmcr, pmcr_n, unimp_mask;
int i, pmc;
__GUEST_ASSERT(expected_pmcr_n <= ARMV8_PMU_MAX_GENERAL_COUNTERS,
"Expected PMCR.N: 0x%lx; ARMv8 general counters: 0x%x",
expected_pmcr_n, ARMV8_PMU_MAX_GENERAL_COUNTERS);
pmcr = read_sysreg(pmcr_el0);
pmcr_n = get_pmcr_n(pmcr);
__GUEST_ASSERT(pmcr_n == expected_pmcr_n,
"Expected PMCR.N: 0x%lx, PMCR.N: 0x%lx",
expected_pmcr_n, pmcr_n);
unimp_mask = GENMASK_ULL(ARMV8_PMU_MAX_GENERAL_COUNTERS - 1, pmcr_n);
check_bitmap_pmu_regs(unimp_mask, false);
for (i = 0; i < ARRAY_SIZE(pmc_accessors); i++) {
for (pmc = 0; pmc < pmcr_n; pmc++)
test_access_pmc_regs(&pmc_accessors[i], pmc);
}
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();
}
static void create_vpmu_vm(void *guest_code)
{
struct kvm_vcpu_init init;
uint8_t pmuver, ec;
uint64_t dfr0, irq = 23;
struct kvm_device_attr irq_attr = {
.group = KVM_ARM_VCPU_PMU_V3_CTRL,
.attr = KVM_ARM_VCPU_PMU_V3_IRQ,
.addr = (uint64_t)&irq,
};
memset(&vpmu_vm, 0, sizeof(vpmu_vm));
vpmu_vm.vm = vm_create(1);
vm_init_descriptor_tables(vpmu_vm.vm);
for (ec = 0; ec < ESR_ELx_EC_MAX + 1; ec++) {
vm_install_sync_handler(vpmu_vm.vm, VECTOR_SYNC_CURRENT, ec,
guest_sync_handler);
}
kvm_get_default_vcpu_target(vpmu_vm.vm, &init);
init.features[0] |= (1 << KVM_ARM_VCPU_PMU_V3);
vpmu_vm.vcpu = aarch64_vcpu_add(vpmu_vm.vm, 0, &init, guest_code);
vcpu_init_descriptor_tables(vpmu_vm.vcpu);
kvm_arch_vm_finalize_vcpus(vpmu_vm.vm);
dfr0 = vcpu_get_reg(vpmu_vm.vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64DFR0_EL1));
pmuver = FIELD_GET(ID_AA64DFR0_EL1_PMUVer, dfr0);
TEST_ASSERT(pmuver != ID_AA64DFR0_EL1_PMUVer_IMP_DEF &&
pmuver >= ID_AA64DFR0_EL1_PMUVer_IMP,
"Unexpected PMUVER (0x%x) on the vCPU with PMUv3", pmuver);
vcpu_ioctl(vpmu_vm.vcpu, KVM_SET_DEVICE_ATTR, &irq_attr);
}
static void destroy_vpmu_vm(void)
{
kvm_vm_free(vpmu_vm.vm);
}
static void run_vcpu(struct kvm_vcpu *vcpu, uint64_t pmcr_n)
{
struct ucall uc;
vcpu_args_set(vcpu, 1, pmcr_n);
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
case UCALL_DONE:
break;
default:
TEST_FAIL("Unknown ucall %lu", uc.cmd);
break;
}
}
static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool expect_fail)
{
struct kvm_vcpu *vcpu;
unsigned int prev;
int ret;
create_vpmu_vm(guest_code);
vcpu = vpmu_vm.vcpu;
prev = get_pmcr_n(vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_PMCR_EL0)));
ret = __vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS, &nr_counters);
if (expect_fail)
TEST_ASSERT(ret && errno == EINVAL,
"Setting more PMU counters (%u) than available (%u) unexpectedly succeeded",
nr_counters, prev);
else
TEST_ASSERT(!ret, KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret));
vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, KVM_ARM_VCPU_PMU_V3_INIT, NULL);
}
static void run_access_test(uint64_t pmcr_n)
{
uint64_t sp;
struct kvm_vcpu *vcpu;
struct kvm_vcpu_init init;
pr_debug("Test with pmcr_n %lu\n", pmcr_n);
test_create_vpmu_vm_with_nr_counters(pmcr_n, false);
vcpu = vpmu_vm.vcpu;
sp = vcpu_get_reg(vcpu, ctxt_reg_alias(vcpu, SYS_SP_EL1));
run_vcpu(vcpu, pmcr_n);
kvm_get_default_vcpu_target(vpmu_vm.vm, &init);
init.features[0] |= (1 << KVM_ARM_VCPU_PMU_V3);
aarch64_vcpu_setup(vcpu, &init);
vcpu_init_descriptor_tables(vcpu);
vcpu_set_reg(vcpu, ctxt_reg_alias(vcpu, SYS_SP_EL1), sp);
vcpu_set_reg(vcpu, ARM64_CORE_REG(regs.pc), (uint64_t)guest_code);
run_vcpu(vcpu, pmcr_n);
destroy_vpmu_vm();
}
static struct pmreg_sets validity_check_reg_sets[] = {
PMREG_SET(SYS_PMCNTENSET_EL0, SYS_PMCNTENCLR_EL0),
PMREG_SET(SYS_PMINTENSET_EL1, SYS_PMINTENCLR_EL1),
PMREG_SET(SYS_PMOVSSET_EL0, SYS_PMOVSCLR_EL0),
};
static void run_pmregs_validity_test(uint64_t pmcr_n)
{
int i;
struct kvm_vcpu *vcpu;
uint64_t set_reg_id, clr_reg_id, reg_val;
uint64_t valid_counters_mask, max_counters_mask;
test_create_vpmu_vm_with_nr_counters(pmcr_n, false);
vcpu = vpmu_vm.vcpu;
valid_counters_mask = get_counters_mask(pmcr_n);
max_counters_mask = get_counters_mask(ARMV8_PMU_MAX_COUNTERS);
for (i = 0; i < ARRAY_SIZE(validity_check_reg_sets); i++) {
set_reg_id = validity_check_reg_sets[i].set_reg_id;
clr_reg_id = validity_check_reg_sets[i].clr_reg_id;
reg_val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(set_reg_id));
TEST_ASSERT((reg_val & (~valid_counters_mask)) == 0,
"Initial read of set_reg: 0x%llx has unimplemented counters enabled: 0x%lx",
KVM_ARM64_SYS_REG(set_reg_id), reg_val);
reg_val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(clr_reg_id));
TEST_ASSERT((reg_val & (~valid_counters_mask)) == 0,
"Initial read of clr_reg: 0x%llx has unimplemented counters enabled: 0x%lx",
KVM_ARM64_SYS_REG(clr_reg_id), reg_val);
vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(set_reg_id), max_counters_mask);
reg_val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(set_reg_id));
TEST_ASSERT((reg_val & (~valid_counters_mask)) == 0,
"Read of set_reg: 0x%llx has unimplemented counters enabled: 0x%lx",
KVM_ARM64_SYS_REG(set_reg_id), reg_val);
reg_val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(clr_reg_id));
TEST_ASSERT((reg_val & (~valid_counters_mask)) == 0,
"Read of clr_reg: 0x%llx has unimplemented counters enabled: 0x%lx",
KVM_ARM64_SYS_REG(clr_reg_id), reg_val);
}
destroy_vpmu_vm();
}
static void run_error_test(uint64_t pmcr_n)
{
pr_debug("Error test with pmcr_n %lu (larger than the host)\n", pmcr_n);
test_create_vpmu_vm_with_nr_counters(pmcr_n, true);
destroy_vpmu_vm();
}
static uint64_t get_pmcr_n_limit(void)
{
uint64_t pmcr;
create_vpmu_vm(guest_code);
pmcr = vcpu_get_reg(vpmu_vm.vcpu, KVM_ARM64_SYS_REG(SYS_PMCR_EL0));
destroy_vpmu_vm();
return get_pmcr_n(pmcr);
}
static bool kvm_supports_nr_counters_attr(void)
{
bool supported;
create_vpmu_vm(NULL);
supported = !__vcpu_has_device_attr(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS);
destroy_vpmu_vm();
return supported;
}
int main(void)
{
uint64_t i, pmcr_n;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3));
TEST_REQUIRE(kvm_supports_vgic_v3());
TEST_REQUIRE(kvm_supports_nr_counters_attr());
pmcr_n = get_pmcr_n_limit();
for (i = 0; i <= pmcr_n; i++) {
run_access_test(i);
run_pmregs_validity_test(i);
}
for (i = pmcr_n + 1; i < ARMV8_PMU_MAX_COUNTERS; i++)
run_error_test(i);
return 0;
}