[BACK]Return to trap.c CVS log [TXT][DIR] Up to [local] / sys / arch / sh / sh

File: [local] / sys / arch / sh / sh / trap.c (download)

Revision 1.1, Tue Mar 4 16:08:59 2008 UTC (16 years, 2 months ago) by nbrk
Branch point for: MAIN

Initial revision

/*	$OpenBSD: trap.c,v 1.12 2007/03/15 10:22:29 art Exp $	*/
/*	$NetBSD: exception.c,v 1.32 2006/09/04 23:57:52 uwe Exp $	*/
/*	$NetBSD: syscall.c,v 1.6 2006/03/07 07:21:50 thorpej Exp $	*/

/*-
 * Copyright (c) 2002 The NetBSD Foundation, Inc. All rights reserved.
 * Copyright (c) 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * the University of Utah, and William Jolitz.
 *
 * 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.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
 *
 *	@(#)trap.c	7.4 (Berkeley) 5/13/91
 */

/*-
 * Copyright (c) 1995 Charles M. Hannum.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * the University of Utah, and William Jolitz.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
 *
 *	@(#)trap.c	7.4 (Berkeley) 5/13/91
 */

/*
 * SH3 Trap and System call handling
 *
 * T.Horiuchi 1998.06.8
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/pool.h>
#include <sys/user.h>
#include <sys/kernel.h>
#include <sys/signal.h>
#include <sys/syscall.h>

#ifdef KTRACE
#include <sys/ktrace.h>
#endif

#include "systrace.h"
#if NSYSTRACE > 0
#include <dev/systrace.h>
#endif

#include <uvm/uvm_extern.h>

#include <sh/cache.h>
#include <sh/cpu.h>
#include <sh/mmu.h>
#include <sh/trap.h>
#include <sh/userret.h>
#ifdef SH4
#include <sh/fpu.h>
#endif

#ifdef DDB
#include <machine/db_machdep.h>
#endif

const char * const exp_type[] = {
	NULL,					/* 000 (reset vector) */
	NULL,					/* 020 (reset vector) */
	"TLB miss/invalid (load)",		/* 040 EXPEVT_TLB_MISS_LD */
	"TLB miss/invalid (store)",		/* 060 EXPEVT_TLB_MISS_ST */
	"initial page write",			/* 080 EXPEVT_TLB_MOD */
	"TLB protection violation (load)",	/* 0a0 EXPEVT_TLB_PROT_LD */
	"TLB protection violation (store)",	/* 0c0 EXPEVT_TLB_PROT_ST */
	"address error (load)",			/* 0e0 EXPEVT_ADDR_ERR_LD */
	"address error (store)",		/* 100 EXPEVT_ADDR_ERR_ST */
	"FPU",					/* 120 EXPEVT_FPU */
	NULL,					/* 140 (reset vector) */
	"unconditional trap (TRAPA)",		/* 160 EXPEVT_TRAPA */
	"reserved instruction code exception",	/* 180 EXPEVT_RES_INST */
	"illegal slot instruction exception",	/* 1a0 EXPEVT_SLOT_INST */
	NULL,					/* 1c0 (external interrupt) */
	"user break point trap",		/* 1e0 EXPEVT_BREAK */
	NULL, NULL, NULL, NULL,			/* 200-260 */
	NULL, NULL, NULL, NULL,			/* 280-2e0 */
	NULL, NULL, NULL, NULL,			/* 300-360 */
	NULL, NULL, NULL, NULL,			/* 380-3e0 */
	NULL, NULL, NULL, NULL,			/* 400-460 */
	NULL, NULL, NULL, NULL,			/* 480-4e0 */
	NULL, NULL, NULL, NULL,			/* 500-560 */
	NULL, NULL, NULL, NULL,			/* 580-5e0 */
	NULL, NULL, NULL, NULL,			/* 600-660 */
	NULL, NULL, NULL, NULL,			/* 680-6e0 */
	NULL, NULL, NULL, NULL,			/* 700-760 */
	NULL, NULL, NULL, NULL,			/* 780-7e0 */
	"FPU disabled",				/* 800 EXPEVT_FPU_DISABLE */
	"slot FPU disabled"			/* 820 EXPEVT_FPU_SLOT_DISABLE */
};
const int exp_types = sizeof exp_type / sizeof exp_type[0];

