blob: 11704f5efc720a73eaf0041dfede5ce1df1b68cb [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 "sw/device/lib/base/abs_mmio.h"
#include "sw/device/lib/base/macros.h"
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/dif/dif_pwrmgr.h"
#include "sw/device/lib/dif/dif_rstmgr.h"
#include "sw/device/lib/dif/dif_rv_core_ibex.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/testing/aon_timer_testutils.h"
#include "sw/device/lib/testing/rstmgr_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"
OTTF_DEFINE_TEST_CONFIG();
/**
* RSTMGR CPU INFO TEST
*
* This has three stages:
*
* 1. After the first startup, a illegal memory access is performed.
* In the exception handler, a software reset is triggered.
*
* 2. After the software reset, the CPU info dump is checked against
* the expected values for this single fault. The watch dog is then set up
* and another illegal memory access is performed. Only this time
* the exception handler performs another illegal read.
* Causing the ibex to be haulted by the alert handler.
* The watch dog will eventually trigger a reset.
*
* 3. After the watchdog reset, the CPU info dump is checked against
* the expected values for this double fault.
*/
// CPU Dump Size and Unmapped Addresses.
enum {
kCpuDumpSize = 8,
kIllegalAddr0 = 0xF0000000,
kIllegalAddr1 = 0xF0000004,
kIllegalAddr2 = 0x00000008,
};
// Declaring the labels used to calculate the expected current and next pc
// after a double fault.
extern const uint32_t _ottf_interrupt_vector;
// The labels to points in the code of which the memory address is needed.
extern const char kSingleFaultAddrLower[];
extern const char kSingleFaultAddrUpper[];
extern const char kSingleFaultAddrCurrentPc[];
extern const char kSingleFaultAddrNextPc[];
extern const char kDoubleFaultFirstAddrLower[];
extern const char kDoubleFaultFirstAddrUpper[];
extern const char kDoubleFaultSecondAddrLower[];
extern const char kDoubleFaultSecondAddrUpper[];
// A handle to the reset manager.
static dif_rstmgr_t rstmgr;
// This variable is used to ensure loads from an address aren't optimised out.
volatile static uint32_t addr_val;
/**
* When true, the exception handler will trigger another fault,
* causing a double fault,
* otherwise it triggers a software reset.
*/
volatile static bool double_fault;
/**
* Overrides the default OTTF exception handler.
*/
void ottf_exception_handler(void) {
if (double_fault) {
OT_ADDRESSABLE_LABEL(kDoubleFaultSecondAddrLower);
mmio_region_write32(mmio_region_from_addr(kIllegalAddr2), 0, 0);
OT_ADDRESSABLE_LABEL(kDoubleFaultSecondAddrUpper);
} else {
CHECK_DIF_OK(dif_rstmgr_software_device_reset(&rstmgr));
// Write to `addr_val` so that the 'last data access' address is
// a known value (the address of addr_val).
addr_val = 1;
OT_ADDRESSABLE_LABEL(kSingleFaultAddrCurrentPc);
wait_for_interrupt(); // Wait for the reset.
OT_ADDRESSABLE_LABEL(kSingleFaultAddrNextPc);
addr_val = 2;
}
CHECK(false,
"This point should be unreachable; "
"a reset or another fault should have occured.");
}
/**
* Gets, parses and returns the cpu info crash dump.
*
* @param ibex A handle to the ibex.
* @return The cpu info crash dump.
*/
static dif_rv_core_ibex_crash_dump_info_t get_dump(
const dif_rv_core_ibex_t *ibex) {
size_t size_read;
dif_rstmgr_cpu_info_dump_segment_t dump[DIF_RSTMGR_CPU_INFO_MAX_SIZE];
CHECK_DIF_OK(dif_rstmgr_cpu_info_dump_read(
&rstmgr, dump, DIF_RSTMGR_CPU_INFO_MAX_SIZE, &size_read));
CHECK(size_read == kCpuDumpSize,
"The observed cpu info dump's size was %d, "
"but it was expected to be %d",
size_read, kCpuDumpSize);
dif_rv_core_ibex_crash_dump_info_t output;
CHECK_DIF_OK(
dif_rv_core_ibex_parse_crash_dump(ibex, dump, size_read, &output));
return output;
}
/**
* Holds the expected cpu info dump values for the current state.
*/
typedef struct rstmgr_cpu_info_test_exp_state {
uint32_t mtval; ///< The last exception address.
uint32_t mpec_l; ///< The last exception PC lower bound.
uint32_t mpec_u; ///< The last exception PC upper bound.
uint32_t mdaa; ///< The last data access address.
uint32_t mnpc; ///< The next PC.
uint32_t mcpc; ///< The current PC.
} rstmgr_cpu_info_test_exp_state_t;
/**
* Holds the expected cpu info dump values for the previous state.
*/
typedef struct rstmgr_cpu_info_test_exp_prev_state {
uint32_t mtval; ///< The exception address for the previous crash.
uint32_t
mpec_l; ///< The last exception PC lower bound for the previous crash.
uint32_t
mpec_u; ///< The last exception PC upper bound for the previous crash.
} rstmgr_cpu_info_test_exp_prev_state_t;
/**
* Checks the 'current' section of the cpu info dump against the given expected
* values.
*
* @param obs_state The cpu info crash dump's current state values.
* @param exp_state The expected values of the current state.
*/
static void check_state(dif_rv_core_ibex_crash_dump_state_t obs_state,
rstmgr_cpu_info_test_exp_state_t exp_state) {
CHECK(exp_state.mtval == obs_state.mtval,
"Last Exception Access Addr: Expected 0x%x != Observed 0x%x",
exp_state.mtval, obs_state.mtval);
CHECK(exp_state.mcpc == obs_state.mcpc,
"Current PC: Expected 0x%x != Observed 0x%x", exp_state.mcpc,
obs_state.mcpc);
CHECK(exp_state.mnpc == obs_state.mnpc,
"Next PC: Expected 0x%x != Observed 0x%x", exp_state.mnpc,
obs_state.mnpc);
CHECK(exp_state.mdaa == obs_state.mdaa,
"Last Data Access Addr: Expected 0x%x != Observed 0x%x", exp_state.mdaa,
obs_state.mdaa);
CHECK(
exp_state.mpec_l <= obs_state.mpec && obs_state.mpec < exp_state.mpec_u,
"The Observed MPEC, 0x%x, was not in the expected range of [0x%x, 0x%x)",
obs_state.mpec, exp_state.mpec_l, exp_state.mpec_u);
}
/**
* Checks the 'previous' section of the cpu info dump against the given expected
* values.
*
* @param obs_prev_state The cpu info crash dump's previous state values.
* @param exp_prev_state The expected values of the previous state.
*/
static void check_prev_state(
dif_rv_core_ibex_previous_crash_dump_state_t obs_prev_state,
rstmgr_cpu_info_test_exp_prev_state_t exp_prev_state) {
CHECK(exp_prev_state.mtval == obs_prev_state.mtval,
"Last Exception Access Addr: Expected 0x%x != Observed 0x%x",
exp_prev_state.mtval, obs_prev_state.mtval);
CHECK(exp_prev_state.mpec_l <= obs_prev_state.mpec &&
obs_prev_state.mpec < exp_prev_state.mpec_u,
"The Observed Previous MPEC, 0x%x, "
"was not in the expected range of [0x%x, 0x%x)",
obs_prev_state.mpec, exp_prev_state.mpec_l, exp_prev_state.mpec_u);
}
bool test_main(void) {
dif_rv_core_ibex_crash_dump_info_t dump;
dif_aon_timer_t aon_timer;
dif_pwrmgr_t pwrmgr;
dif_rv_core_ibex_t ibex;
// Initialize Handles.
CHECK_DIF_OK(dif_rstmgr_init(
mmio_region_from_addr(TOP_EARLGREY_RSTMGR_AON_BASE_ADDR), &rstmgr));
CHECK_DIF_OK(dif_aon_timer_init(
mmio_region_from_addr(TOP_EARLGREY_AON_TIMER_AON_BASE_ADDR), &aon_timer));
CHECK_DIF_OK(dif_pwrmgr_init(
mmio_region_from_addr(TOP_EARLGREY_PWRMGR_AON_BASE_ADDR), &pwrmgr));
CHECK_DIF_OK(dif_rv_core_ibex_init(
mmio_region_from_addr(TOP_EARLGREY_RV_CORE_IBEX_CFG_BASE_ADDR), &ibex));
switch (rstmgr_testutils_reason_get()) {
case kDifRstmgrResetInfoPor: // The first power-up.
LOG_INFO("Triggering single fault.");
// Enable cpu info.
CHECK_DIF_OK(dif_rstmgr_cpu_info_set_enabled(&rstmgr, kDifToggleEnabled));
double_fault = false;
OT_ADDRESSABLE_LABEL(kSingleFaultAddrLower);
addr_val = mmio_region_read32(mmio_region_from_addr(kIllegalAddr0), 0);
OT_ADDRESSABLE_LABEL(kSingleFaultAddrUpper);
CHECK(false,
"This should be unreachable; a single fault should have occured.");
break;
case kDifRstmgrResetInfoSw: // The power-up after the single fault.
LOG_INFO("Checking CPU info dump after single fault.");
dump = get_dump(&ibex);
CHECK(
dump.double_fault == kDifToggleDisabled,
"CPU Info dump shows a double fault after experiencing only a single "
"fault.");
check_state(dump.fault_state,
(rstmgr_cpu_info_test_exp_state_t){
.mtval = (uint32_t)kIllegalAddr0,
.mpec_l = (uint32_t)kSingleFaultAddrLower,
.mpec_u = (uint32_t)kSingleFaultAddrUpper,
.mdaa = (uint32_t)&addr_val,
.mcpc = (uint32_t)kSingleFaultAddrCurrentPc,
.mnpc = (uint32_t)kSingleFaultAddrNextPc,
});
LOG_INFO("Setting up watch dog and triggering a double fault.");
uint32_t bark_cycles = aon_timer_testutils_get_aon_cycles_from_us(100);
uint32_t bite_cycles = aon_timer_testutils_get_aon_cycles_from_us(100);
// Set wdog as a reset source.
CHECK_DIF_OK(dif_pwrmgr_set_request_sources(
&pwrmgr, kDifPwrmgrReqTypeReset, kDifPwrmgrResetRequestSourceTwo,
kDifToggleEnabled));
// Setup the watchdog bark and bite timeouts.
aon_timer_testutils_watchdog_config(&aon_timer, bark_cycles, bite_cycles,
false);
// Enable cpu info.
CHECK_DIF_OK(dif_rstmgr_cpu_info_set_enabled(&rstmgr, kDifToggleEnabled));
double_fault = true;
OT_ADDRESSABLE_LABEL(kDoubleFaultFirstAddrLower);
addr_val = mmio_region_read32(mmio_region_from_addr(kIllegalAddr1), 0);
OT_ADDRESSABLE_LABEL(kDoubleFaultFirstAddrUpper);
CHECK(false,
"This should be unreachable; a double fault should have occured.");
break;
case kDifRstmgrResetInfoWatchdog: // The power-up after the double fault.
LOG_INFO("Checking CPU info dump after double fault.");
dump = get_dump(&ibex);
CHECK(dump.double_fault == kDifToggleEnabled,
"CPU Info dump doesn't show a double fault has happened.");
// After #15219 was merged, the execution stops more predictably
// once fetch_en is dropped due to a double fault.
// The current pc should now always be the instruction after the
// instruction that issues the illegal load.
// The next pc is always the exception handler, because that's
// where execution would have gone if it had not halted
check_state(dump.fault_state,
(rstmgr_cpu_info_test_exp_state_t){
.mtval = (uint32_t)kIllegalAddr2,
.mpec_l = (uint32_t)kDoubleFaultSecondAddrLower,
.mpec_u = (uint32_t)kDoubleFaultSecondAddrUpper,
.mdaa = (uint32_t)kIllegalAddr2,
.mcpc = (uint32_t)kDoubleFaultSecondAddrLower + 4,
.mnpc = (uint32_t)&_ottf_interrupt_vector,
});
check_prev_state(dump.previous_fault_state,
(rstmgr_cpu_info_test_exp_prev_state_t){
.mtval = (uint32_t)kIllegalAddr1,
.mpec_l = (uint32_t)kDoubleFaultFirstAddrLower,
.mpec_u = (uint32_t)kDoubleFaultFirstAddrUpper,
});
return true;
default:
CHECK(false, "Device was reset by an unexpected source.");
break;
}
return false;
}