uml: redo host capability detection and disabling
[safe/jmp/linux-2.6] / arch / um / os-Linux / signal.c
index f11b312..3f1694b 100644 (file)
@@ -1,27 +1,57 @@
 /*
  * Copyright (C) 2004 PathScale, Inc
+ * Copyright (C) 2004 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
  * Licensed under the GPL
  */
 
-#include <signal.h>
-#include <stdio.h>
-#include <unistd.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <stdarg.h>
-#include <string.h>
-#include <sys/mman.h>
-#include "user_util.h"
-#include "user.h"
-#include "signal_kern.h"
-#include "sysdep/sigcontext.h"
-#include "sysdep/signal.h"
-#include "sigcontext.h"
-#include "mode.h"
+#include <errno.h>
+#include <signal.h>
+#include <strings.h>
+#include "as-layout.h"
+#include "kern_util.h"
 #include "os.h"
+#include "sysdep/barrier.h"
+#include "sysdep/sigcontext.h"
+#include "user.h"
+
+/* Copied from linux/compiler-gcc.h since we can't include it directly */
+#define barrier() __asm__ __volatile__("": : :"memory")
 
-/* These are the asynchronous signals.  SIGVTALRM and SIGARLM are handled
- * together under SIGVTALRM_BIT.  SIGPROF is excluded because we want to
+void (*sig_info[NSIG])(int, struct uml_pt_regs *) = {
+       [SIGTRAP]       = relay_signal,
+       [SIGFPE]        = relay_signal,
+       [SIGILL]        = relay_signal,
+       [SIGWINCH]      = winch,
+       [SIGBUS]        = bus_handler,
+       [SIGSEGV]       = segv_handler,
+       [SIGIO]         = sigio_handler,
+       [SIGVTALRM]     = timer_handler };
+
+static void sig_handler_common(int sig, struct sigcontext *sc)
+{
+       struct uml_pt_regs r;
+       int save_errno = errno;
+
+       r.is_user = 0;
+       if (sig == SIGSEGV) {
+               /* For segfaults, we want the data from the sigcontext. */
+               copy_sc(&r, sc);
+               GET_FAULTINFO_FROM_SC(r.faultinfo, sc);
+       }
+
+       /* enable signals if sig isn't IRQ signal */
+       if ((sig != SIGIO) && (sig != SIGWINCH) && (sig != SIGVTALRM))
+               unblock_signals();
+
+       (*sig_info[sig])(sig, &r);
+
+       errno = save_errno;
+}
+
+/*
+ * These are the asynchronous signals.  SIGPROF is excluded because we want to
  * be able to profile all of UML, not just the non-critical sections.  If
  * profiling is not thread-safe, then that is not my problem.  We can disable
  * profiling when SMP is enabled in that case.
 #define SIGVTALRM_BIT 1
 #define SIGVTALRM_MASK (1 << SIGVTALRM_BIT)
 
-#define SIGALRM_BIT 2
-#define SIGALRM_MASK (1 << SIGALRM_BIT)
+static int signals_enabled;
+static unsigned int signals_pending;
 
-static int signals_enabled = 1;
-static int pending = 0;
-
-void sig_handler(ARCH_SIGHDLR_PARAM)
+void sig_handler(int sig, struct sigcontext *sc)
 {
-       struct sigcontext *sc;
        int enabled;
 
-       /* Must be the first thing that this handler does - x86_64 stores
-        * the sigcontext in %rdx, and we need to save it before it has a
-        * chance to get trashed.
-        */
-
-       ARCH_GET_SIGCONTEXT(sc, sig);
-
        enabled = signals_enabled;
-       if(!enabled && (sig == SIGIO)){
-               pending |= SIGIO_MASK;
+       if (!enabled && (sig == SIGIO)) {
+               signals_pending |= SIGIO_MASK;
                return;
        }
 
        block_signals();
 
-       CHOOSE_MODE_PROC(sig_handler_common_tt, sig_handler_common_skas,
-                        sig, sc);
+       sig_handler_common(sig, sc);
 
        set_signals(enabled);
 }
 
-extern int timer_irq_inited;
-
-static void real_alarm_handler(int sig, struct sigcontext *sc)
+static void real_alarm_handler(struct sigcontext *sc)
 {
-       if(!timer_irq_inited){
-               signals_enabled = 1;
-               return;
-       }
-
-       if(sig == SIGALRM)
-               switch_timers(0);
-
-       CHOOSE_MODE_PROC(sig_handler_common_tt, sig_handler_common_skas,
-                        sig, sc);
-
-       if(sig == SIGALRM)
-               switch_timers(1);
+       struct uml_pt_regs regs;
 
+       if (sc != NULL)
+               copy_sc(&regs, sc);
+       regs.is_user = 0;
+       unblock_signals();
+       timer_handler(SIGVTALRM, &regs);
 }
 
