Merge branch "preempt" in "trunk".
[bertos.git] / bertos / kern / signal.c
index c21de17875e32d38b582d00824ab87ec8615a1b2..19941f9477ae8775fe025a919a396bcc050bf76c 100644 (file)
@@ -26,9 +26,8 @@
  * invalidate any other reasons why the executable file might be covered by
  * the GNU General Public License.
  *
- * Copyright 2004 Develer S.r.l. (http://www.develer.com/)
+ * Copyright 2004, 2008 Develer S.r.l. (http://www.develer.com/)
  * Copyright 1999, 2000, 2001 Bernie Innocenti <bernie@codewiz.org>
- *
  * -->
  *
  * \brief IPC signals implementation.
  *  - Do not sleep between starting the asynchronous task that will fire
  *    SIG_SINGLE, and the call to  sig_wait().
  *  - Do not call system functions that may implicitly sleep, such as
- *    timer_delayTickes().
+ *    timer_delayTicks().
  *
  * \version $Id$
- *
  * \author Bernie Innocenti <bernie@codewiz.org>
  */
 
 #include "signal.h"
 
+#include "cfg/cfg_timer.h"
 #include <cfg/debug.h>
-#include <drv/timer.h>
+#include <cfg/depend.h>
+
+#include <cpu/irq.h>
 #include <kern/proc.h>
 #include <kern/proc_p.h>
+
 
 #if CONFIG_KERN_SIGNALS
 
+// Check config dependencies
+CONFIG_DEPEND(CONFIG_KERN_SIGNALS, CONFIG_KERN);
+
 /**
  * Check if any of the signals in \a sigs has occurred and clear them.
+ *
  * \return the signals that have occurred.
  */
 sigmask_t sig_check(sigmask_t sigs)
 {
        sigmask_t result;
-       cpuflags_t flags;
+       cpu_flags_t flags;
 
        IRQ_SAVE_DISABLE(flags);
-       result = CurrentProcess->sig_recv & sigs;
-       CurrentProcess->sig_recv &= ~sigs;
+       result = current_process->sig_recv & sigs;
+       current_process->sig_recv &= ~sigs;
        IRQ_RESTORE(flags);
 
        return result;
@@ -135,29 +140,61 @@ sigmask_t sig_check(sigmask_t sigs)
 sigmask_t sig_wait(sigmask_t sigs)
 {
        sigmask_t result;
-       cpuflags_t flags;
 
-       IRQ_SAVE_DISABLE(flags);
+       /* Sleeping with IRQs disabled or preemption forbidden is illegal */
+       IRQ_ASSERT_ENABLED();
+       ASSERT(proc_preemptAllowed());
+
+       /*
+        * This is subtle: there's a race condition where a concurrent
+        * process or an interrupt may call sig_signal() to set a bit in
+        * Process.sig_recv just after we have checked for it, but before
+        * we've set Process.sig_wait to let them know we want to be awaken.
+        *
+        * In this case, we'd deadlock with the signal bit already set
+        * and the process never being reinserted into the ready list.
+        */
+       IRQ_DISABLE;
 
        /* Loop until we get at least one of the signals */
-       while (!(result = CurrentProcess->sig_recv & sigs))
+       while (!(result = current_process->sig_recv & sigs))
        {
-               /* go to sleep and proc_schedule() another process */
-               CurrentProcess->sig_wait = sigs;
-               proc_schedule();
+               /*
+                * Tell "them" that we want to be awaken when any of these
+                * signals arrives.
+                */
+               current_process->sig_wait = sigs;
+
+               /*
+                * Go to sleep and proc_switch() to another process.
+                *
+                * We re-enable IRQs because proc_switch() does not
+                * guarantee to save and restore the interrupt mask.
+                */
+               IRQ_ENABLE;
+               proc_switch();
+               IRQ_DISABLE;
 
-               /* When we come back here, a signal must be arrived */
-               ASSERT(!CurrentProcess->sig_wait);
-               ASSERT(CurrentProcess->sig_recv);
+               /*
+                * When we come back here, the wait mask must have been
+                * cleared by someone through sig_signal(), and at least
+                * one of the signals we were expecting must have been
+                * delivered to us.
+                */
+               ASSERT(!current_process->sig_wait);
+               ASSERT(current_process->sig_recv & sigs);
        }
 
        /* Signals found: clear them and return */
-       CurrentProcess->sig_recv &= ~sigs;
+       current_process->sig_recv &= ~sigs;
 
-       IRQ_RESTORE(flags);
+       IRQ_ENABLE;
        return result;
 }
 
+#if CONFIG_TIMER_EVENTS
+
+#include <drv/timer.h>
 /**
  * Sleep until any of the signals in \a sigs or \a timeout ticks elapse.
  * If the timeout elapse a SIG_TIMEOUT is added to the received signal(s).
@@ -168,7 +205,7 @@ sigmask_t sig_waitTimeout(sigmask_t sigs, ticks_t timeout)
 {
        Timer t;
        sigmask_t res;
-       cpuflags_t flags;
+       cpu_flags_t flags;
 
        ASSERT(!sig_check(SIG_TIMEOUT));
        ASSERT(!(sigs & SIG_TIMEOUT));
@@ -188,6 +225,8 @@ sigmask_t sig_waitTimeout(sigmask_t sigs, ticks_t timeout)
        return res;
 }
 
+#endif // CONFIG_TIMER_EVENTS
+
 
 /**
  * Send the signals \a sigs to the process \a proc.
@@ -197,7 +236,9 @@ sigmask_t sig_waitTimeout(sigmask_t sigs, ticks_t timeout)
  */
 void sig_signal(Process *proc, sigmask_t sigs)
 {
-       cpuflags_t flags;
+       cpu_flags_t flags;
+
+       /* See comment in sig_wait() for why this protection is necessary */
        IRQ_SAVE_DISABLE(flags);
 
        /* Set the signals */
@@ -206,13 +247,17 @@ void sig_signal(Process *proc, sigmask_t sigs)
        /* Check if process needs to be awoken */
        if (proc->sig_recv & proc->sig_wait)
        {
-               /* Wake up process and enqueue in ready list */
+               /*
+                * Wake up process and enqueue in ready list.
+                *
+                * Move this process to the head of the ready list, so that it
+                * will be chosen at the next scheduling point.
+                */
                proc->sig_wait = 0;
-               SCHED_ENQUEUE(proc);
+               SCHED_ENQUEUE_HEAD(proc);
        }
 
        IRQ_RESTORE(flags);
 }
 
 #endif /* CONFIG_KERN_SIGNALS */
-