blob: f3346795a10ed275da2e88c0b7bd75165c72ec8f [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 <stdbool.h>
#include <stdint.h>
#include "sw/device/lib/base/bitfield.h"
#include "sw/device/lib/base/macros.h"
#include "sw/device/lib/dif/dif_sram_ctrl.h"
#include "sw/device/lib/runtime/ibex.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/testing/check.h"
#include "sw/device/lib/testing/lc_ctrl_testutils.h"
#include "sw/device/lib/testing/otp_ctrl_testutils.h"
#include "sw/device/lib/testing/sram_ctrl_testutils.h"
#include "sw/device/lib/testing/test_framework/ottf.h"
#include "sw/device/lib/testing/test_framework/ottf_isrs.h"
#include "sw/device/lib/testing/test_framework/ottf_macros.h"
#include "sw/device/lib/testing/test_framework/test_status.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
const test_config_t kTestConfig;
static dif_sram_ctrl_t sram_ctrl;
/**
* This flag is used to verify that the execution from SRAM was successful.
* Declared as volatile, because it is referenced in the fault handler, as well
* as the main test flow.
*/
static volatile bool exception_observed;
/**
* Main SRAM start and end addresses (inclusive).
*/
static const uint32_t kRamStartAddr = TOP_EARLGREY_SRAM_CTRL_MAIN_RAM_BASE_ADDR;
static const uint32_t kRamEndAddr = TOP_EARLGREY_SRAM_CTRL_MAIN_RAM_BASE_ADDR +
TOP_EARLGREY_SRAM_CTRL_MAIN_RAM_SIZE_BYTES -
1;
/**
* OTP HW partition relative IFETCH offset in bytes.
*
* x = OTP_CTRL_PARAM_EN_SRAM_IFETCH_OFFSET (1728)
* y = OTP_CTRL_PARAM_HW_CFG_OFFSET (1664)
* IFETCH_OFFSET = (x - y) / 8 = 8
*/
static const uint32_t kOtpIfetchHwRelativeOffset = 8;
/**
* Executes the return instruction from MAIN SRAM.
*
* This function will return on success, or cause an exception if the
* execution is disabled.
*/
OT_ATTR_NAKED
OT_ATTR_SECTION(".data")
void execute_code_in_sram(void) { asm volatile("jalr zero, 0(ra)"); }
static bool otp_ifetch_enabled(void) {
dif_otp_ctrl_t otp;
CHECK_DIF_OK(dif_otp_ctrl_init(
mmio_region_from_addr(TOP_EARLGREY_OTP_CTRL_CORE_BASE_ADDR), &otp));
dif_otp_ctrl_config_t config = {
.check_timeout = 100000,
.integrity_period_mask = 0x3ffff,
.consistency_period_mask = 0x3ffffff,
};
CHECK_DIF_OK(dif_otp_ctrl_configure(&otp, config));
CHECK_DIF_OK(dif_otp_ctrl_dai_read_start(&otp, kDifOtpCtrlPartitionHwCfg,
kOtpIfetchHwRelativeOffset));
otp_ctrl_testutils_wait_for_dai(&otp);
uint32_t value;
CHECK_DIF_OK(dif_otp_ctrl_dai_read32_end(&otp, &value));
// OTP stores IFETCH state in a single bit (enabled/disabled).
return bitfield_bit32_read(value, 0);
}
/**
* Overrides the default OTTF exception handler.
*
* This exception handler only processes the faults that are relevant to this
* test. It falls into an infinite `wait_for_interrupt` routine (by calling
* `abort()`) for the rest.
*
* The controlled fault originates in the retention SRAM, which means that
* normally the return address would be calculated relative to the trapped
* instruction. However, due to execution from retention SRAM being permanently
* disabled, this approach would not work.
*
* Instead the control flow needs to be returned to the caller. In other words,
* sram_execution_test -> retention_sram -> exception_handler
* -> sram_execution_test.
*
* Before the jump into the exception handler, the register set is saved on
* stack by the OTTF exception handler entry subroutine, which means that the
* return address can be loaded from there. See comments below for more details.
*/
void ottf_exeception_handler(void) {
// The frame address is the address of the stack location that holds the
// `mepc`, since the OTTF exception handler entry code saves the `mepc` to
// the top of the stack before transferring control flow to the exception
// handler function (which is overridden here). See the `handler_exception`
// subroutine in `sw/device/lib/testing/testing/ottf_isrs.S` for more details.
uintptr_t mepc_stack_addr = (uintptr_t)OT_FRAME_ADDR();
// The return address of the function that holds the trapping instruction is
// the second top-most value placed on the stack by the OTTF exception handler
// entry code. We grab this off the stack so that we can use it to overwrite
// the `mepc` value stored on the stack, so that the `ottf_isr_exit`
// subroutine (in `sw/device/lib/testing/test_framework/ottf_isrs.S`) will
// restore control flow to the `sram_execution_test` function as described
// above.
uintptr_t ret_addr = *(uintptr_t *)(mepc_stack_addr + OTTF_WORD_SIZE);
LOG_INFO("Handling exception: mepc = %p, (trapped) return address = %p",
ibex_mepc_read(), ret_addr);
uint32_t mcause = ibex_mcause_read();
ottf_exc_id_t exception_id = mcause & kIdMax;
switch (exception_id) {
case kInstrAccessFault:
LOG_INFO("Instruction access fault handler");
exception_observed = true;
*(uintptr_t *)mepc_stack_addr = ret_addr;
break;
case kIllegalInstrFault:
LOG_INFO("Illegal instruction fault handler");
exception_observed = true;
*(uintptr_t *)mepc_stack_addr = ret_addr;
break;
default:
LOG_FATAL("Unexpected exception id = 0x%x", exception_id);
abort();
}
}
/**
* Performs the tests.
*
* When chip is in one of the lifecycle states where debug functions are
* enabled, execution from SRAM is enabled if the EN_SRAM_IFETCH
* (OTP) is disabled. When EN_SRAM_IFETCH (OTP) is enabled, EXEC CSR
* determines whether the execution from SRAM is enabled.
*/
bool test_main(void) {
uintptr_t func_address = (uintptr_t)execute_code_in_sram;
CHECK(func_address >= kRamStartAddr && func_address <= kRamEndAddr,
"Test code resides outside of the Main SRAM: function address = %x",
func_address);
CHECK_DIF_OK(dif_sram_ctrl_init(
mmio_region_from_addr(TOP_EARLGREY_SRAM_CTRL_MAIN_REGS_BASE_ADDR),
&sram_ctrl));
dif_lc_ctrl_t lc;
CHECK_DIF_OK(dif_lc_ctrl_init(
mmio_region_from_addr(TOP_EARLGREY_LC_CTRL_BASE_ADDR), &lc));
if (otp_ifetch_enabled()) {
dif_toggle_t state;
CHECK_DIF_OK(dif_sram_ctrl_exec_get_enabled(&sram_ctrl, &state));
if (state == kDifToggleDisabled) {
bool locked;
CHECK_DIF_OK(
dif_sram_ctrl_is_locked(&sram_ctrl, kDifSramCtrlLockExec, &locked));
CHECK(!locked,
"Execution is disabled and locked, cannot perform the test");
CHECK_DIF_OK(
dif_sram_ctrl_exec_set_enabled(&sram_ctrl, kDifToggleEnabled));
exception_observed = false;
execute_code_in_sram();
CHECK(!exception_observed,
"Exception observed whilst executing from SRAM!");
}
} else if (lc_ctrl_testutils_debug_func_enabled(&lc)) {
exception_observed = false;
execute_code_in_sram();
CHECK(!exception_observed,
"Exception observed whilst executing from SRAM!");
} else {
LOG_FATAL("Execution from SRAM cannot be enabled, cannot run the test");
}
return true;
}