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:
parent
a5574d9485
commit
e6973c764c
@ -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
|
||||
|
@ -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:
|
||||
*
|
||||
|
@ -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 ? */
|
||||
|
||||
|
@ -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().
|
||||
*/
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user