blob: 59a22b1a5d20f087866c9d17f46e20230c493c13 [file] [log] [blame]
/*
* Copyright (c) 2018-2025, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch.h>
#include <arch_helpers.h>
#include <debug.h>
#include <events.h>
#include <irq.h>
#include <mmio.h>
#include <plat_topology.h>
#include <platform.h>
#include <power_management.h>
#include <psci.h>
#include <stdlib.h>
#include <test_helpers.h>
#include <tftf_lib.h>
#include <timer.h>
static event_t cpu_ready[PLATFORM_CORE_COUNT];
/* Used to confirm the CPU is woken up by IRQ_WAKE_SGI or Timer IRQ */
static volatile int requested_irq_received[PLATFORM_CORE_COUNT];
/* Used to count number of CPUs woken up by IRQ_WAKE_SGI */
static int multiple_timer_count;
/* Used to count number of CPUs woken up by Timer IRQ */
static int timer_switch_count;
/* Timer step value of a platform */
static unsigned int timer_step_value;
/* Used to program the interrupt time */
static unsigned long long next_int_time;
/* Lock to prevent concurrently modifying next_int_time */
static spinlock_t int_timer_access_lock;
/* Lock to prevent concurrently modifying irq handler data structures */
static spinlock_t irq_handler_lock;
/* Variable to confirm all cores are inside the testcase */
static volatile unsigned int all_cores_inside_test;
/*
* Used by test cases to confirm if the programmed timer is fired. It also
* keeps track of how many timer irq's are received.
*/
static int requested_irq_handler(void *data)
{
unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
unsigned int irq_id = *(unsigned int *) data;
assert(irq_id == tftf_irq_get_my_sgi_num(IRQ_WAKE_SGI) || irq_id == tftf_get_timer_irq());
assert(requested_irq_received[core_pos] == 0);
if (irq_id == tftf_get_timer_irq()) {
spin_lock(&irq_handler_lock);
timer_switch_count++;
spin_unlock(&irq_handler_lock);
}
requested_irq_received[core_pos] = 1;
return 0;
}
/*
* Used by test cases to confirm if the programmed timer is fired. It also
* keeps track of how many WAKE_SGI's are received.
*/
static int multiple_timer_handler(void *data)
{
unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
unsigned int irq_id = *(unsigned int *) data;
assert(irq_id == tftf_irq_get_my_sgi_num(IRQ_WAKE_SGI) || irq_id == tftf_get_timer_irq());
assert(requested_irq_received[core_pos] == 0);
if (irq_id == tftf_irq_get_my_sgi_num(IRQ_WAKE_SGI)) {
spin_lock(&irq_handler_lock);
multiple_timer_count++;
spin_unlock(&irq_handler_lock);
}
requested_irq_received[core_pos] = 1;
return 0;
}
/*
* @Test_Aim@ Validates timer interrupt framework and platform timer driver for
* generation and routing of interrupt to a powered on core.
*
* Returns SUCCESS or waits forever in wfi()
*/
test_result_t test_timer_framework_interrupt(void)
{
unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
int ret;
/* Initialise common variable across tests */
requested_irq_received[core_pos] = 0;
/* Register timer handler to confirm it received the timer interrupt */
ret = tftf_timer_register_handler(requested_irq_handler);
if (ret != 0) {
tftf_testcase_printf("Failed to register timer handler:0x%x\n", ret);
return TEST_RESULT_FAIL;
}
ret = tftf_program_timer(tftf_get_timer_step_value() + 1);
if (ret != 0) {
tftf_testcase_printf("Failed to program timer:0x%x\n", ret);
return TEST_RESULT_FAIL;
}
wfi();
while (!requested_irq_received[core_pos])
;
ret = tftf_timer_unregister_handler();
if (ret != 0) {
tftf_testcase_printf("Failed to unregister timer handler:0x%x\n", ret);
return TEST_RESULT_SKIPPED;
}
return TEST_RESULT_SUCCESS;
}
static test_result_t timer_target_power_down_cpu(void)
{
unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
unsigned int power_state;
unsigned int stateid;
int ret;
unsigned long timer_delay;
tftf_send_event(&cpu_ready[core_pos]);
/* Initialise common variable across tests */
requested_irq_received[core_pos] = 0;
/* Construct the state-id for power down */
ret = tftf_psci_make_composite_state_id(MPIDR_AFFLVL0,
PSTATE_TYPE_POWERDOWN, &stateid);
if (ret != PSCI_E_SUCCESS) {
tftf_testcase_printf("Failed to construct composite state\n");
return TEST_RESULT_FAIL;
}
/* Register timer handler to confirm it received the timer interrupt */
ret = tftf_timer_register_handler(requested_irq_handler);
if (ret != 0) {
tftf_testcase_printf("Failed to register timer handler:0x%x\n", ret);
return TEST_RESULT_FAIL;
}
/* Wait for all cores to be up */
while (!all_cores_inside_test)
;
power_state = tftf_make_psci_pstate(MPIDR_AFFLVL0,
PSTATE_TYPE_POWERDOWN, stateid);
spin_lock(&int_timer_access_lock);
timer_delay = PLAT_SUSPEND_ENTRY_TIME + next_int_time;
next_int_time -= 2 * (timer_step_value + PLAT_SUSPEND_ENTRY_EXIT_TIME);
spin_unlock(&int_timer_access_lock);
ret = tftf_program_timer_and_suspend(timer_delay, power_state,
NULL, NULL);
if (ret != 0) {
tftf_testcase_printf(
"Failed to program timer or suspend CPU: 0x%x\n", ret);
return TEST_RESULT_FAIL;
}
while (!requested_irq_received[core_pos])
;
ret = tftf_timer_unregister_handler();
if (ret != 0) {
tftf_testcase_printf("Failed to unregister timer handler:0x%x\n", ret);
return TEST_RESULT_SKIPPED;
}
return TEST_RESULT_SUCCESS;
}
/*
* @Test_Aim@ Validates routing of timer interrupt to the lowest requested
* timer interrupt core on power down.
*
* Power up all the cores and each requests a timer interrupt lesser than
* the previous requested core by timer step value. Doing this ensures
* at least some cores would be waken by Time IRQ.
*
* Returns SUCCESS if all cores power up on getting the interrupt.
*/
test_result_t test_timer_target_power_down_cpu(void)
{
unsigned int lead_mpid = read_mpidr_el1() & MPID_MASK;
unsigned int cpu_mpid, cpu_node;
unsigned int core_pos;
unsigned int rc;
unsigned int valid_cpu_count;
SKIP_TEST_IF_LESS_THAN_N_CPUS(2);
for (unsigned int i = 0; i < PLATFORM_CORE_COUNT; i++) {
tftf_init_event(&cpu_ready[i]);
requested_irq_received[i] = 0;
}
if (!timer_step_value)
timer_step_value = tftf_get_timer_step_value();
timer_switch_count = 0;
all_cores_inside_test = 0;
/*
* To be sure none of the CPUs do not fall in an atomic slice,
* all CPU's program the timer as close as possible with a time
* difference of twice the sum of step value and suspend entry
* exit time.
*/
next_int_time = 2 * (timer_step_value + PLAT_SUSPEND_ENTRY_EXIT_TIME) * (PLATFORM_CORE_COUNT + 2);
/*
* Preparation step: Power on all cores.
*/
for_each_cpu(cpu_node) {
cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
/* Skip lead CPU as it is already on */
if (cpu_mpid == lead_mpid)
continue;
rc = tftf_cpu_on(cpu_mpid,
(uintptr_t) timer_target_power_down_cpu,
0);
if (rc != PSCI_E_SUCCESS) {
tftf_testcase_printf(
"Failed to power on CPU 0x%x (%d)\n",
cpu_mpid, rc);
return TEST_RESULT_SKIPPED;
}
}
/* Wait for all non-lead CPUs to be ready */
for_each_cpu(cpu_node) {
cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
/* Skip lead CPU */
if (cpu_mpid == lead_mpid)
continue;
core_pos = platform_get_core_pos(cpu_mpid);
tftf_wait_for_event(&cpu_ready[core_pos]);
}
all_cores_inside_test = 1;
rc = timer_target_power_down_cpu();
valid_cpu_count = 0;
/* Wait for all cores to complete the test */
for_each_cpu(cpu_node) {
cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
core_pos = platform_get_core_pos(cpu_mpid);
while (!requested_irq_received[core_pos])
;
valid_cpu_count++;
}
if (timer_switch_count != valid_cpu_count) {
tftf_testcase_printf("Expected timer switch: %d Actual: %d\n",
valid_cpu_count, timer_switch_count);
return TEST_RESULT_FAIL;
}
return TEST_RESULT_SUCCESS;
}
static test_result_t timer_same_interval(void)
{
unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
unsigned int power_state;
unsigned int stateid;
int ret;
tftf_send_event(&cpu_ready[core_pos]);
/* Initialise common variable across tests */
requested_irq_received[core_pos] = 0;
/* Construct the state-id for power down */
ret = tftf_psci_make_composite_state_id(MPIDR_AFFLVL0,
PSTATE_TYPE_POWERDOWN, &stateid);
if (ret != PSCI_E_SUCCESS) {
tftf_testcase_printf("Failed to construct composite state\n");
return TEST_RESULT_FAIL;
}
/* Register timer handler to confirm it received the timer interrupt */
ret = tftf_timer_register_handler(multiple_timer_handler);
if (ret != 0) {
tftf_testcase_printf("Failed to register timer handler:0x%x\n", ret);
return TEST_RESULT_FAIL;
}
/* Wait for all cores to be up */
while (!all_cores_inside_test)
;
/*
* Lets hope with in Suspend entry time + 10ms, at least some of the CPU's
* have same interval
*/
power_state = tftf_make_psci_pstate(MPIDR_AFFLVL0,
PSTATE_TYPE_POWERDOWN, stateid);
ret = tftf_program_timer_and_suspend(PLAT_SUSPEND_ENTRY_TIME + 10,
power_state, NULL, NULL);
if (ret != 0) {
tftf_testcase_printf(
"Failed to program timer or suspend CPU: 0x%x\n", ret);
}
while (!requested_irq_received[core_pos])
;
ret = tftf_timer_unregister_handler();
if (ret != 0) {
tftf_testcase_printf("Failed to unregister timer handler:0x%x\n", ret);
return TEST_RESULT_SKIPPED;
}
return TEST_RESULT_SUCCESS;
}
/*
* @Test_Aim@ Validates routing of timer interrupt when multiple cores
* requested same time.
*
* Power up all the cores and each core requests same time.
*
* Returns SUCCESS if all cores get an interrupt and power up.
*/
test_result_t test_timer_target_multiple_same_interval(void)
{
unsigned int lead_mpid = read_mpidr_el1() & MPID_MASK;
unsigned int cpu_mpid, cpu_node;
unsigned int core_pos;
unsigned int rc;
SKIP_TEST_IF_LESS_THAN_N_CPUS(2);
for (unsigned int i = 0; i < PLATFORM_CORE_COUNT; i++) {
tftf_init_event(&cpu_ready[i]);
requested_irq_received[i] = 0;
}
multiple_timer_count = 0;
all_cores_inside_test = 0;
/*
* Preparation step: Power on all cores.
*/
for_each_cpu(cpu_node) {
cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
/* Skip lead CPU as it is already on */
if (cpu_mpid == lead_mpid)
continue;
rc = tftf_cpu_on(cpu_mpid,
(uintptr_t) timer_same_interval,
0);
if (rc != PSCI_E_SUCCESS) {
tftf_testcase_printf(
"Failed to power on CPU 0x%x (%d)\n",
cpu_mpid, rc);
return TEST_RESULT_SKIPPED;
}
}
/* Wait for all non-lead CPUs to be ready */
for_each_cpu(cpu_node) {
cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
/* Skip lead CPU */
if (cpu_mpid == lead_mpid)
continue;
core_pos = platform_get_core_pos(cpu_mpid);
tftf_wait_for_event(&cpu_ready[core_pos]);
}
core_pos = platform_get_core_pos(lead_mpid);
/* Initialise common variable across tests */
requested_irq_received[core_pos] = 0;
all_cores_inside_test = 1;
rc = timer_same_interval();
/* Wait for all cores to complete the test */
for_each_cpu(cpu_node) {
cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
core_pos = platform_get_core_pos(cpu_mpid);
while (!requested_irq_received[core_pos])
;
}
if (rc != TEST_RESULT_SUCCESS)
return rc;
/* At least 2 CPUs requests should fall in same timer period. */
return multiple_timer_count ? TEST_RESULT_SUCCESS : TEST_RESULT_SKIPPED;
}
static test_result_t do_stress_test(void)
{
unsigned int power_state;
unsigned int stateid;
unsigned int timer_int_interval;
unsigned int verify_cancel;
unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
unsigned long long end_time;
unsigned long long current_time;
int ret;
tftf_send_event(&cpu_ready[core_pos]);
end_time = read_cntpct_el0() + read_cntfrq_el0() * 10;
/* Construct the state-id for power down */
ret = tftf_psci_make_composite_state_id(MPIDR_AFFLVL0,
PSTATE_TYPE_POWERDOWN, &stateid);
if (ret != PSCI_E_SUCCESS) {
tftf_testcase_printf("Failed to construct composite state\n");
return TEST_RESULT_FAIL;
}
/* Register a handler to confirm its woken by programmed interrupt */
ret = tftf_timer_register_handler(requested_irq_handler);
if (ret != 0) {
tftf_testcase_printf("Failed to register timer handler:0x%x\n", ret);
return TEST_RESULT_FAIL;
}
do {
current_time = read_cntpct_el0();
if (current_time > end_time)
break;
timer_int_interval = 1 + rand() % 5;
verify_cancel = rand() % 5;
requested_irq_received[core_pos] = 0;
/*
* If verify_cancel == 0, cancel the programmed timer. As it can
* take values from 0 to 4, we will be cancelling only 20% of
* times.
*/
if (!verify_cancel) {
ret = tftf_program_timer(PLAT_SUSPEND_ENTRY_TIME +
timer_int_interval);
if (ret != 0) {
tftf_testcase_printf("Failed to program timer: "
"0x%x\n", ret);
return TEST_RESULT_FAIL;
}
ret = tftf_cancel_timer();
if (ret != 0) {
tftf_testcase_printf("Failed to cancel timer: "
"0x%x\n", ret);
return TEST_RESULT_FAIL;
}
} else {
power_state = tftf_make_psci_pstate(
MPIDR_AFFLVL0, PSTATE_TYPE_POWERDOWN,
stateid);
ret = tftf_program_timer_and_suspend(
PLAT_SUSPEND_ENTRY_TIME + timer_int_interval,
power_state, NULL, NULL);
if (ret != 0) {
tftf_testcase_printf("Failed to program timer "
"or suspend: 0x%x\n", ret);
return TEST_RESULT_FAIL;
}
if (!requested_irq_received[core_pos]) {
/*
* Cancel the interrupt as the CPU has been
* woken by some other interrupt
*/
ret = tftf_cancel_timer();
if (ret != 0) {
tftf_testcase_printf("Failed to cancel timer:0x%x\n", ret);
return TEST_RESULT_FAIL;
}
}
}
} while (1);
ret = tftf_timer_unregister_handler();
if (ret != 0) {
tftf_testcase_printf("Failed to unregister timer handler:0x%x\n", ret);
return TEST_RESULT_SKIPPED;
}
return TEST_RESULT_SUCCESS;
}
/*
* @Test_Aim@ Stress tests timer framework by requesting combination of
* timer requests with SUSPEND and cancel calls.
*
* Returns SUCCESS if all the cores successfully wakes up from suspend
* and returns back to the framework.
*/
test_result_t stress_test_timer_framework(void)
{
unsigned int lead_mpid = read_mpidr_el1() & MPID_MASK;
unsigned int cpu_mpid, cpu_node;
unsigned int core_pos;
unsigned int rc;
for (unsigned int i = 0; i < PLATFORM_CORE_COUNT; i++) {
tftf_init_event(&cpu_ready[i]);
requested_irq_received[i] = 0;
}
/*
* Preparation step: Power on all cores.
*/
for_each_cpu(cpu_node) {
cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
/* Skip lead CPU as it is already on */
if (cpu_mpid == lead_mpid)
continue;
rc = tftf_cpu_on(cpu_mpid,
(uintptr_t) do_stress_test,
0);
if (rc != PSCI_E_SUCCESS) {
tftf_testcase_printf(
"Failed to power on CPU 0x%x (%d)\n",
cpu_mpid, rc);
return TEST_RESULT_SKIPPED;
}
}
/* Wait for all non-lead CPUs to be ready */
for_each_cpu(cpu_node) {
cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
/* Skip lead CPU */
if (cpu_mpid == lead_mpid)
continue;
core_pos = platform_get_core_pos(cpu_mpid);
tftf_wait_for_event(&cpu_ready[core_pos]);
}
return do_stress_test();
}