[BACK]Return to exception.c CVS log [TXT][DIR] Up to [local] / prex-old / sys / kern

File: [local] / prex-old / sys / kern / exception.c (download)

Revision 1.1, Tue Jun 3 09:38:46 2008 UTC (15 years, 11 months ago) by nbrk
Branch point for: MAIN

Initial revision

/*-
 * 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, u_long))
{
	task_t self;
	list_t head, n;
	thread_t th;

	if (handler != NULL && !user_area(handler))
		return EFAULT;

	sched_lock();
	self = cur_task();
	if (self->exc_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->exc_bitmap = 0;
			irq_unlock();
			/*
			 * If the thread is waiting for an exception,
			 * cancel it.
			 */
			if (th->sleep_event == &exception_event)
				sched_unsleep(th, SLP_BREAK);
		}
	}
	self->exc_handler = handler;
	sched_unlock();
	return 0;
}

/*
 * exception_raise - raise an exception for specified task.
 * @task: task id
 * @exc:  exception code
 *
 * 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;
	} else if (task != cur_task() && !task_capable(CAP_KILL)) {
		err = EPERM;
	} else if (task == &kern_task || task->exc_handler == NULL ||
		 list_empty(&task->threads)) {
		err = EPERM;
	} else {
		err = exception_post(task, exc);
	}
	sched_unlock();
	return err;
}

/*
 * Post an exception to the specified task.
 */
int
exception_post(task_t task, int exc)
{
	list_t head, n;
	thread_t th;

	if (exc < 0 || exc >= NR_EXCS)
		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->sleep_event == &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->exc_bitmap |= (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.
 * @exc: exception code returned.
 *
 * The routine returns EINTR on success.
 */
int
exception_wait(int *exc)
{
	int i, rc;

	if (cur_task()->exc_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 < NR_EXCS; i++) {
		if (cur_thread->exc_bitmap & (1 << i))
			break;
	}
	irq_unlock();
	ASSERT(i != NR_EXCS);
	sched_unlock();

	if (umem_copyout(&i, exc, sizeof(int)))
		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 < NR_EXCS);

	/* Mark pending bit */
	irq_lock();
	cur_thread->exc_bitmap |= (1 << exc);
	irq_unlock();
}

/*
 * 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, u_long);
	uint32_t bitmap;
	int exc;

	sched_lock();
	irq_lock();
	bitmap = th->exc_bitmap;
	irq_unlock();

	if (bitmap != 0) {
		/*
		 * Find a pending exception.
		 */
		for (exc = 0; exc < NR_EXCS; exc++) {
			if (bitmap & (1 << exc))
				break;
		}
		handler = self->exc_handler;
		if (handler == NULL) {
			printk("Exception #%d is not handled by task.\n", exc);
			printk("Terminate task:%s (id:%x)\n",
			       self->name ? self->name : "no name", self);
			task_terminate(self);
			goto out;
		}
		/*
		 * Transfer control to an exception handler.
		 */
		context_save(&th->context, exc);
		context_set(&th->context, CTX_UENTRY, (u_long)handler);

		irq_lock();
		th->exc_bitmap &= ~(1 << exc);
		irq_unlock();
	}
 out:
	sched_unlock();
}

/*
 * exception_return() is called from exception handler to restore
 * the original context.
 * @regs: context pointer which is passed to exception handler.
 *
 * TODO: should validate passed data area.
 */
int
exception_return(void *regs)
{

	if ((regs == NULL) || !user_area(regs))
		return EFAULT;

	context_restore(&cur_thread->context, regs);
	return 0;
}

void
exception_init(void)
{

	event_init(&exception_event, "exception");
}