Files
codezero/src/arch/arm/exception.c
Bahadir Balban 78917835c7 Substantially fixed cap_split() behaviour.
Need to fix ipc flags capability checking yet.
2009-11-09 19:15:52 +02:00

364 lines
8.8 KiB
C

/*
* 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)
;
}