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