void general_exception(struct proc *, struct trapframe *, uint32_t);
void tlb_exception(struct proc *, struct trapframe *, uint32_t);
void ast(struct proc *, struct trapframe *);
void syscall(struct proc *, struct trapframe *);
void cachectl(struct proc *, struct trapframe *);

/*
 * void general_exception(struct proc *p, struct trapframe *tf):
 *	p  ... curproc when exception occured.
 *	tf ... full user context.
 *	va ... fault va for user mode EXPEVT_ADDR_ERR_{LD,ST}
 */
void
general_exception(struct proc *p, struct trapframe *tf, uint32_t va)
{
	int expevt = tf->tf_expevt;
	int tra;
	boolean_t usermode = !KERNELMODE(tf->tf_ssr);
	union sigval sv;

	uvmexp.traps++;

	/*
	 * This function is entered at splhigh. Restore the interrupt
	 * level to what it was when the trap occured.
	 */
	splx(tf->tf_ssr & PSL_IMASK);

	if (usermode) {
		if (p == NULL)
			goto do_panic;
		KDASSERT(p->p_md.md_regs == tf); /* check exception depth */
		expevt |= EXP_USER;
	}

	switch (expevt) {
	case EXPEVT_BREAK:
		if (kdb_trap(EXPEVT_BREAK, 0, tf))
			return;
		else
			goto do_panic;
		break;
	case EXPEVT_TRAPA:
		/* Check for ddb request */
		tra = _reg_read_4(SH_(TRA));
		if (tra == (_SH_TRA_BREAK << 2) &&
		    kdb_trap(expevt, tra, tf))
			return;
		else
			goto do_panic;
		break;
	case EXPEVT_TRAPA | EXP_USER:
		/* Check for debugger break */
		tra = _reg_read_4(SH_(TRA));
		switch (tra) {
		case _SH_TRA_BREAK << 2:
			tf->tf_spc -= 2; /* back to the breakpoint address */
			sv.sival_ptr = (void *)tf->tf_spc;
			trapsignal(p, SIGTRAP, expevt & ~EXP_USER, TRAP_BRKPT,
			    sv);
			goto out;
		case _SH_TRA_SYSCALL << 2:
			syscall(p, tf);
			return;
		case _SH_TRA_CACHECTL << 2:
			cachectl(p, tf);
			return;
		default:
			sv.sival_ptr = (void *)tf->tf_spc;
			trapsignal(p, SIGILL, expevt & ~EXP_USER, ILL_ILLTRP,
			    sv);
			goto out;
		}
		break;

	case EXPEVT_ADDR_ERR_LD: /* FALLTHROUGH */
	case EXPEVT_ADDR_ERR_ST:
		KDASSERT(p && p->p_md.md_pcb->pcb_onfault != NULL);
		if (p == NULL || p->p_md.md_pcb->pcb_onfault == 0)
			goto do_panic;
		tf->tf_spc = (int)p->p_md.md_pcb->pcb_onfault;
		break;

	case EXPEVT_ADDR_ERR_LD | EXP_USER: /* FALLTHROUGH */
	case EXPEVT_ADDR_ERR_ST | EXP_USER:
		sv.sival_ptr = (void *)va;
		if (((int)va) < 0)
			trapsignal(p, SIGSEGV, expevt & ~EXP_USER, SEGV_ACCERR,
			    sv);
		else
			trapsignal(p, SIGBUS, expevt & ~EXP_USER, BUS_ADRALN,
			    sv);
		goto out;

	case EXPEVT_RES_INST | EXP_USER: /* FALLTHROUGH */
	case EXPEVT_SLOT_INST | EXP_USER:
		sv.sival_ptr = (void *)tf->tf_spc;
		trapsignal(p, SIGILL, expevt & ~EXP_USER, ILL_ILLOPC, sv);
		goto out;

	case EXPEVT_BREAK | EXP_USER:
		sv.sival_ptr = (void *)tf->tf_spc;
		trapsignal(p, SIGTRAP, expevt & ~EXP_USER, TRAP_TRACE, sv);
		goto out;

#ifdef SH4
	case EXPEVT_FPU_DISABLE | EXP_USER: /* FALLTHROUGH */
	case EXPEVT_FPU_SLOT_DISABLE | EXP_USER:
		sv.sival_ptr = (void *)tf->tf_spc;
		trapsignal(p, SIGILL, expevt & ~EXP_USER, ILL_COPROC, sv);
		goto out;

	case EXPEVT_FPU | EXP_USER:
	    {
		int fpscr, sigi;

		/* XXX worth putting in the trapframe? */
		__asm__ __volatile__ ("sts fpscr, %0" : "=r" (fpscr));
		fpscr = (fpscr & FPSCR_CAUSE_MASK) >> FPSCR_CAUSE_SHIFT;
		if (fpscr & FPEXC_E)
			sigi = FPE_FLTINV;	/* XXX any better value? */
		else if (fpscr & FPEXC_V)
			sigi = FPE_FLTINV;
		else if (fpscr & FPEXC_Z)
			sigi = FPE_FLTDIV;
		else if (fpscr & FPEXC_O)
			sigi = FPE_FLTOVF;
		else if (fpscr & FPEXC_U)
			sigi = FPE_FLTUND;
		else if (fpscr & FPEXC_I)
			sigi = FPE_FLTRES;
		else
			sigi = 0;	/* shouldn't happen */
		sv.sival_ptr = (void *)tf->tf_spc;
		trapsignal(p, SIGFPE, expevt & ~EXP_USER, sigi, sv);
	    }
		goto out;
#endif

	default:
		goto do_panic;
	}

	if (!usermode)
		return;
out:
	userret(p);
	return;

do_panic:
	if ((expevt >> 5) < exp_types && exp_type[expevt >> 5] != NULL)
		printf("fatal %s", exp_type[expevt >> 5]);
	else
		printf("EXPEVT 0x%03x", expevt);
	printf(" in %s mode\n", expevt & EXP_USER ? "user" : "kernel");
	printf("va %p spc %p ssr %p pr %p \n",
	    va, tf->tf_spc, tf->tf_ssr, tf->tf_pr);

	panic("general_exception");
	/* NOTREACHED */
}


