riscv/syscall: Optimize user service call performance

This patch changes how user service calls are executed:
Instead of using the common interrupt logic, execute the user service
call directly.

Why? When a user makes a service call request, all of the service call
parameters are already loaded into the correct registers, thus it makes
no sense to first clobber them and then reload them, which is what the
old logic does. It is much more effective to run the system call directly.

During a user system call the interrupts must be re-enabled, which the
new logic does as soon as we know the exception is a user service call
request.

This patch does NOT change the behavior of reserved system calls (like
switch_context), only the user service call request is affected.
This commit is contained in:
Ville Juven 2024-05-20 09:45:28 +03:00 committed by Alan Carvalho de Assis
parent a5574d9485
commit e6973c764c
6 changed files with 127 additions and 219 deletions

View File

@ -96,14 +96,6 @@
/* Configuration ************************************************************/
/* If this is a kernel build, how many nested system calls should we
* support?
*/
#ifndef CONFIG_SYS_NNEST
# define CONFIG_SYS_NNEST 2
#endif
/* Processor PC */
#define REG_EPC_NDX 0
@ -539,18 +531,6 @@
#ifndef __ASSEMBLY__
/* This structure represents the return state from a system call */
#ifdef CONFIG_LIB_SYSCALL
struct xcpt_syscall_s
{
uintptr_t sysreturn; /* The return PC */
#ifndef CONFIG_BUILD_FLAT
uintptr_t int_ctx; /* Interrupt context (i.e. m-/sstatus) */
#endif
};
#endif
/* The following structure is included in the TCB and defines the complete
* state of the thread.
*/
@ -582,16 +562,6 @@ struct xcptcontext
uintptr_t sigreturn;
#endif
#ifdef CONFIG_LIB_SYSCALL
/* The following array holds information needed to return from each nested
* system call.
*/
uint8_t nsyscalls;
struct xcpt_syscall_s syscall[CONFIG_SYS_NNEST];
#endif
#ifdef CONFIG_ARCH_ADDRENV
#ifdef CONFIG_ARCH_KERNEL_STACK
/* In this configuration, all syscalls execute from an internal kernel

View File

@ -77,15 +77,6 @@
#define SYS_switch_context (2)
#ifdef CONFIG_LIB_SYSCALL
/* SYS call 3:
*
* void riscv_syscall_return(void);
*/
#define SYS_syscall_return (3)
#endif /* CONFIG_LIB_SYSCALL */
#ifndef CONFIG_BUILD_FLAT
/* SYS call 4:
*

View File

@ -30,6 +30,10 @@
#include <sys/types.h>
#ifdef CONFIG_LIB_SYSCALL
# include <syscall.h>
#endif
#include "chip.h"
#include "riscv_percpu.h"
@ -73,6 +77,7 @@
.section EXCEPTION_SECTION
.global exception_common
.global return_from_exception
.global return_from_syscall
.align 8
exception_common:
@ -99,24 +104,69 @@ exception_common:
addi sp, sp, -XCPTCONTEXT_SIZE
save_ctx sp
csrr s0, CSR_STATUS
REGSTORE s0, REG_INT_CTX(sp) /* status */
csrr s0, CSR_STATUS /* s0=status */
csrr s1, CSR_EPC /* s1=exception PC */
csrr s2, CSR_CAUSE /* s2=cause */
#ifdef CONFIG_ARCH_KERNEL_STACK
csrr s0, CSR_SCRATCH
REGLOAD s0, RISCV_PERCPU_USP(s0)
csrr s3, CSR_SCRATCH
REGLOAD s3, RISCV_PERCPU_USP(s3)
#else
addi s0, sp, XCPTCONTEXT_SIZE
addi s3, sp, XCPTCONTEXT_SIZE
#endif
REGSTORE s0, REG_X2(sp) /* original SP */
REGSTORE s0, REG_INT_CTX(sp)
REGSTORE s1, REG_EPC(sp)
REGSTORE s3, REG_SP(sp)
csrr s0, CSR_EPC
REGSTORE s0, REG_EPC(sp) /* exception PC */
#ifdef CONFIG_LIB_SYSCALL
/* Check whether it is an exception or interrupt */
blt s2, x0, handle_irq /* If cause < 0 it is interrupt */
/* Is it a system call ? */
li s3, RISCV_IRQ_ECALLU /* Is it a system call ? */
bne s2, s3, handle_irq
/* Is it one of the reserved system calls ? */
li s3, CONFIG_SYS_RESERVED
blt a0, s3, handle_irq /* If a0 < CONFIG_SYS_RESERVED */
/* It is a system call, re-enable interrupts if they were enabled */
andi s3, s0, STATUS_PIE
beqz s3, 1f
csrs CSR_STATUS, STATUS_IE
1:
addi s1, s1, 0x4 /* Must move EPC forward by +4 */
REGSTORE s1, REG_EPC(sp) /* Updated EPC to user context */
csrr tp, CSR_SCRATCH /* Load kernel TP */
REGLOAD tp, RISCV_PERCPU_TCB(tp)
call x1, dispatch_syscall /* Dispatch the system call */
return_from_syscall:
/* System call is done, disable interrupts */
csrc CSR_STATUS, STATUS_IE
/* Clean up after system call */
REGSTORE a0, REG_A0(sp) /* Syscall return value to user context */
mv a0, sp /* Return to same context */
tail return_from_exception
handle_irq:
#endif
/* Setup arg0(exception cause), arg1(context) */
csrr a0, CSR_CAUSE /* exception cause */
mv a0, s2 /* exception cause */
mv a1, sp /* context = sp */
#if CONFIG_ARCH_INTERRUPTSTACK > 15
@ -152,9 +202,17 @@ return_from_exception:
REGLOAD s0, REG_EPC(sp) /* restore sepc */
csrw CSR_EPC, s0
REGLOAD s0, REG_INT_CTX(sp) /* restore sstatus */
REGLOAD s0, REG_INT_CTX(sp) /* restore status */
csrw CSR_STATUS, s0
#ifdef CONFIG_LIB_SYSCALL
/* Store tcb to scratch register */
call x1, nxsched_self
csrr s1, CSR_SCRATCH
REGSTORE a0, RISCV_PERCPU_TCB(s1)
#endif
#ifdef CONFIG_ARCH_KERNEL_STACK
/* Returning to userspace ? */

