blob: 2a968d100ac09f91deef4c65bccabdaac418892d [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include "sw/device/lib/base/math.h"
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/dif/dif_aon_timer.h"
#include "sw/device/lib/dif/dif_rv_plic.h"
#include "sw/device/lib/dif/dif_rv_timer.h"
#include "sw/device/lib/runtime/irq.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/testing/aon_timer_testutils.h"
#include "sw/device/lib/testing/rand_testutils.h"
#include "sw/device/lib/testing/rv_plic_testutils.h"
#include "sw/device/lib/testing/test_framework/FreeRTOSConfig.h"
#include "sw/device/lib/testing/test_framework/check.h"
#include "sw/device/lib/testing/test_framework/ottf_main.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
OTTF_DEFINE_TEST_CONFIG();
static const uint32_t kPlicTarget = kTopEarlgreyPlicTargetIbex0;
static const uint32_t kTickFreqHz = 1000 * 1000; // 1Mhz / 1us
static dif_aon_timer_t aon_timer;
static dif_rv_timer_t rv_timer;
static dif_rv_plic_t plic;
static volatile dif_aon_timer_irq_t irq;
static volatile top_earlgrey_plic_peripheral_t peripheral;
static volatile uint64_t irq_tick;
// TODO:(lowrisc/opentitan#9984): Add timing API to the test framework
/**
* Initialize the rv timer to count the tick.
*
* The `ibex_mcycle_read` function uses the `mcycle` register to count
* instructions cycles executed and measure time elapsed between two instants,
* however it stops counting when the `wfi` is called. As this test is based on
* the `wfi` instruction the best approach then to measure the time elapsed is
* to use the mtime register, which is basically attached to rv_timer in the
* opentitan.
* https://docs.opentitan.org/hw/ip/rv_timer/doc/
*
* This is fine due to the test running in a single thread of execution,
* however, care should be taken in case it changes. OTTF configures the
* timer in vPortSetupTimerInterrupt, and re-initialising it inside the test
* could potentially break or cause unexpected behaviour of the test framework.
*/
static_assert(configUSE_PREEMPTION == 0,
"rv_timer may be initialized already by FreeRtos");
static void tick_init(void) {
CHECK_DIF_OK(dif_rv_timer_init(
mmio_region_from_addr(TOP_EARLGREY_RV_TIMER_BASE_ADDR), &rv_timer));
CHECK_DIF_OK(dif_rv_timer_reset(&rv_timer));
// Compute and set tick parameters (i.e., step, prescale, etc.).
dif_rv_timer_tick_params_t tick_params;
CHECK_DIF_OK(dif_rv_timer_approximate_tick_params(kClockFreqPeripheralHz,
kTickFreqHz, &tick_params));
CHECK_DIF_OK(
dif_rv_timer_set_tick_params(&rv_timer, kPlicTarget, tick_params));
CHECK_DIF_OK(dif_rv_timer_counter_set_enabled(&rv_timer, kPlicTarget,
kDifToggleEnabled));
}
/**
* Read the current rv timer count/tick.
*
* @return The current rv timer count.
*/
static uint64_t tick_count_get(void) {
uint64_t tick = 0;
CHECK_DIF_OK(dif_rv_timer_counter_read(&rv_timer, kPlicTarget, &tick));
return tick;
}
/**
* Execute the aon timer interrupt test.
*/
static void execute_test(dif_aon_timer_t *aon_timer, uint64_t irq_time_us,
dif_aon_timer_irq_t expected_irq) {
// The interrupt time should be `irq_time_us ±5%`.
uint64_t variation = udiv64_slow(irq_time_us * 5, 100, NULL);
CHECK(variation > 0);
uint64_t sleep_range_h = irq_time_us + variation;
uint64_t sleep_range_l = irq_time_us - variation;
// Add 600 cpu cycles of overhead to cover irq handling.
sleep_range_h += udiv64_slow(600 * 1000000, kClockFreqCpuHz, NULL);
uint32_t count_cycles =
aon_timer_testutils_get_aon_cycles_from_us(irq_time_us);
LOG_INFO("Setting interrupt for %u us (%u cycles)", (uint32_t)irq_time_us,
count_cycles);
// Set the default value to a different value than expected.
peripheral = kTopEarlgreyPlicPeripheralUnknown;
irq = kDifAonTimerIrqWdogTimerBark;
if (expected_irq == kDifAonTimerIrqWkupTimerExpired) {
// Setup the wake up interrupt.
aon_timer_testutils_wakeup_config(aon_timer, count_cycles);
} else {
// Change the default value since the expectation is different.
irq = kDifAonTimerIrqWkupTimerExpired;
// Setup the wdog bark interrupt.
aon_timer_testutils_watchdog_config(aon_timer,
/*bark_cycles=*/count_cycles,
/*bite_cycles=*/count_cycles * 4,
/*pause_in_sleep=*/false);
}
// Capture the current tick to measure the time the IRQ will take.
uint32_t start_tick = tick_count_get();
uint32_t time_elapsed = 0;
// Disable interrupts to be certain interrupt doesn't occur between while
// condition check and `wait_for_interrupt` (so WFI misses that interrupt).
irq_global_ctrl(false);
// Only enter WFI loop if we haven't already seen the interrupt.
if (peripheral != kTopEarlgreyPlicPeripheralAonTimerAon) {
do {
wait_for_interrupt();
// WFI ignores global interrupt enable, so enable it now and then
// immediately disable it. If there is an interrupt pending it will be
// taken here between the enable and disable. This confines the interrupt
// to a known place avoiding missed wakeup issues.
irq_global_ctrl(true);
irq_global_ctrl(false);
time_elapsed = irq_tick - start_tick;
} while (peripheral != kTopEarlgreyPlicPeripheralAonTimerAon &&
time_elapsed < sleep_range_h);
}
CHECK(time_elapsed < sleep_range_h && time_elapsed > sleep_range_l,
"Timer took %u usec which is not in the range %u usec and %u usec",
(uint32_t)time_elapsed, (uint32_t)sleep_range_l,
(uint32_t)sleep_range_h);
CHECK(peripheral == kTopEarlgreyPlicPeripheralAonTimerAon,
"Interrupt from incorrect peripheral: exp = %d, obs = %d",
kTopEarlgreyPlicPeripheralAonTimerAon, peripheral);
CHECK(irq == expected_irq, "Interrupt type incorrect: exp = %d, obs = %d",
kDifAonTimerIrqWkupTimerExpired, irq);
LOG_INFO("Test completed in %u us", (uint32_t)irq_time_us);
}
/**
* External interrupt handler.
*/
void ottf_external_isr(void) {
// Calc the elapsed time since the activation of the IRQ.
irq_tick = tick_count_get();
dif_rv_plic_irq_id_t irq_id;
CHECK_DIF_OK(dif_rv_plic_irq_claim(&plic, kPlicTarget, &irq_id));
peripheral = (top_earlgrey_plic_peripheral_t)
top_earlgrey_plic_interrupt_for_peripheral[irq_id];
if (peripheral == kTopEarlgreyPlicPeripheralAonTimerAon) {
irq =
(dif_aon_timer_irq_t)(irq_id -
(dif_rv_plic_irq_id_t)
kTopEarlgreyPlicIrqIdAonTimerAonWkupTimerExpired);
if (irq_id == kTopEarlgreyPlicIrqIdAonTimerAonWkupTimerExpired) {
CHECK_DIF_OK(dif_aon_timer_wakeup_stop(&aon_timer));
} else if (irq_id == kTopEarlgreyPlicIrqIdAonTimerAonWdogTimerBark) {
CHECK_DIF_OK(dif_aon_timer_watchdog_stop(&aon_timer));
}
CHECK_DIF_OK(dif_aon_timer_irq_acknowledge(&aon_timer, irq));
}
// Complete the IRQ by writing the IRQ source to the Ibex specific CC.
// register
CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic, kPlicTarget, irq_id));
}
bool test_main(void) {
// Enable global and external IRQ at Ibex.
irq_global_ctrl(true);
irq_external_ctrl(true);
// Initialize the rv timer to compute the tick.
tick_init();
// Initialize aon timer.
CHECK_DIF_OK(dif_aon_timer_init(
mmio_region_from_addr(TOP_EARLGREY_AON_TIMER_AON_BASE_ADDR), &aon_timer));
// Initialize the PLIC.
mmio_region_t plic_base_addr =
mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR);
CHECK_DIF_OK(dif_rv_plic_init(plic_base_addr, &plic));
// Enable all the AON interrupts used in this test.
rv_plic_testutils_irq_range_enable(
&plic, kPlicTarget, kTopEarlgreyPlicIrqIdAonTimerAonWkupTimerExpired,
kTopEarlgreyPlicIrqIdAonTimerAonWdogTimerBark);
// Executing the test using random time bounds calculated from the clock
// frequency to make sure the aon timer is generating the interrupt after the
// chosen time and there's no error in the reference time measurement. This
// calculation is required as the various platforms used for testing have
// differing clocks frequencies. A minimum amount of cycles is required for
// the interrupt to note the elapsed time so the test fails with an
// unacceptable time variance when the sleep time is too low.
enum {
kMinCycles = 30 * 1000,
kMaxCycles = 45 * 1000,
};
uint64_t low_time_range =
udiv64_slow(kMinCycles * (uint64_t)1000000, kClockFreqCpuHz, NULL);
uint64_t high_time_range =
udiv64_slow(kMaxCycles * (uint64_t)1000000, kClockFreqCpuHz, NULL);
// no error in the reference time measurement.
uint64_t irq_time =
rand_testutils_gen32_range(low_time_range, high_time_range);
execute_test(&aon_timer, irq_time,
/*expected_irq=*/kDifAonTimerIrqWkupTimerExpired);
irq_time = rand_testutils_gen32_range(low_time_range, high_time_range);
execute_test(&aon_timer, irq_time,
/*expected_irq=*/kDifAonTimerIrqWdogTimerBark);
return true;
}