xref: /linux/tools/testing/selftests/kvm/loongarch/pmu_test.c (revision 01f492e1817e858d1712f2489d0afbaa552f417b)
111c84019SSong Gao // SPDX-License-Identifier: GPL-2.0
211c84019SSong Gao /*
311c84019SSong Gao  * LoongArch KVM PMU event counting test
411c84019SSong Gao  *
511c84019SSong Gao  * Test hardware event counting: CPU_CYCLES, INSTR_RETIRED,
611c84019SSong Gao  * BRANCH_INSTRUCTIONS and BRANCH_MISSES.
711c84019SSong Gao  */
811c84019SSong Gao #include <linux/bitops.h>
911c84019SSong Gao #include "kvm_util.h"
1011c84019SSong Gao #include "pmu.h"
1111c84019SSong Gao #include "loongarch/processor.h"
1211c84019SSong Gao 
13*e47b8e1dSSong Gao static int pmu_irq_count;
14*e47b8e1dSSong Gao 
1511c84019SSong Gao /* Check PMU support */
1611c84019SSong Gao static bool has_pmu_support(void)
1711c84019SSong Gao {
1811c84019SSong Gao 	uint32_t cfg6;
1911c84019SSong Gao 
2011c84019SSong Gao 	/* Read CPUCFG6 to check PMU */
2111c84019SSong Gao 	cfg6 = read_cpucfg(LOONGARCH_CPUCFG6);
2211c84019SSong Gao 
2311c84019SSong Gao 	/* Check PMU present bit */
2411c84019SSong Gao 	if (!(cfg6 & CPUCFG6_PMP))
2511c84019SSong Gao 		return false;
2611c84019SSong Gao 
2711c84019SSong Gao 	/* Check that at least one counter exists */
2811c84019SSong Gao 	if (((cfg6 & CPUCFG6_PMNUM) >> CPUCFG6_PMNUM_SHIFT) == 0)
2911c84019SSong Gao 		return false;
3011c84019SSong Gao 
3111c84019SSong Gao 	return true;
3211c84019SSong Gao }
3311c84019SSong Gao 
3411c84019SSong Gao /* Dump PMU capabilities */
3511c84019SSong Gao static void dump_pmu_caps(void)
3611c84019SSong Gao {
3711c84019SSong Gao 	uint32_t cfg6;
3811c84019SSong Gao 	int nr_counters, counter_bits;
3911c84019SSong Gao 
4011c84019SSong Gao 	cfg6 = read_cpucfg(LOONGARCH_CPUCFG6);
4111c84019SSong Gao 	nr_counters = ((cfg6 & CPUCFG6_PMNUM) >> CPUCFG6_PMNUM_SHIFT) + 1;
4211c84019SSong Gao 	counter_bits = ((cfg6 & CPUCFG6_PMBITS) >> CPUCFG6_PMBITS_SHIFT) + 1;
4311c84019SSong Gao 
4411c84019SSong Gao 	pr_info("PMU capabilities:\n");
4511c84019SSong Gao 	pr_info("  Counters present: %s\n", cfg6 & CPUCFG6_PMP ? "yes" : "no");
4611c84019SSong Gao 	pr_info("  Number of counters: %d\n", nr_counters);
4711c84019SSong Gao 	pr_info("  Counter width: %d bits\n", counter_bits);
4811c84019SSong Gao }
4911c84019SSong Gao 
5011c84019SSong Gao /* Guest test code - runs inside VM */
5111c84019SSong Gao static void guest_pmu_base_test(void)
5211c84019SSong Gao {
5311c84019SSong Gao 	int i;
5411c84019SSong Gao 	uint32_t cfg6, pmnum;
5511c84019SSong Gao 	uint64_t cnt[4];
5611c84019SSong Gao 
5711c84019SSong Gao 	cfg6 = read_cpucfg(LOONGARCH_CPUCFG6);
5811c84019SSong Gao 	pmnum = (cfg6 >> 4) & 0xf;
5911c84019SSong Gao 	GUEST_PRINTF("CPUCFG6 = 0x%x\n", cfg6);
6011c84019SSong Gao 	GUEST_PRINTF("PMP enabled: %s\n", (cfg6 & 0x1) ? "YES" : "NO");
6111c84019SSong Gao 	GUEST_PRINTF("Number of counters (PMNUM): %x\n", pmnum + 1);
6211c84019SSong Gao 	GUEST_ASSERT(pmnum == 3);
6311c84019SSong Gao 
6411c84019SSong Gao 	GUEST_PRINTF("Clean csr_perfcntr0-3\n");
6511c84019SSong Gao 	csr_write(0, LOONGARCH_CSR_PERFCNTR0);
6611c84019SSong Gao 	csr_write(0, LOONGARCH_CSR_PERFCNTR1);
6711c84019SSong Gao 	csr_write(0, LOONGARCH_CSR_PERFCNTR2);
6811c84019SSong Gao 	csr_write(0, LOONGARCH_CSR_PERFCNTR3);
6911c84019SSong Gao 	GUEST_PRINTF("Set csr_perfctrl0 for cycles event\n");
7011c84019SSong Gao 	csr_write(PMU_ENVENT_ENABLED |
7111c84019SSong Gao 		LOONGARCH_PMU_EVENT_CYCLES, LOONGARCH_CSR_PERFCTRL0);
7211c84019SSong Gao 	GUEST_PRINTF("Set csr_perfctrl1 for instr_retired event\n");
7311c84019SSong Gao 	csr_write(PMU_ENVENT_ENABLED |
7411c84019SSong Gao 		LOONGARCH_PMU_EVENT_INSTR_RETIRED, LOONGARCH_CSR_PERFCTRL1);
7511c84019SSong Gao 	GUEST_PRINTF("Set csr_perfctrl2 for branch_instructions event\n");
7611c84019SSong Gao 	csr_write(PMU_ENVENT_ENABLED |
7711c84019SSong Gao 		PERF_COUNT_HW_BRANCH_INSTRUCTIONS, LOONGARCH_CSR_PERFCTRL2);
7811c84019SSong Gao 	GUEST_PRINTF("Set csr_perfctrl3 for branch_misses event\n");
7911c84019SSong Gao 	csr_write(PMU_ENVENT_ENABLED |
8011c84019SSong Gao 		PERF_COUNT_HW_BRANCH_MISSES, LOONGARCH_CSR_PERFCTRL3);
8111c84019SSong Gao 
8211c84019SSong Gao 	for (i = 0; i < NUM_LOOPS; i++)
8311c84019SSong Gao 		cpu_relax();
8411c84019SSong Gao 
8511c84019SSong Gao 	cnt[0] = csr_read(LOONGARCH_CSR_PERFCNTR0);
8611c84019SSong Gao 	GUEST_PRINTF("csr_perfcntr0 is %lx\n", cnt[0]);
8711c84019SSong Gao 	cnt[1] = csr_read(LOONGARCH_CSR_PERFCNTR1);
8811c84019SSong Gao 	GUEST_PRINTF("csr_perfcntr1 is %lx\n", cnt[1]);
8911c84019SSong Gao 	cnt[2] = csr_read(LOONGARCH_CSR_PERFCNTR2);
9011c84019SSong Gao 	GUEST_PRINTF("csr_perfcntr2 is %lx\n", cnt[2]);
9111c84019SSong Gao 	cnt[3] = csr_read(LOONGARCH_CSR_PERFCNTR3);
9211c84019SSong Gao 	GUEST_PRINTF("csr_perfcntr3 is %lx\n", cnt[3]);
9311c84019SSong Gao 
9411c84019SSong Gao 	GUEST_PRINTF("assert csr_perfcntr0 >EXPECTED_CYCLES_MIN && csr_perfcntr0 < UPPER_BOUND\n");
9511c84019SSong Gao 	GUEST_ASSERT(cnt[0] > EXPECTED_CYCLES_MIN && cnt[0] < UPPER_BOUND);
9611c84019SSong Gao 	GUEST_PRINTF("assert csr_perfcntr1 > EXPECTED_INSTR_MIN && csr_perfcntr1 < UPPER_BOUND\n");
9711c84019SSong Gao 	GUEST_ASSERT(cnt[1] > EXPECTED_INSTR_MIN && cnt[1] < UPPER_BOUND);
9811c84019SSong Gao 	GUEST_PRINTF("assert csr_perfcntr2 > 0 && csr_perfcntr2 < UPPER_BOUND\n");
9911c84019SSong Gao 	GUEST_ASSERT(cnt[2] > 0 && cnt[2] < UPPER_BOUND);
10011c84019SSong Gao 	GUEST_PRINTF("assert csr_perfcntr3 > 0 && csr_perfcntr3 < UPPER_BOUND\n");
10111c84019SSong Gao 	GUEST_ASSERT(cnt[3] > 0 && cnt[3] < UPPER_BOUND);
10211c84019SSong Gao }
10311c84019SSong Gao 
104*e47b8e1dSSong Gao static void guest_irq_handler(struct ex_regs *regs)
105*e47b8e1dSSong Gao {
106*e47b8e1dSSong Gao 	unsigned int intid;
107*e47b8e1dSSong Gao 
108*e47b8e1dSSong Gao 	pmu_irq_disable();
109*e47b8e1dSSong Gao 	intid = !!(regs->estat & BIT(INT_PMI));
110*e47b8e1dSSong Gao 	GUEST_ASSERT_EQ(intid, 1);
111*e47b8e1dSSong Gao 	GUEST_PRINTF("Get PMU interrupt\n");
112*e47b8e1dSSong Gao 	WRITE_ONCE(pmu_irq_count, pmu_irq_count + 1);
113*e47b8e1dSSong Gao }
114*e47b8e1dSSong Gao 
115*e47b8e1dSSong Gao static void guest_pmu_interrupt_test(void)
116*e47b8e1dSSong Gao {
117*e47b8e1dSSong Gao 	uint64_t cnt;
118*e47b8e1dSSong Gao 
119*e47b8e1dSSong Gao 	csr_write(PMU_OVERFLOW - 1, LOONGARCH_CSR_PERFCNTR0);
120*e47b8e1dSSong Gao 	csr_write(PMU_ENVENT_ENABLED | CSR_PERFCTRL_PMIE | LOONGARCH_PMU_EVENT_CYCLES, LOONGARCH_CSR_PERFCTRL0);
121*e47b8e1dSSong Gao 
122*e47b8e1dSSong Gao 	cpu_relax();
123*e47b8e1dSSong Gao 
124*e47b8e1dSSong Gao 	GUEST_ASSERT_EQ(pmu_irq_count, 1);
125*e47b8e1dSSong Gao 	cnt = csr_read(LOONGARCH_CSR_PERFCNTR0);
126*e47b8e1dSSong Gao 	GUEST_PRINTF("csr_perfcntr0 is %lx\n", cnt);
127*e47b8e1dSSong Gao 	GUEST_PRINTF("PMU interrupt test success\n");
128*e47b8e1dSSong Gao 
129*e47b8e1dSSong Gao }
130*e47b8e1dSSong Gao 
13111c84019SSong Gao static void guest_code(void)
13211c84019SSong Gao {
13311c84019SSong Gao 	guest_pmu_base_test();
13411c84019SSong Gao 
135*e47b8e1dSSong Gao 	pmu_irq_enable();
136*e47b8e1dSSong Gao 	local_irq_enable();
137*e47b8e1dSSong Gao 	guest_pmu_interrupt_test();
138*e47b8e1dSSong Gao 
13911c84019SSong Gao 	GUEST_DONE();
14011c84019SSong Gao }
14111c84019SSong Gao 
14211c84019SSong Gao int main(int argc, char *argv[])
14311c84019SSong Gao {
14411c84019SSong Gao 	int ret = 0;
14511c84019SSong Gao 	struct kvm_device_attr attr;
14611c84019SSong Gao 	struct kvm_vcpu *vcpu;
14711c84019SSong Gao 	struct kvm_vm *vm;
14811c84019SSong Gao 	struct ucall uc;
14911c84019SSong Gao 
15011c84019SSong Gao 	/* Check host KVM PMU support */
15111c84019SSong Gao 	if (!has_pmu_support()) {
15211c84019SSong Gao 		print_skip("PMU not supported by host hardware\n");
15311c84019SSong Gao 		dump_pmu_caps();
15411c84019SSong Gao 		return KSFT_SKIP;
15511c84019SSong Gao 	}
15611c84019SSong Gao 	pr_info("Host support PMU\n");
15711c84019SSong Gao 
15811c84019SSong Gao 	/* Dump PMU capabilities */
15911c84019SSong Gao 	dump_pmu_caps();
16011c84019SSong Gao 
16111c84019SSong Gao 	vm = vm_create(VM_MODE_P47V47_16K);
16211c84019SSong Gao 	vcpu = vm_vcpu_add(vm, 0, guest_code);
16311c84019SSong Gao 
164*e47b8e1dSSong Gao 	pmu_irq_count = 0;
16511c84019SSong Gao 	vm_init_descriptor_tables(vm);
16611c84019SSong Gao 	loongarch_vcpu_setup(vcpu);
167*e47b8e1dSSong Gao 	vm_install_exception_handler(vm, EXCCODE_INT, guest_irq_handler);
168*e47b8e1dSSong Gao 	sync_global_to_guest(vm, pmu_irq_count);
16911c84019SSong Gao 
17011c84019SSong Gao 	attr.group = KVM_LOONGARCH_VM_FEAT_CTRL,
17111c84019SSong Gao 	attr.attr = KVM_LOONGARCH_VM_FEAT_PMU,
17211c84019SSong Gao 
17311c84019SSong Gao 	ret = ioctl(vm->fd, KVM_HAS_DEVICE_ATTR, &attr);
17411c84019SSong Gao 
17511c84019SSong Gao 	if (ret == 0) {
17611c84019SSong Gao 		pr_info("PMU is enabled in VM\n");
17711c84019SSong Gao 	} else {
17811c84019SSong Gao 		print_skip("PMU not enabled by VM config\n");
17911c84019SSong Gao 		return KSFT_SKIP;
18011c84019SSong Gao 	}
18111c84019SSong Gao 
18211c84019SSong Gao 	while (1) {
18311c84019SSong Gao 		vcpu_run(vcpu);
18411c84019SSong Gao 		switch (get_ucall(vcpu, &uc)) {
18511c84019SSong Gao 		case UCALL_PRINTF:
18611c84019SSong Gao 			printf("%s", (const char *)uc.buffer);
18711c84019SSong Gao 			break;
18811c84019SSong Gao 		case UCALL_DONE:
18911c84019SSong Gao 			printf("PMU test PASSED\n");
19011c84019SSong Gao 			goto done;
19111c84019SSong Gao 		case UCALL_ABORT:
19211c84019SSong Gao 			printf("PMU test FAILED\n");
19311c84019SSong Gao 			ret = -1;
19411c84019SSong Gao 			goto done;
19511c84019SSong Gao 		default:
19611c84019SSong Gao 			printf("Unexpected exit\n");
19711c84019SSong Gao 			ret = -1;
19811c84019SSong Gao 			goto done;
19911c84019SSong Gao 		}
20011c84019SSong Gao 	}
20111c84019SSong Gao 
20211c84019SSong Gao done:
20311c84019SSong Gao 	kvm_vm_free(vm);
20411c84019SSong Gao 	return ret;
20511c84019SSong Gao }
206