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