File: [local] / prex / sys / kern / exception.c (download)
Revision 1.1.1.1 (vendor branch), Tue Aug 19 12:46:56 2008 UTC (15 years, 10 months ago) by nbrk
Branch: MAIN, KOHSUKE
CVS Tags: PREX_0_8_BASE, HEAD Changes since 1.1: +0 -0 lines
Initial import of Prex, Portable Real-time Embedded POSIX microkernel system.
I have totally new directions in my development (more focused on real hardware, not virtual one).
Old hacks are available in prex-old module. They will be carefully re-designed and merged soon.
|
/*-
* Copyright (c) 2005-2007, Kohsuke Ohtani
* 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. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
*/
/*
* exception.c - exception handling routines
*/
/**
* An user mode task can specify its own exception handler with
* exception_setup() system call.
*
* There are two different types of exceptions in a system - H/W and
* S/W exception. The kernel determines to which thread it delivers
* depending on the exception type.
*
* - H/W exception
*
* This type of exception is caused by H/W trap & fault. The
* exception will be sent to the thread which caused the trap.
* If no handler is specified by the task, it will be terminated
* by the kernel immediately.
*
* - S/W exception
*
* The user mode task can send S/W exception to another task by
* exception_raise() system call.
* The exception will be sent to the thread that is sleeping with
* exception_wait() call. If no thread is waiting for the exception,
* the exception is sent to the first thread in the target task.
*
* Kernel supports 32 types of exceptions. The following pre-defined
* exceptions are raised by kernel itself.
*
* Exception Type Reason
* --------- ---- -----------------------
* SIGILL h/w illegal instruction
* SIGTRAP h/w break point
* SIGFPE h/w math error
* SIGSEGV h/w invalid memory access
* SIGALRM s/w alarm event
*
* The POSIX emulation library will setup own exception handler to
* convert the Prex exceptions into UNIX signals. It will maintain its
* own signal mask, and transfer control to the POSIX signal handler.
*/
#include <kernel.h>
#include <event.h>
#include <task.h>
#include <thread.h>
#include <sched.h>
#include <task.h>
#include <irq.h>
#include <exception.h>
static struct event exception_event;
/*
* Install an exception handler for the current task.
*
* NULL can be specified as handler to remove current handler.
* If handler is removed, all pending exceptions are discarded
* immediately. In this case, all threads blocked in
* exception_wait() are unblocked.
*
* Only one exception handler can be set per task. If the
* previous handler exists in task, exception_setup() just
* override that handler.
*/
int
exception_setup(void (*handler)(int))
{
task_t self = cur_task();
list_t head, n;
thread_t th;
if (handler != NULL && !user_area(handler))
return EFAULT;
sched_lock();
if (self->handler && handler == NULL) {
/*
* Remove existing exception handler. Do clean up
* job for all threads in the target task.
*/
head = &self->threads;
for (n = list_first(head); n != head; n = list_next(n)) {
/*
* Clear pending exceptions.
*/
th = list_entry(n, struct thread, task_link);
irq_lock();
th->excbits = 0;
irq_unlock();
/*
* If the thread is waiting for an exception,
* cancel it.
*/
if (th->slpevt == &exception_event)
sched_unsleep(th, SLP_BREAK);
}
}
self->handler = handler;
sched_unlock();
return 0;
}
/*
* exception_raise - system call to raise an exception.
*
* The exception pending flag is marked here, and it is
* processed by exception_deliver() later. If the task
* want to raise an exception to another task, the caller
* task must have CAP_KILL capability. If the exception
* is sent to the kernel task, this routine just returns
* error.
*/
int
exception_raise(task_t task, int exc)
{
int err;
sched_lock();
if (!task_valid(task)) {
err = ESRCH;
goto out;
}
if (task != cur_task() && !task_capable(CAP_KILL)) {
err = EPERM;
goto out;
}
if (task == &kern_task || task->handler == NULL ||
list_empty(&task->threads)) {
err = EPERM;
goto out;
}
err = exception_post(task, exc);
out:
sched_unlock();
return err;
}
/*
* exception_post-- the internal version of exception_raise().
*/
int
exception_post(task_t task, int exc)
{
list_t head, n;
thread_t th;
if (exc < 0 || exc >= NEXC)
return EINVAL;
/*
* Determine which thread should we send an exception.
* First, search the thread that is waiting an exception
* by calling exception_wait(). Then, if no thread is
* waiting exceptions, it is sent to the master thread in
* task.
*/
head = &task->threads;
for (n = list_first(head); n != head; n = list_next(n)) {
th = list_entry(n, struct thread, task_link);
if (th->slpevt == &exception_event)
break;
}
if (n == head) {
n = list_first(head);
th = list_entry(n, struct thread, task_link);
}
/*
* Mark pending bit for this exception.
*/
irq_lock();
th->excbits |= (1 << exc);
irq_unlock();
/*
* Wakeup the target thread regardless of its
* waiting event.
*/
sched_unsleep(th, SLP_INTR);
return 0;
}
/*
* exception_wait - block a current thread until some exceptions
* are raised to the current thread.
*
* The routine returns EINTR on success.
*/
int
exception_wait(int *exc)
{
task_t self = cur_task();
int i, rc;
self = cur_task();
if (self->handler == NULL)
return EINVAL;
if (!user_area(exc))
return EFAULT;
sched_lock();
/*
* Sleep until some exceptions occur.
*/
rc = sched_sleep(&exception_event);
if (rc == SLP_BREAK) {
sched_unlock();
return EINVAL;
}
irq_lock();
for (i = 0; i < NEXC; i++) {
if (cur_thread->excbits & (1 << i))
break;
}
irq_unlock();
ASSERT(i != NEXC);
sched_unlock();
if (umem_copyout(&i, exc, sizeof(i)))
return EFAULT;
return EINTR;
}
/*
* Mark an exception flag for the current thread.
*
* This is called from architecture dependent code when H/W
* trap is occurred. If current task does not have exception
* handler, then current task will be terminated. This routine
* may be called at interrupt level.
*/
void
exception_mark(int exc)
{
ASSERT(exc > 0 && exc < NEXC);
/* Mark pending bit */
irq_lock();
cur_thread->excbits |= (1 << exc);
irq_unlock();
}
/*
* exception_deliver - deliver pending exception to the task.
*
* Check if pending exception exists for current task, and
* deliver it to the exception handler if needed. All
* exception is delivered at the time when the control goes
* back to the user mode. This routine is called from
* architecture dependent code. Some application may use
* longjmp() during its signal handler. So, current context
* must be saved to user mode stack.
*/
void
exception_deliver(void)
{
thread_t th = cur_thread;
task_t self = cur_task();
void (*handler)(int);
uint32_t bitmap;
int exc;
sched_lock();
irq_lock();
bitmap = th->excbits;
irq_unlock();
if (bitmap != 0) {
/*
* Find a pending exception.
*/
for (exc = 0; exc < NEXC; exc++) {
if (bitmap & (1 << exc))
break;
}
handler = self->handler;
if (handler == NULL) {
DPRINTF(("Exception #%d is not handled by task.\n",
exc));
DPRINTF(("Terminate task:%s (id:%x)\n",
self->name != NULL ? self->name : "no name",
self));
task_terminate(self);
goto out;
}
/*
* Transfer control to an exception handler.
*/
context_save(&th->ctx);
context_set(&th->ctx, CTX_UENTRY, (vaddr_t)handler);
context_set(&th->ctx, CTX_UARG, (vaddr_t)exc);
irq_lock();
th->excbits &= ~(1 << exc);
irq_unlock();
}
out:
sched_unlock();
}
/*
* exception_return() is called from exception handler to
* restore the original context.
*/
int
exception_return(void)
{
context_restore(&cur_thread->ctx);
return 0;
}
void
exception_init(void)
{
event_init(&exception_event, "exception");
}