/* $NetBSD: except.c,v 1.31 2014/03/08 15:46:20 skrll Exp $ */
/*-
 * Copyright (c) 1998, 1999, 2000 Ben Harris
 * 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.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * 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.
 */
/*
 * except.c -- ARM exception handling.
 */

#include <sys/param.h>

__KERNEL_RCSID(0, "$NetBSD: except.c,v 1.31 2014/03/08 15:46:20 skrll Exp $");

#include "opt_ddb.h"

#include <sys/errno.h>
#include <sys/kauth.h>
#include <sys/kernel.h>
#include <sys/syslog.h>
#include <sys/systm.h>
#include <sys/cpu.h>
#include <sys/lwp.h>
#include <sys/proc.h>
#include <sys/intr.h>

#include <uvm/uvm_extern.h>

#include <arm/locore.h>

#include <machine/machdep.h>
#include <machine/pcb.h>

#ifdef DEBUG
#include <arch/arm/arm/disassem.h>
#endif

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

void syscall(struct trapframe *);
static void do_fault(struct trapframe *, struct lwp *, struct vm_map *,
    vaddr_t, vm_prot_t);
static void data_abort_fixup(struct trapframe *);
static vaddr_t data_abort_address(struct trapframe *, vsize_t *);
static vm_prot_t data_abort_atype(struct trapframe *);
static bool data_abort_usrmode(struct trapframe *);
#ifdef DEBUG
static void printregs(struct trapframe *tf);
#endif
#ifdef DIAGNOSTIC
void checkvectors(void);
#endif

int want_resched;

#ifdef DIAGNOSTIC
void
checkvectors(void)
{
	uint32_t *ptr;

	/* Check that the vectors are valid */
	for (ptr = (uint32_t *)0; ptr < (uint32_t *)0x1c; ptr++)
		if (*ptr != 0xe59ff114)
			panic("CPU vectors mangled");
}
#endif

void
prefetch_abort_handler(struct trapframe *tf)
{
	struct lwp * const l = curlwp;
	struct proc * const p = l->l_proc;

	/* Enable interrupts if they were enabled before the trap. */
	if ((tf->tf_r15 & R15_IRQ_DISABLE) == 0)
		int_on();

	/*
	 * XXX Not done yet:
	 * Check if the page being requested is already present.  If
	 * so, call the undefined instruction handler instead (ARM3 ds
	 * p15).
	 */

	curcpu()->ci_data.cpu_ntrap++;

	if (TRAP_USERMODE(tf)) {
		lwp_settrapframe(l, tf);
		LWP_CACHE_CREDS(l, p);
	} else {
#ifdef DDB
		db_printf("Prefetch abort in kernel mode\n");
		kdb_trap(T_FAULT, tf);
#else
#ifdef DEBUG
		printf("Prefetch abort:\n");
		printregs(tf);
#endif
		panic("prefetch abort in kernel mode");
#endif
	}

	/* User-mode prefetch abort */
	vaddr_t pc = tf->tf_r15 & R15_PC;

	do_fault(tf, l, &p->p_vmspace->vm_map, pc, VM_PROT_EXECUTE);

	userret(l);
}

void
data_abort_handler(struct trapframe *tf)
{
	struct lwp * const l = curlwp;
	struct proc * const p = l->l_proc;
	vm_prot_t atype;
	bool usrmode, twopages;
	struct vm_map *map;
	vaddr_t va;
	vsize_t asize;

	/*
	 * Data aborts in kernel mode are possible (copyout etc), so
	 * we hope the compiler (or programmer) has ensured that
	 * R14_svc gets saved.
	 *
	 * We may need to fix up an STM or LDM instruction.  This
	 * involves seeing if the base was being written back, and if
	 * so resetting it (by counting the number of registers being
	 * transferred) before retrying (ARM 2 ds pp 10 & 33).
	 */

	/* Enable interrupts if they were enabled before the trap. */
	if ((tf->tf_r15 & R15_IRQ_DISABLE) == 0)
		int_on();
	curcpu()->ci_data.cpu_ntrap++;
	if ((tf->tf_r15 & R15_MODE) == R15_MODE_USR) {
		lwp_settrapframe(l, tf);
		LWP_CACHE_CREDS(l, p);
	}
	data_abort_fixup(tf);
	va = data_abort_address(tf, &asize);
	atype = data_abort_atype(tf);
	usrmode = data_abort_usrmode(tf);
	twopages = (trunc_page(va) != round_page(va + asize) - PAGE_SIZE);
	if (!usrmode && va >= VM_MIN_KERNEL_ADDRESS)
		map = kernel_map;
	else
		map = &p->p_vmspace->vm_map;
	do_fault(tf, l, map, va, atype);
	if (twopages)
		do_fault(tf, l, map, va + asize - 4, atype);

	if (TRAP_USERMODE(tf))
		userret(l);
}

