| // 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_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_rstmgr.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/alert_handler_testutils.h" |
| #include "sw/device/lib/testing/aon_timer_testutils.h" |
| #include "sw/device/lib/testing/rstmgr_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(); |
| |
| /** |
| * Program the alert handler to escalate on alerts upto phase 2 (i.e. reset) but |
| * the phase 1 (i.e. wipe secrets) should occur and last during the time the |
| * wdog is programmed to bark. |
| */ |
| enum { |
| kWdogBarkMicros = 3 * 100, // 300 us |
| kWdogBiteMicros = 4 * 100, // 400 us |
| kEscalationPhase0Micros = 1 * 100, // 100 us |
| // The cpu value is set slightly larger, since if they are the same it is |
| // possible busy_spin_micros in execute_test can complete before the |
| // interrupt. |
| kEscalationPhase0MicrosCpu = kEscalationPhase0Micros + 20, // 120 us |
| kEscalationPhase1Micros = 5 * 100, // 500 us |
| kEscalationPhase2Micros = 100, // 100 us |
| }; |
| |
| static_assert( |
| kWdogBarkMicros < kWdogBiteMicros && |
| kWdogBarkMicros > kEscalationPhase0Micros && |
| kWdogBarkMicros < (kEscalationPhase0Micros + kEscalationPhase1Micros) && |
| kWdogBiteMicros < (kEscalationPhase0Micros + kEscalationPhase1Micros), |
| "The wdog bark and bite shall happens during the escalation phase 1"); |
| |
| /** |
| * Objects to access the peripherals used in this test via dif API. |
| */ |
| static const uint32_t kPlicTarget = kTopEarlgreyPlicTargetIbex0; |
| static dif_aon_timer_t aon_timer; |
| static dif_rv_plic_t plic; |
| static dif_pwrmgr_t pwrmgr; |
| static dif_rstmgr_t rstmgr; |
| static dif_alert_handler_t alert_handler; |
| |
| /** |
| * External ISR. |
| * |
| * Handles all peripheral interrupts on Ibex. PLIC asserts an external interrupt |
| * line to the CPU, which results in a call to this OTTF ISR. This ISR |
| * overrides the default OTTF implementation. |
| */ |
| void ottf_external_isr(void) { |
| dif_rv_plic_irq_id_t irq_id; |
| CHECK_DIF_OK(dif_rv_plic_irq_claim(&plic, kPlicTarget, &irq_id)); |
| |
| top_earlgrey_plic_peripheral_t peripheral = (top_earlgrey_plic_peripheral_t) |
| top_earlgrey_plic_interrupt_for_peripheral[irq_id]; |
| |
| if (peripheral == kTopEarlgreyPlicPeripheralAonTimerAon) { |
| uint32_t irq = |
| (irq_id - (dif_rv_plic_irq_id_t) |
| kTopEarlgreyPlicIrqIdAonTimerAonWkupTimerExpired); |
| |
| // We should not get aon timer interrupts since escalation suppresses them. |
| LOG_ERROR("Unexpected aon timer interrupt %d", irq); |
| } else if (peripheral == kTopEarlgreyPlicPeripheralAlertHandler) { |
| // Check the class. |
| dif_alert_handler_class_state_t state; |
| CHECK_DIF_OK(dif_alert_handler_get_class_state( |
| &alert_handler, kDifAlertHandlerClassA, &state)); |
| CHECK(state == kDifAlertHandlerClassStatePhase0, "Wrong phase %d", state); |
| |
| uint32_t irq = |
| (irq_id - |
| (dif_rv_plic_irq_id_t)kTopEarlgreyPlicIrqIdAlertHandlerClassa); |
| |
| // Deals with the alert cause: we expect it to be from the pwrmgr. |
| dif_alert_handler_alert_t alert = kTopEarlgreyAlertIdPwrmgrAonFatalFault; |
| bool is_cause = false; |
| CHECK_DIF_OK( |
| dif_alert_handler_alert_is_cause(&alert_handler, alert, &is_cause)); |
| CHECK(is_cause); |
| |
| // Acknowledge the cause and irq; neither of them affect escalation. |
| CHECK_DIF_OK(dif_alert_handler_alert_acknowledge(&alert_handler, alert)); |
| CHECK_DIF_OK( |
| dif_alert_handler_alert_is_cause(&alert_handler, alert, &is_cause)); |
| CHECK(!is_cause); |
| |
| CHECK_DIF_OK(dif_alert_handler_irq_acknowledge(&alert_handler, 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)); |
| } |
| |
| /** |
| * Initialize the peripherals used in this test. |
| */ |
| void init_peripherals(void) { |
| mmio_region_t base_addr = |
| mmio_region_from_addr(TOP_EARLGREY_PWRMGR_AON_BASE_ADDR); |
| CHECK_DIF_OK(dif_pwrmgr_init(base_addr, &pwrmgr)); |
| |
| base_addr = mmio_region_from_addr(TOP_EARLGREY_RSTMGR_AON_BASE_ADDR); |
| CHECK_DIF_OK(dif_rstmgr_init(base_addr, &rstmgr)); |
| |
| base_addr = mmio_region_from_addr(TOP_EARLGREY_AON_TIMER_AON_BASE_ADDR); |
| CHECK_DIF_OK(dif_aon_timer_init(base_addr, &aon_timer)); |
| |
| base_addr = mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR); |
| CHECK_DIF_OK(dif_rv_plic_init(base_addr, &plic)); |
| |
| rv_plic_testutils_irq_range_enable( |
| &plic, kPlicTarget, kTopEarlgreyPlicIrqIdAonTimerAonWkupTimerExpired, |
| kTopEarlgreyPlicIrqIdAonTimerAonWdogTimerBark); |
| |
| base_addr = mmio_region_from_addr(TOP_EARLGREY_ALERT_HANDLER_BASE_ADDR); |
| CHECK_DIF_OK(dif_alert_handler_init(base_addr, &alert_handler)); |
| } |
| |
| /** |
| * Program the alert handler to escalate on alerts upto phase 2 (i.e. reset) but |
| * the phase 1 (i.e. wipe secrets) should occur and last during the time the |
| * wdog is programed to bark. |
| */ |
| static void alert_handler_config(void) { |
| dif_alert_handler_alert_t alerts[] = {kTopEarlgreyAlertIdPwrmgrAonFatalFault}; |
| dif_alert_handler_class_t alert_classes[] = {kDifAlertHandlerClassA}; |
| |
| dif_alert_handler_escalation_phase_t esc_phases[] = { |
| {.phase = kDifAlertHandlerClassStatePhase0, |
| .signal = 0, |
| .duration_cycles = udiv64_slow( |
| kEscalationPhase0Micros * kClockFreqPeripheralHz, 1000000, NULL)}, |
| {.phase = kDifAlertHandlerClassStatePhase1, |
| .signal = 1, |
| .duration_cycles = udiv64_slow( |
| kEscalationPhase1Micros * kClockFreqPeripheralHz, 1000000, NULL)}, |
| {.phase = kDifAlertHandlerClassStatePhase2, |
| .signal = 3, |
| .duration_cycles = udiv64_slow( |
| kEscalationPhase2Micros * kClockFreqPeripheralHz, 1000000, NULL)}}; |
| |
| dif_alert_handler_class_config_t class_config[] = {{ |
| .auto_lock_accumulation_counter = kDifToggleDisabled, |
| .accumulator_threshold = 0, |
| .irq_deadline_cycles = |
| udiv64_slow(10 * kClockFreqPeripheralHz, 1000000, NULL), |
| .escalation_phases = esc_phases, |
| .escalation_phases_len = ARRAYSIZE(esc_phases), |
| .crashdump_escalation_phase = kDifAlertHandlerClassStatePhase3, |
| }}; |
| |
| 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, |
| kDifToggleEnabled); |
| // Enables alert handler irq. |
| CHECK_DIF_OK(dif_alert_handler_irq_set_enabled( |
| &alert_handler, kDifAlertHandlerIrqClassa, kDifToggleEnabled)); |
| } |
| |
| /** |
| * Execute the aon timer interrupt test. |
| */ |
| static void execute_test(dif_aon_timer_t *aon_timer) { |
| uint32_t bark_cycles = |
| aon_timer_testutils_get_aon_cycles_from_us(kWdogBarkMicros); |
| uint32_t bite_cycles = |
| aon_timer_testutils_get_aon_cycles_from_us(kWdogBiteMicros); |
| |
| LOG_INFO( |
| "Wdog will bark after %u/%u us/cycles and bite after %u/%u us/cycles", |
| (uint32_t)kWdogBarkMicros, bark_cycles, (uint32_t)kWdogBiteMicros, |
| bite_cycles); |
| |
| // Setup the wdog bark and bite timeouts. |
| aon_timer_testutils_watchdog_config(aon_timer, bark_cycles, bite_cycles, |
| /*pause_in_sleep=*/false); |
| |
| // Trigger the alert handler to escalate. |
| dif_pwrmgr_alert_t alert = kDifPwrmgrAlertFatalFault; |
| CHECK_DIF_OK(dif_pwrmgr_alert_force(&pwrmgr, alert)); |
| busy_spin_micros(kEscalationPhase0MicrosCpu); |
| |
| // This should never run since escalation turns the CPU off. |
| CHECK(false, "The alert handler failed to escalate"); |
| } |
| |
| bool test_main(void) { |
| // Enable global and external IRQ at Ibex. |
| irq_global_ctrl(true); |
| irq_external_ctrl(true); |
| |
| init_peripherals(); |
| |
| // Enable all the AON interrupts used in this test. |
| rv_plic_testutils_irq_range_enable(&plic, kPlicTarget, |
| kTopEarlgreyPlicIrqIdAlertHandlerClassa, |
| kTopEarlgreyPlicIrqIdAlertHandlerClassd); |
| |
| alert_handler_config(); |
| |
| // Check if there was a HW reset caused by the escalation. |
| dif_rstmgr_reset_info_bitfield_t rst_info; |
| rst_info = rstmgr_testutils_reason_get(); |
| rstmgr_testutils_reason_clear(); |
| |
| CHECK(rst_info == kDifRstmgrResetInfoPor || |
| rst_info == kDifRstmgrResetInfoEscalation, |
| "Wrong reset reason %02X", rst_info); |
| |
| if (rst_info == kDifRstmgrResetInfoPor) { |
| LOG_INFO("Booting for the first time, starting test"); |
| execute_test(&aon_timer); |
| } else if (rst_info == kDifRstmgrResetInfoEscalation) { |
| LOG_INFO("Booting for the second time due to escalation reset"); |
| |
| return true; |
| } else { |
| LOG_ERROR("Unexpected rst_info=0x%x", rst_info); |
| } |
| return false; |
| } |