/*
 * void tlb_exception(struct proc *p, struct trapframe *tf, uint32_t va):
 *	p  ... curproc when exception occured.
 *	tf ... full user context.
 *	va ... fault address.
 */
void
tlb_exception(struct proc *p, struct trapframe *tf, uint32_t va)
{
	struct vm_map *map;
	pmap_t pmap;
	union sigval sv;
	boolean_t usermode;
	int err, track, ftype;
	const char *panic_msg;

#define TLB_ASSERT(assert, msg)				\
		do {					\
			if (!(assert)) {		\
				panic_msg =  msg;	\
				goto tlb_panic;		\
			}				\
		} while(/*CONSTCOND*/0)

	/*
	 * This function is entered at splhigh. Restore the interrupt
	 * level to what it was when the trap occured.
	 */
	splx(tf->tf_ssr & PSL_IMASK);

	usermode = !KERNELMODE(tf->tf_ssr);
	if (usermode) {
		KDASSERT(p->p_md.md_regs == tf);
	} else {
		KDASSERT(p == NULL ||		/* idle */
		    p == &proc0 ||		/* kthread */
		    p->p_md.md_regs != tf);	/* other */
	}

	switch (tf->tf_expevt) {
	case EXPEVT_TLB_MISS_LD:
		track = PVH_REFERENCED;
		ftype = VM_PROT_READ;
		break;
	case EXPEVT_TLB_MISS_ST:
		track = PVH_REFERENCED;
		ftype = VM_PROT_WRITE;
		break;
	case EXPEVT_TLB_MOD:
		track = PVH_REFERENCED | PVH_MODIFIED;
		ftype = VM_PROT_WRITE;
		break;
	case EXPEVT_TLB_PROT_LD:
		TLB_ASSERT((int)va > 0,
		    "kernel virtual protection fault (load)");
		if (usermode) {
			sv.sival_ptr = (void *)va;
			trapsignal(p, SIGSEGV, tf->tf_expevt, SEGV_ACCERR, sv);
			goto user_fault;
		} else {
			TLB_ASSERT(p->p_md.md_pcb->pcb_onfault != NULL,
			    "no copyin/out fault handler (load protection)");
			tf->tf_spc = (int)p->p_md.md_pcb->pcb_onfault;
		}
		return;

	case EXPEVT_TLB_PROT_ST:
		track = 0;	/* call uvm_fault first. (COW) */
		ftype = VM_PROT_WRITE;
		break;

	default:
		TLB_ASSERT(0, "impossible expevt");
	}

	/* Select address space */
	if (usermode) {
		TLB_ASSERT(p != NULL, "no curproc");
		map = &p->p_vmspace->vm_map;
		pmap = map->pmap;
	} else {
		if ((int)va < 0) {
			map = kernel_map;
			pmap = pmap_kernel();
		} else {
			TLB_ASSERT(p != NULL &&
			    p->p_md.md_pcb->pcb_onfault != NULL,
			    "invalid user-space access from kernel mode");
			if (va == 0) {
				tf->tf_spc = (int)p->p_md.md_pcb->pcb_onfault;
				return;
			}
			map = &p->p_vmspace->vm_map;
			pmap = map->pmap;
		}
	}

	/* Lookup page table. if entry found, load it. */
	if (track && __pmap_pte_load(pmap, va, track)) {
		if (usermode)
			userret(p);
		return;
	}

	err = uvm_fault(map, va, 0, ftype);

	/* User stack extension */
	if (map != kernel_map &&
	    (va >= (vaddr_t)p->p_vmspace->vm_maxsaddr) &&
	    (va < USRSTACK)) {
		if (err == 0)
			uvm_grow(p, va);
		else if (err == EACCES)
			err = EFAULT;
	}

	/* Page in. load PTE to TLB. */
	if (err == 0) {
		boolean_t loaded = __pmap_pte_load(pmap, va, track);
		TLB_ASSERT(loaded, "page table entry not found");
		if (usermode)
			userret(p);
		return;
	}

	/* Page not found. */
	if (usermode) {
		sv.sival_ptr = (void *)va;
		if (err == ENOMEM) {
			printf("UVM: pid %d (%s), uid %d killed: out of swap\n",
			    p->p_pid, p->p_comm,
			    p->p_cred && p->p_ucred ?
				(int)p->p_ucred->cr_uid : -1);
			trapsignal(p, SIGKILL, tf->tf_expevt, SEGV_MAPERR, sv);
		} else
			trapsignal(p, SIGSEGV, tf->tf_expevt, SEGV_MAPERR, sv);
		goto user_fault;
	} else {
		TLB_ASSERT(p->p_md.md_pcb->pcb_onfault,
		    "no copyin/out fault handler (page not found)");
		tf->tf_spc = (int)p->p_md.md_pcb->pcb_onfault;
	}
	return;

user_fault:
	userret(p);
	ast(p, tf);
	return;

tlb_panic:
	panic("tlb_exception: %s\n"
	      "expevt=%x va=%08x ssr=%08x spc=%08x proc=%p onfault=%p",
	      panic_msg, tf->tf_expevt, va, tf->tf_ssr, tf->tf_spc,
	      p, p ? p->p_md.md_pcb->pcb_onfault : NULL);
#undef	TLB_ASSERT
}


