feat(rme): add PMU Realm tests

This patch adds Realm PMU payload tests with
PMU interrupt handling.

Signed-off-by: AlexeiFedorov <Alexei.Fedorov@arm.com>
Change-Id: I86ef96252e04c57db385e129227cc0d7dcd1fec2
diff --git a/realm/realm_pmuv3.c b/realm/realm_pmuv3.c
new file mode 100644
index 0000000..862e93e
--- /dev/null
+++ b/realm/realm_pmuv3.c
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch_helpers.h>
+#include <arm_arch_svc.h>
+#include <debug.h>
+#include <drivers/arm/gic_v3.h>
+
+#include <host_realm_pmu.h>
+#include <realm_rsi.h>
+
+/* PMUv3 events */
+#define PMU_EVT_SW_INCR		0x0
+#define PMU_EVT_INST_RETIRED	0x8
+#define PMU_EVT_CPU_CYCLES	0x11
+#define PMU_EVT_MEM_ACCESS	0x13
+
+#define NOP_REPETITIONS		50
+#define MAX_COUNTERS		32
+
+#define PRE_OVERFLOW		~(0xF)
+
+#define	DELAY_MS		3000ULL
+
+static inline void read_all_counters(u_register_t *array, int impl_ev_ctrs)
+{
+	array[0] = read_pmccntr_el0();
+	for (unsigned int i = 0U; i < impl_ev_ctrs; i++) {
+		array[i + 1] = read_pmevcntrn_el0(i);
+	}
+}
+
+static inline void read_all_counter_configs(u_register_t *array, int impl_ev_ctrs)
+{
+	array[0] = read_pmccfiltr_el0();
+	for (unsigned int i = 0U; i < impl_ev_ctrs; i++) {
+		array[i + 1] = read_pmevtypern_el0(i);
+	}
+}
+
+static inline void read_all_pmu_configs(u_register_t *array)
+{
+	array[0] = read_pmcntenset_el0();
+	array[1] = read_pmcr_el0();
+	array[2] = read_pmselr_el0();
+}
+
+static inline void enable_counting(void)
+{
+	write_pmcr_el0(read_pmcr_el0() | PMCR_EL0_E_BIT);
+	/* This function means we are about to use the PMU, synchronize */
+	isb();
+}
+
+static inline void disable_counting(void)
+{
+	write_pmcr_el0(read_pmcr_el0() & ~PMCR_EL0_E_BIT);
+	/* We also rely that disabling really did work */
+	isb();
+}
+
+static inline void clear_counters(void)
+{
+	write_pmcr_el0(read_pmcr_el0() | PMCR_EL0_C_BIT | PMCR_EL0_P_BIT);
+	isb();
+}
+
+static void pmu_reset(void)
+{
+	/* Reset all counters */
+	write_pmcr_el0(read_pmcr_el0() |
+			PMCR_EL0_DP_BIT | PMCR_EL0_C_BIT | PMCR_EL0_P_BIT);
+
+	/* Disable all counters */
+	write_pmcntenclr_el0(PMU_CLEAR_ALL);
+
+	/* Clear overflow status */
+	write_pmovsclr_el0(PMU_CLEAR_ALL);
+
+	/* Disable overflow interrupts on all counters */
+	write_pmintenclr_el1(PMU_CLEAR_ALL);
+	isb();
+}
+
+/*
+ * This test runs in Realm EL1, don't bother enabling counting at lower ELs
+ * and secure world. TF-A has other controls for them and counting there
+ * doesn't impact us.
+ */
+static inline void enable_cycle_counter(void)
+{
+	/*
+	 * Set PMCCFILTR_EL0.U != PMCCFILTR_EL0.RLU
+	 * to disable counting in Realm EL0.
+	 * Set PMCCFILTR_EL0.P = PMCCFILTR_EL0.RLK
+	 * to enable counting in Realm EL1.
+	 * Set PMCCFILTR_EL0.NSH = PMCCFILTR_EL0_EL0.RLH
+	 * to disable event counting in Realm EL2.
+	 */
+	write_pmccfiltr_el0(PMCCFILTR_EL0_U_BIT |
+			    PMCCFILTR_EL0_P_BIT | PMCCFILTR_EL0_RLK_BIT |
+			    PMCCFILTR_EL0_NSH_BIT | PMCCFILTR_EL0_RLH_BIT);
+	write_pmcntenset_el0(read_pmcntenset_el0() | PMCNTENSET_EL0_C_BIT);
+	isb();
+}
+
+static inline void enable_event_counter(int ctr_num)
+{
+	/*
+	 * Set PMEVTYPER_EL0.U != PMEVTYPER_EL0.RLU
+	 * to disable event counting in Realm EL0.
+	 * Set PMEVTYPER_EL0.P = PMEVTYPER_EL0.RLK
+	 * to enable counting in Realm EL1.
+	 * Set PMEVTYPER_EL0.NSH = PMEVTYPER_EL0.RLH
+	 * to disable event counting in Realm EL2.
+	 */
+	write_pmevtypern_el0(ctr_num,
+			PMEVTYPER_EL0_U_BIT |
+			PMEVTYPER_EL0_P_BIT | PMEVTYPER_EL0_RLK_BIT |
+			PMEVTYPER_EL0_NSH_BIT | PMEVTYPER_EL0_RLH_BIT |
+			(PMU_EVT_INST_RETIRED & PMEVTYPER_EL0_EVTCOUNT_BITS));
+	write_pmcntenset_el0(read_pmcntenset_el0() |
+		PMCNTENSET_EL0_P_BIT(ctr_num));
+	isb();
+}
+
+/* Doesn't really matter what happens, as long as it happens a lot */
+static inline void execute_nops(void)
+{
+	for (unsigned int i = 0U; i < NOP_REPETITIONS; i++) {
+		__asm__ ("orr x0, x0, x0\n");
+	}
+}
+
+/*
+ * Try the cycle counter with some NOPs to see if it works
+ */
+bool test_pmuv3_cycle_works_realm(void)
+{
+	u_register_t ccounter_start;
+	u_register_t ccounter_end;
+
+	pmu_reset();
+
+	enable_cycle_counter();
+	enable_counting();
+
+	ccounter_start = read_pmccntr_el0();
+	execute_nops();
+	ccounter_end = read_pmccntr_el0();
+	disable_counting();
+	clear_counters();
+
+	realm_printf("Realm: counted from %lu to %lu\n",
+		ccounter_start, ccounter_end);
+	if (ccounter_start != ccounter_end) {
+		return true;
+	}
+	return false;
+}
+
+/*
+ * Try an event counter with some NOPs to see if it works.
+ */
+bool test_pmuv3_event_works_realm(void)
+{
+	u_register_t evcounter_start;
+	u_register_t evcounter_end;
+
+	if (GET_CNT_NUM == 0) {
+		realm_printf("Realm: no event counters implemented\n");
+		return false;
+	}
+
+	pmu_reset();
+
+	enable_event_counter(0);
+	enable_counting();
+
+	/*
+	 * If any is enabled it will be in the first range.
+	 */
+	evcounter_start = read_pmevcntrn_el0(0);
+	execute_nops();
+	disable_counting();
+	evcounter_end = read_pmevcntrn_el0(0);
+	clear_counters();
+
+	realm_printf("Realm: counted from %lu to %lu\n",
+		evcounter_start, evcounter_end);
+	if (evcounter_start != evcounter_end) {
+		return true;
+	}
+	return false;
+}
+
+/*
+ * Check if entering/exiting RMM (with a NOP) preserves all PMU registers.
+ */
+bool test_pmuv3_rmm_preserves(void)
+{
+	u_register_t ctr_start[MAX_COUNTERS] = {0};
+	u_register_t ctr_cfg_start[MAX_COUNTERS] = {0};
+	u_register_t pmu_cfg_start[3];
+	u_register_t ctr_end[MAX_COUNTERS] = {0};
+	u_register_t ctr_cfg_end[MAX_COUNTERS] = {0};
+	u_register_t pmu_cfg_end[3];
+	unsigned int impl_ev_ctrs = GET_CNT_NUM;
+
+	realm_printf("Realm: testing %u event counters\n", impl_ev_ctrs);
+
+	pmu_reset();
+
+	/* Pretend counters have just been used */
+	enable_cycle_counter();
+	enable_event_counter(0);
+	enable_counting();
+	execute_nops();
+	disable_counting();
+
+	/* Get before reading */
+	read_all_counters(ctr_start, impl_ev_ctrs);
+	read_all_counter_configs(ctr_cfg_start, impl_ev_ctrs);
+	read_all_pmu_configs(pmu_cfg_start);
+
+	/* Give RMM a chance to scramble everything */
+	(void)rsi_get_version();
+
+	/* Get after reading */
+	read_all_counters(ctr_end, impl_ev_ctrs);
+	read_all_counter_configs(ctr_cfg_end, impl_ev_ctrs);
+	read_all_pmu_configs(pmu_cfg_end);
+
+	if (memcmp(ctr_start, ctr_end, sizeof(ctr_start)) != 0) {
+		realm_printf("Realm: SMC call did not preserve %s\n",
+				"counters");
+		return false;
+	}
+
+	if (memcmp(ctr_cfg_start, ctr_cfg_end, sizeof(ctr_cfg_start)) != 0) {
+		realm_printf("Realm: SMC call did not preserve %s\n",
+				"counter config");
+		return false;
+	}
+
+	if (memcmp(pmu_cfg_start, pmu_cfg_end, sizeof(pmu_cfg_start)) != 0) {
+		realm_printf("Realm: SMC call did not preserve %s\n",
+				"PMU registers");
+		return false;
+	}
+
+	return true;
+}
+
+bool test_pmuv3_overflow_interrupt(void)
+{
+	unsigned long priority_bits, priority;
+	uint64_t delay_time = DELAY_MS;
+
+	pmu_reset();
+
+	/* Get the number of priority bits implemented */
+	priority_bits = ((read_icv_ctrl_el1() >> ICV_CTLR_EL1_PRIbits_SHIFT) &
+				ICV_CTLR_EL1_PRIbits_MASK) + 1UL;
+
+	/* Unimplemented bits are RES0 and start from LSB */
+	priority = (0xFFUL << (8UL - priority_bits)) & 0xFFUL;
+
+	/* Set the priority mask register to allow all interrupts */
+	write_icv_pmr_el1(priority);
+
+	/* Enable Virtual Group 1 interrupts */
+	write_icv_igrpen1_el1(ICV_IGRPEN1_EL1_Enable);
+
+	/* Enable IRQ */
+	enable_irq();
+
+	write_pmevcntrn_el0(0, PRE_OVERFLOW);
+	enable_event_counter(0);
+
+	/* Enable interrupt on event counter #0 */
+	write_pmintenset_el1((1UL << 0));
+
+	realm_printf("Realm: waiting for PMU vIRQ...\n");
+
+	enable_counting();
+	execute_nops();
+
+	/*
+	 * Interrupt handler will clear
+	 * Performance Monitors Interrupt Enable Set register
+	 * as part of handling the overflow interrupt.
+	 */
+	while ((read_pmintenset_el1() != 0UL) && (delay_time != 0ULL)) {
+		--delay_time;
+	}
+
+	/* Disable IRQ */
+	disable_irq();
+
+	pmu_reset();
+
+	if (delay_time == 0ULL) {
+		realm_printf("Realm: PMU vIRQ %sreceived in %llums\n",	"not ",
+				DELAY_MS);
+		return false;
+	}
+
+	realm_printf("Realm: PMU vIRQ %sreceived in %llums\n", "",
+			DELAY_MS - delay_time);
+
+	return true;
+}