*
* \brief Hardware independent timer driver (implementation)
*
- * \version $Id$
* \author Bernie Innocenti <bernie@codewiz.org>
+ * \author Francesco Sacchi <batt@develer.com>
*/
#include "timer.h"
+#include "hw/hw_timer.h"
#include "cfg/cfg_timer.h"
#include "cfg/cfg_wdt.h"
-#include "cfg/cfg_kern.h"
+#include "cfg/cfg_proc.h"
+#include "cfg/cfg_signal.h"
#include <cfg/os.h>
#include <cfg/debug.h>
#include <cfg/module.h>
#include <cpu/attr.h>
#include <cpu/types.h>
#include <cpu/irq.h>
+#include <cpu/power.h> // cpu_relax()
+
+#include <kern/proc_p.h> // proc_decQuantun()
/*
* Include platform-specific binding code if we're hosted.
//#include OS_CSOURCE(timer)
#include <emul/timer_posix.c>
#else
- #include CPU_CSOURCE(timer)
+ #ifndef WIZ_AUTOGEN
+ #warning Deprecated: now you should include timer_<cpu> directly in the makefile. Remove this line and the following once done.
+ #include CPU_CSOURCE(timer)
+ #endif
#endif
/*
*/
REGISTER static List timers_queue;
-
/**
- * Add the specified timer to the software timer service queue.
- * When the delay indicated by the timer expires, the timer
- * device will execute the event associated with it.
- *
- * \note Interrupt safe
+ * This function really does the job. It adds \a timer to \a queue.
+ * \see timer_add for details.
*/
-void timer_add(Timer *timer)
+INLINE void timer_addToList(Timer *timer, List *queue)
{
- Timer *node;
- cpu_flags_t flags;
-
-
/* Inserting timers twice causes mayhem. */
ASSERT(timer->magic != TIMER_MAGIC_ACTIVE);
DB(timer->magic = TIMER_MAGIC_ACTIVE;)
- IRQ_SAVE_DISABLE(flags);
-
- /* Calculate expiration time for this timer */
- timer->tick = _clock + timer->_delay;
-
/*
* Search for the first node whose expiration time is
* greater than the timer we want to add.
*/
- node = (Timer *)LIST_HEAD(&timers_queue);
+ Timer *node = (Timer *)LIST_HEAD(queue);
while (node->link.succ)
{
/*
/* Enqueue timer request into the list */
INSERT_BEFORE(&timer->link, &node->link);
-
- IRQ_RESTORE(flags);
}
+/**
+ * Add the specified timer to the software timer service queue.
+ * When the delay indicated by the timer expires, the timer
+ * device will execute the event associated with it.
+ *
+ * \note Interrupt safe
+ */
+void timer_add(Timer *timer)
+{
+ ATOMIC(
+ /* Calculate expiration time for this timer */
+ timer->tick = _clock + timer->_delay;
+
+ timer_addToList(timer, &timers_queue);
+ );
+}
/**
* Remove a timer from the timers queue before it has expired.
return timer;
}
+
+INLINE void timer_poll(List *queue)
+{
+ Timer *timer;
+
+ /*
+ * Check the first timer request in the list and process
+ * it when it has expired. Repeat this check until the
+ * first node has not yet expired. Since the list is sorted
+ * by expiry time, all the following requests are guaranteed
+ * to expire later.
+ */
+ while ((timer = (Timer *)LIST_HEAD(queue))->link.succ)
+ {
+ /* This request in list has not yet expired? */
+ if (timer_clock() - timer->tick < 0)
+ break;
+
+ /* Retreat the expired timer */
+ REMOVE(&timer->link);
+ DB(timer->magic = TIMER_MAGIC_INACTIVE;)
+
+ /* Execute the associated event */
+ event_do(&timer->expire);
+ }
+}
+
+/**
+ * Add \a timer to \a queue.
+ * \see synctimer_poll() for details.
+ */
+void synctimer_add(Timer *timer, List *queue)
+{
+ timer->tick = timer_clock() + timer->_delay;
+
+ timer_addToList(timer, queue);
+}
+
+void synctimer_readd(Timer *timer, List *queue)
+{
+ timer->tick += timer->_delay;
+ timer_addToList(timer, queue);
+}
+
+
+/**
+ * Simple synchronous timer based scheduler polling routine.
+ *
+ * Sometimes you would like to have a proper scheduler,
+ * but you can't afford it due to memory constraints.
+ *
+ * This is a simple replacement: you can create events and call
+ * them periodically at specific time intervals.
+ * All you have to do is to set up normal timers, and call synctimer_add()
+ * instead of timer_add() to add the events to your specific queue.
+ * Then, in the main loop or wherever you want, you can call
+ * synctimer_poll() to process expired events. The associated callbacks will be
+ * executed.
+ * As this is done synchronously you don't have to worry about race conditions.
+ * You can kill an event by simply calling synctimer_abort().
+ *
+ */
+void synctimer_poll(List *queue)
+{
+ timer_poll(queue);
+}
+
#endif /* CONFIG_TIMER_EVENTS */
/**
* Wait for the specified amount of timer ticks.
+ *
+ * \note Sleeping while preemption is disabled fallbacks to a busy wait sleep.
*/
void timer_delayTicks(ticks_t delay)
{
/* We shouldn't sleep with interrupts disabled */
IRQ_ASSERT_ENABLED();
-#if defined(CONFIG_KERN_SIGNALS) && CONFIG_KERN_SIGNALS
+#if CONFIG_KERN_SIGNALS
Timer t;
-
- ASSERT(!sig_check(SIG_SINGLE));
- timer_setSignal(&t, proc_current(), SIG_SINGLE);
- timer_setDelay(&t, delay);
- timer_add(&t);
- sig_wait(SIG_SINGLE);
-
-#else /* !CONFIG_KERN_SIGNALS */
-
- ticks_t start = timer_clock();
-
- /* Busy wait */
- while (timer_clock() - start < delay)
+ DB(t.magic = TIMER_MAGIC_INACTIVE;)
+ if (proc_preemptAllowed())
{
-#if CONFIG_WATCHDOG
- wdt_reset();
-#endif
+ ASSERT(!sig_check(SIG_SINGLE));
+ timer_setSignal(&t, proc_current(), SIG_SINGLE);
+ timer_setDelay(&t, delay);
+ timer_add(&t);
+ sig_wait(SIG_SINGLE);
}
-
+ else
#endif /* !CONFIG_KERN_SIGNALS */
+ {
+ ticks_t start = timer_clock();
+
+ /* Busy wait */
+ while (timer_clock() - start < delay)
+ cpu_relax();
+ }
}
hptime_t now, prev = timer_hw_hpread();
hptime_t delta;
- for(;;)
+ for (;;)
{
now = timer_hw_hpread();
/*
- * We rely on hptime_t being unsigned here to
- * reduce the modulo to an AND in the common
- * case of TIMER_HW_CNT.
+ * The timer counter may wrap here and "prev" can become
+ * greater than "now". So, be sure to always evaluate a
+ * coherent timer difference:
+ *
+ * 0 prev now TIMER_HW_CNT
+ * |_____|_______________|_____|
+ * ^^^^^^^^^^^^^^^
+ * delta = now - prev
+ *
+ * 0 now prev TIMER_HW_CNT
+ * |_____|_______________|_____|
+ * ^^^^^ ^^^^^
+ * delta = (TIMER_HW_CNT - prev) + now
+ *
+ * NOTE: TIMER_HW_CNT can be any value, not necessarily a power
+ * of 2. For this reason the "%" operator is not suitable for
+ * the generic case.
*/
- delta = (now - prev) % TIMER_HW_CNT;
+ delta = (now < prev) ? ((hptime_t)TIMER_HW_CNT - prev + now) :
+ (now - prev);
if (delta >= delay)
break;
delay -= delta;
}
#endif /* CONFIG_TIMER_UDELAY */
-
/**
* Timer interrupt handler. Find soft timers expired and
* trigger corresponding events.
#pragma interrupt saveall
#endif
-#if CONFIG_TIMER_EVENTS
- Timer *timer;
-#endif
-
/*
* On systems sharing IRQ line and vector, this check is needed
* to ensure that IRQ is generated by timer source.
TIMER_STROBE_ON;
- /* Perform hw IRQ handling */
- timer_hw_irq();
-
/* Update the master ms counter */
++_clock;
-#if CONFIG_TIMER_EVENTS
- /*
- * Check the first timer request in the list and process
- * it when it has expired. Repeat this check until the
- * first node has not yet expired. Since the list is sorted
- * by expiry time, all the following requests are guaranteed
- * to expire later.
- */
- while ((timer = (Timer *)LIST_HEAD(&timers_queue))->link.succ)
- {
- /* This request in list has not yet expired? */
- if (_clock - timer->tick < 0)
- break;
+ /* Update the current task's quantum (if enabled). */
+ proc_decQuantum();
- /* Retreat the expired timer */
- REMOVE(&timer->link);
- DB(timer->magic = TIMER_MAGIC_INACTIVE;)
+ #if CONFIG_TIMER_EVENTS
+ timer_poll(&timers_queue);
+ #endif
- /* Execute the associated event */
- event_do(&timer->expire);
- }
-#endif /* CONFIG_TIMER_EVENTS */
+ /* Perform hw IRQ handling */
+ timer_hw_irq();
TIMER_STROBE_OFF;
}
}
-#if (ARCH & ARCH_EMUL)
+#if (ARCH & ARCH_EMUL) || (CPU_ARM_AT91)
/**
- * Stop timer (only used by emulator)
+ * Stop timer
*/
void timer_cleanup(void)
{
MOD_CLEANUP(timer);
timer_hw_cleanup();
-
- // Hmmm... apparently, the demo app does not cleanup properly
- //ASSERT(LIST_EMPTY(&timers_queue));
}
-#endif /* ARCH_EMUL */
+#endif