riscv: Introduce RISCV_ALWAYS_SWITCH_THROUGH_ECALL

Some early RISC-V SoCs have a problem when an `mret` instruction is used
outside a trap handler.

After the latest Zephyr RISC-V huge rework, the arch_switch code is
indeed calling `mret` when not in handler mode, breaking some early
RISC-V platforms.

Optionally restore the old behavior by adding a new
CONFIG_RISCV_ALWAYS_SWITCH_THROUGH_ECALL symbol.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index 72f094b..b8030cb 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -28,6 +28,16 @@
 	  global pointer at program start or earlier than any instruction
 	  using GP relative addressing.
 
+config RISCV_ALWAYS_SWITCH_THROUGH_ECALL
+	bool "Do not use mret outside a trap handler context"
+	depends on !RISCV_PMP
+	help
+	  Use mret instruction only when in a trap handler.
+	  This is for RISC-V implementations that require every mret to be
+	  balanced with an ecall. This is not required by the RISC-V spec
+	  and most people should say n here to minimize context switching
+	  overhead.
+
 menu "RISCV Processor Options"
 
 config INCLUDE_RESET_VECTOR
diff --git a/arch/riscv/core/isr.S b/arch/riscv/core/isr.S
index 7a9968c..de9ade6 100644
--- a/arch/riscv/core/isr.S
+++ b/arch/riscv/core/isr.S
@@ -278,6 +278,15 @@
 	beq t0, t1, do_irq_offload
 #endif
 
+#ifdef CONFIG_RISCV_ALWAYS_SWITCH_THROUGH_ECALL
+	li t1, RV_ECALL_SCHEDULE
+	bne t0, t1, skip_schedule
+	lr a0, __z_arch_esf_t_a0_OFFSET(sp)
+	lr a1, __z_arch_esf_t_a1_OFFSET(sp)
+	j reschedule
+skip_schedule:
+#endif
+
 	/* default fault code is K_ERR_KERNEL_OOPS */
 	li a0, 3
 	j 1f
@@ -483,7 +492,7 @@
 	call z_check_stack_sentinel
 #endif
 
-reschedule:
+check_reschedule:
 
 	/* Get pointer to current thread on this CPU */
 	lr a1, ___cpu_t_current_OFFSET(s0)
@@ -501,6 +510,8 @@
 	addi sp, sp, 16
 	beqz a0, no_reschedule
 
+reschedule:
+
 	/*
 	 * Perform context switch:
 	 * a0 = new thread
diff --git a/arch/riscv/include/kernel_arch_func.h b/arch/riscv/include/kernel_arch_func.h
index 7de898f..1eee9e9 100644
--- a/arch/riscv/include/kernel_arch_func.h
+++ b/arch/riscv/include/kernel_arch_func.h
@@ -44,8 +44,11 @@
 	struct k_thread *new = switch_to;
 	struct k_thread *old = CONTAINER_OF(switched_from, struct k_thread,
 					    switch_handle);
-
+#ifdef CONFIG_RISCV_ALWAYS_SWITCH_THROUGH_ECALL
+	arch_syscall_invoke2((uintptr_t)new, (uintptr_t)old, RV_ECALL_SCHEDULE);
+#else
 	z_riscv_switch(new, old);
+#endif
 }
 
 FUNC_NORETURN void z_riscv_fatal_error(unsigned int reason,
diff --git a/include/zephyr/arch/riscv/syscall.h b/include/zephyr/arch/riscv/syscall.h
index be58172..e9d9211 100644
--- a/include/zephyr/arch/riscv/syscall.h
+++ b/include/zephyr/arch/riscv/syscall.h
@@ -21,6 +21,7 @@
  */
 #define RV_ECALL_RUNTIME_EXCEPT		0
 #define RV_ECALL_IRQ_OFFLOAD		1
+#define RV_ECALL_SCHEDULE		2
 
 #ifndef _ASMLANGUAGE