| /* |
| * Copyright (c) 2018, Arm Limited. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <amu.h> |
| #include <arch.h> |
| #include <arch_helpers.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <irq.h> |
| #include <plat_topology.h> |
| #include <platform.h> |
| #include <power_management.h> |
| #include <tftf_lib.h> |
| #include <timer.h> |
| |
| #define SUSPEND_TIME_1_SEC 1000 |
| |
| static volatile int wakeup_irq_received[PLATFORM_CORE_COUNT]; |
| |
| /* Dummy timer handler that sets a flag to check it has been called. */ |
| static int suspend_wakeup_handler(void *data) |
| { |
| u_register_t core_mpid = read_mpidr_el1() & MPID_MASK; |
| unsigned int core_pos = platform_get_core_pos(core_mpid); |
| |
| assert(wakeup_irq_received[core_pos] == 0); |
| |
| wakeup_irq_received[core_pos] = 1; |
| |
| return 0; |
| } |
| |
| /* |
| * Helper function to suspend a CPU to power level 0 and wake it up with |
| * a timer. |
| */ |
| static test_result_t suspend_and_resume_this_cpu(void) |
| { |
| uint32_t stateid; |
| int psci_ret; |
| test_result_t result = TEST_RESULT_SUCCESS; |
| |
| u_register_t core_mpid = read_mpidr_el1() & MPID_MASK; |
| unsigned int core_pos = platform_get_core_pos(core_mpid); |
| |
| /* Prepare wakeup timer. IRQs need to be enabled. */ |
| wakeup_irq_received[core_pos] = 0; |
| |
| tftf_timer_register_handler(suspend_wakeup_handler); |
| |
| /* Program timer to fire interrupt after timer expires */ |
| tftf_program_timer(SUSPEND_TIME_1_SEC); |
| |
| /* |
| * Suspend the calling CPU to power level 0 and power |
| * state. |
| */ |
| psci_ret = tftf_psci_make_composite_state_id(PSTATE_AFF_LVL_0, |
| PSTATE_TYPE_POWERDOWN, |
| &stateid); |
| if (psci_ret != PSCI_E_SUCCESS) { |
| mp_printf("Failed to make composite state ID @ CPU %d. rc = %x\n", |
| core_pos, psci_ret); |
| result = TEST_RESULT_FAIL; |
| } else { |
| unsigned int power_state = tftf_make_psci_pstate(PSTATE_AFF_LVL_0, |
| PSTATE_TYPE_POWERDOWN, stateid); |
| psci_ret = tftf_cpu_suspend(power_state); |
| |
| if (!wakeup_irq_received[core_pos]) { |
| mp_printf("Didn't receive wakeup IRQ in CPU %d.\n", |
| core_pos); |
| result = TEST_RESULT_FAIL; |
| } |
| |
| if (psci_ret != PSCI_E_SUCCESS) { |
| mp_printf("Failed to suspend from CPU %d. ret: %x\n", |
| core_pos, psci_ret); |
| result = TEST_RESULT_FAIL; |
| } |
| } |
| |
| /* Wake up. Remove timer after waking up.*/ |
| tftf_cancel_timer(); |
| tftf_timer_unregister_handler(); |
| |
| return result; |
| } |
| |
| /* |
| * Helper function that checks whether the value of a group0 counter is valid |
| * or not. The first 3 counters (0,1,2) cannot have values of zero but the last |
| * counter that counts "memory stall cycles" can have a value of zero, under |
| * certain circumstances. |
| * |
| * Return values: |
| * 0 = valid counter value |
| * -1 = invalid counter value |
| */ |
| static int amu_group0_cnt_valid(unsigned int idx, uint64_t value) |
| { |
| int answer = 0; |
| |
| if ((idx <= 2) && (value == 0)) |
| answer = -1; |
| |
| return answer; |
| } |
| |
| /* |
| * Check that group0 counters are valid. As EL3 has enabled the counters before |
| * the first entry to NS world, the counters should have increased by the time |
| * we reach this test case. |
| */ |
| test_result_t test_amu_valid_ctr(void) |
| { |
| int i; |
| |
| if (!amu_supported()) |
| return TEST_RESULT_SKIPPED; |
| |
| /* If counters are not enabled, then skip the test */ |
| if (read_amcntenset0_el0() != AMU_GROUP0_COUNTERS_MASK) |
| return TEST_RESULT_SKIPPED; |
| |
| for (i = 0; i < AMU_GROUP0_NR_COUNTERS; i++) { |
| uint64_t value; |
| |
| value = amu_group0_cnt_read(i); |
| if (amu_group0_cnt_valid(i, value)) { |
| tftf_testcase_printf("Group0 counter %d has invalid value %lld\n", i, value); |
| return TEST_RESULT_FAIL; |
| } |
| } |
| |
| return TEST_RESULT_SUCCESS; |
| } |
| |
| /* |
| * Check that the counters are non-decreasing during |
| * a suspend/resume cycle. |
| */ |
| test_result_t test_amu_suspend_resume(void) |
| { |
| uint64_t group0_ctrs[AMU_GROUP0_MAX_NR_COUNTERS]; |
| int i; |
| |
| if (!amu_supported()) |
| return TEST_RESULT_SKIPPED; |
| |
| /* If counters are not enabled, then skip the test */ |
| if (read_amcntenset0_el0() != AMU_GROUP0_COUNTERS_MASK) |
| return TEST_RESULT_SKIPPED; |
| |
| /* Save counters values before suspend */ |
| for (i = 0; i < AMU_GROUP0_NR_COUNTERS; i++) |
| group0_ctrs[i] = amu_group0_cnt_read(i); |
| |
| /* Suspend/resume current core */ |
| suspend_and_resume_this_cpu(); |
| |
| /* |
| * Check if counter values are >= than the stored values. |
| * If they are not, the AMU context save/restore in EL3 is buggy. |
| */ |
| for (i = 0; i < AMU_GROUP0_NR_COUNTERS; i++) { |
| uint64_t value; |
| |
| value = amu_group0_cnt_read(i); |
| if (value < group0_ctrs[i]) { |
| tftf_testcase_printf("Invalid counter value: before: %llx, after: %llx\n", |
| (unsigned long long)group0_ctrs[i], |
| (unsigned long long)value); |
| return TEST_RESULT_FAIL; |
| } |
| } |
| |
| return TEST_RESULT_SUCCESS; |
| } |