View File

@ -246,29 +246,6 @@ pid_t riscv_fork(const struct fork_s *context)
fregs[REG_FS11] = context->fs11; /* Saved register fs11 */
#endif
#ifdef CONFIG_LIB_SYSCALL
/* If we got here via a syscall, then we are going to have to setup some
* syscall return information as well.
*/
if (parent->xcp.nsyscalls > 0)
{
int index;
for (index = 0; index < parent->xcp.nsyscalls; index++)
{
child->cmn.xcp.syscall[index].sysreturn =
parent->xcp.syscall[index].sysreturn;
#ifndef CONFIG_BUILD_FLAT
child->cmn.xcp.syscall[index].int_ctx =
parent->xcp.syscall[index].int_ctx;
#endif
}
child->cmn.xcp.nsyscalls = parent->xcp.nsyscalls;
}
#endif /* CONFIG_LIB_SYSCALL */
/* And, finally, start the child task. On a failure, nxtask_start_fork()
* will discard the TCB by calling nxtask_abort_fork().
*/

View File

@ -438,16 +438,6 @@ void *riscv_perform_syscall(uintptr_t *regs);
#define riscv_switchcontext(prev, next) \
sys_call2(SYS_switch_context, (uintptr_t)prev, (uintptr_t)next)
#ifdef CONFIG_BUILD_KERNEL
/* SYS call 3:
*
* void riscv_syscall_return(void);
*/
#define riscv_syscall_return() sys_call0(SYS_syscall_return)
#endif /* CONFIG_BUILD_KERNEL */
#undef EXTERN
#ifdef __cplusplus
}

View File

