blob: 478e40ca6282b4511cdef4d8b7c3194655a01e2f [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/mmio.h"
#include "sw/device/lib/dif/dif_alert_handler.h"
#include "sw/device/lib/dif/dif_aon_timer.h"
#include "sw/device/lib/dif/dif_pwrmgr.h"
#include "sw/device/lib/dif/dif_rv_core_ibex.h"
#include "sw/device/lib/dif/dif_rv_timer.h"
#include "sw/device/lib/runtime/ibex.h"
#include "sw/device/lib/runtime/irq.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/testing/alert_handler_testutils.h"
#include "sw/device/lib/testing/aon_timer_testutils.h"
#include "sw/device/lib/testing/pwrmgr_testutils.h"
#include "sw/device/lib/testing/test_framework/check.h"
#include "sw/device/lib/testing/test_framework/ottf_isrs.h"
#include "sw/device/lib/testing/test_framework/ottf_main.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
#include "sw/device/lib/testing/autogen/isr_testutils.h"
OTTF_DEFINE_TEST_CONFIG();
typedef void (*isr_handler)(void);
static volatile isr_handler expected_isr_handler;
static volatile dif_rv_core_ibex_nmi_state_t nmi_state;
static volatile dif_alert_handler_class_state_t alert_state;
static volatile bool nmi_fired = false;
static volatile bool ext_irq_fired = false;
static volatile bool irq_is_pending = false;
static dif_pwrmgr_t pwrmgr;
static dif_rv_core_ibex_t rv_core_ibex;
static dif_aon_timer_t aon_timer;
static dif_alert_handler_t alert_handler;
/**
* Program the alert handler to escalate on alerts upto phase 2 (i.e. reset)
* but at the phase 1 (i.e. wipe secrets) the NMI interrupt handler should clear
* the escalation.
*/
enum {
kEscalationPhase0Micros = 100 * 1000, // 10 ms
kEscalationPhase2Micros = 100, // 100 us
kIrqDeadlineMicros = 10, // 10 us
kWdogBarkMicros = 500, // 500 us
};
/**
* Defines how many times the two NMI tests are repeated back to back.
*/
const int kNumTestIterations = 3;
static void alert_handler_config(void) {
dif_alert_handler_escalation_phase_t esc_phases[] = {
{.phase = kDifAlertHandlerClassStatePhase0,
.signal = 0,
.duration_cycles =
alert_handler_testutils_get_cycles_from_us(kEscalationPhase0Micros)},
{.phase = kDifAlertHandlerClassStatePhase2,
.signal = 3,
.duration_cycles = alert_handler_testutils_get_cycles_from_us(
kEscalationPhase2Micros)}};
dif_alert_handler_class_config_t class_config[] = {{
.auto_lock_accumulation_counter = kDifToggleDisabled,
.accumulator_threshold = 0,
.irq_deadline_cycles =
alert_handler_testutils_get_cycles_from_us(kIrqDeadlineMicros),
.escalation_phases = esc_phases,
.escalation_phases_len = ARRAYSIZE(esc_phases),
.crashdump_escalation_phase = kDifAlertHandlerClassStatePhase3,
}};
dif_alert_handler_alert_t alerts[] = {kTopEarlgreyAlertIdPwrmgrAonFatalFault};
dif_alert_handler_class_t alert_classes[] = {kDifAlertHandlerClassA};
dif_alert_handler_class_t classes[] = {kDifAlertHandlerClassA};
dif_alert_handler_config_t config = {
.alerts = alerts,
.alert_classes = alert_classes,
.alerts_len = ARRAYSIZE(alerts),
.classes = classes,
.class_configs = class_config,
.classes_len = ARRAYSIZE(class_config),
.ping_timeout = 0,
};
alert_handler_testutils_configure_all(&alert_handler, config,
/*lock=*/kDifToggleDisabled);
}
/**
* Handle the wdog bark NMI IRQ.
*/
static void wdog_handler(void) {
bool is_pending;
// The watchdog bark external interrupt is also connected to the NMI input
// of rv_core_ibex. We therefore expect the interrupt to be pending on the
// peripheral side (the check is done later in the test function).
CHECK_DIF_OK(dif_aon_timer_irq_is_pending(
&aon_timer, kDifAonTimerIrqWdogTimerBark, &is_pending));
irq_is_pending = is_pending;
// Stop the watchdog.
CHECK_DIF_OK(dif_aon_timer_watchdog_stop(&aon_timer));
// In order to handle the NMI we need to acknowledge the interrupt status
// bit it at the peripheral side. Note that this test does not use the
// PLIC, so there is nothing to acknowledge on the PLIC side. We are hence
// not using the isr_testutils_aon_timer_isr function here.
CHECK_DIF_OK(
dif_aon_timer_irq_acknowledge(&aon_timer, kDifAonTimerIrqWdogTimerBark));
}
/**
* Handle the alert handler NMI IRQ.
*/
static void alert_handler_handler(void) {
bool can_clear;
dif_alert_handler_class_state_t state;
// In this particular case the alert handler is configured to send out an NMI
// when class A enters escalation phase 0. We therefore get the alert handler
// state and check later in the test function whether class A has indeed
// escalated.
CHECK_DIF_OK(dif_alert_handler_get_class_state(
&alert_handler, kDifAlertHandlerClassA, &state));
alert_state = state;
// We expect the class to be clearable since it has not been locked.
CHECK_DIF_OK(dif_alert_handler_escalation_can_clear(
&alert_handler, kDifAlertHandlerClassA, &can_clear));
CHECK(can_clear, "Alert handler is locked.");
// Clear the class in order to stop the alert handler from sending
// an NMI to the processor.
CHECK_DIF_OK(dif_alert_handler_escalation_clear(&alert_handler,
kDifAlertHandlerClassA));
}
/**
* Execute the wdog bark NMI interrupt test.
*/
static void wdog_nmi_test(void) {
// Setup to handle the incoming wdog NMI IRQ.
expected_isr_handler = wdog_handler;
nmi_fired = false;
nmi_state = (dif_rv_core_ibex_nmi_state_t){0};
// Setup the wdog bark interrupt.
uint32_t count_cycles =
aon_timer_testutils_get_aon_cycles_from_us(kWdogBarkMicros);
aon_timer_testutils_watchdog_config(&aon_timer,
/*bark_cycles=*/count_cycles,
/*bite_cycles=*/UINT32_MAX,
/*pause_in_sleep=*/false);
LOG_INFO("Wait for aon_timer NMI");
IBEX_SPIN_FOR(nmi_fired, kWdogBarkMicros * 2);
// We expect the watchdog bark interrupt to be pending on the peripheral side.
CHECK(irq_is_pending, "Expected watchdog bark interrupt to be pending");
// Check that the wdog NMI was enabled and barked during the NMI IRQ.
CHECK(nmi_state.wdog_enabled && nmi_state.wdog_barked,
"Wdog NMI state not expected:\n\t"
"wdog_enable:%x\n\twdog_raised:%"
"x\n",
nmi_state.wdog_enabled, nmi_state.wdog_barked);
// Check that the wdog NMI is enable and is cleared.
CHECK_DIF_OK(dif_rv_core_ibex_get_nmi_state(
&rv_core_ibex, (dif_rv_core_ibex_nmi_state_t *)&nmi_state));
CHECK(nmi_state.wdog_enabled && !nmi_state.wdog_barked,
"Wdog NMI state not expected:\n\t"
"wdog_enable:%x\n\twdog_raised:%"
"x\n",
nmi_state.wdog_enabled, nmi_state.wdog_barked);
}
/**
* Execute the alert handler NMI interrupt test.
*/
static void alert_handler_nmi_test(void) {
alert_handler_config();
// Setup to handle the incoming alert handler NMI IRQ.
expected_isr_handler = alert_handler_handler;
nmi_fired = false;
nmi_state = (dif_rv_core_ibex_nmi_state_t){0};
// Trigger the alert handler to escalate.
CHECK_DIF_OK(dif_pwrmgr_alert_force(&pwrmgr, kDifPwrmgrAlertFatalFault));
LOG_INFO("Wait for alert NMI");
IBEX_SPIN_FOR(nmi_fired, kEscalationPhase0Micros);
// We expect class A to be in escalation phase 0 at this point.
CHECK(alert_state == kDifAlertHandlerClassStatePhase0,
"Alert handler class A is expected to be in phase 0.");
// Check that the alert handler NMI was enabled and raised an alert during the
// NMI IRQ.
CHECK(nmi_state.alert_enabled && nmi_state.alert_raised,
"Alert handler NMI state not expected:\n\t"
"alert_enable:%x\n\talert_raised:%x\n",
nmi_state.alert_enabled, nmi_state.alert_raised);
// Check that the alert handler NMI is enabled and is cleared.
CHECK_DIF_OK(dif_rv_core_ibex_get_nmi_state(
&rv_core_ibex, (dif_rv_core_ibex_nmi_state_t *)&nmi_state));
CHECK(nmi_state.alert_enabled && !nmi_state.alert_raised,
"Alert handler NMI state not expected:\n\t"
"alert_enable:%x\n\talert_raised:%x\n",
nmi_state.alert_enabled, nmi_state.alert_raised);
}
/**
* OTTF external NMI internal IRQ handler.
* This functions overrides the OTTF weak definition.
* It calls the `expected_isr_handler` function pointer that is previously set
* by the test to handler the specific peripheral IRQ.
*/
void ottf_external_nmi_handler(void) {
nmi_fired = true;
expected_isr_handler();
CHECK_DIF_OK(dif_rv_core_ibex_get_nmi_state(
&rv_core_ibex, (dif_rv_core_ibex_nmi_state_t *)&nmi_state));
CHECK_DIF_OK(dif_rv_core_ibex_clear_nmi_state(&rv_core_ibex,
kDifRvCoreIbexNmiSourceAll));
}
/**
* OTTF external IRQ handler
* This functions overrides the OTTF weak definition.
*/
void ottf_external_isr(void) { ext_irq_fired = true; }
/**
* Initialized all peripherals used in this test.
*/
void init_peripherals(void) {
mmio_region_t addr =
mmio_region_from_addr(TOP_EARLGREY_RV_CORE_IBEX_CFG_BASE_ADDR);
CHECK_DIF_OK(dif_rv_core_ibex_init(addr, &rv_core_ibex));
addr = mmio_region_from_addr(TOP_EARLGREY_AON_TIMER_AON_BASE_ADDR);
CHECK_DIF_OK(dif_aon_timer_init(addr, &aon_timer));
addr = mmio_region_from_addr(TOP_EARLGREY_ALERT_HANDLER_BASE_ADDR);
CHECK_DIF_OK(dif_alert_handler_init(addr, &alert_handler));
addr = mmio_region_from_addr(TOP_EARLGREY_PWRMGR_AON_BASE_ADDR);
CHECK_DIF_OK(dif_pwrmgr_init(addr, &pwrmgr));
}
/**
* This test do the following steps:
* - Trigger a watchdog bark to generate a NMI.
* - Check rv_core_ibex's NMI interrupt register and clear the interrupt.
* - Repeat the previous steps with alert handler.
*/
bool test_main(void) {
// Disable external interrupts via the PLIC entirely.
// The NMI should still get through to the processor regardless.
irq_global_ctrl(false);
irq_external_ctrl(false);
init_peripherals();
// Enable both NMIs.
CHECK_DIF_OK(
dif_rv_core_ibex_enable_nmi(&rv_core_ibex, kDifRvCoreIbexNmiSourceWdog));
CHECK_DIF_OK(
dif_rv_core_ibex_enable_nmi(&rv_core_ibex, kDifRvCoreIbexNmiSourceAlert));
// Execute both NMI tests several times back-to-back to verify that
// the causes are successfully cleared in the NMI handlers.
for (int k = 0; k < kNumTestIterations; ++k) {
LOG_INFO("Test iteration %d begin", k);
alert_handler_nmi_test();
wdog_nmi_test();
LOG_INFO("Test iteration %d end", k);
}
// Double check that no external interrupts have occurred.
CHECK(ext_irq_fired == false, "Unexpected external interrupt triggered.");
// Double check that the system has not been reset due to escalation and that
// the reset reason is still POR.
pwrmgr_testutils_is_wakeup_reason(&pwrmgr, 0);
return true;
}