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/aarch64/realm_exceptions.S b/realm/aarch64/realm_exceptions.S
index 6ce8810..99b601d 100644
--- a/realm/aarch64/realm_exceptions.S
+++ b/realm/aarch64/realm_exceptions.S
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, Arm Limited. All rights reserved.
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -14,7 +14,7 @@
  */
 .macro unhandled_exception name
 	vector_entry \name
-	b crash_dump
+	b	crash_dump
 	end_vector_entry \name
 .endm
 
@@ -106,11 +106,11 @@
 	mov	x19, sp
 	bl	tftf_sync_exception_handler
 	cbnz	x0, 0f
-	mov x0, x19
+	mov	x0, x19
 	/* Save original stack pointer value on the stack */
 	add	x1, x0, #0x100
 	str	x1, [x0, #0xf8]
-	b print_exception
+	b	print_exception
 0:	restore_gp_regs
 	add	sp, sp, #0x100
 	eret
diff --git a/realm/platform.h b/realm/include/platform.h
similarity index 65%
rename from realm/platform.h
rename to realm/include/platform.h
index 2c6ad27..de91c16 100644
--- a/realm/platform.h
+++ b/realm/include/platform.h
@@ -1,12 +1,12 @@
 /*
- * Copyright (c) 2022, Arm Limited. All rights reserved.
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  *
  */
 
-#ifndef REALM_PLATFORM_H
-#define REALM_PLATFORM_H
+#ifndef PLATFORM_H
+#define PLATFORM_H
 
 /*
  * Helper that returns a linear core ID from a MPID
@@ -17,4 +17,4 @@
 	return 0U;
 }
 
-#endif /* REALM_PLATFORM_H */
+#endif /* PLATFORM_H */
diff --git a/realm/realm_rsi.h b/realm/include/realm_rsi.h
similarity index 95%
rename from realm/realm_rsi.h
rename to realm/include/realm_rsi.h
index 34721cd..c7ea5a5 100644
--- a/realm/realm_rsi.h
+++ b/realm/include/realm_rsi.h
@@ -9,6 +9,7 @@
 #define REALM_RSI_H
 
 #include <stdint.h>
+#include <host_shared_data.h>
 #include <tftf_lib.h>
 
 #define SMC_RSI_CALL_BASE	0xC4000190
@@ -63,12 +64,6 @@
 	RSI_ERROR_COUNT
 } rsi_status_t;
 