/*
 * General page fault handler.
 */
void
do_fault(struct trapframe *tf, struct lwp *l,
    struct vm_map *map, vaddr_t va, vm_prot_t atype)
{
	int error;

	if (pmap_fault(map->pmap, va, atype))
		return;

	struct pcb * const pcb = lwp_getpcb(l);
	void * const onfault = pcb->pcb_onfault;
	const bool user = TRAP_USERMODE(tf);

	if (cpu_intr_p()) {
		KASSERT(!user);
		error = EFAULT;
	} else {
		pcb->pcb_onfault = NULL;
		error = uvm_fault(map, va, atype);
		pcb->pcb_onfault = onfault;
	}

	if (error != 0) {
		ksiginfo_t ksi;

		if (onfault != NULL) {
			tf->tf_r0 = error;
			tf->tf_r15 = (tf->tf_r15 & ~R15_PC) |
			    (register_t)onfault;
			return;
		}
#ifdef DDB
		if (db_validating) {
			db_faulted = true;
			tf->tf_r15 += INSN_SIZE;
			return;
		}
#endif
		if (!user) {
#ifdef DDB
			db_printf("Unhandled data abort in kernel mode\n");
			kdb_trap(T_FAULT, tf);
#else
#ifdef DEBUG
			printf("Unhandled data abort:\n");
			printregs(tf);
#endif
			panic("unhandled data abort in kernel mode");
#endif
		}

		KSI_INIT_TRAP(&ksi);

		if (error == ENOMEM) {
			printf("UVM: pid %d (%s), uid %d killed: "
			    "out of swap\n",
			    l->l_proc->p_pid, l->l_proc->p_comm,
			    l->l_cred ? kauth_cred_geteuid(l->l_cred) : -1);
			ksi.ksi_signo = SIGKILL;
		} else
			ksi.ksi_signo = SIGSEGV;
		ksi.ksi_code = (error == EPERM) ? SEGV_ACCERR : SEGV_MAPERR;
		ksi.ksi_addr = (void *) va;
		trapsignal(l, &ksi);
	} else if (!user) {
		ucas_ras_check(tf);
	}
}

/*
 * In order for the following macro to work, any function using it
 * must ensure that tf->r15 is copied into getreg(15).  This is safe
 * with the current trapframe layout on arm26, but be careful.
 */
#define getreg(r) (((register_t *)&tf->tf_r0)[r])

/*
 * Undo any effects of the aborted instruction that need to be undone
 * in order for us to restart it.  This is just a case of spotting
 * aborted LDMs and STMs and reversing any base writeback.  This code
 * is derived loosely from the arm32 late-abort fixup.
 */
static void
data_abort_fixup(struct trapframe *tf)
{
	register_t insn;
	int rn, count, loop;

	getreg(15) = tf->tf_r15;
	/* Get the faulting instruction */
       	insn = *(register_t *)(tf->tf_r15 & R15_PC);
	if ((insn & 0x0e000000) == 0x08000000 &&
	    (insn & 1 << 21)) {
		/* LDM/STM with writeback*/
		rn = (insn >> 16) & 0x0f;
		if (rn == 15)
			return; /* No writeback on R15 */
		/* Count registers transferred */
		count = 0;
		for (loop = 0; loop < 16; ++loop) {
			if (insn & (1<<loop))
				++count;
		}
		if (insn & (1 << 23)) /* up/down bit -- we reverse it. */
			getreg(rn) -= count * 4;
		else
			getreg(rn) += count * 4;
	}
}

