riscv_fork.c: Fix race condition when handling parent integer registers
We need to record the parent's integer register context upon exception
entry to a separate non-volatile area. Why?
Because xcp.regs can move due to a context switch within the fork() system
call, be it either via interrupt or a synchronization point.
Fix this by adding a "sregs" area where the saved user context is placed.
The critical section within fork() is also unnecessary.
diff --git a/arch/risc-v/include/irq.h b/arch/risc-v/include/irq.h
index fb16b84..cd2665c 100644
--- a/arch/risc-v/include/irq.h
+++ b/arch/risc-v/include/irq.h
@@ -613,6 +613,12 @@
uintreg_t *regs;
+#ifdef CONFIG_LIB_SYSCALL
+ /* User integer registers upon system call entry */
+
+ uintreg_t *sregs;
+#endif
+
/* FPU register save area */
#if defined(CONFIG_ARCH_FPU) && defined(CONFIG_ARCH_LAZYFPU)
diff --git a/arch/risc-v/src/common/riscv_fork.c b/arch/risc-v/src/common/riscv_fork.c
index 6f17f8d..76a784a 100644
--- a/arch/risc-v/src/common/riscv_fork.c
+++ b/arch/risc-v/src/common/riscv_fork.c
@@ -108,19 +108,14 @@
uintptr_t newtop;
uintptr_t stacktop;
uintptr_t stackutil;
- irqstate_t flags;
#ifdef CONFIG_SCHED_THREAD_LOCAL
uintptr_t tp;
#endif
UNUSED(context);
- /* parent regs may change in irq, we should disable irq here */
-
- flags = up_irq_save();
-
/* Allocate and initialize a TCB for the child task. */
- child = nxtask_setup_fork((start_t)parent->xcp.regs[REG_RA]);
+ child = nxtask_setup_fork((start_t)parent->xcp.sregs[REG_RA]);
if (!child)
{
sinfo("nxtask_setup_fork failed\n");
@@ -130,15 +125,15 @@
/* Copy parent user stack to child */
stacktop = (uintptr_t)parent->stack_base_ptr + parent->adj_stack_size;
- DEBUGASSERT(stacktop > parent->xcp.regs[REG_SP]);
- stackutil = stacktop - parent->xcp.regs[REG_SP];
+ DEBUGASSERT(stacktop > parent->xcp.sregs[REG_SP]);
+ stackutil = stacktop - parent->xcp.sregs[REG_SP];
/* Copy goes to child's user stack top */
newtop = (uintptr_t)child->cmn.stack_base_ptr + child->cmn.adj_stack_size;
newsp = newtop - stackutil;
- memcpy((void *)newsp, (const void *)parent->xcp.regs[REG_SP], stackutil);
+ memcpy((void *)newsp, (const void *)parent->xcp.sregs[REG_SP], stackutil);
#ifdef CONFIG_SCHED_THREAD_LOCAL
/* Save child's thread pointer */
@@ -169,7 +164,7 @@
/* Copy the parent integer context (overwrites child's SP and TP) */
- memcpy(child->cmn.xcp.regs, parent->xcp.regs, XCPTCONTEXT_SIZE);
+ memcpy(child->cmn.xcp.regs, parent->xcp.sregs, XCPTCONTEXT_SIZE);
/* Save FPU */
@@ -183,8 +178,6 @@
child->cmn.xcp.regs[REG_TP] = tp;
#endif
- up_irq_restore(flags);
-
/* And, finally, start the child task. On a failure, nxtask_start_fork()
* will discard the TCB by calling nxtask_abort_fork().
*/
diff --git a/arch/risc-v/src/common/riscv_swint.c b/arch/risc-v/src/common/riscv_swint.c
index ef64f4d..9126116 100644
--- a/arch/risc-v/src/common/riscv_swint.c
+++ b/arch/risc-v/src/common/riscv_swint.c
@@ -161,7 +161,7 @@
/* Set the user register context to TCB */
- rtcb->xcp.regs = context;
+ rtcb->xcp.sregs = context;
/* Indicate that we are in a syscall handler */