@ -48,10 +48,16 @@
* Pre-processor Definitions
****************************************************************************/
#ifdef CONFIG_LIB_SYSCALL
# define TCB_FLAGS_OFFSET offsetof(struct tcb_s, flags)
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
#ifdef CONFIG_LIB_SYSCALL
/****************************************************************************
* Name: dispatch_syscall
*
@ -69,14 +75,38 @@
*
****************************************************************************/
#ifdef CONFIG_LIB_SYSCALL
static void dispatch_syscall(void) naked_function;
static void dispatch_syscall(void)
uintptr_t dispatch_syscall(unsigned int nbr, uintptr_t parm1,
uintptr_t parm2, uintptr_t parm3,
uintptr_t parm4, uintptr_t parm5,
uintptr_t parm6)
{
register long a0 asm("a0") = (long)(nbr);
register long a1 asm("a1") = (long)(parm1);
register long a2 asm("a2") = (long)(parm2);
register long a3 asm("a3") = (long)(parm3);
register long a4 asm("a4") = (long)(parm4);
register long a5 asm("a5") = (long)(parm5);
register long a6 asm("a6") = (long)(parm6);
uintptr_t ret;
/* Valid system call ? */
if (a0 > SYS_maxsyscall)
{
/* Nope, get out */
return -ENOSYS;
}
/* ra gets clobbered below, but it does not matter */
asm volatile
(
"addi sp, sp, -" STACK_FRAME_SIZE "\n" /* Create a stack frame to hold ra */
REGSTORE " ra, 0(sp)\n" /* Save ra in the stack frame */
REGLOAD " t0, %1(tp)\n" /* Load tcb->flags */
"ori t0, t0, %2\n" /* tcb->flags |= TCB_FLAG_SYSCALL */
REGSTORE " t0, %1(tp)\n"
"addi a0, a0, -%3\n" /* Offset a0 to account for the reserved syscalls */
"la t0, g_stublookup\n" /* t0=The base of the stub lookup table */
#ifdef CONFIG_ARCH_RV32
"slli a0, a0, 2\n" /* a0=Offset for the stub lookup table */
@ -86,16 +116,26 @@ static void dispatch_syscall(void)
"add t0, t0, a0\n" /* t0=The address in the table */
REGLOAD " t0, 0(t0)\n" /* t0=The address of the stub for this syscall */
"jalr ra, t0\n" /* Call the stub (modifies ra) */
REGLOAD " ra, 0(sp)\n" /* Restore ra */
"addi sp, sp, " STACK_FRAME_SIZE "\n" /* Destroy the stack frame */
"mv a2, a0\n" /* a2=Save return value in a0 */
"li a0, 3\n" /* a0=SYS_syscall_return (3) */
#ifdef CONFIG_ARCH_USE_S_MODE
"j sys_call2" /* Return from the syscall */
#else
"ecall" /* Return from the syscall */
#endif
REGLOAD " t0, %1(tp)\n" /* Load tcb->flags */
"andi t0, t0, ~%2\n" /* tcb->flags &= ~TCB_FLAG_SYSCALL */
REGSTORE " t0, %1(tp)\n"
: "+r"(a0)
: "i"(TCB_FLAGS_OFFSET),
"i"(TCB_FLAG_SYSCALL),
"i"(CONFIG_SYS_RESERVED),
"r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a6)
: "t0", "memory"
);
/* a0 gets clobbered below, save it locally here */
ret = a0;
/* Unmask any pending signals now */
nxsig_unmask_pendingsignal();
return ret;
}
#endif
@ -185,70 +225,6 @@ int riscv_swint(int irq, void *context, void *arg)
}
break;
/* A0=SYS_syscall_return: This is a SYSCALL return command:
*
* void up_sycall_return(void);
*
* At this point, the following values are saved in context:
*
* A0 = SYS_syscall_return
*
* We need to restore the saved return address and return in
* unprivileged thread mode.
*/
#ifdef CONFIG_LIB_SYSCALL
case SYS_syscall_return:
{
struct tcb_s *rtcb = nxsched_self();
int index = (int)rtcb->xcp.nsyscalls - 1;
/* Make sure that there is a saved syscall return address. */
DEBUGASSERT(index >= 0);
/* Setup to return to the saved syscall return address in
* the original mode.
*/
regs[REG_EPC] = rtcb->xcp.syscall[index].sysreturn;
#ifndef CONFIG_BUILD_FLAT
regs[REG_INT_CTX] = rtcb->xcp.syscall[index].int_ctx;
#endif
/* The return value must be in A0-A1.
* dispatch_syscall() temporarily moved the value for R0 into A2.
*/
regs[REG_A0] = regs[REG_A2];
#ifdef CONFIG_ARCH_KERNEL_STACK
/* If this is the outermost SYSCALL and if there is a saved user
* stack pointer, then restore the user stack pointer on this
* final return to user code.
*/
if (index == 0 && rtcb->xcp.ustkptr != NULL)
{
regs[REG_SP] = (uintptr_t)rtcb->xcp.ustkptr;
rtcb->xcp.ustkptr = NULL;
}
#endif
/* Save the new SYSCALL nesting level */
rtcb->xcp.nsyscalls = index;
/* Handle any signal actions that were deferred while processing
* the system call.
*/
rtcb->flags &= ~TCB_FLAG_SYSCALL;
nxsig_unmask_pendingsignal();
}
break;
#endif
/* R0=SYS_task_start: This a user task start
*
* void up_task_start(main_t taskentry, int argc,
@ -391,6 +367,7 @@ int riscv_swint(int irq, void *context, void *arg)
if (rtcb->xcp.kstack != NULL)
{
uintptr_t usp;
uintptr_t *usr_regs;
/* Store the current kernel stack pointer so it is not lost */
@ -398,7 +375,9 @@ int riscv_swint(int irq, void *context, void *arg)
/* Copy "info" into user stack */
usp = rtcb->xcp.saved_regs[REG_SP];
usr_regs = (uintptr_t *)((uintptr_t)rtcb->xcp.ktopstk -
XCPTCONTEXT_SIZE);
usp = usr_regs[REG_SP];
/* Create a frame for info and copy the kernel info */
@ -454,66 +433,9 @@ int riscv_swint(int irq, void *context, void *arg)
break;
#endif
/* This is not an architecture-specify system call. If NuttX is built
* as a standalone kernel with a system call interface, then all of the
* additional system calls must be handled as in the default case.
*/
default:
{
#ifdef CONFIG_LIB_SYSCALL
struct tcb_s *rtcb = nxsched_self();
int index = rtcb->xcp.nsyscalls;
/* Verify that the SYS call number is within range */
DEBUGASSERT(CURRENT_REGS[REG_A0] < SYS_maxsyscall);
/* Make sure that we got here that there is a no saved syscall
* return address. We cannot yet handle nested system calls.
*/
DEBUGASSERT(index < CONFIG_SYS_NNEST);
/* Setup to return to dispatch_syscall in privileged mode. */
rtcb->xcp.syscall[index].sysreturn = regs[REG_EPC];
#ifndef CONFIG_BUILD_FLAT
rtcb->xcp.syscall[index].int_ctx = regs[REG_INT_CTX];
#endif
rtcb->xcp.nsyscalls = index + 1;
regs[REG_EPC] = (uintptr_t)dispatch_syscall;
#ifndef CONFIG_BUILD_FLAT
regs[REG_INT_CTX] |= STATUS_PPP; /* Privileged mode */
#endif
/* Offset A0 to account for the reserved values */
regs[REG_A0] -= CONFIG_SYS_RESERVED;
/* Indicate that we are in a syscall handler. */
rtcb->flags |= TCB_FLAG_SYSCALL;
#else
svcerr("ERROR: Bad SYS call: %" PRIdPTR "\n", regs[REG_A0]);
#endif
#ifdef CONFIG_ARCH_KERNEL_STACK
/* If this is the first level system call, we must store the user
* stack pointer so it doesn't get lost.
*/
if (index == 0 && rtcb->xcp.kstack != NULL)
{
DEBUGASSERT(rtcb->xcp.ustkptr == NULL);
rtcb->xcp.ustkptr = (uintptr_t *)regs[REG_SP];
regs[REG_SP] = (uintptr_t)regs;
}
#endif
}
DEBUGPANIC();
break;
}