/*
 * Work out where a data abort happened.  This involves emulating the
 * faulting instruction.  Some of this code is derived from the arm32
 * abort fixup stuff too.
 */
static vaddr_t
data_abort_address(struct trapframe *tf, vsize_t *vsp)
{
	register_t insn;
	int rn, rm, offset, shift, p, i, u;
	vaddr_t base;

	getreg(15) = tf->tf_r15;
	/* Get the faulting instruction */
       	insn = *(register_t *)(tf->tf_r15 & R15_PC);
	if ((insn & 0x0c000000) == 0x04000000) {
		/* Single data transfer */
		*vsp = 1; /* or 4, but it doesn't really matter */
		rn = (insn & 0x000f0000) >> 16;
		base = getreg(rn);
		if (rn == 15)
			base = (base & R15_PC) + 8;
		p = insn & 1 << 24;
		if (p == 0)
			/* Post-indexed, so offset doesn't concern us */
			return base;
		u = insn & 1 << 23;
		i = insn & 1 << 25;
		if (i == 0) {
			/* Immediate offset (str r0, [r1, #42]) */
			offset = insn & 0x00000fff;
			if (u == 0)
				return base - offset;
			else
				return base + offset;
		}
		rm = insn & 0x0000000f;
		offset = getreg(rm);
		if (rm == 15)
			offset += 8;
		if ((insn & 1 << 4) == 0)
			/* immediate shift */
			shift = (insn & 0x00000f80) >> 7;
		else
			goto croak; /* Undefined instruction */
		switch ((insn & 0x00000060) >> 5) {
		case 0: /* Logical left */
			offset = (int)(((u_int)offset) << shift);
			break;
		case 1: /* Logical Right */
			if (shift == 0) shift = 32;
			offset = (int)(((u_int)offset) >> shift);
			break;
		case 2: /* Arithmetic Right */
			if (shift == 0) shift = 32;
			offset = (int)(((int)offset) >> shift);
			break;
		case 3:
			if (shift == 0) /* Rotate Right Extended */
				offset = (int)((tf->tf_r15 & R15_FLAG_C) << 2 |
				    ((u_int)offset) >> 1);
			else /* Rotate Right */
				offset = (int)((u_int)offset >> shift |
				    (u_int)offset << (32 - shift));
		}
		if (u == 0)
			return base - offset;
		else
			return base + offset;
	} else if ((insn & 0x0e000000) == 0x08000000) {
		int loop, count;

		/* LDM/STM */
		rn = (insn >> 16) & 0x0f;
		p = insn & 1 << 24;
		u = insn & 1 << 23;
		/* Count registers transferred */
		count = 0;
		for (loop = 0; loop < 16; ++loop)
			if (insn & (1<<loop))
				++count;
		*vsp = count * 4;
		/* Need to find both ends of the range being transferred. */
		if (u == 0) /* up/down bit */
			if (p == 0) /* pre/post bit */
				return getreg(rn) - *vsp + 4;	/* ...DA */
			else
				return getreg(rn) - *vsp;	/* ...DB */
		else
			if (p == 0)
				return getreg(rn);		/* ...IA */
			else
				return getreg(rn) + 4;		/* ...IB */
#if defined(CPU_ARM250) || defined(CPU_ARM3)
	} else if ((insn & 0x0fb00ff0) == 0x01000090) {
		/* SWP */
		*vsp = 1; /* or 4, but who cares? */
		rm = insn & 0x0000000f;
		base = getreg(rm);
		if (rm == 15)
			return base + 8;
		else
			return base;
#endif
	}
croak:
#ifdef DEBUG
	printf("Data abort:\n");
	printregs(tf);
	printf("pc -> ");
	disassemble(tf->tf_r15 & R15_PC);
#endif
	panic("data_abort_address");
}