/*
 * void ast(struct proc *p, struct trapframe *tf):
 *	p  ... curproc when exception occured.
 *	tf ... full user context.
 *	This is called upon exception return. if return from kernel to user,
 *	handle asynchronous software traps and context switch if needed.
 */
void
ast(struct proc *p, struct trapframe *tf)
{
	if (KERNELMODE(tf->tf_ssr))
		return;
	KDASSERT(p != NULL);
	KDASSERT(p->p_md.md_regs == tf);

	while (p->p_md.md_astpending) {
		uvmexp.softs++;
		p->p_md.md_astpending = 0;

		if (p->p_flag & P_OWEUPC) {
			ADDUPROF(p);
		}

		if (want_resched) {
			/* We are being preempted. */
			preempt(NULL);
		}

		userret(p);
	}
}

void
cachectl(struct proc *p, struct trapframe *tf)
{
	vaddr_t va;
	vsize_t len;

	if (!SH_HAS_UNIFIED_CACHE) {
		va = (vaddr_t)tf->tf_r4;
		len = (vsize_t)tf->tf_r5;

		if (/* va < VM_MIN_ADDRESS || */ va >= VM_MAXUSER_ADDRESS ||
		    va + len <= va || va + len >= VM_MAXUSER_ADDRESS)
			len = 0;

		if (len != 0)
			sh_icache_sync_range_index(va, len);
	}

	userret(p);
}

