* arch/arm/src/arm_a_r/arm_syscall.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <syscall.h>
#include <nuttx/addrenv.h>
#include <nuttx/arch.h>
#include <nuttx/macro.h>
#include <nuttx/sched.h>
#include "addrenv.h"
#include "arm.h"
#include "arm_internal.h"
#include "sched/sched.h"
#include "signal/signal.h"
* Private Functions
****************************************************************************/
* Name: dump_syscall
*
* Description:
* Dump the syscall registers
*
****************************************************************************/
static void dump_syscall(const char *tag, uint32_t cmd, const uint32_t *regs)
{
* and R1..R7 = variable number of arguments depending on the system call.
*/
#ifdef CONFIG_LIB_SYSCALL
if (cmd >= CONFIG_SYS_RESERVED)
{
svcinfo("SYSCALL %s: regs: %p cmd: %" PRId32 " name: %s\n", tag,
regs, cmd, g_funcnames[cmd - CONFIG_SYS_RESERVED]);
}
else
#endif
{
svcinfo("SYSCALL %s: regs: %p cmd: %" PRId32 "\n", tag, regs, cmd);
}
svcinfo(" R0: %08" PRIx32 " %08" PRIx32 " %08" PRIx32 " %08" PRIx32
" %08" PRIx32 " %08" PRIx32 " %08" PRIx32 " %08" PRIx32 "\n",
regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3],
regs[REG_R4], regs[REG_R5], regs[REG_R6], regs[REG_R7]);
svcinfo(" R8: %08" PRIx32 " %08" PRIx32 " %08" PRIx32 " %08" PRIx32
" %08" PRIx32 " %08" PRIx32 " %08" PRIx32 " %08" PRIx32 "\n",
regs[REG_R8], regs[REG_R9], regs[REG_R10], regs[REG_R11],
regs[REG_R12], regs[REG_R13], regs[REG_R14], regs[REG_R15]);
svcinfo("CPSR: %08" PRIx32 "\n", regs[REG_CPSR]);
}
* Public Functions
****************************************************************************/
* Name: arm_syscall
*
* Description:
* SVC interrupts will vector here with insn=the SVC instruction and
* xcp=the interrupt context
*
* The handler may get the SVC number be de-referencing the return
* address saved in the xcp and decoding the SVC instruction
*
****************************************************************************/
uint32_t *arm_syscall(uint32_t *regs)
{
struct tcb_s **running_task = &g_running_task;
struct tcb_s *tcb = this_task();
uint32_t cmd;
#ifndef CONFIG_BUILD_FLAT
uint32_t cpsr;
#endif
DEBUGASSERT(!up_interrupt_context());
up_set_interrupt_context(true);
sched_note_irqhandler(NR_IRQS, arm_syscall, true);
if (*running_task != NULL)
{
nxsched_suspend_scheduler(*running_task);
}
cmd = regs[REG_R0];
* should not be overwriten
*/
if (cmd != SYS_restore_context)
{
(*running_task)->xcp.regs = regs;
}
* and R1..R7 = variable number of arguments depending on the system call.
*/
dump_syscall("Entry", cmd, regs);
switch (cmd)
{
*
* void arm_syscall_return(void);
*
* At this point, the following values are saved in context:
*
* R0 = 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 = this_task();
int index = (int)rtcb->xcp.nsyscalls - 1;
DEBUGASSERT(index >= 0);
* the original mode.
*/
regs[REG_PC] = rtcb->xcp.syscall[index].sysreturn;
#ifndef CONFIG_BUILD_FLAT
regs[REG_CPSR] = rtcb->xcp.syscall[index].cpsr |
(regs[REG_CPSR] & (PSR_F_BIT | PSR_I_BIT));
#endif
* temporarily moved the value for R0 into R2.
*/
regs[REG_R0] = regs[REG_R2];
#ifdef CONFIG_ARCH_KERNEL_STACK
* 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] = (uint32_t)rtcb->xcp.ustkptr;
rtcb->xcp.ustkptr = NULL;
}
#endif
rtcb->xcp.nsyscalls = index;
* the system call.
*/
atomic_and(&rtcb->flags, ~TCB_FLAG_SYSCALL);
nxsig_unmask_pendingsignal();
regs = tcb->xcp.regs;
}
break;
#endif
case SYS_switch_context:
case SYS_restore_context:
#ifdef CONFIG_ARCH_ADDRENV
* will post sem to hpwork, so we have to call before restore context
*/
addrenv_switch(tcb);
tcb = this_task();
#endif
break_critical_section();
*running_task = tcb;
regs = tcb->xcp.regs;
break;
*
* void up_task_start(main_t taskentry, int argc, char *argv[])
* noreturn_function;
*
* At this point, the following values are saved in context:
*
* R0 = SYS_task_start
* R1 = taskentry
* R2 = argc
* R3 = argv
*/
#ifndef CONFIG_BUILD_FLAT
case SYS_task_start:
{
* unprivileged mode. We need:
*
* R0 = argc
* R1 = argv
* PC = taskentry
* CSPR = user mode
*/
#ifdef CONFIG_BUILD_KERNEL
regs[REG_PC] = regs[REG_R1];
regs[REG_R0] = regs[REG_R2];
regs[REG_R1] = regs[REG_R3];
#else
regs[REG_PC] = (uint32_t)USERSPACE->task_startup & ~1;
regs[REG_R0] = regs[REG_R1];
regs[REG_R1] = regs[REG_R2];
regs[REG_R2] = regs[REG_R3];
#endif
cpsr = regs[REG_CPSR] & ~PSR_MODE_MASK;
regs[REG_CPSR] = cpsr | PSR_MODE_USR;
}
break;
#endif
*
* void up_pthread_start(pthread_startroutine_t entrypt,
* pthread_addr_t arg) noreturn_function;
*
* At this point, the following values are saved in context:
*
* R0 = SYS_pthread_start
* R1 = entrypt
* R2 = arg
*/
#if !defined(CONFIG_BUILD_FLAT) && !defined(CONFIG_DISABLE_PTHREAD)
case SYS_pthread_start:
{
* unprivileged mode. We need:
*
* R0 = entrypt
* R1 = arg
* PC = startup
* CSPR = user mode
*/
regs[REG_PC] = regs[REG_R1];
regs[REG_R0] = regs[REG_R2];
regs[REG_R1] = regs[REG_R3];
cpsr = regs[REG_CPSR] & ~PSR_MODE_MASK;
regs[REG_CPSR] = cpsr | PSR_MODE_USR;
}
break;
#endif
#if !defined(CONFIG_BUILD_FLAT) && !defined(CONFIG_DISABLE_SIGNALS)
*
* void signal_handler(_sa_sigaction_t sighand, int signo,
* siginfo_t *info, void *ucontext);
*
* At this point, the following values are saved in context:
*
* R0 = SYS_signal_handler
* R1 = sighand
* R2 = signo
* R3 = info
* ucontext (on the stack)
*/
case SYS_signal_handler:
{
struct tcb_s *rtcb = this_task();
DEBUGASSERT(rtcb->xcp.sigreturn == 0);
rtcb->xcp.sigreturn = regs[REG_PC];
* unprivileged mode.
*/
#ifdef CONFIG_BUILD_KERNEL
regs[REG_PC] = (uint32_t)ARCH_DATA_RESERVE->ar_sigtramp;
#else
regs[REG_PC] = (uint32_t)USERSPACE->signal_handler & ~1;
#endif
cpsr = regs[REG_CPSR] & ~PSR_MODE_MASK;
regs[REG_CPSR] = cpsr | PSR_MODE_USR;
* userpace_s signal_handler.
*/
regs[REG_R0] = regs[REG_R1];
regs[REG_R1] = regs[REG_R2];
regs[REG_R2] = regs[REG_R3];
regs[REG_R3] = regs[REG_R4];
#ifdef CONFIG_ARCH_KERNEL_STACK
* on the kernel stack now. We need to switch back to the user
* stack before dispatching the signal handler to the user code.
* The existence of an allocated kernel stack is sufficient
* information to make this decision.
*/
if (rtcb->xcp.kstack != NULL)
{
uint32_t usp;
DEBUGASSERT(rtcb->xcp.kstkptr == NULL);
if (rtcb->sigdeliver)
{
usp = rtcb->xcp.saved_regs[REG_SP];
}
else
{
usp = rtcb->xcp.regs[REG_SP];
}
usp = usp - sizeof(siginfo_t);
memcpy((void *)usp, (void *)regs[REG_R2], sizeof(siginfo_t));
rtcb->xcp.kstkptr = (uint32_t *)regs[REG_SP];
regs[REG_SP] = usp;
regs[REG_R2] = usp;
}
#endif
}
break;
#endif
#if !defined(CONFIG_BUILD_FLAT) && !defined(CONFIG_DISABLE_SIGNALS)
*
* void signal_handler_return(void);
*
* At this point, the following values are saved in context:
*
* R0 = SYS_signal_handler_return
*/
case SYS_signal_handler_return:
{
struct tcb_s *rtcb = this_task();
DEBUGASSERT(rtcb->xcp.sigreturn != 0);
regs[REG_PC] = rtcb->xcp.sigreturn;
cpsr = regs[REG_CPSR] & ~PSR_MODE_MASK;
regs[REG_CPSR] = cpsr | PSR_MODE_SYS;
rtcb->xcp.sigreturn = 0;
#ifdef CONFIG_ARCH_KERNEL_STACK
* to back to the kernel user stack before returning to the kernel
* mode signal trampoline.
*/
if (rtcb->xcp.kstack != NULL)
{
DEBUGASSERT(rtcb->xcp.kstkptr != NULL);
regs[REG_SP] = (uint32_t)rtcb->xcp.kstkptr;
rtcb->xcp.kstkptr = NULL;
}
#endif
}
break;
#endif
case SYS_assert_handler:
{
_assert((const char *)regs[REG_R1], (int)regs[REG_R2],
(const char *)regs[REG_R3], (void *)running_regs(), false);
}
break;
* 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 = this_task();
int index = rtcb->xcp.nsyscalls;
DEBUGASSERT(cmd >= CONFIG_SYS_RESERVED && cmd < SYS_maxsyscall);
* cannot yet handle nested system calls.
*/
DEBUGASSERT(index < CONFIG_SYS_NNEST);
* we can use gdb backtrace from syscall to user space.
*/
regs[REG_IP] = regs[REG_PC];
rtcb->xcp.syscall[index].sysreturn = regs[REG_PC];
#ifndef CONFIG_BUILD_FLAT
rtcb->xcp.syscall[index].cpsr = regs[REG_CPSR];
#endif
regs[REG_PC] = (uint32_t)arm_dispatch_syscall;
#ifndef CONFIG_BUILD_FLAT
cpsr = regs[REG_CPSR] & ~PSR_MODE_MASK;
regs[REG_CPSR] = cpsr | PSR_MODE_SYS;
#endif
regs[REG_R0] -= CONFIG_SYS_RESERVED;
atomic_or(&rtcb->flags, TCB_FLAG_SYSCALL);
#ifdef CONFIG_ARCH_KERNEL_STACK
* kernel stack, then switch to the kernel stack.
*/
if (index == 0 && rtcb->xcp.kstack != NULL)
{
rtcb->xcp.ustkptr = (uint32_t *)regs[REG_SP];
if (rtcb->xcp.kstkptr != NULL)
{
regs[REG_SP] = (uint32_t)rtcb->xcp.kstkptr;
}
else
{
regs[REG_SP] = (uint32_t)rtcb->xcp.kstack +
ARCH_KERNEL_STACKSIZE;
}
}
#endif
rtcb->xcp.nsyscalls = index + 1;
#else
svcerr("ERROR: Bad SYS call: 0x%" PRIx32 "\n", regs[REG_R0]);
#endif
}
break;
}
dump_syscall("Exit", cmd, regs);
sched_note_irqhandler(NR_IRQS, arm_syscall, false);
CP15_SET(TPIDRURW, (uintptr_t)tcb->stack_alloc_ptr);
nxsched_resume_scheduler(tcb);
up_set_interrupt_context(false);
* and will be marked as NULL to avoid misusage.
*/
(*running_task)->xcp.regs = NULL;
* on return from the exception. That capability is only used with the
* SYS_context_switch system call.
*/
return regs;
}