/*
 * Memory exception handling in process context.
 *
 * Copyright (C) 2007, 2008 Bahadir Balban
 */
#include <l4/generic/scheduler.h>
#include <l4/generic/thread.h>
#include <l4/api/thread.h>
#include <l4/generic/space.h>
#include <l4/generic/tcb.h>
#include <l4/lib/printk.h>
#include <l4/api/ipc.h>
#include <l4/api/kip.h>
#include <l4/api/errno.h>
#include INC_PLAT(printascii.h)
#include INC_ARCH(exception.h)
#include INC_GLUE(memlayout.h)
#include INC_GLUE(memory.h)
#include INC_GLUE(message.h)
#include INC_GLUE(ipc.h)
#include INC_SUBARCH(mm.h)

/* Abort debugging conditions */
//#define DEBUG_ABORTS
#if defined (DEBUG_ABORTS)
#define dbg_abort(...)	dprintk(__VA_ARGS__)
#else
#define dbg_abort(...)
#endif

struct ipc_state {
	u32 mr[MR_TOTAL];
	unsigned int flags;
};

void ipc_save_state(struct ipc_state *state)
{
	unsigned int *mr0_current = KTCB_REF_MR0(current);

	BUG_ON(!mr0_current);

	/* Save primary message registers */
	for (int i = 0; i < MR_TOTAL; i++)
		state->mr[i] = mr0_current[i];

	/* Save ipc flags */
	state->flags = tcb_get_ipc_flags(current);
}

void ipc_restore_state(struct ipc_state *state)
{
	unsigned int *mr0_current = KTCB_REF_MR0(current);

	BUG_ON(!mr0_current);

	/* Restore primary message registers */
	for (int i = 0; i < MR_TOTAL; i++)
		mr0_current[i] = state->mr[i];

	/* Restore ipc flags */
	tcb_set_ipc_flags(current, state->flags);
}

/* Send data fault ipc to the faulty task's pager */
int fault_ipc_to_pager(u32 faulty_pc, u32 fsr, u32 far)
{
	int err;

	/* mr[0] has the fault tag. The rest is the fault structure */
	u32 mr[MR_TOTAL] = {
		[MR_TAG] = L4_IPC_TAG_PFAULT,
		[MR_SENDER] = current->tid
	};

	fault_kdata_t *fault = (fault_kdata_t *)&mr[MR_UNUSED_START];

	/* Fill in fault information to pass over during ipc */
	fault->faulty_pc = faulty_pc;
	fault->fsr = fsr;
	fault->far = far;

	/*
	 * Write pte of the abort address,
	 * which is different on pabt/dabt
	 */
	if (is_prefetch_abort(fsr))
		fault->pte = virt_to_pte(faulty_pc);
	else
		fault->pte = virt_to_pte(far);

	/*
	 * System calls save arguments (and message registers)
	 * on the kernel stack. They are then referenced from
	 * the caller's ktcb. Here, we forge a fault structure
	 * as if an ipc syscall has occured. Then the reference
	 * to the fault structure is set in the ktcb such that
	 * it lies on the mr0 offset when referred as the syscall
	 * context.
	 */

	/*
	 * Assign fault such that it overlaps
	 * as the MR0 reference in ktcb.
	 */
	current->syscall_regs = (syscall_context_t *)
				((unsigned long)&mr[0] -
				 offsetof(syscall_context_t, r3));

	/* Set current flags to short ipc */
	tcb_set_ipc_flags(current, IPC_FLAGS_SHORT);

	/* Detect if a pager is self-faulting */
	if (current->tid == current->pagerid) {
		printk("Pager (%d) faulted on itself. FAR: 0x%x, PC: 0x%x Exiting.\n",
		       current->tid, fault->far, fault->faulty_pc);
		thread_destroy(current);
	}

	/* Send ipc to the task's pager */
	if ((err = ipc_sendrecv(current->pagerid,
				current->pagerid, 0)) < 0) {
			BUG_ON(current->nlocks);

		/* Return on interrupt */
		if (err == -EINTR) {
			printk("Thread (%d) page-faulted "
			       "and got interrupted by its pager.\n",
			       current->tid);
			return err;
		} else { /* Suspend on any other error */
			printk("Thread (%d) faulted in kernel "
			       "and an error occured during "
			       "page-fault ipc. err=%d. Suspending task.\n",
			       current->tid, err);
			current->flags |= TASK_SUSPENDING;
			sched_suspend_sync();
		}
	}
	return 0;
}

/*
 * When a task calls the kernel and the supplied user buffer is
 * not mapped, the kernel generates a page fault to the task's
 * pager so that the pager can make the decision on mapping the
 * buffer. Remember that if a task maps its own user buffer to
 * itself this way, the kernel can access it, since it shares
 * that task's page table.
 */
int pager_pagein_request(unsigned long addr, unsigned long size,
			 unsigned int flags)
{
	int err;
	u32 abort = 0;
	unsigned long npages = __pfn(align_up(size, PAGE_SIZE));
	struct ipc_state ipc_state;

	set_abort_type(abort, ARM_DABT);

	// printk("%s: Kernel initiating paging-in requests\n", __FUNCTION__);

	/* Save current ipc state */
	ipc_save_state(&ipc_state);

	/* For every page to be used by the kernel send a page-in request */
	for (int i = 0; i < npages; i++)
		if ((err = fault_ipc_to_pager(0, abort,
					      addr + (i * PAGE_SIZE))) < 0)
			return err;

	/* Restore ipc state */
	ipc_restore_state(&ipc_state);

	return 0;
}