void
syscall(struct proc *p, struct trapframe *tf)
{
	caddr_t params;
	const struct sysent *callp;
	int error, oerror, opc, nsys;
	size_t argsize;
	register_t code, args[8], rval[2], ocode;

	uvmexp.syscalls++;

	opc = tf->tf_spc;
	ocode = code = tf->tf_r0;

	nsys = p->p_emul->e_nsysent;
	callp = p->p_emul->e_sysent;

	params = (caddr_t)tf->tf_r15;

	switch (code) {
	case SYS_syscall:
		/*
		 * Code is first argument, followed by actual args.
		 */
	        code = tf->tf_r4;
		break;
	case SYS___syscall:
		/*
		 * Like syscall, but code is a quad, so as to maintain
		 * quad alignment for the rest of the arguments.
		 */
		if (callp != sysent)
			break;
#if _BYTE_ORDER == BIG_ENDIAN
		code = tf->tf_r5;
#else
		code = tf->tf_r4;
#endif
		break;
	default:
		break;
	}
	if (code < 0 || code >= nsys)
		callp += p->p_emul->e_nosys;		/* illegal */
	else
		callp += code;
	argsize = callp->sy_argsize;
#ifdef DIAGNOSTIC
	if (argsize > sizeof args) {
		callp += p->p_emul->e_nosys - code;
		goto bad;
	}
#endif

	switch (ocode) {
	case SYS_syscall:
		if (argsize) {
			args[0] = tf->tf_r5;
			args[1] = tf->tf_r6;
			args[2] = tf->tf_r7;
			if (argsize > 3 * sizeof(int)) {
				argsize -= 3 * sizeof(int);
				error = copyin(params, (caddr_t)&args[3],
					       argsize);
			} else
				error = 0;
		} else
			error = 0;
		break;
	case SYS___syscall:
		if (argsize) {
			args[0] = tf->tf_r6;
			args[1] = tf->tf_r7;
			if (argsize > 2 * sizeof(int)) {
				argsize -= 2 * sizeof(int);
				error = copyin(params, (caddr_t)&args[2],
					       argsize);
			} else
				error = 0;
		} else
			error = 0;
		break;
	default:
		if (argsize) {
			args[0] = tf->tf_r4;
			args[1] = tf->tf_r5;
			args[2] = tf->tf_r6;
			args[3] = tf->tf_r7;
			if (argsize > 4 * sizeof(int)) {
				argsize -= 4 * sizeof(int);
				error = copyin(params, (caddr_t)&args[4],
					       argsize);
			} else
				error = 0;
		} else
			error = 0;
		break;
	}

	if (error)
		goto bad;

#ifdef SYSCALL_DEBUG
	scdebug_call(p, code, args);
#endif
#ifdef KTRACE
	if (KTRPOINT(p, KTR_SYSCALL))
		ktrsyscall(p, code, callp->sy_argsize, args);
#endif

	rval[0] = 0;
	rval[1] = tf->tf_r1;
#if NSYSTRACE > 0
	if (ISSET(p->p_flag, P_SYSTRACE))
		error = systrace_redirect(code, p, args, rval);
	else
#endif
		error = (*callp->sy_call)(p, args, rval);

	switch (oerror = error) {
	case 0:
		tf->tf_r0 = rval[0];
		tf->tf_r1 = rval[1];
		tf->tf_ssr |= PSL_TBIT;	/* T bit */
		break;
	case ERESTART:
		/* 2 = TRAPA instruction size */
		tf->tf_spc = opc - 2;
		break;
	case EJUSTRETURN:
		/* nothing to do */
		break;
	default:
bad:
		if (p->p_emul->e_errno)
			error = p->p_emul->e_errno[error];
		tf->tf_r0 = error;
		tf->tf_ssr &= ~PSL_TBIT;	/* T bit */
		break;
	}

#ifdef SYSCALL_DEBUG
	scdebug_ret(p, code, oerror, rval);
#endif
	userret(p);
#ifdef KTRACE
	if (KTRPOINT(p, KTR_SYSRET))
		ktrsysret(p, code, oerror, rval[0]);
#endif
}

/*
 * void child_return(void *arg):
 *
 *	uvm_fork sets this routine to proc_trampoline's service function.
 *	when returning from here, jump to userland.
 */
void
child_return(void *arg)
{
	struct proc *p = arg;
	struct trapframe *tf = p->p_md.md_regs;

	tf->tf_r0 = 0;
	tf->tf_ssr |= PSL_TBIT; /* This indicates no error. */

	userret(p);

#ifdef KTRACE
	if (KTRPOINT(p, KTR_SYSRET))
		ktrsysret(p,
		    (p->p_flag & P_PPWAIT) ? SYS_vfork : SYS_fork, 0, 0);
#endif
}