Add the actual tests for the SBI PMU extension. Functions related to shared memory (FID #7 and #8) are untested for now. Signed-off-by: James Raphael Tiovalen --- riscv/Makefile | 1 + riscv/sbi-tests.h | 1 + riscv/sbi-pmu.c | 461 ++++++++++++++++++++++++++++++++++++++++++++++ riscv/sbi.c | 2 + 4 files changed, 465 insertions(+) create mode 100644 riscv/sbi-pmu.c diff --git a/riscv/Makefile b/riscv/Makefile index c0dd5465..75a108c1 100644 --- a/riscv/Makefile +++ b/riscv/Makefile @@ -21,6 +21,7 @@ all: $(tests) sbi-deps += $(TEST_DIR)/sbi-asm.o sbi-deps += $(TEST_DIR)/sbi-dbtr.o sbi-deps += $(TEST_DIR)/sbi-fwft.o +sbi-deps += $(TEST_DIR)/sbi-pmu.o sbi-deps += $(TEST_DIR)/sbi-sse.o all_deps += $(sbi-deps) diff --git a/riscv/sbi-tests.h b/riscv/sbi-tests.h index c1ebf016..509ec547 100644 --- a/riscv/sbi-tests.h +++ b/riscv/sbi-tests.h @@ -99,6 +99,7 @@ static inline bool env_enabled(const char *env) void split_phys_addr(phys_addr_t paddr, unsigned long *hi, unsigned long *lo); void sbi_bad_fid(int ext); +void check_pmu(void); void check_sse(void); void check_dbtr(void); diff --git a/riscv/sbi-pmu.c b/riscv/sbi-pmu.c new file mode 100644 index 00000000..5d2e034a --- /dev/null +++ b/riscv/sbi-pmu.c @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SBI PMU test suite + * + * Copyright (C) 2025, James Raphael Tiovalen + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "sbi-tests.h" + +#define SBI_PMU_COUNTER_TEST_INIT_VALUE 0x7FFFFFFF + +struct sbi_ext_pmu_ctr_csr_map { + bool mapped; + bool is_fw_ctr; + unsigned long ctr_idx; + unsigned long csr; +}; + +static unsigned long number_of_counters; +static struct sbi_ext_pmu_ctr_csr_map *sbi_ext_pmu_ctr_csr_map; + +static unsigned long get_counter_idx_from_csr(unsigned long csr); +struct sbi_ext_pmu_test_ctr sbi_ext_pmu_get_candidate_fw_counter_for_test(void); +uint64_t sbi_ext_pmu_read_fw_counter(unsigned long ctr_idx); + +static unsigned long get_counter_idx_from_csr(unsigned long csr) +{ + for (unsigned long i = 0; i < number_of_counters; i++) { + if (sbi_ext_pmu_ctr_csr_map[i].mapped && + sbi_ext_pmu_ctr_csr_map[i].csr == csr) { + return sbi_ext_pmu_ctr_csr_map[i].ctr_idx; + } + } + + assert_msg(false, "CSR %lx not found in the map", csr); +} + +struct sbi_ext_pmu_test_ctr sbi_ext_pmu_get_candidate_fw_counter_for_test(void) +{ + struct sbi_ext_pmu_test_ctr test_ctr = {0}; + + if (!sbi_probe(SBI_EXT_TIME)) { + test_ctr.ctr_idx = -1; + return test_ctr; + } + + set_cidx_type(test_ctr.eid, SBI_EXT_PMU_EVENT_FW); + set_cidx_code(test_ctr.eid, SBI_EXT_PMU_FW_SET_TIMER); + + /* Since any firmware counter can be used for testing, return the first one found */ + for (unsigned long i = 0; i < number_of_counters; i++) { + if (sbi_ext_pmu_ctr_csr_map[i].mapped && sbi_ext_pmu_ctr_csr_map[i].is_fw_ctr) { + test_ctr.ctr_idx = sbi_ext_pmu_ctr_csr_map[i].ctr_idx; + return test_ctr; + } + } + + test_ctr.ctr_idx = -1; + return test_ctr; +} + +uint64_t sbi_ext_pmu_read_fw_counter(unsigned long ctr_idx) +{ + struct sbiret ret; + uint64_t ctr_val = 0; + + ret = sbi_pmu_counter_fw_read(ctr_idx); + report(ret.error == SBI_SUCCESS, + "expected to read lower bits of firmware counter %ld successfully, got %ld", ctr_idx, ret.error); + + ctr_val = ret.value; + + ret = sbi_pmu_counter_fw_read_hi(ctr_idx); + report(ret.error == SBI_SUCCESS, + "expected to read upper bits of firmware counter %ld successfully, got %ld", ctr_idx, ret.error); + + ctr_val += ((uint64_t)ret.value << 32); + + return ctr_val; +} + +void check_pmu(void) +{ + struct sbiret ret; + unsigned long valid_counter_info = 0, num_of_hw_counters = 0; + uint64_t cycle_count, instret_count, test_counter_value; + bool timer_counter_found = false; + union sbi_ext_pmu_ctr_info info; + unsigned long test_eid = 0, set_timer_count = 0; + int test_counter_idx; + struct sbi_ext_pmu_test_ctr test_ctr = {0}; + + report_prefix_push("pmu"); + + if (!sbi_probe(SBI_EXT_PMU)) { + report_skip("PMU extension unavailable"); + report_prefix_pop(); + return; + } + + sbi_bad_fid(SBI_EXT_PMU); + + report_prefix_push("pmu_num_counters"); + + ret = sbi_pmu_num_counters(); + if (ret.error) { + report_fail("failed to get number of counters (error=%ld)", ret.error); + report_prefix_popn(2); + return; + } + number_of_counters = ret.value; + + /* CSR_CYCLE, CSR_TIME, and CSR_INSTRET are mandatory counters */ + if (number_of_counters < 3) { + report_fail("number of counters is %ld, expected at least 3", number_of_counters); + report_prefix_popn(2); + return; + } + + report_info("number of counters is %ld", number_of_counters); + + report_prefix_pop(); + + report_prefix_push("sbi_pmu_counter_get_info"); + + fdt_pmu_setup(); + + sbi_ext_pmu_ctr_csr_map = calloc(number_of_counters, + sizeof(struct sbi_ext_pmu_ctr_csr_map)); + + for (unsigned long i = 0; i < number_of_counters; i++) { + sbi_ext_pmu_ctr_csr_map[i].mapped = false; + sbi_ext_pmu_ctr_csr_map[i].is_fw_ctr = false; + sbi_ext_pmu_ctr_csr_map[i].ctr_idx = 0; + sbi_ext_pmu_ctr_csr_map[i].csr = 0; + } + + for (unsigned long i = 0; i < number_of_counters; i++) { + ret = sbi_pmu_counter_get_info(i); + + if (ret.error == SBI_ERR_INVALID_PARAM && !timer_counter_found) { + /* Assume that this is the CSR_TIME counter and skip it */ + timer_counter_found = true; + sbi_ext_pmu_ctr_csr_map[i].ctr_idx = i; + sbi_ext_pmu_ctr_csr_map[i].csr = CSR_TIME; + valid_counter_info++; + report_info("skipping CSR_TIME counter with index %ld", i); + continue; + } else if (ret.error) { + free(sbi_ext_pmu_ctr_csr_map); + fdt_pmu_free(); + report_fail("failed to get counter info (error=%ld)", ret.error); + report_prefix_popn(2); + return; + } + + info = *(union sbi_ext_pmu_ctr_info *)&ret.value; + + if (info.type == SBI_EXT_PMU_CTR_TYPE_HW) { + sbi_ext_pmu_ctr_csr_map[i].mapped = true; + sbi_ext_pmu_ctr_csr_map[i].ctr_idx = i; + sbi_ext_pmu_ctr_csr_map[i].csr = info.csr; + + if ((info.csr == CSR_CYCLE) || (info.csr == CSR_INSTRET)) + valid_counter_info += info.width == 63; + else + valid_counter_info++; + + num_of_hw_counters++; + } else if (info.type == SBI_EXT_PMU_CTR_TYPE_FW) { + sbi_ext_pmu_ctr_csr_map[i].mapped = true; + sbi_ext_pmu_ctr_csr_map[i].is_fw_ctr = true; + sbi_ext_pmu_ctr_csr_map[i].ctr_idx = i; + valid_counter_info++; + } else { + free(sbi_ext_pmu_ctr_csr_map); + fdt_pmu_free(); + report_fail("unknown counter type %d", info.type); + report_prefix_popn(2); + return; + } + } + + report(valid_counter_info == number_of_counters, + "number of counters with valid info is %ld", valid_counter_info); + + ret = sbi_pmu_counter_get_info(number_of_counters); + report(ret.error == SBI_ERR_INVALID_PARAM, + "expected %d when counter_idx == num_counters, got %ld", SBI_ERR_INVALID_PARAM, ret.error); + + report_prefix_pop(); + + report_prefix_push("sbi_pmu_counter_config_matching"); + + cycle_count = pmu_get_cycles(); + instret_count = pmu_get_instret(); + + set_cidx_type(test_eid, SBI_EXT_PMU_EVENT_HW_GENERAL); + set_cidx_code(test_eid, SBI_EXT_PMU_HW_CPU_CYCLES); + ret = sbi_pmu_counter_config_matching(get_counter_idx_from_csr(CSR_CYCLE), + 1, + SBI_EXT_PMU_CFG_FLAG_CLEAR_VALUE, + test_eid, + 0); + + if (ret.error) { + free(sbi_ext_pmu_ctr_csr_map); + fdt_pmu_free(); + report_fail("failed to configure counter (error=%ld)", ret.error); + report_prefix_popn(2); + return; + } + + test_counter_value = pmu_get_cycles(); + + report(test_counter_value < cycle_count, + "expected cycle count to reset (%ld < %ld)", test_counter_value, cycle_count); + + set_cidx_code(test_eid, SBI_EXT_PMU_HW_INSTRUCTIONS); + ret = sbi_pmu_counter_config_matching(get_counter_idx_from_csr(CSR_INSTRET), + 1, + SBI_EXT_PMU_CFG_FLAG_CLEAR_VALUE, + test_eid, + 0); + + if (ret.error) { + free(sbi_ext_pmu_ctr_csr_map); + fdt_pmu_free(); + report_fail("failed to configure counter (error=%ld)", ret.error); + report_prefix_popn(2); + return; + } + + test_counter_value = pmu_get_instret(); + + report(test_counter_value < instret_count, + "expected instret count to reset (%ld < %ld)", test_counter_value, instret_count); + + set_cidx_code(test_eid, SBI_EXT_PMU_HW_CPU_CYCLES); + test_counter_idx = sbi_ext_pmu_get_first_counter_for_hw_event(test_eid); + + report_info("first counter for test hw event %ld is %d", test_eid, test_counter_idx); + + if (test_counter_idx <= 0) { + report_skip("failed to get first counter for test hw event"); + } else { + test_counter_value = pmu_get_cycles(); + ret = sbi_pmu_counter_config_matching(test_counter_idx, 0, SBI_EXT_PMU_CFG_FLAG_CLEAR_VALUE, + test_eid, 0); + report(ret.error == SBI_ERR_INVALID_PARAM, + "expected %d when counter_idx_mask == 0, got %ld", SBI_ERR_INVALID_PARAM, ret.error); + report(pmu_get_cycles() > test_counter_value, + "expected cycle counter to be unaffected when configuring counter %d", test_counter_idx); + } + + test_ctr = sbi_ext_pmu_get_candidate_hw_counter_for_test(); + test_counter_idx = test_ctr.ctr_idx; + test_eid = test_ctr.eid; + + report_info("testing hardware counter %d with event %ld", test_counter_idx, test_eid); + + ret = sbi_pmu_counter_config_matching(test_counter_idx, 1, + SBI_EXT_PMU_CFG_FLAG_SKIP_MATCH, + test_eid, 0); + report(ret.error == SBI_ERR_INVALID_PARAM, + "expected %d when skipping match before configuring counter, got %ld", + SBI_ERR_INVALID_PARAM, ret.error); + + ret = sbi_pmu_counter_config_matching(test_counter_idx, 1, + SBI_EXT_PMU_CFG_FLAG_CLEAR_VALUE | SBI_EXT_PMU_CFG_FLAG_AUTO_START, + test_eid, 0); + if (ret.error) { + free(sbi_ext_pmu_ctr_csr_map); + fdt_pmu_free(); + report_fail("failed to configure counter (error=%ld)", ret.error); + report_prefix_popn(2); + return; + } + report(ret.value == test_counter_idx, + "expected counter %d to be configured (%ld == %d)", test_counter_idx, ret.value, test_counter_idx); + + test_counter_value = pmu_get_counter(sbi_ext_pmu_ctr_csr_map[test_counter_idx].csr); + + report(test_counter_value > 0, + "expected counter %d to auto-start (%ld > 0)", + test_counter_idx, + test_counter_value); + + test_eid = sbi_ext_pmu_get_first_unsupported_hw_event(test_counter_idx); + ret = sbi_pmu_counter_config_matching(test_counter_idx, 1, + SBI_EXT_PMU_CFG_FLAG_CLEAR_VALUE | SBI_EXT_PMU_CFG_FLAG_AUTO_START, + test_eid, 0); + report(ret.error == SBI_ERR_NOT_SUPPORTED, + "expected counter %d to be unable to monitor event %ld, got %ld", + test_counter_idx, test_eid, ret.error); + + test_eid = test_ctr.eid; + + report_prefix_pop(); + + report_prefix_push("sbi_pmu_counter_start"); + + ret = sbi_pmu_counter_start(test_counter_idx, 0, 0, 0); + report(ret.error == SBI_ERR_INVALID_PARAM, + "expected %d when counter_idx_mask == 0, got %ld", SBI_ERR_INVALID_PARAM, ret.error); + + ret = sbi_pmu_counter_start(test_counter_idx, 1, 0, 0); + report(ret.error == SBI_ERR_ALREADY_STARTED, + "expected counter %d to be already started, got %ld", test_counter_idx, ret.error); + + report_prefix_pop(); + + report_prefix_push("sbi_pmu_counter_stop"); + + ret = sbi_pmu_counter_stop(test_counter_idx, 0, 0); + report(ret.error == SBI_ERR_INVALID_PARAM, + "expected %d when counter_idx_mask == 0, got %ld", SBI_ERR_INVALID_PARAM, ret.error); + + ret = sbi_pmu_counter_stop(test_counter_idx, 1, 0); + report(ret.error == SBI_SUCCESS, + "expected counter %d to be stopped, got %ld", test_counter_idx, ret.error); + + ret = sbi_pmu_counter_stop(test_counter_idx, 1, 0); + report(ret.error == SBI_ERR_ALREADY_STOPPED, + "expected counter %d to be already stopped, got %ld", test_counter_idx, ret.error); + + report_prefix_pop(); + + report_prefix_push("sbi_pmu_counter_start"); + + ret = sbi_pmu_counter_start(test_counter_idx, 1, SBI_EXT_PMU_START_SET_INIT_VALUE, + SBI_PMU_COUNTER_TEST_INIT_VALUE); + report(ret.error == SBI_SUCCESS, + "expected counter %d to be started with initial value, got %ld", test_counter_idx, ret.error); + + test_counter_value = pmu_get_counter(sbi_ext_pmu_ctr_csr_map[test_counter_idx].csr); + report(test_counter_value > SBI_PMU_COUNTER_TEST_INIT_VALUE, + "expected counter %d to start with initial value (%ld > %d)", + test_counter_idx, test_counter_value, SBI_PMU_COUNTER_TEST_INIT_VALUE); + + report_prefix_pop(); + + report_prefix_push("sbi_pmu_counter_stop"); + + ret = sbi_pmu_counter_stop(test_counter_idx, 1, 0); + report(ret.error == SBI_SUCCESS, + "expected counter %d to be stopped, got %ld", test_counter_idx, ret.error); + + report_prefix_pop(); + + report_prefix_push("sbi_pmu_counter_fw_read"); + + ret = sbi_pmu_counter_fw_read(number_of_counters); + report(ret.error == SBI_ERR_INVALID_PARAM, + "expected %d when counter_idx == num_counters, got %ld", SBI_ERR_INVALID_PARAM, ret.error); + + ret = sbi_pmu_counter_fw_read_hi(number_of_counters); + report(ret.error == SBI_ERR_INVALID_PARAM, + "expected %d when counter_idx == num_counters, got %ld", SBI_ERR_INVALID_PARAM, ret.error); + + test_ctr = sbi_ext_pmu_get_candidate_fw_counter_for_test(); + test_counter_idx = test_ctr.ctr_idx; + test_eid = test_ctr.eid; + + if (test_counter_idx < 0) { + free(sbi_ext_pmu_ctr_csr_map); + fdt_pmu_free(); + report_skip("no firmware counters available for testing"); + report_prefix_popn(2); + return; + } + + report_info("testing firmware counter %d with event 0x%lx", test_counter_idx, test_eid); + + ret = sbi_pmu_counter_config_matching(test_counter_idx, 1, + SBI_EXT_PMU_CFG_FLAG_CLEAR_VALUE | SBI_EXT_PMU_CFG_FLAG_AUTO_START, + test_eid, 0); + if (ret.error) { + free(sbi_ext_pmu_ctr_csr_map); + fdt_pmu_free(); + report_fail("failed to configure counter (error=%ld)", ret.error); + report_prefix_popn(2); + return; + } + report(ret.value == test_counter_idx, + "expected counter %d to be configured (%ld == %d)", test_counter_idx, ret.value, test_counter_idx); + + test_counter_value = sbi_ext_pmu_read_fw_counter(test_counter_idx); + + report(test_counter_value == set_timer_count, + "expected counter %d to be cleared (%ld == %ld)", + test_counter_idx, + test_counter_value, + set_timer_count); + + ret = sbi_pmu_counter_start(test_counter_idx, 0, 0, 0); + report(ret.error == SBI_ERR_INVALID_PARAM, + "expected %d when counter_idx_mask == 0, got %ld", SBI_ERR_INVALID_PARAM, ret.error); + + ret = sbi_pmu_counter_start(test_counter_idx, 1, 0, 0); + report(ret.error == SBI_ERR_ALREADY_STARTED, + "expected counter %d to be already started, got %ld", test_counter_idx, ret.error); + + sbi_set_timer(0); + set_timer_count++; + test_counter_value = sbi_ext_pmu_read_fw_counter(test_counter_idx); + + report(test_counter_value == set_timer_count, + "expected counter %d to have incremented (%ld == %ld)", + test_counter_idx, + test_counter_value, + set_timer_count); + + sbi_set_timer(ULONG_MAX); + set_timer_count++; + test_counter_value = sbi_ext_pmu_read_fw_counter(test_counter_idx); + + report(test_counter_value == set_timer_count, + "expected counter %d to have incremented (%ld == %ld)", + test_counter_idx, + test_counter_value, + set_timer_count); + + ret = sbi_pmu_counter_stop(test_counter_idx, 0, 0); + report(ret.error == SBI_ERR_INVALID_PARAM, + "expected %d when counter_idx_mask == 0, got %ld", SBI_ERR_INVALID_PARAM, ret.error); + + ret = sbi_pmu_counter_stop(test_counter_idx, 1, 0); + report(ret.error == SBI_SUCCESS, + "expected counter %d to be stopped, got %ld", test_counter_idx, ret.error); + + ret = sbi_pmu_counter_stop(test_counter_idx, 1, 0); + report(ret.error == SBI_ERR_ALREADY_STOPPED, + "expected counter %d to be already stopped, got %ld", test_counter_idx, ret.error); + + sbi_set_timer(ULONG_MAX); + test_counter_value = sbi_ext_pmu_read_fw_counter(test_counter_idx); + + report(test_counter_value == set_timer_count, + "expected counter %d to be unchanged after stop (%ld == %ld)", + test_counter_idx, + test_counter_value, + set_timer_count); + + free(sbi_ext_pmu_ctr_csr_map); + fdt_pmu_free(); + report_prefix_popn(2); +} diff --git a/riscv/sbi.c b/riscv/sbi.c index 3b8aadce..fdb6a38a 100644 --- a/riscv/sbi.c +++ b/riscv/sbi.c @@ -32,6 +32,7 @@ #define HIGH_ADDR_BOUNDARY ((phys_addr_t)1 << 32) +void check_pmu(void); void check_sse(void); void check_fwft(void); @@ -1557,6 +1558,7 @@ int main(int argc, char **argv) check_time(); check_ipi(); check_hsm(); + check_pmu(); check_dbcn(); check_susp(); check_sse(); -- 2.43.0