-enum host_call_cmd {
-	HOST_CALL_GET_SHARED_BUFF_CMD = 1U,
-	HOST_CALL_EXIT_SUCCESS_CMD,
-	HOST_CALL_EXIT_FAILED_CMD
-};
-
 struct rsi_realm_config {
 	/* IPA width in bits */
 	SET_MEMBER(unsigned long ipa_width, 0, 0x1000);	/* Offset 0 */
@@ -100,6 +95,7 @@
 
 
 #define RSI_ABI_VERSION		SMC_RSI_FID(0U)
+
 /*
  * arg0 == struct rsi_realm_config address
  */
diff --git a/realm/include/realm_tests.h b/realm/include/realm_tests.h
new file mode 100644
index 0000000..0e653ba
--- /dev/null
+++ b/realm/include/realm_tests.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef REALM_TESTS_H
+#define REALM_TESTS_H
+
+bool test_pmuv3_cycle_works_realm(void);
+bool test_pmuv3_event_works_realm(void);
+bool test_pmuv3_rmm_preserves(void);
+bool test_pmuv3_overflow_interrupt(void);
+
+#endif /* REALM_TESTS_H */
+
diff --git a/realm/realm.mk b/realm/realm.mk
index 638f02e..b033627 100644
--- a/realm/realm.mk
+++ b/realm/realm.mk
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2022, Arm Limited. All rights reserved.
+# Copyright (c) 2022-2023, Arm Limited. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -18,15 +18,17 @@
 	-Iinclude/runtime_services					\
 	-Iinclude/runtime_services/host_realm_managment			\
 	-Irealm								\
-	-Irealm/aarch64
+	-Irealm/aarch64							\
+	-Irealm/include
 
 REALM_SOURCES:=								\
 	$(addprefix realm/,						\
 	aarch64/realm_entrypoint.S					\
 	aarch64/realm_exceptions.S					\
 	realm_debug.c							\
-	realm_payload_main.c						\
 	realm_interrupt.c						\
+	realm_payload_main.c						\
+	realm_pmuv3.c							\
 	realm_rsi.c							\
 	realm_shared_data.c						\
 	)
diff --git a/realm/realm_interrupt.c b/realm/realm_interrupt.c
index 02ab55c..7f8dc15 100644
--- a/realm/realm_interrupt.c
+++ b/realm/realm_interrupt.c
@@ -1,13 +1,27 @@
 /*
- * Copyright (c) 2022, Arm Limited. All rights reserved.
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
 
+#include <arch_helpers.h>
 #include <debug.h>
+#include <host_realm_pmu.h>
 
-/* dummy interrupt handler as for now*/
+/* Realm interrupt handler */
 void realm_interrupt_handler(void)
 {
-	INFO("%s\n", __func__);
+	/* Read INTID and acknowledge interrupt */
+	unsigned long iar1_el1 = read_icv_iar1_el1();
+
+	/* Deactivate interrupt */
+	write_icv_eoir1_el1(iar1_el1);
+
+	/* Clear PMU interrupt */
+	if (iar1_el1 == PMU_VIRQ) {
+		write_pmintenclr_el1(read_pmintenset_el1());
+		isb();
+	} else {
+		panic();
+	}
 }
diff --git a/realm/realm_payload_main.c b/realm/realm_payload_main.c
index bd4dec7..414c329 100644
--- a/realm/realm_payload_main.c
+++ b/realm/realm_payload_main.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, Arm Limited. All rights reserved.
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  *
@@ -12,17 +12,18 @@
 #include <host_shared_data.h>
 #include "realm_def.h"
 #include <realm_rsi.h>
+#include <realm_tests.h>
 #include <tftf_lib.h>
 
 /*
- * This function reads sleep time in ms from shared buffer and spins PE in a loop
- * for that time period.
+ * This function reads sleep time in ms from shared buffer and spins PE
+ * in a loop for that time period.
  */
 static void realm_sleep_cmd(void)
 {
 	uint64_t sleep = realm_shared_data_get_host_val(HOST_SLEEP_INDEX);
 
-	INFO("REALM_PAYLOAD: Realm payload going to sleep for %llums\n", sleep);
+	realm_printf("Realm: going to sleep for %llums\n", sleep);
 	waitms(sleep);
 }
 
@@ -35,11 +36,11 @@
 
 	version = rsi_get_version();
 	if (version == (u_register_t)SMC_UNKNOWN) {
-		ERROR("SMC_RSI_ABI_VERSION failed (%ld)", (long)version);
+		realm_printf("SMC_RSI_ABI_VERSION failed (%ld)", (long)version);
 		return;
 	}
 
-	INFO("RSI ABI version %u.%u (expected: %u.%u)",
+	realm_printf("RSI ABI version %u.%u (expected: %u.%u)",
 	RSI_ABI_VERSION_GET_MAJOR(version),
 	RSI_ABI_VERSION_GET_MINOR(version),
 	RSI_ABI_VERSION_GET_MAJOR(RSI_ABI_VERSION),
@@ -56,12 +57,13 @@
  */
 void realm_payload_main(void)
 {
-	uint8_t cmd = 0U;
 	bool test_succeed = false;
 
 	realm_set_shared_structure((host_shared_data_t *)rsi_get_ns_buffer());
+
 	if (realm_get_shared_structure() != NULL) {
-		cmd = realm_shared_data_get_realm_cmd();
+		uint8_t cmd = realm_shared_data_get_realm_cmd();
+
 		switch (cmd) {
 		case REALM_SLEEP_CMD:
 			realm_sleep_cmd();
@@ -71,8 +73,20 @@
 			realm_get_rsi_version();
 			test_succeed = true;
 			break;
+		case REALM_PMU_CYCLE:
+			test_succeed = test_pmuv3_cycle_works_realm();
+			break;
+		case REALM_PMU_EVENT:
+			test_succeed = test_pmuv3_event_works_realm();
+			break;
+		case REALM_PMU_PRESERVE:
+			test_succeed = test_pmuv3_rmm_preserves();
+			break;
+		case REALM_PMU_INTERRUPT:
+			test_succeed = test_pmuv3_overflow_interrupt();
+			break;
 		default:
-			INFO("REALM_PAYLOAD: %s invalid cmd=%hhu", __func__, cmd);
+			realm_printf("%s() invalid cmd %u\n", __func__, cmd);
 			break;
 		}
 	}
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;
+}