root/sys/arch/aarch64/aarch64/aarch32_syscall.c
/*      $NetBSD: aarch32_syscall.c,v 1.9 2024/02/07 04:20:26 msaitoh Exp $      */

/*
 * Copyright (c) 2018 Ryo Shimizu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: aarch32_syscall.c,v 1.9 2024/02/07 04:20:26 msaitoh Exp $");

#include <sys/param.h>
#include <sys/ktrace.h>
#include <sys/proc.h>
#include <sys/syscallvar.h>
#include <uvm/uvm_extern.h>

#include <aarch64/armreg.h>
#include <aarch64/frame.h>
#include <aarch64/machdep.h>
#include <aarch64/userret.h>

#ifndef EMULNAME
#error EMULNAME is not defined
#endif

#ifndef NARGREG
#define NARGREG         4               /* 4 args are in registers */
#endif

static void EMULNAME(syscall)(struct trapframe *);

union args {
        register_t a64[EMULNAMEU(SYS_MAXSYSARGS)];
        register32_t a32[EMULNAMEU(SYS_MAXSYSARGS)];
};

void
EMULNAME(syscall)(struct trapframe *tf)
{
        struct lwp * const l = curlwp;
        struct proc * const p = l->l_proc;
        const struct sysent *callp;
        union args args64buf, args32buf;
        register_t rval[2];
        register32_t *args32 = args32buf.a32;
        int error, i;
        bool do_trace, thumbmode;

        curcpu()->ci_data.cpu_nsyscall++; /* XXX unsafe curcpu() */

        thumbmode = (tf->tf_spsr & SPSR_A32_T) ? true : false;
#ifdef SYSCALL_CODE_REG
        /*
         * mov.w r<SYSCALL_CODE_REG>, #<syscall_no>
         * svc #<SYSCALL_CODE_REG_SVC>
         */
#ifdef SYSCALL_CODE_REG_SVC
        if ((tf->tf_esr & 0xffff) != SYSCALL_CODE_REG_SVC) {
                error = EINVAL;
                goto bad;
        }
#endif
        uint32_t code = tf->tf_reg[SYSCALL_CODE_REG];
#if (SYSCALL_CODE_REG == 0)
        int regstart = 1;               /* args start from r1 */
        int nargs_reg = NARGREG - 1;    /* number of argument in registers */
#else
        int regstart = 0;               /* args start from r0 */
        int nargs_reg = NARGREG;        /* number of argument in registers */
#endif
#else /* SYSCALL_CODE_REG */
        uint32_t code = tf->tf_esr & 0xffff;    /* XXX: 16-23bits are omitted */
        if (thumbmode) {
                if (code != 255) {
                        do_trapsignal(l, SIGILL, ILL_ILLTRP,
                            (void *)(tf->tf_pc - 2), tf->tf_esr);
                        error = EINVAL;
                        goto bad;
                }
                code = tf->tf_reg[0];
                tf->tf_reg[0] = tf->tf_reg[12]; /* orig $r0 is saved to $ip */
        }
        int regstart = 0;               /* args start from r0 */
        int nargs_reg = NARGREG;        /* number of argument in registers */
#endif /* SYSCALL_CODE_REG */

#ifdef SYSCALL_CODE_REMAP
        code = SYSCALL_CODE_REMAP(code);
#endif

        code %= EMULNAMEU(SYS_NSYSENT);
        callp = p->p_emul->e_sysent + code;
#ifndef SYSCALL_NO_INDIRECT
        if (__predict_false(callp->sy_flags & SYCALL_INDIRECT)) {
                int off = 1;
#ifdef NETBSD32_SYS_netbsd32____syscall /* XXX ugly: apply only for NETBSD32 */
                /*
                 * For __syscall(2), 1st argument is quad_t, which is
                 * stored in r0 and r1.
                 */
                if (code == NETBSD32_SYS_netbsd32____syscall)
                        off = 2;
#endif
                nargs_reg -= off;
                regstart = off; /* args start from r1 or r2 */
#ifdef __AARCH64EB__
                if (off == 2)
                        code = tf->tf_reg[1];
                else
#endif
                        code = tf->tf_reg[0];
                code %= EMULNAMEU(SYS_NSYSENT);
                callp = p->p_emul->e_sysent + code;

                /* don't allow nested syscall */
                if (__predict_false(callp->sy_flags & SYCALL_INDIRECT)) {
                        error = EINVAL;
                        goto bad;
                }
        }
#endif /* SYSCALL_NO_INDIRECT */

        /* number of argument to fetch from sp */
        KASSERT(callp->sy_narg <= EMULNAMEU(SYS_MAXSYSARGS));
        int nargs_sp = callp->sy_narg - nargs_reg;

        /* fetch arguments from tf and sp, and store to args32buf[] */
        for (i = 0; i < nargs_reg; i++)
                *args32++ = (uint32_t)tf->tf_reg[regstart++];
        if (nargs_sp > 0) {
                error = copyin(
                    (void*)(uintptr_t)(uint32_t)tf->tf_reg[13], /* sp = r13 */
                    args32, nargs_sp * sizeof(register32_t));
                if (error)
                        goto bad;
        }

        rval[0] = 0;
        rval[1] = tf->tf_reg[1];
#if 0
        error = sy_invoke(callp, l, args32buf.a32, rval, code);
#else
        /*
         * XXX: trace_enter()/trace_exit() called from sy_invoke() expects
         *      64bit args, but sy_invoke doesn't take care of it.
         *      therefore call trace_enter(), sy_call(), trace_exit() manually.
         */
#ifdef KDTRACE_HOOKS
#define KDTRACE_ENTRY(a)        (a)
#else
#define KDTRACE_ENTRY(a)        (0)
#endif
        do_trace = p->p_trace_enabled &&
            ((callp->sy_flags & SYCALL_INDIRECT) == 0);
        if (__predict_false(do_trace ||
            KDTRACE_ENTRY(callp->sy_entry) ||
            KDTRACE_ENTRY(callp->sy_return))) {
                /* build 64bit args for trace_enter()/trace_exit() */
                int nargs = callp->sy_narg;
                for (i = 0; i < nargs; i++)
                        args64buf.a64[i] = args32buf.a32[i];
        }

        if (__predict_false(do_trace || KDTRACE_ENTRY(callp->sy_entry)))
                error = trace_enter(code, callp, args64buf.a64);

        if (error == 0)
                error = sy_call(callp, l, args32buf.a32, rval);

        if (__predict_false(do_trace || KDTRACE_ENTRY(callp->sy_return)))
                trace_exit(code, callp, args64buf.a64, rval, error);
#endif

        if (__predict_true(error == 0)) {
                tf->tf_reg[0] = rval[0];
#ifndef SYSCALL_NO_RVAL1
                tf->tf_reg[1] = rval[1];
#endif
                tf->tf_spsr &= ~NZCV_C;
        } else {
                switch (error) {
                case ERESTART:
                        /* redo system call insn */
                        if (thumbmode)
                                tf->tf_pc -= 2;
                        else
                                tf->tf_pc -= 4;
                        break;
                case EJUSTRETURN:
                        /* nothing to do */
                        break;
                default:
                bad:
#ifndef __HAVE_MINIMAL_EMUL
                        if (p->p_emul->e_errno)
                                error = p->p_emul->e_errno[error];
#elif defined(SYSCALL_EMUL_ERRNO)
                        error = SYSCALL_EMUL_ERRNO(error);
#endif
                        tf->tf_reg[0] = error;
                        tf->tf_spsr |= NZCV_C;
                        break;
                }
        }

        userret(l);
}

void EMULNAME(syscall_intern)(struct proc *);

void
EMULNAME(syscall_intern)(struct proc *p)
{
        p->p_md.md_syscall = EMULNAME(syscall);
}