callout: add a new module which is an alternative to the scheduler
[aversive.git] / modules / base / callout / callout.c
diff --git a/modules/base/callout/callout.c b/modules/base/callout/callout.c
new file mode 100644 (file)
index 0000000..f6c73be
--- /dev/null
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) <2014>, Olivier Matz <zer0@droids-corp.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of the University of California, Berkeley nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS AND 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.
+ */
+
+/* Inspired from Intel DPDK rte_timer library */
+/*-
+ * Copyright (c) <2010>, Intel Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of Intel Corporation nor the names of its
+ *   contributors may be used to endorse or promote products derived
+ *   from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
+ * COPYRIGHT OWNER 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.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <aversive.h>
+#include <aversive/pgmspace.h>
+#include <aversive/queue.h>
+
+#include "callout.h"
+
+/* allow to browse a list while modifying the current element */
+#define LIST_FOREACH_SAFE(cur, next, head, field)                      \
+       for ((cur) = LIST_FIRST((head)),                                \
+                    (next) = ((cur) ? LIST_NEXT((cur), field) : NULL); \
+            (cur);                                                     \
+            (cur) = (next),                                            \
+                    (next) = ((cur) ? LIST_NEXT((cur), field) : NULL))
+
+#ifdef CALLOUT_STATS
+/* called with irq locked */
+#define CALLOUT_STAT_ADD(cm, field, x) do {    \
+       cm->stats.field += x;                   \
+       } while(0)
+#else
+#define CALLOUT_STAT_ADD(cm, field, x) do { } while(0)
+#endif
+
+#ifdef CALLOUT_DEBUG
+#define callout_dprintf_P(fmt, ...) printf_P(PSTR("%s(): " fmt), __FUNCTION__, \
+                                        __VA_ARGS__)
+#else
+#define callout_dprintf_P(...) do { } while (0)
+#endif
+
+/* Initialize a callout manager */
+void
+callout_mgr_init(struct callout_mgr *cm, get_time_t *get_time)
+{
+       memset(cm, 0, sizeof(*cm));
+       cm->get_time = get_time;
+       LIST_INIT(&cm->sched_list);
+}
+
+/* Initialize the timer handle tim for use */
+void
+callout_init(struct callout *tim, callout_cb_t f, void *arg, uint8_t priority)
+{
+       memset(tim, 0, sizeof(*tim));
+       tim->f = f;
+       tim->arg = arg;
+       tim->priority = priority;
+}
+
+/*
+ * Add a timer in the scheduled list (timer must not already be in a list). The
+ * timers are sorted in the list according the expire time (the closer timers
+ * first).
+ *
+ * called with irq locked
+ */
+static void
+callout_add_in_sched_list(struct callout_mgr *cm, struct callout *tim)
+{
+       struct callout *t, *prev_t;
+
+       callout_dprintf_P("cm=%p tim=%p\r\n", cm, tim);
+       tim->state = CALLOUT_STATE_SCHEDULED;
+
+       /* list is empty */
+       if (LIST_EMPTY(&cm->sched_list)) {
+               LIST_INSERT_HEAD(&cm->sched_list, tim, next);
+               return;
+       }
+
+       /* 'tim' expires before first entry */
+       t = LIST_FIRST(&cm->sched_list);
+       if ((int16_t)(tim->expire - t->expire) <= 0) {
+               LIST_INSERT_HEAD(&cm->sched_list, tim, next);
+               return;
+       }
+
+       /* find an element that will expire after 'tim' */
+       LIST_FOREACH(t, &cm->sched_list, next) {
+               if ((int16_t)(tim->expire - t->expire) <= 0) {
+                       LIST_INSERT_BEFORE(t, tim, next);
+                       return;
+               }
+               prev_t = t;
+       }
+
+       /* not found, insert at the end of the list */
+       LIST_INSERT_AFTER(prev_t, tim, next);
+}
+
+/*
+ * Add a timer in the local expired list (timer must not already be in a
+ * list). The timers are sorted in the list according to the priority (high
+ * priority first).
+ *
+ * called with irq locked
+ */
+static void
+callout_add_in_expired_list(struct callout_mgr *cm,
+       struct callout_list *expired_list, struct callout *tim)
+{
+       struct callout *t, *prev_t;
+
+       (void)cm; /* avoid warning if debug is disabled */
+
+       callout_dprintf_P("cm=%p tim=%p\r\n", cm, tim);
+       tim->state = CALLOUT_STATE_EXPIRED;
+
+       /* list is empty */
+       if (LIST_EMPTY(expired_list)) {
+               LIST_INSERT_HEAD(expired_list, tim, next);
+               return;
+       }
+
+       /* 'tim' has a higher prio */
+       t = LIST_FIRST(expired_list);
+       if (tim->priority >= t->priority) {
+               LIST_INSERT_HEAD(expired_list, tim, next);
+               return;
+       }
+
+       /* find an element that will expire after 'tim' */
+       LIST_FOREACH(t, expired_list, next) {
+               if (tim->priority >= t->priority) {
+                       LIST_INSERT_BEFORE(t, tim, next);
+                       return;
+               }
+               prev_t = t;
+       }
+
+       /* not found, insert at the end of the list */
+       LIST_INSERT_AFTER(prev_t, tim, next);
+}
+
+/*
+ * del from list (timer must be in a list)
+ */
+static void
+callout_del(struct callout_mgr *cm, struct callout *tim)
+{
+       (void)cm; /* avoid warning if debug is disabled */
+       callout_dprintf_P("cm=%p tim=%p\r\n", cm, tim);
+       LIST_REMOVE(tim, next);
+}
+
+/* Reset and start the timer associated with the timer handle tim */
+static int
+__callout_schedule(struct callout_mgr *cm, struct callout *tim,
+       uint16_t expire)
+{
+       uint8_t flags;
+
+       callout_dprintf_P("cm=%p tim=%p expire=%d\r\n",
+                         cm, tim, expire);
+
+       IRQ_LOCK(flags);
+       CALLOUT_STAT_ADD(cm, schedule, 1);
+
+       /* remove it from list */
+       if (tim->state != CALLOUT_STATE_STOPPED) {
+               /* stats */
+               if (tim->state == CALLOUT_STATE_SCHEDULED)
+                       CALLOUT_STAT_ADD(cm, cur_scheduled, -1);
+               else if (tim->state == CALLOUT_STATE_EXPIRED)
+                       CALLOUT_STAT_ADD(cm, cur_expired, -1);
+               if (tim->state == CALLOUT_STATE_RUNNING)
+                       CALLOUT_STAT_ADD(cm, cur_running, -1);
+
+               callout_del(cm, tim);
+       }
+
+       tim->expire = expire;
+       CALLOUT_STAT_ADD(cm, cur_scheduled, 1);
+       callout_add_in_sched_list(cm, tim);
+       IRQ_UNLOCK(flags);
+
+       return 0;
+}
+
+/* Reset and start the timer associated with the timer handle tim */
+int
+callout_schedule(struct callout_mgr *cm, struct callout *tim,
+       uint16_t ticks)
+{
+       return __callout_schedule(cm, tim, cm->get_time() + ticks);
+}
+
+/* Reset and start the timer associated with the timer handle tim */
+int
+callout_reschedule(struct callout_mgr *cm, struct callout *tim,
+       uint16_t ticks)
+{
+       return __callout_schedule(cm, tim, tim->expire + ticks);
+}
+
+/* Stop the timer associated with the timer handle tim */
+void
+callout_stop(struct callout_mgr *cm, struct callout *tim)
+{
+       uint8_t flags;
+
+       callout_dprintf_P("cm=%p tim=%p\r\n", cm, tim);
+
+       IRQ_LOCK(flags);
+       if (tim->state != CALLOUT_STATE_STOPPED) {
+
+               /* stats */
+               if (tim->state == CALLOUT_STATE_SCHEDULED)
+                       CALLOUT_STAT_ADD(cm, cur_scheduled, -1);
+               else if (tim->state == CALLOUT_STATE_EXPIRED)
+                       CALLOUT_STAT_ADD(cm, cur_expired, -1);
+               if (tim->state == CALLOUT_STATE_RUNNING)
+                       CALLOUT_STAT_ADD(cm, cur_running, -1);
+               CALLOUT_STAT_ADD(cm, stop, 1);
+
+               /* remove it from list */
+               callout_del(cm, tim);
+               tim->state = CALLOUT_STATE_STOPPED;
+       }
+       IRQ_UNLOCK(flags);
+}
+
+/* must be called periodically, run all timer that expired */
+void callout_manage(struct callout_mgr *cm)
+{
+       struct callout_list expired_list;
+       struct callout_list reschedule_list;
+       struct callout *tim, *tim_next;
+       uint16_t cur_time;
+       uint8_t old_prio;
+       int16_t diff;
+
+       CALLOUT_STAT_ADD(cm, manage, 1);
+       callout_dprintf_P("cm=%p\r\n", cm);
+
+       /* maximize the number of self-recursions */
+       if (cm->nb_recursion >= CALLOUT_MAX_RECURSION) {
+               CALLOUT_STAT_ADD(cm, max_recursion, 1);
+               return;
+       }
+
+       cli();
+       cm->nb_recursion++;
+       LIST_INIT(&expired_list);
+       LIST_INIT(&reschedule_list);
+       cur_time = cm->get_time();
+       old_prio = cm->cur_priority;
+
+       /* move all expired timers in a local list */
+       LIST_FOREACH_SAFE(tim, tim_next, &cm->sched_list, next) {
+
+               diff = cur_time - tim->expire;
+
+               /* check the expiration time (tasks are sorted) */
+               if (diff < 0)
+                       break;
+
+               callout_dprintf_P("cm=%p diff=%d\r\n", cm, diff);
+
+               /* check the priority, if it's too low, inc stats */
+               if (tim->priority <= cm->cur_priority) {
+                       if (diff < 16484)
+                               CALLOUT_STAT_ADD(cm, delayed, 1);
+                       else {
+                               /* reschedule to avoid an overflow */
+                               CALLOUT_STAT_ADD(cm, hard_delayed, 1);
+                               LIST_REMOVE(tim, next);
+                               tim->expire = cur_time;
+                               LIST_INSERT_HEAD(&reschedule_list, tim, next);
+                       }
+                       continue;
+               }
+
+               LIST_REMOVE(tim, next);
+               callout_add_in_expired_list(cm, &expired_list, tim);
+               CALLOUT_STAT_ADD(cm, cur_scheduled, -1);
+               CALLOUT_STAT_ADD(cm, cur_expired, 1);
+       }
+
+       /* reschedule hard_delayed timers, this does not happen usually */
+       while (!LIST_EMPTY(&reschedule_list)) {
+               tim = LIST_FIRST(&reschedule_list);
+               LIST_REMOVE(tim, next);
+               callout_add_in_sched_list(cm, tim);
+       }
+
+       /* for each timer of 'expired' list, execute callback */
+       while (!LIST_EMPTY(&expired_list)) {
+               tim = LIST_FIRST(&expired_list);
+               LIST_REMOVE(tim, next);
+
+               /* execute callback function */
+               CALLOUT_STAT_ADD(cm, cur_expired, -1);
+               CALLOUT_STAT_ADD(cm, cur_running, 1);
+               tim->state = CALLOUT_STATE_RUNNING;
+               cm->cur_priority = tim->priority;
+               sei();
+               tim->f(cm, tim, tim->arg);
+               cli();
+       }
+
+       cm->cur_priority = old_prio;
+       cm->nb_recursion--;
+       sei();
+}
+
+/* set the current priority level */
+uint8_t callout_mgr_set_prio(struct callout_mgr *cm, uint8_t new_prio)
+{
+       uint8_t old_prio;
+
+       old_prio = cm->cur_priority;
+       if (new_prio <= old_prio)
+               return old_prio;
+
+       cm->cur_priority = new_prio;
+       return old_prio;
+}
+
+/* restore the current priority level */
+void callout_mgr_restore_prio(struct callout_mgr *cm, uint8_t old_prio)
+{
+       cm->cur_priority = old_prio;
+}
+
+/* dump statistics about timers */
+void callout_dump_stats(struct callout_mgr *cm)
+{
+#ifdef CALLOUT_STATS
+       printf_P(PSTR("Timer statistics:\r\n"));
+       printf_P(PSTR("  schedule = %"PRIu32"\r\n"), cm->stats.schedule);
+       printf_P(PSTR("  stop = %"PRIu32"\r\n"), cm->stats.stop);
+       printf_P(PSTR("  manage = %"PRIu32"\r\n"), cm->stats.manage);
+       printf_P(PSTR("  max_recursion = %"PRIu32"\r\n"), cm->stats.max_recursion);
+       printf_P(PSTR("  delayed = %"PRIu32"\r\n"), cm->stats.delayed);
+       printf_P(PSTR("  hard_delayed = %"PRIu32"\r\n"), cm->stats.hard_delayed);
+
+       printf_P(PSTR("  cur_scheduled = %u\r\n"), cm->stats.cur_scheduled);
+       printf_P(PSTR("  cur_expired = %u\r\n"), cm->stats.cur_expired);
+       printf_P(PSTR("  cur_running = %u\r\n"), cm->stats.cur_running);
+#else
+       printf_P(PSTR("No timer statistics, CALLOUT_STATS is disabled\r\n"));
+#endif
+}