[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.