| // 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; |
| } |