-void alarm_handler(ARCH_SIGHDLR_PARAM)
+void alarm_handler(int sig, struct sigcontext *sc)
 {
-       struct sigcontext *sc;
        int enabled;
 
-       ARCH_GET_SIGCONTEXT(sc, sig);
-
        enabled = signals_enabled;
-       if(!signals_enabled){
-               if(sig == SIGVTALRM)
-                       pending |= SIGVTALRM_MASK;
-               else pending |= SIGALRM_MASK;
-
+       if (!signals_enabled) {
+               signals_pending |= SIGVTALRM_MASK;
                return;
        }
 
        block_signals();
 
-       real_alarm_handler(sig, sc);
+       real_alarm_handler(sc);
        set_signals(enabled);
 }
 
-extern void do_boot_timer_handler(struct sigcontext * sc);
-
-void boot_timer_handler(ARCH_SIGHDLR_PARAM)
+void timer_init(void)
 {
-       struct sigcontext *sc;
-       int enabled;
-
-       ARCH_GET_SIGCONTEXT(sc, sig);
-
-       enabled = signals_enabled;
-       if(!enabled){
-               if(sig == SIGVTALRM)
-                       pending |= SIGVTALRM_MASK;
-               else pending |= SIGALRM_MASK;
-               return;
-       }
-
-       block_signals();
-
-       do_boot_timer_handler(sc);
-       set_signals(enabled);
+       set_handler(SIGVTALRM, (__sighandler_t) alarm_handler,
+                   SA_ONSTACK | SA_RESTART, SIGUSR1, SIGIO, SIGWINCH, -1);
 }
 
 void set_sigstack(void *sig_stack, int size)
@@ -135,20 +121,55 @@ void set_sigstack(void *sig_stack, int size)
                                     .ss_sp     = (__ptr_t) sig_stack,
                                     .ss_size   = size - sizeof(void *) });
 
-       if(sigaltstack(&stack, NULL) != 0)
+       if (sigaltstack(&stack, NULL) != 0)
                panic("enabling signal stack failed, errno = %d\n", errno);
 }
 
-void remove_sigstack(void)
+void (*handlers[_NSIG])(int sig, struct sigcontext *sc);
+
+void handle_signal(int sig, struct sigcontext *sc)
 {
-       stack_t stack = ((stack_t) { .ss_flags  = SS_DISABLE,
-                                    .ss_sp     = NULL,
-                                    .ss_size   = 0 });
+       unsigned long pending = 1UL << sig;
+
+       do {
+               int nested, bail;
+
+               /*
+                * pending comes back with one bit set for each
+                * interrupt that arrived while setting up the stack,
+                * plus a bit for this interrupt, plus the zero bit is
+                * set if this is a nested interrupt.
+                * If bail is true, then we interrupted another
+                * handler setting up the stack.  In this case, we
+                * have to return, and the upper handler will deal
+                * with this interrupt.
+                */
+               bail = to_irq_stack(&pending);
+               if (bail)
+                       return;
+
+               nested = pending & 1;
+               pending &= ~1;
 
-       if(sigaltstack(&stack, NULL) != 0)
-               panic("disabling signal stack failed, errno = %d\n", errno);
+               while ((sig = ffs(pending)) != 0){
+                       sig--;
+                       pending &= ~(1 << sig);
+                       (*handlers[sig])(sig, sc);
+               }
+
+               /*
+                * Again, pending comes back with a mask of signals
+                * that arrived while tearing down the stack.  If this
+                * is non-zero, we just go back, set up the stack
+                * again, and handle the new interrupts.
+                */
+               if (!nested)
+                       pending = from_irq_stack(nested);
+       } while (pending);
 }
 
