blob: 58c05f07414bc23bf795a2b40eee7d91462766bc [file] [log] [blame]
/*
* 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(RSI_ABI_VERSION_VAL);
/* 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;
}