Software APIs
ibex.h
Go to the documentation of this file.
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5#ifndef OPENTITAN_SW_DEVICE_LIB_RUNTIME_IBEX_H_
6#define OPENTITAN_SW_DEVICE_LIB_RUNTIME_IBEX_H_
7
8#include <stdbool.h>
9#include <stddef.h>
10#include <stdint.h>
11
16
17// IBEX_SPIN_FOR needs a dependency on check.h, but the build fails if a
18// dependency on sw_lib_testing_check is added.
19
20/**
21 * @file
22 * @brief This header provides Ibex-specific functions and enums, such as
23 * cycle-accurate busy loops.
24 */
25
26/**
27 * An Ibex exception type.
28 *
29 * This enum is used to decode RISC-V exception causes generated by Ibex.
30 */
31typedef enum ibex_exc {
32 kIbexExcInstrMisaligned = 0,
33 kIbexExcInstrAccessFault = 1,
34 kIbexExcIllegalInstrFault = 2,
35 kIbexExcBreakpoint = 3,
36 kIbexExcLoadAccessFault = 5,
37 kIbexExcStoreAccessFault = 7,
38 kIbexExcUserECall = 8,
39 kIbexExcMachineECall = 11,
40 kIbexExcMax = 31
42
43/**
44 * An Ibex internal IRQ type.
45 *
46 * This enum is used to decode RISC-V internal IRQs generated by Ibex.
47 */
48typedef enum ibex_internal_irq {
49 kIbexInternalIrqLoadInteg = 0xffffffe0,
50 kIbexInternalIrqNmi = 0x8000001f
52
53/**
54 * A spinwait timeout type.
55 */
56typedef struct ibex_timeout {
57 /**
58 * The number of cycles to timeout.
59 */
60 uint64_t cycles;
61 /**
62 * The initial cycle count.
63 */
64 uint64_t start;
66
67/**
68 * Read the cycle counter.
69 *
70 * The value of the counter is stored across two 32-bit registers: `mcycle` and
71 * `mcycleh`. This function is guaranteed to return a valid 64-bit cycle
72 * counter value, even if `mcycle` overflows before reading `mcycleh`.
73 *
74 * Adapted from: The RISC-V Instruction Set Manual, Volume I: Unprivileged ISA
75 * V20191213, pp. 61.
76 */
78inline uint64_t ibex_mcycle_read(void) {
79 uint32_t cycle_low = 0;
80 uint32_t cycle_high = 0;
81 uint32_t cycle_high_2 = 0;
82 asm volatile(
83 "1:"
84 " csrr %0, mcycleh;" // Read `mcycleh`.
85 " csrr %1, mcycle;" // Read `mcycle`.
86 " csrr %2, mcycleh;" // Read `mcycleh` again.
87 " bne %0, %2, 1b;" // Try again if `mcycle` overflowed before
88 // reading `mcycleh`.
89 : "=r"(cycle_high), "=r"(cycle_low), "=r"(cycle_high_2)
90 :);
91 return (uint64_t)cycle_high << 32 | cycle_low;
92}
93
94/**
95 * Reads the mcause register.
96 *
97 * When an exception is encountered, the corresponding exception code is stored
98 * in mcause register.
99 *
100 * A list of the exception codes can be found at:
101 * https://ibex-core.readthedocs.io/en/latest/03_reference/
102 * exception_interrupts.html#exceptions
103 */
105uint32_t ibex_mcause_read(void);
106
107/**
108 * Reads the mtval register.
109 *
110 * When an exception is encountered, the Machine Trap Value (mtval) register
111 * can holds exception-specific information to assist software in handling the
112 * trap.
113 *
114 * From the Ibex documentation (found at
115 * https://ibex-core.readthedocs.io/en/latest/03_reference/cs_registers.html)
116 * - In the case of errors in the load-store unit mtval holds the address of
117 * the transaction causing the error.
118 *
119 * - If a transaction is misaligned, mtval holds the address of the missing
120 * transaction part.
121 *
122 * - In the case of illegal instruction exceptions, mtval holds the actual
123 * faulting instruction.
124 *
125 * - For all other exceptions, mtval is 0.
126 */
128uint32_t ibex_mtval_read(void);
129
130/**
131 * Reads the mepc register.
132 *
133 * When an exception is encountered, the current program counter is saved in
134 * mepc, and the core jumps to the exception address. When an MRET instruction
135 * is executed, the value from mepc replaces the current program counter.
136 *
137 * From the Ibex documentation (found at
138 * https://ibex-core.readthedocs.io/en/latest/03_reference/cs_registers.html)
139 *
140 * Please note that in case of a fault, mepc must be modified to hold the
141 * address of the next instruction, which can be at the 2byte (16bit) or 4byte
142 * (32bit) offset, dependent on the fault cause instruction type (standard or
143 * compressed).
144 *
145 * @return The mepc register value.
146 */
148uint32_t ibex_mepc_read(void);
149
150/**
151 * Writes the mepc register.
152 *
153 * When an exception is encountered, the current program counter is saved in
154 * mepc, and the core jumps to the exception address. When an MRET instruction
155 * is executed, the value from mepc replaces the current program counter.
156 *
157 * From the Ibex documentation (found at
158 * https://ibex-core.readthedocs.io/en/latest/03_reference/cs_registers.html)
159 *
160 * Please note that in case of a fault, mepc must be modified to hold the
161 * address of the next instruction, which can be at the 2byte (16bit) or 4byte
162 * (32bit) offset, dependent on the fault cause instruction type (standard or
163 * compressed).
164 *
165 * @param The new value to be written to the mepc register.
166 */
167void ibex_mepc_write(uint32_t mepc);
168
169/**
170 * Initializes the ibex timeout based on current mcycle count.
171 *
172 * @param timeout_usec Timeout in microseconds.
173 * @return The initialized timeout value.
174 */
176inline ibex_timeout_t ibex_timeout_init(uint32_t timeout_usec) {
177 return (ibex_timeout_t){
178 .cycles = udiv64_slow(kClockFreqCpuHz * timeout_usec, 1000000, NULL),
179 .start = ibex_mcycle_read(),
180 };
181}
182
183/**
184 * Check whether the timeout has expired.
185 *
186 * @param timeout Holds the counter start value.
187 * @return True if the timeout has expired and false otherwise.
188 */
190inline bool ibex_timeout_check(const ibex_timeout_t *timeout) {
191 return ibex_mcycle_read() - timeout->start > timeout->cycles;
192}
193
194/**
195 * Returns the time elapsed in microseconds since `ibex_timeout_init` was
196 * called.
197 *
198 * @param timeout Holds the counter start value..
199 * @return Time elapsed in microseconds.
200 */
202inline uint64_t ibex_timeout_elapsed(const ibex_timeout_t *timeout) {
203 return udiv64_slow((ibex_mcycle_read() - timeout->start) * 1000000,
204 kClockFreqCpuHz, NULL);
205}
206
207/**
208 * Convenience macro to spin with timeout in microseconds.
209 *
210 * @param expr An expression that is evaluated multiple times until true.
211 * @param timeout_usec Timeout in microseconds.
212 */
213// TODO(#17495) Remove this macro and rename `IBEX_TRY_SPIN_FOR` when all the
214// test are refactored.
215#define IBEX_SPIN_FOR(expr, timeout_usec) \
216 do { \
217 const ibex_timeout_t timeout_ = ibex_timeout_init(timeout_usec); \
218 while (!(expr)) { \
219 CHECK(!ibex_timeout_check(&timeout_), \
220 "Timed out after %d usec (%d CPU cycles) waiting for " #expr, \
221 (uint32_t)timeout_usec, (uint32_t)timeout_.cycles); \
222 } \
223 } while (0)
224
225/**
226 * Convenience macro to spin with timeout in microseconds.
227 *
228 * @param expr An expression that is evaluated multiple times until true.
229 * @param timeout_usec Timeout in microseconds.
230 * @return `kDeadlineExceeded` in case of timeout.
231 */
232#define IBEX_TRY_SPIN_FOR(expr, timeout_usec) \
233 do { \
234 const ibex_timeout_t timeout_ = ibex_timeout_init(timeout_usec); \
235 while (!(expr)) { \
236 if (ibex_timeout_check(&timeout_)) { \
237 return DEADLINE_EXCEEDED(); \
238 } \
239 } \
240 } while (0)
241
242#endif // OPENTITAN_SW_DEVICE_LIB_RUNTIME_IBEX_H_