+extern void hard_handler(int sig);
+
 void set_handler(int sig, void (*handler)(int), int flags, ...)
 {
        struct sigaction action;
@@ -156,82 +177,103 @@ void set_handler(int sig, void (*handler)(int), int flags, ...)
        sigset_t sig_mask;
        int mask;
 
-       va_start(ap, flags);
-       action.sa_handler = handler;
+       handlers[sig] = (void (*)(int, struct sigcontext *)) handler;
+       action.sa_handler = hard_handler;
+
        sigemptyset(&action.sa_mask);
-       while((mask = va_arg(ap, int)) != -1){
+
+       va_start(ap, flags);
+       while ((mask = va_arg(ap, int)) != -1)
                sigaddset(&action.sa_mask, mask);
-       }
        va_end(ap);
+
+       if (sig == SIGSEGV)
+               flags |= SA_NODEFER;
+
        action.sa_flags = flags;
        action.sa_restorer = NULL;
-       if(sigaction(sig, &action, NULL) < 0)
+       if (sigaction(sig, &action, NULL) < 0)
                panic("sigaction failed - errno = %d\n", errno);
 
        sigemptyset(&sig_mask);
        sigaddset(&sig_mask, sig);
-       if(sigprocmask(SIG_UNBLOCK, &sig_mask, NULL) < 0)
+       if (sigprocmask(SIG_UNBLOCK, &sig_mask, NULL) < 0)
                panic("sigprocmask failed - errno = %d\n", errno);
 }
 
 int change_sig(int signal, int on)
 {
-       sigset_t sigset, old;
+       sigset_t sigset;
 
        sigemptyset(&sigset);
        sigaddset(&sigset, signal);
-       sigprocmask(on ? SIG_UNBLOCK : SIG_BLOCK, &sigset, &old);
-       return(!sigismember(&old, signal));
+       if (sigprocmask(on ? SIG_UNBLOCK : SIG_BLOCK, &sigset, NULL) < 0)
+               return -errno;
+
+       return 0;
 }
 
 void block_signals(void)
 {
        signals_enabled = 0;
+       /*
+        * This must return with signals disabled, so this barrier
+        * ensures that writes are flushed out before the return.
+        * This might matter if gcc figures out how to inline this and
+        * decides to shuffle this code into the caller.
+        */
+       barrier();
 }
 
 void unblock_signals(void)
 {
        int save_pending;
 
-       if(signals_enabled == 1)
+       if (signals_enabled == 1)
                return;
 
-       /* We loop because the IRQ handler returns with interrupts off.  So,
+       /*
+        * We loop because the IRQ handler returns with interrupts off.  So,
         * interrupts may have arrived and we need to re-enable them and
-        * recheck pending.
+        * recheck signals_pending.
         */
-       while(1){
-               /* Save and reset save_pending after enabling signals.  This
-                * way, pending won't be changed while we're reading it.
+       while (1) {
+               /*
+                * Save and reset save_pending after enabling signals.  This
+                * way, signals_pending won't be changed while we're reading it.
                 */
                signals_enabled = 1;
 
-               save_pending = pending;
-               if(save_pending == 0)
+               /*
+                * Setting signals_enabled and reading signals_pending must
+                * happen in this order.
+                */
+               barrier();
+
+               save_pending = signals_pending;
+               if (save_pending == 0)
                        return;
 
-               pending = 0;
+               signals_pending = 0;
 
-               /* We have pending interrupts, so disable signals, as the
+               /*
+                * We have pending interrupts, so disable signals, as the
                 * handlers expect them off when they are called.  They will
                 * be enabled again above.
                 */
 
                signals_enabled = 0;
 
-               /* Deal with SIGIO first because the alarm handler might
+               /*
+                * Deal with SIGIO first because the alarm handler might
                 * schedule, leaving the pending SIGIO stranded until we come
                 * back here.
                 */
-               if(save_pending & SIGIO_MASK)
-                       CHOOSE_MODE_PROC(sig_handler_common_tt,
-                                        sig_handler_common_skas, SIGIO, NULL);
+               if (save_pending & SIGIO_MASK)
+                       sig_handler_common(SIGIO, NULL);
 
-               if(save_pending & SIGALRM_MASK)
-                       real_alarm_handler(SIGALRM, NULL);
-
-               if(save_pending & SIGVTALRM_MASK)
-                       real_alarm_handler(SIGVTALRM, NULL);
+               if (save_pending & SIGVTALRM_MASK)
+                       real_alarm_handler(NULL);
        }
 }
 
@@ -243,18 +285,13 @@ int get_signals(void)
 int set_signals(int enable)
 {
        int ret;
-       if(signals_enabled == enable)
+       if (signals_enabled == enable)
                return enable;
 
        ret = signals_enabled;
-       if(enable)
+       if (enable)
                unblock_signals();
        else block_signals();
 
        return ret;
 }
-
-void os_usr1_signal(int on)
-{
-       change_sig(SIGUSR1, on);
-}