/*
 * We need to know whether the page should be mapped as R or R/W.  We
 * need to disassemble the instruction responsible and determine if it
 * was a read or write instruction.  This code is based on the arm32
 * version.
 */
static vm_prot_t
data_abort_atype(struct trapframe *tf)
{
	register_t insn;

	insn = *(register_t *)(tf->tf_r15 & R15_PC);
	/* STR instruction ? */
	if ((insn & 0x0c100000) == 0x04000000)
		return VM_PROT_WRITE;
	/* STM or CDT instruction ? */
	else if ((insn & 0x0a100000) == 0x08000000)
		return VM_PROT_WRITE;
#if defined(CPU_ARM250) || defined(CPU_ARM3)
	/* SWP instruction ? */
	else if ((insn & 0x0fb00ff0) == 0x01000090)
		return VM_PROT_READ | VM_PROT_WRITE;
#endif
	return VM_PROT_READ;
}

/*
 * Work out what effective mode was in use when a data abort occurred.
 */
static bool
data_abort_usrmode(struct trapframe *tf)
{
	register_t insn;

	if (TRAP_USERMODE(tf))
		return true;
	insn = *(register_t *)(tf->tf_r15 & R15_PC);
	if ((insn & 0x0d200000) == 0x04200000)
		/* LDR[B]T and STR[B]T */
		return true;
	return false;
}

void
address_exception_handler(struct trapframe *tf)
{
	struct lwp * const l = curlwp;
	struct pcb * const pcb = lwp_getpcb(l);
	ksiginfo_t ksi;

	/* Enable interrupts if they were enabled before the trap. */
	if ((tf->tf_r15 & R15_IRQ_DISABLE) == 0)
		int_on();

	curcpu()->ci_data.cpu_ntrap++;
	if (TRAP_USERMODE(tf)) {
		lwp_settrapframe(l, tf);
		LWP_CACHE_CREDS(l, l->l_proc);
	}

	if (pcb->pcb_onfault != NULL) {
		tf->tf_r0 = EFAULT;
		tf->tf_r15 = (tf->tf_r15 & ~R15_PC) |
		    (uintptr_t)pcb->pcb_onfault;
		return;
	}

	vaddr_t pc = tf->tf_r15 & R15_PC;

	if (!TRAP_USERMODE(tf)) {
#ifdef DDB
		db_printf("Address exception in kernel mode\n");
		kdb_trap(T_FAULT, tf);
#else
#ifdef DEBUG
		printf("Address exception:\n");
		printregs(tf);
		printf("pc -> ");
		disassemble(pc);
#endif
		panic("address exception in kernel mode");
#endif
	}

	KSI_INIT_TRAP(&ksi);
	ksi.ksi_signo = SIGSEGV;
	ksi.ksi_code = SEGV_MAPERR;
	ksi.ksi_addr = (void *) pc;
	trapsignal(l, &ksi);
	userret(l);
}

#ifdef DEBUG
static void
printregs(struct trapframe *tf)
{

	printf("R0  = 0x%08x  R1  = 0x%08x  R2  = 0x%08x  R3  = 0x%08x\n"
	       "R4  = 0x%08x  R5  = 0x%08x  R6  = 0x%08x  R7  = 0x%08x\n"
	       "R8  = 0x%08x  R9  = 0x%08x  R10 = 0x%08x  R11 = 0x%08x\n"
	       "R12 = 0x%08x  R13 = 0x%08x  R14 = 0x%08x  R15 = 0x%08x\n",
	       tf->tf_r0, tf->tf_r1, tf->tf_r2, tf->tf_r3,
	       tf->tf_r4, tf->tf_r5, tf->tf_r6, tf->tf_r7,
	       tf->tf_r8, tf->tf_r9, tf->tf_r10, tf->tf_r11,
	       tf->tf_r12, tf->tf_r13, tf->tf_r14, tf->tf_r15);
}
#endif

/* irq_handler is over in irq.c */
