| // Copyright lowRISC contributors. | |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| // SPDX-License-Identifier: Apache-2.0 | |
| #ifndef OPENTITAN_SW_DEVICE_LIB_RUNTIME_IBEX_H_ | |
| #define OPENTITAN_SW_DEVICE_LIB_RUNTIME_IBEX_H_ | |
| #include <stdbool.h> | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include "sw/device/lib/arch/device.h" | |
| #include "sw/device/lib/base/math.h" | |
| #include "sw/device/lib/base/stdasm.h" | |
| // IBEX_SPIN_FOR needs a dependency on check.h, but the build fails if a | |
| // dependency on sw_lib_testing_check is added. | |
| /** | |
| * @file | |
| * @brief This header provides Ibex-specific functions and enums, such as | |
| * cycle-accurate busy loops. | |
| */ | |
| /** | |
| * An Ibex exception type. | |
| * | |
| * This enum is used to decode RISC-V exception causes generated by Ibex. | |
| */ | |
| typedef enum ibex_exc { | |
| kIbexExcInstrMisaligned = 0, | |
| kIbexExcInstrAccessFault = 1, | |
| kIbexExcIllegalInstrFault = 2, | |
| kIbexExcBreakpoint = 3, | |
| kIbexExcLoadAccessFault = 5, | |
| kIbexExcStoreAccessFault = 7, | |
| kIbexExcUserECall = 8, | |
| kIbexExcMachineECall = 11, | |
| kIbexExcMax = 31 | |
| } ibex_exc_t; | |
| /** | |
| * An Ibex internal IRQ type. | |
| * | |
| * This enum is used to decode RISC-V internal IRQs generated by Ibex. | |
| */ | |
| typedef enum ibex_internal_irq { | |
| kIbexInternalIrqLoadInteg = 0xffffffe0, | |
| kIbexInternalIrqNmi = 0x8000001f | |
| } ibex_internal_irq_t; | |
| /** | |
| * A spinwait timeout type. | |
| */ | |
| typedef struct ibex_timeout { | |
| /** | |
| * The number of cycles to timeout. | |
| */ | |
| uint64_t cycles; | |
| /** | |
| * The initial cycle count. | |
| */ | |
| uint64_t start; | |
| } ibex_timeout_t; | |
| /** | |
| * Read the cycle counter. | |
| * | |
| * The value of the counter is stored across two 32-bit registers: `mcycle` and | |
| * `mcycleh`. This function is guaranteed to return a valid 64-bit cycle | |
| * counter value, even if `mcycle` overflows before reading `mcycleh`. | |
| * | |
| * Adapted from: The RISC-V Instruction Set Manual, Volume I: Unprivileged ISA | |
| * V20191213, pp. 61. | |
| */ | |
| inline uint64_t ibex_mcycle_read(void) { | |
| uint32_t cycle_low = 0; | |
| uint32_t cycle_high = 0; | |
| uint32_t cycle_high_2 = 0; | |
| asm volatile( | |
| "1:" | |
| " csrr %0, mcycleh;" // Read `mcycleh`. | |
| " csrr %1, mcycle;" // Read `mcycle`. | |
| " csrr %2, mcycleh;" // Read `mcycleh` again. | |
| " bne %0, %2, 1b;" // Try again if `mcycle` overflowed before | |
| // reading `mcycleh`. | |
| : "=r"(cycle_high), "=r"(cycle_low), "=r"(cycle_high_2) | |
| :); | |
| return (uint64_t)cycle_high << 32 | cycle_low; | |
| } | |
| /** | |
| * Reads the mcause register. | |
| * | |
| * When an exception is encountered, the corresponding exception code is stored | |
| * in mcause register. | |
| * | |
| * A list of the exception codes can be found at: | |
| * https://ibex-core.readthedocs.io/en/latest/03_reference/ | |
| * exception_interrupts.html#exceptions | |
| */ | |
| uint32_t ibex_mcause_read(void); | |
| /** | |
| * Reads the mtval register. | |
| * | |
| * When an exception is encountered, the Machine Trap Value (mtval) register | |
| * can holds exception-specific information to assist software in handling the | |
| * trap. | |
| * | |
| * From the Ibex documentation (found at | |
| * https://ibex-core.readthedocs.io/en/latest/03_reference/cs_registers.html) | |
| * - In the case of errors in the load-store unit mtval holds the address of | |
| * the transaction causing the error. | |
| * | |
| * - If a transaction is misaligned, mtval holds the address of the missing | |
| * transaction part. | |
| * | |
| * - In the case of illegal instruction exceptions, mtval holds the actual | |
| * faulting instruction. | |
| * | |
| * - For all other exceptions, mtval is 0. | |
| */ | |
| uint32_t ibex_mtval_read(void); | |
| /** | |
| * Reads the mepc register. | |
| * | |
| * When an exception is encountered, the current program counter is saved in | |
| * mepc, and the core jumps to the exception address. When an MRET instruction | |
| * is executed, the value from mepc replaces the current program counter. | |
| * | |
| * From the Ibex documentation (found at | |
| * https://ibex-core.readthedocs.io/en/latest/03_reference/cs_registers.html) | |
| * | |
| * Please note that in case of a fault, mepc must be modified to hold the | |
| * address of the next instruction, which can be at the 2byte (16bit) or 4byte | |
| * (32bit) offset, dependent on the fault cause instruction type (standard or | |
| * compressed). | |
| * | |
| * @return The mepc register value. | |
| */ | |
| uint32_t ibex_mepc_read(void); | |
| /** | |
| * Writes the mepc register. | |
| * | |
| * When an exception is encountered, the current program counter is saved in | |
| * mepc, and the core jumps to the exception address. When an MRET instruction | |
| * is executed, the value from mepc replaces the current program counter. | |
| * | |
| * From the Ibex documentation (found at | |
| * https://ibex-core.readthedocs.io/en/latest/03_reference/cs_registers.html) | |
| * | |
| * Please note that in case of a fault, mepc must be modified to hold the | |
| * address of the next instruction, which can be at the 2byte (16bit) or 4byte | |
| * (32bit) offset, dependent on the fault cause instruction type (standard or | |
| * compressed). | |
| * | |
| * @param The new value to be written to the mepc register. | |
| */ | |
| void ibex_mepc_write(uint32_t mepc); | |
| /** | |
| * Initializes the ibex timeout based on current mcycle count. | |
| * | |
| * @param timeout_usec Timeout in microseconds. | |
| * @return The initialized timeout value. | |
| */ | |
| inline ibex_timeout_t ibex_timeout_init(uint32_t timeout_usec) { | |
| return (ibex_timeout_t){ | |
| .cycles = udiv64_slow(kClockFreqCpuHz * timeout_usec, 1000000, NULL), | |
| .start = ibex_mcycle_read(), | |
| }; | |
| } | |
| /** | |
| * Check whether the timeout has expired. | |
| * | |
| * @param timeout Holds the counter start value. | |
| * @return True if the timeout has expired and false otherwise. | |
| */ | |
| inline bool ibex_timeout_check(const ibex_timeout_t *timeout) { | |
| return ibex_mcycle_read() - timeout->start > timeout->cycles; | |
| } | |
| /** | |
| * Returns the time elapsed in microseconds since `ibex_timeout_init` was | |
| * called. | |
| * | |
| * @param timeout Holds the counter start value.. | |
| * @return Time elapsed in microseconds. | |
| */ | |
| inline uint64_t ibex_timeout_elapsed(const ibex_timeout_t *timeout) { | |
| return udiv64_slow((ibex_mcycle_read() - timeout->start) * 1000000, | |
| kClockFreqCpuHz, NULL); | |
| } | |
| /** | |
| * Convenience macro to spin with timeout in microseconds. | |
| * | |
| * @param expr An expression that is evaluated multiple times until true. | |
| * @param timeout_usec Timeout in microseconds. | |
| */ | |
| #define IBEX_SPIN_FOR(expr, timeout_usec) \ | |
| do { \ | |
| const ibex_timeout_t timeout_ = ibex_timeout_init(timeout_usec); \ | |
| while (!(expr)) { \ | |
| CHECK(!ibex_timeout_check(&timeout_), \ | |
| "Timed out after %d usec (%d CPU cycles) waiting for " #expr, \ | |
| timeout_usec, (uint32_t)timeout_.cycles); \ | |
| } \ | |
| } while (0) | |
| #endif // OPENTITAN_SW_DEVICE_LIB_RUNTIME_IBEX_H_ |