int check_aborts(u32 faulted_pc, u32 fsr, u32 far)
{
	int ret = 0;

	if (is_prefetch_abort(fsr)) {
		dbg_abort("Prefetch abort @ ", faulted_pc);
		return 0;
	}

	switch (fsr & ARM_FSR_MASK) {
	/* Aborts that are expected on page faults: */
	case DABT_PERM_PAGE:
		dbg_abort("Page permission fault @ ", far);
		ret = 0;
		break;
	case DABT_XLATE_PAGE:
		dbg_abort("Page translation fault @ ", far);
		ret = 0;
		break;
	case DABT_XLATE_SECT:
		dbg_abort("Section translation fault @ ", far);
		ret = 0;
		break;

	/* Aborts that can't be handled by a pager yet: */
	case DABT_TERMINAL:
		dprintk("Terminal fault dabt @ ", far);
		ret = -EINVAL;
		break;
	case DABT_VECTOR:
		dprintk("Vector abort (obsolete!) @ ", far);
		ret = -EINVAL;
		break;
	case DABT_ALIGN:
		dprintk("Alignment fault dabt @ ", far);
		ret = -EINVAL;
		break;
	case DABT_EXT_XLATE_LEVEL1:
		dprintk("External LVL1 translation fault @ ", far);
		ret = -EINVAL;
		break;
	case DABT_EXT_XLATE_LEVEL2:
		dprintk("External LVL2 translation fault @ ", far);
		ret = -EINVAL;
		break;
	case DABT_DOMAIN_SECT:
		dprintk("Section domain fault dabt @ ", far);
		ret = -EINVAL;
		break;
	case DABT_DOMAIN_PAGE:
		dprintk("Page domain fault dabt @ ", far);
		ret = -EINVAL;
		break;
	case DABT_PERM_SECT:
		dprintk("Section permission fault dabt @ ", far);
		ret = -EINVAL;
		break;
	case DABT_EXT_LFETCH_SECT:
		dprintk("External section linefetch fault dabt @ ", far);
		ret = -EINVAL;
		break;
	case DABT_EXT_LFETCH_PAGE:
		dprintk("Page perm fault dabt @ ", far);
		ret = -EINVAL;
		break;
	case DABT_EXT_NON_LFETCH_SECT:
		dprintk("External section non-linefetch fault dabt @ ", far);
		ret = -EINVAL;
		break;
	case DABT_EXT_NON_LFETCH_PAGE:
		dprintk("External page non-linefetch fault dabt @ ", far);
		ret = -EINVAL;
		break;
	default:
		dprintk("FATAL: Unrecognised/Unknown data abort @ ", far);
		dprintk("FATAL: FSR code: ", fsr);
		ret = -EINVAL;
	}
	return ret;
}

/*
 * @r0: The address where the program counter was during the fault.
 * @r1: Contains the fault status register
 * @r2: Contains the fault address register
 */
void data_abort_handler(u32 faulted_pc, u32 fsr, u32 far)
{
	set_abort_type(fsr, ARM_DABT);

	dbg_abort("Data abort @ PC: ", faulted_pc);

	//printk("Data abort: %d, PC: 0x%x\n", current->tid, faulted_pc);

	/* Check for more details */
	if (check_aborts(faulted_pc, fsr, far) < 0) {
		printascii("This abort can't be handled by any pager.\n");
		goto error;
	}
	if (KERN_ADDR(faulted_pc))
		goto error;

	/* This notifies the pager */
	fault_ipc_to_pager(faulted_pc, fsr, far);

	if (current->flags & TASK_SUSPENDING) {
		BUG_ON(current->nlocks);
		sched_suspend_sync();
	} else if (current->flags & TASK_EXITING) {
		BUG_ON(current->nlocks);
		sched_exit_sync();
	}

	return;

error:
	disable_irqs();
	dprintk("Unhandled data abort @ PC address: ", faulted_pc);
	dprintk("FAR:", far);
	dprintk("FSR:", fsr);
	printascii("Kernel panic.\n");
	printascii("Halting system...\n");
	while (1)
		;
}

void prefetch_abort_handler(u32 faulted_pc, u32 fsr, u32 far, u32 lr)
{
	set_abort_type(fsr, ARM_PABT);
	if (check_aborts(faulted_pc, fsr, far) < 0) {
		printascii("This abort can't be handled by any pager.\n");
		goto error;
	}

	if (KERN_ADDR(lr))
		goto error;
	fault_ipc_to_pager(faulted_pc, fsr, far);

	if (current->flags & TASK_SUSPENDING) {
		BUG_ON(current->nlocks);
		sched_suspend_sync();
	} else if (current->flags & TASK_EXITING) {
		BUG_ON(current->nlocks);
		sched_exit_sync();
	}

	return;

error:
	disable_irqs();
	dprintk("Unhandled prefetch abort @ address: ", faulted_pc);
	dprintk("FAR:", far);
	dprintk("FSR:", fsr);
	dprintk("LR:", lr);
	printascii("Kernel panic.\n");
	printascii("Halting system...\n");
	while (1)
		;
}

void dump_undef_abort(u32 undef_addr, unsigned int spsr)
{
	dprintk("Undefined instruction at address: ", undef_addr);
	printk("Undefined instruction: %d, PC: 0x%x, Mode: %s\n",
	       current->tid, undef_addr,
	       (spsr & ARM_MODE_MASK) == ARM_MODE_SVC ? "SVC" : "User");
	printascii("Halting system...\n");
	BUG();
}

extern int current_irq_nest_count;

/*
 * This is called right where the nest count is increased
 * in case the nesting is beyond the predefined max limit.
 * It is another matter whether this limit is enough to
 * guarantee the kernel stack is not overflown.
 */
void irq_overnest_error(void)
{
	dprintk("Irqs nested beyond limit. Current count: ",
		current_irq_nest_count);
	printascii("Halting system...\n");
	while(1)
		;
}

