[sw/ottf] Fix processing of `mepc` on synchronous IRQ. When an IRQ is fired on RISC-V processors, the PC is stored in the `mepc` register. This address is then used as the return address when the `mret` instruction is called upon returning from an ISR. When the IRQ is synchronous, e.g. an exception, the `mepc` address must be updated to point to the **_next_** instruction **_after_** the instruction that caused the exception, otherwise we will get stuck in a continuous exception cycle. Since we support RISC-V compressed instructions, we must first check whether the faulting instruction is compressed (i.e., 16-bits) or not (i.e., 32-bits) in order to know what offset (2 or 4) to add to the `mepc` before returning from the ISR. Until now, the OTTF ISR handlers assume the trapped instruction was uncompressed. This fixes #9208. Signed-off-by: Timothy Trippel <ttrippel@google.com>
diff --git a/sw/device/lib/testing/test_framework/freertos_port.S b/sw/device/lib/testing/test_framework/freertos_port.S index 6bc7ac3..6995f75 100644 --- a/sw/device/lib/testing/test_framework/freertos_port.S +++ b/sw/device/lib/testing/test_framework/freertos_port.S
@@ -4,6 +4,7 @@ // Ibex does not implement additional registers beyond the RV32I spec. #define PORT_WORD_SIZE 4 +#define PORT_HALF_WORD_SIZE (PORT_WORD_SIZE / 2) #define PORT_CONTEXT_SIZE (30 * PORT_WORD_SIZE) .extern pxCurrentTCB @@ -16,6 +17,36 @@ // ----------------------------------------------------------------------------- /** + * Save MEPC to the stack. + * + * NOTE: this IRQ is synchronous, therefore, we must update the ISR return + * address to point to the instruction after the one that triggered this IRQ. + * Since we support the RISC-V compressed instructions extension, we need to + * check if the two least significant bits of the instruction are + * b11 (0x3), which means that the trapped instruction is not compressed, + * i.e., the trapped instruction is 32bits = 4bytes. Otherwise, the trapped + * instruction is 16bits = 2bytes. + */ + +.balign 4 +.type process_mepc_on_synchronous_irq, @function +process_mepc_on_synchronous_irq: + csrr t0, mepc + li t1, 0x3 + and t2, t0, t1 + beq t2, t1, L_32bit_trap_instr + addi t0, t0, PORT_HALF_WORD_SIZE + ret +L_32bit_trap_instr: + addi t0, t0, PORT_WORD_SIZE + ret + + // Set size so this function can be disassembled. + .size process_mepc_on_synchronous_irq, .-process_mepc_on_synchronous_irq + +// ----------------------------------------------------------------------------- + +/** * Exception handler. */ .balign 4 @@ -57,11 +88,9 @@ csrr t0, mstatus sw t0, 29 * PORT_WORD_SIZE(sp) - // Save MEPC to the stack. - // NOTE: this IRQ is synchronous, therefore, we must update the ISR return - // address to point to the instruction after the one that triggered this IRQ. - csrr t0, mepc - addi t0, t0, PORT_WORD_SIZE + // Save MEPC to the stack after updating it to the next instruction (since + // this is a synchronous IRQ). + jal process_mepc_on_synchronous_irq sw t0, 0(sp) // Store stack pointer to current TCB. @@ -121,11 +150,9 @@ csrr t0, mstatus sw t0, 29 * PORT_WORD_SIZE(sp) - // Save MEPC to the stack. - // NOTE: this IRQ is synchronous, therefore, we must update the ISR return - // address to point to the instruction after the one that triggered this IRQ. - csrr t0, mepc - addi t0, t0, PORT_WORD_SIZE + // Save MEPC to the stack after updating it to the next instruction (since + // this is a synchronous IRQ). + jal process_mepc_on_synchronous_irq sw t0, 0(sp) // Store stack pointer to current TCB.