blob: dbf80b5157f935dfd45361fff0ba4bb0e797e3df [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/silicon_creator/rom/rom_epmp.h"
#include <stdbool.h>
#include <stdint.h>
#include "sw/device/lib/arch/device.h"
#include "sw/device/lib/base/abs_mmio.h"
#include "sw/device/lib/base/csr.h"
#include "sw/device/lib/base/memory.h"
#include "sw/device/lib/base/stdasm.h"
#include "sw/device/lib/dif/dif_pinmux.h"
#include "sw/device/lib/dif/dif_sram_ctrl.h"
#include "sw/device/lib/runtime/hart.h"
#include "sw/device/lib/runtime/ibex.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/runtime/print.h"
#include "sw/device/lib/testing/pinmux_testutils.h"
#include "sw/device/lib/testing/test_framework/status.h"
#include "sw/device/silicon_creator/lib/base/sec_mmio.h"
#include "sw/device/silicon_creator/lib/drivers/flash_ctrl.h"
#include "sw/device/silicon_creator/lib/drivers/uart.h"
#include "sw/device/silicon_creator/lib/epmp_test_unlock.h"
#include "sw/device/silicon_creator/rom/rom_epmp.h"
#include "flash_ctrl_regs.h" // Generated.
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
/**
* ROM ePMP test.
*
* This test uses the ROM linker script and ePMP setup code to initialize
* its own ePMP configuration and then attempts to execute instructions in
* various address spaces. Typically execution in these address spaces should be
* blocked unless the unlock function has been called with a region containing
* the address of the access.
*/
/**
* Get the value of the `mcause` register.
*
* @returns The encoded interrupt or exception cause.
*/
static uint32_t get_mcause(void) {
uint32_t mcause;
CSR_READ(CSR_REG_MCAUSE, &mcause);
return mcause;
}
/**
* Get the value of the `mepc` register.
*
* @returns The value of the machine exception program counter.
*/
static uint32_t get_mepc(void) {
uint32_t mepc;
CSR_READ(CSR_REG_MEPC, &mepc);
return mepc;
}
/**
* Set the value of the `mepc` register.
*
* After an exception has been handled execution will be resumed at the address
* contained within `mepc`.
*
* @param pc The value to set the machine exception program counter to.
*/
static void set_mepc(uint32_t pc) { CSR_WRITE(CSR_REG_MEPC, pc); }
/**
* Interrupt handlers.
*
* If operating correctly this test should only trigger exceptions. Interrupts
* are therefore not recovered.
*/
void rom_nmi_handler(void) { wait_for_interrupt(); }
void rom_interrupt_handler(void) { wait_for_interrupt(); }
/**
* The type of last exception (if any) received.
*
* Set by the exception handler.
*/
volatile ibex_exc_t exception_received = 0;
/**
* The `mepc` value for the last exception (if any) received.
*
* Set by the exception handler.
*/
volatile uintptr_t exception_pc = 0;
/**
* Exception handler.
*
* Handle instruction access faults and illegal instructions by setting
* `exception_received` and `exception_pc` and then returning to the code
* that jumped (via a call) to the offending instruction.
*
* This will likely only work correctly if the instruction exception was
* caused by a jump from `execute` to an invalid instruction (whether illegal
* or inaccessible).
*
* For all other exceptions hang (could also shutdown) so as not to hide them.
*/
void rom_exception_handler(void) __attribute__((interrupt));
void rom_exception_handler(void) {
uint32_t mcause = get_mcause();
if (mcause == kIbexExcInstrAccessFault ||
mcause == kIbexExcIllegalInstrFault) {
exception_received = (ibex_exc_t)mcause;
exception_pc = get_mepc();
// Return to caller.
uintptr_t ret = (uintptr_t)__builtin_return_address(0);
set_mepc((uint32_t)ret);
return;
}
// Wait forever if an unexpected exception is encountered.
wait_for_interrupt();
}
/**
* Attempt to execute the code at `pc` by calling it like a function.
*
* Typically the contents of `pc` should be an invalid instruction such
* as an all zero value. In this case if execution was blocked by PMP an
* instruction fault exception will be raised. If however execution was
* allowed then an illegal instruction exception will be raised instead.
*
* The interrupt handler will arrange for control to be returned to the
* caller on encountering either an instruction fault or illegal
* instruction error so this function will report a result in either
* case.
*
* @param pc The address of the instruction to try and execute.
* @param expect The expected exception that will be raised.
* @returns Whether the expected exception was raised at the correct PC.
*/
static bool execute(const void *pc, ibex_exc_t expect) {
exception_pc = 0;
exception_received = kIbexExcMax;
// Jump to the target PC.
//
// Using a `call` here (`jal` or `jalr`) sets the return address (`ra`)
// register. When an exception is raised the interrupt handler will recover
// by restarting execution at the address in `ra` thereby making it appear
// as if the call returned normally.
//
// ...
// jal ra, pc # <- Set return address and jump to pc.
// ... # <- Interrupt handler restarts execution at the next
// # instruction in the caller, here.
//
// pc:
// unimp # <- Illegal instruction or access fault. Enter interrupt
// handler.
//
((void (*)(void))pc)();
// Be careful to ensure that the exception was raised when trying to
// execute `pc` just in case a valid instruction is actually executed
// and then execution continued to a point where an exception is
// raised.
if (exception_received != kIbexExcMax && exception_pc != (uintptr_t)pc) {
return false;
}
return exception_received == expect;
}
/**
* An instruction that has all bits set. This value is specifically chosen to
* match an erased flash.
*
* Attempts to execute this instruction, `unimp`, will result in an illegal
* instruction exception.
*
* Note that if compressed instructions are enabled only the first two bytes
* will be decoded (as `c.unimp`).
*/
static const uint32_t kUnimpInstruction = UINT32_MAX;
/**
* Illegal instruction residing in .rodata.
*/
static const uint32_t illegal_ins_ro[] = {
kUnimpInstruction,
};
/**
* Illegal instruction residing in .bss.
*/
static uint32_t illegal_ins_rw[] = {
0,
};
/**
* Report whether the given pointer points to a location with the provided
* address space.
*
* @param ptr Pointer to test.
* @param start Address of the start of the address space.
* @param size The size of the address space in bytes.
* @returns Whether the pointer is in the address space.
*/
static bool is_in_address_space(const void *ptr, uintptr_t start,
uintptr_t size) {
return (uintptr_t)ptr >= start && (uintptr_t)ptr < (start + size);
}
/**
* Set to false if a test fails.
*/
static bool passed = false;
/**
* Custom CHECK macro to assert a condition that if false should cause the
* test to fail. Note: we can't use the normal CHECK macro because it tries to
* write to the DV address space but that is locked by the ePMP configuration.
*/
#define CHECK(condition) \
if (!(condition)) { \
LOG_ERROR("CHECK-fail: " #condition); \
passed = false; \
}
/**
* Test that .rodata in the ROM is not executable.
*/
static void test_noexec_rodata(void) {
CHECK(is_in_address_space(illegal_ins_ro, TOP_EARLGREY_ROM_CTRL_ROM_BASE_ADDR,
TOP_EARLGREY_ROM_CTRL_ROM_SIZE_BYTES));
CHECK(execute(illegal_ins_ro, kIbexExcInstrAccessFault));
}
/**
* Test that the .bss section in RAM is not executable.
*/
static void test_noexec_rwdata(void) {
dif_sram_ctrl_t sram_ctrl;
CHECK(dif_sram_ctrl_init(
mmio_region_from_addr(TOP_EARLGREY_SRAM_CTRL_MAIN_REGS_BASE_ADDR),
&sram_ctrl) == kDifOk);
CHECK(dif_sram_ctrl_exec_set_enabled(&sram_ctrl, kDifToggleEnabled) ==
kDifOk);
CHECK(is_in_address_space(illegal_ins_rw, TOP_EARLGREY_RAM_MAIN_BASE_ADDR,
TOP_EARLGREY_RAM_MAIN_SIZE_BYTES));
CHECK(execute(illegal_ins_rw, kIbexExcInstrAccessFault));
}
/**
* Test that eFlash is not executable.
*/
static void test_noexec_eflash(void) {
// Ideally we'd check all of eFlash but that takes a very long time in
// simulation. Instead, check the first and last words are not executable and
// check a sample of other addresses.
uint32_t *eflash = (uint32_t *)TOP_EARLGREY_EFLASH_BASE_ADDR;
size_t eflash_len = TOP_EARLGREY_EFLASH_SIZE_BYTES / sizeof(eflash[0]);
CHECK(execute(&eflash[0], kIbexExcInstrAccessFault));
CHECK(execute(&eflash[eflash_len - 1], kIbexExcInstrAccessFault));
// Step size is picked arbitrarily but should provide a reasonable sample of
// addresses.
size_t step = eflash_len / 999;
for (size_t i = step; i < eflash_len; i += step) {
if (!execute(&eflash[i], kIbexExcInstrAccessFault)) {
LOG_ERROR("eflash execution not blocked @ %p", &eflash[i]);
passed = false;
break;
}
}
}
/**
* Test that the MMIO address space (specifically the retention RAM) is not
* executable.
*/
static void test_noexec_mmio(void) {
// Note: execution of retention RAM always fails regardless of controller or
// ePMP configurations however it doesn't hurt to check it anyway.
dif_sram_ctrl_t ret_ram_ctrl;
CHECK(dif_sram_ctrl_init(mmio_region_from_addr(
TOP_EARLGREY_SRAM_CTRL_RET_AON_REGS_BASE_ADDR),
&ret_ram_ctrl) == kDifOk);
CHECK(dif_sram_ctrl_exec_set_enabled(&ret_ram_ctrl, kDifToggleEnabled) ==
kDifOk);
uint32_t *ret_ram = (uint32_t *)TOP_EARLGREY_RAM_RET_AON_BASE_ADDR;
size_t ret_ram_len = TOP_EARLGREY_RAM_RET_AON_SIZE_BYTES / sizeof(ret_ram[0]);
ret_ram[0] = kUnimpInstruction;
CHECK(execute(&ret_ram[0], kIbexExcInstrAccessFault));
ret_ram[ret_ram_len - 1] = kUnimpInstruction;
CHECK(execute(&ret_ram[ret_ram_len - 1], kIbexExcInstrAccessFault));
}
/**
* Test the function used to unlock execution of the ROM extension.
*
* Unlock a section of eFlash to simulate the unlocking of the ROM_EXT text.
* Accesses within the unlocked region should execute (and generate an illegal
* instruction exception in this case) while accesses outside the unlocked
* region should still fail with an instruction access fault exception.
*
* @param epmp The ePMP state to update.
*/
static void test_unlock_exec_eflash(void) {
// Define a region to unlock (this is somewhat arbitrary but must be word-
// aligned and beyond the ROM region, since this same image is placed in the
// flash).
uint32_t *eflash = (uint32_t *)TOP_EARLGREY_EFLASH_BASE_ADDR;
size_t eflash_len = TOP_EARLGREY_EFLASH_SIZE_BYTES / sizeof(eflash[0]);
uint32_t *image = &eflash[eflash_len / 5];
size_t image_len = eflash_len / 7;
epmp_region_t region = {.start = (uintptr_t)&image[0],
.end = (uintptr_t)&image[image_len]};
// Unlock execution of the region and check that the same changes are made
// to the ePMP state.
rom_epmp_unlock_rom_ext_rx(region);
CHECK(epmp_state_check() == kErrorOk);
// Verify that execution within the region succeeds.
// The image must consist of `unimp` instructions so that an illegal
// instruction exception is generated. Because the region is not written and
// tests begin with the flash erased, this instruction is expected to be
// UINT32_MAX.
CHECK(image[0] == kUnimpInstruction);
CHECK(execute(&image[0], kIbexExcIllegalInstrFault));
CHECK(image[image_len - 1] == kUnimpInstruction);
CHECK(execute(&image[image_len - 1], kIbexExcIllegalInstrFault));
// Verify that execution just outside the region still fails.
CHECK(execute(&image[-1], kIbexExcInstrAccessFault));
CHECK(execute(&image[image_len], kIbexExcInstrAccessFault));
}
void rom_main(void) {
// Initialize global variables here so that they don't end up in the .data
// section since OpenTitan ROM does not have one.
passed = true;
exception_received = kIbexExcMax;
illegal_ins_rw[0] = kUnimpInstruction;
// Initialize sec_mmio.
sec_mmio_init();
// Configure debug ROM ePMP entry.
rom_epmp_config_debug_rom(kLcStateProd);
// Initialize pinmux configuration so we can use the UART.
dif_pinmux_t pinmux;
OT_DISCARD(dif_pinmux_init(
mmio_region_from_addr(TOP_EARLGREY_PINMUX_AON_BASE_ADDR), &pinmux));
pinmux_testutils_init(&pinmux);
// Enable execution of code in flash.
flash_ctrl_init();
flash_ctrl_exec_set(FLASH_CTRL_PARAM_EXEC_EN);
SEC_MMIO_WRITE_INCREMENT(kFlashCtrlSecMmioInit + kFlashCtrlSecMmioExecSet);
// Configure UART0 as stdout.
uart_init(kUartNCOValue);
base_set_stdout((buffer_sink_t){
.data = NULL,
.sink = uart_sink,
});
// Start the tests.
LOG_INFO("Starting ROM ePMP functional test.");
// Initialize shadow copy of the ePMP register configuration.
memset(&epmp_state, 0, sizeof(epmp_state));
rom_epmp_state_init(kLcStateProd);
CHECK(epmp_state_check() == kErrorOk);
// Test that execution outside the ROM text is blocked by default.
test_noexec_rodata();
test_noexec_rwdata();
test_noexec_eflash();
test_noexec_mmio();
// Test that execution is unlocked for a sub-region of eFlash correctly.
// Simulates the unlocking of the ROM extension text.
test_unlock_exec_eflash();
// The test of the ROM's ePMP configuration is now complete. Unlock the
// DV address space so that the test result can be reported. Assumes that PMP
// entry 6 is allocated for this purpose.
CHECK(epmp_unlock_test_status());
// Report the test status.
//
// Note that it is only now, after the DV address space has been unlocked that
// we can signal that the test has started unfortunately.
test_status_set(kTestStatusInTest);
test_status_set(passed ? kTestStatusPassed : kTestStatusFailed);
// Unreachable if reporting the test status correctly caused the
// test to stop.
while (true) {
wait_for_interrupt();
}
}