From 79e5586d0c0b198b94484d15c843d9732ab3e7ed Mon Sep 17 00:00:00 2001
From: zhuoshuguo <shuguo.zhuo@inria.fr>
Date: Thu, 13 Oct 2016 18:44:28 +0200
Subject: [PATCH] gnrc_mac: add timeout module.

Co-Authored-By: zhuoshuguo <zhuosgzju@gmail.com>
---
 Makefile.dep                               |   1 +
 sys/include/net/gnrc/mac/timeout.h         | 147 +++++++++++++++++++++
 sys/net/gnrc/link_layer/gnrc_mac/timeout.c | 147 +++++++++++++++++++++
 tests/gnrc_mac_timeout/Makefile            |  10 ++
 tests/gnrc_mac_timeout/README.md           |  14 ++
 tests/gnrc_mac_timeout/main.c              | 119 +++++++++++++++++
 6 files changed, 438 insertions(+)
 create mode 100644 sys/include/net/gnrc/mac/timeout.h
 create mode 100644 sys/net/gnrc/link_layer/gnrc_mac/timeout.c
 create mode 100644 tests/gnrc_mac_timeout/Makefile
 create mode 100644 tests/gnrc_mac_timeout/README.md
 create mode 100644 tests/gnrc_mac_timeout/main.c

diff --git a/Makefile.dep b/Makefile.dep
index d01275961d..5aceacf8b7 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -29,6 +29,7 @@ endif
 ifneq (,$(filter gnrc_mac,$(USEMODULE)))
   USEMODULE += gnrc_priority_pktqueue
   USEMODULE += csma_sender
+  USEMODULE += evtimer
 endif
 
 ifneq (,$(filter gnrc_gomach,$(USEMODULE)))
diff --git a/sys/include/net/gnrc/mac/timeout.h b/sys/include/net/gnrc/mac/timeout.h
new file mode 100644
index 0000000000..9a84940bfb
--- /dev/null
+++ b/sys/include/net/gnrc/mac/timeout.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 Daniel Krebs
+ *               2017 INRIA
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     net_gnrc_mac
+ * @{
+ *
+ * @file
+ * @brief       Timeout APIs used by GNRC_MAC
+ *
+ * @author      Daniel Krebs <github@daniel-krebs.net>
+ * @author      Shuguo Zhuo  <shuguo.zhuo@inria.fr>
+ */
+
+#ifndef NET_GNRC_MAC_TIMEOUT_H
+#define NET_GNRC_MAC_TIMEOUT_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "evtimer_msg.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief definition for GNRC_MAC timeout event type
+ */
+#define GNRC_MAC_EVENT_TIMEOUT_TYPE        (0x4400)
+
+/**
+ * @brief Definitions of GNRC_MAC timeout types.
+ *
+ * This structure can be extended to contain more needed
+ * timeout types of different MAC protocols. Please guard
+ * them by appropriate \#ifdef directives when applicable.
+ */
+typedef enum {
+    GNRC_MAC_TIMEOUT_DISABLED = 0,    /**< Timeout is disabled, not in used. */
+} gnrc_mac_timeout_type_t;
+
+/**
+ * @brief Structure of the GNRC_MAC timeout event.
+ */
+typedef struct {
+    evtimer_msg_event_t msg_event;      /**< The timeout message event. */
+    gnrc_mac_timeout_type_t type;       /**< GNRC_MAC timeout type. */
+} gnrc_mac_timeout_event_t;
+
+/**
+ * @brief Structure holding the GNRC_MAC timeouts.
+ */
+typedef struct {
+    evtimer_t evtimer;                                          /**< evtimer entity which
+                                                                     stores the timeout list. */
+    gnrc_mac_timeout_event_t *timeouts;                         /**< The gnrc_mac timeout
+                                                                     unites. */
+    uint8_t timeout_num;                                        /**< Timeout number. */
+} gnrc_mac_timeout_t;
+
+/**
+ * @brief   Initialize the MAC timeout module of gnrc_mac before using,
+ *          it also sets the timeout callback function.
+ *
+ * @param[in,out] mac_timeout  gnrc_mac timeout management unit
+ * @param[in] timeouts         gnrc_mac timeouts
+ * @param[in] num              timeout number
+ */
+void gnrc_mac_init_timeouts(gnrc_mac_timeout_t *mac_timeout,
+                            gnrc_mac_timeout_event_t timeouts[],
+                            uint8_t num);
+
+/**
+ * @brief   Set a MAC timeout of @p type.
+ *
+ * @param[in,out] mac_timeout  gnrc_mac timeout management unit
+ * @param[in]     type         the MAC timeout type
+ * @param[in]     offset       the timeout offset
+ * @param[in]     pid          the targeted thread pid
+ */
+void gnrc_mac_set_timeout(gnrc_mac_timeout_t *mac_timeout, gnrc_mac_timeout_type_t type,
+                          uint32_t offset, kernel_pid_t pid);
+
+/**
+ * @brief   Find a MAC timeout of @p type.
+ *
+ * @param[in] mac_timeout  gnrc_mac timeout management unit
+ * @param[in] type         the MAC timeout type
+ *
+ * @return                 Return index >= 0 if found timeout, -ENONENT if not found
+ */
+int gnrc_mac_find_timeout(gnrc_mac_timeout_t *mac_timeout, gnrc_mac_timeout_type_t type);
+
+/**
+ * @brief   Clear a MAC timeout of @p type.
+ *
+ * @param[in,out] mac_timeout  gnrc_mac timeout management unit
+ * @param[in]     type         the MAC timeout type
+ */
+void gnrc_mac_clear_timeout(gnrc_mac_timeout_t *mac_timeout, gnrc_mac_timeout_type_t type);
+
+/**
+ * @brief   Check whether a MAC timeout of @p type is running or not.
+ *
+ * @param[in] mac_timeout  gnrc_mac timeout management unit
+ * @param[in] type         the MAC timeout type
+ *
+ * @return                 true, if the time of @p type is running
+ * @return                 false, if the time of @p type is not running, or not exist
+ */
+static inline bool gnrc_mac_timeout_is_running(gnrc_mac_timeout_t *mac_timeout,
+                                               gnrc_mac_timeout_type_t type)
+{
+    assert(mac_timeout);
+    return (gnrc_mac_find_timeout(mac_timeout, type) >= 0);
+}
+
+/**
+ * @brief   Check whether a MAC timeout of @p type has expired or not.
+ *
+ * @param[in,out] mac_timeout  gnrc_mac timeout management unit
+ * @param[in]     type         the MAC timeout type
+ *
+ * @return                     true, if the MAC time of @p type is expired
+ * @return                     false, if the MAC time of @p type is not expired, or not exist
+ */
+bool gnrc_mac_timeout_is_expired(gnrc_mac_timeout_t *mac_timeout, gnrc_mac_timeout_type_t type);
+
+/**
+ * @brief   Reset all the MAC timeouts.
+ *
+ * @param[in,out] mac_timeout  gnrc_mac timeout management unit
+ */
+void gnrc_mac_reset_timeouts(gnrc_mac_timeout_t *mac_timeout);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NET_GNRC_MAC_TIMEOUT_H */
diff --git a/sys/net/gnrc/link_layer/gnrc_mac/timeout.c b/sys/net/gnrc/link_layer/gnrc_mac/timeout.c
new file mode 100644
index 0000000000..1b3f8eb5db
--- /dev/null
+++ b/sys/net/gnrc/link_layer/gnrc_mac/timeout.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 Daniel Krebs
+ *               2017 INRIA
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     net_gnrc_mac
+ * @{
+ *
+ * @file
+ * @brief       Implementation of timeout module of GNRC_MAC
+ *
+ * @author      Daniel Krebs <github@daniel-krebs.net>
+ * @author      Shuguo Zhuo  <shuguo.zhuo@inria.fr>
+ * @}
+ */
+
+#include "net/gnrc.h"
+#include "net/gnrc/mac/timeout.h"
+
+#define ENABLE_DEBUG    (0)
+#include "debug.h"
+
+void gnrc_mac_init_timeouts(gnrc_mac_timeout_t *mac_timeout,
+                            gnrc_mac_timeout_event_t timeouts[],
+                            uint8_t num)
+{
+    assert(mac_timeout);
+    assert(timeouts);
+    assert(num);
+
+    mac_timeout->timeouts = timeouts;
+    mac_timeout->timeout_num = num;
+
+    for (int i = 0; i < mac_timeout->timeout_num; i++) {
+        mac_timeout->timeouts[i].msg_event.event.next = NULL;
+        mac_timeout->timeouts[i].type = GNRC_MAC_TIMEOUT_DISABLED;
+    }
+
+    evtimer_init_msg(&mac_timeout->evtimer);
+}
+
+
+int gnrc_mac_find_timeout(gnrc_mac_timeout_t *mac_timeout, gnrc_mac_timeout_type_t type)
+{
+    assert(mac_timeout);
+    assert(mac_timeout->timeout_num);
+
+    for (unsigned i = 0; i < mac_timeout->timeout_num; i++) {
+        if (mac_timeout->timeouts[i].type == type) {
+            return i;
+        }
+    }
+    return -ENOENT;
+}
+
+gnrc_mac_timeout_event_t *_gnrc_mac_acquire_timeout(gnrc_mac_timeout_t *mac_timeout,
+                                                    gnrc_mac_timeout_type_t type)
+{
+    assert(mac_timeout);
+    assert(mac_timeout->timeout_num);
+
+    if (gnrc_mac_timeout_is_running(mac_timeout, type)) {
+        return NULL;
+    }
+
+    for (unsigned i = 0; i < mac_timeout->timeout_num; i++) {
+        if (mac_timeout->timeouts[i].type == GNRC_MAC_TIMEOUT_DISABLED) {
+            mac_timeout->timeouts[i].type = type;
+            return &mac_timeout->timeouts[i];
+        }
+    }
+    return NULL;
+}
+
+void gnrc_mac_set_timeout(gnrc_mac_timeout_t *mac_timeout, gnrc_mac_timeout_type_t type,
+                          uint32_t offset, kernel_pid_t pid)
+{
+    assert(mac_timeout);
+
+    gnrc_mac_timeout_event_t *timeout_event;
+    if ((timeout_event = _gnrc_mac_acquire_timeout(mac_timeout, type))) {
+        DEBUG("[gnrc_mac] Set timeout type-%d in %" PRIu32 " us\n",
+              type, offset);
+        timeout_event->msg_event.event.offset = offset;
+        timeout_event->msg_event.msg.type = GNRC_MAC_EVENT_TIMEOUT_TYPE;
+        timeout_event->msg_event.msg.content.ptr = (void *) timeout_event;
+        timeout_event->msg_event.msg.sender_pid = pid;
+        evtimer_add(&mac_timeout->evtimer, &timeout_event->msg_event.event);
+    }
+    else {
+        DEBUG("[gnrc_mac] Cannot set timeout type-%d, too many concurrent timeouts\n",
+              type);
+    }
+}
+
+void gnrc_mac_clear_timeout(gnrc_mac_timeout_t *mac_timeout, gnrc_mac_timeout_type_t type)
+{
+    assert(mac_timeout);
+
+    int index = gnrc_mac_find_timeout(mac_timeout, type);
+    if (index >= 0) {
+        mac_timeout->timeouts[index].type = GNRC_MAC_TIMEOUT_DISABLED;
+        evtimer_del(&mac_timeout->evtimer,
+                    &mac_timeout->timeouts[index].msg_event.event);
+    }
+}
+
+bool gnrc_mac_timeout_is_expired(gnrc_mac_timeout_t *mac_timeout, gnrc_mac_timeout_type_t type)
+{
+    assert(mac_timeout);
+
+    int index = gnrc_mac_find_timeout(mac_timeout, type);
+    if (index >= 0) {
+        evtimer_event_t *list;
+        list = (evtimer_event_t *)&mac_timeout->evtimer.events;
+        while (list->next) {
+            if (list->next == &mac_timeout->timeouts[index].msg_event.event) {
+                return false;
+            }
+            list = list->next;
+        }
+
+        /* if we reach here, timeout is expired */
+        mac_timeout->timeouts[index].type = GNRC_MAC_TIMEOUT_DISABLED;
+        return true;
+    }
+    return false;
+}
+
+void gnrc_mac_reset_timeouts(gnrc_mac_timeout_t *mac_timeout)
+{
+    assert(mac_timeout);
+    assert(mac_timeout->timeout_num);
+
+    for (unsigned i = 0; i < mac_timeout->timeout_num; i++) {
+        if (mac_timeout->timeouts[i].type != GNRC_MAC_TIMEOUT_DISABLED) {
+            mac_timeout->timeouts[i].type = GNRC_MAC_TIMEOUT_DISABLED;
+            evtimer_del(&mac_timeout->evtimer,
+                        &mac_timeout->timeouts[i].msg_event.event);
+        }
+    }
+}
diff --git a/tests/gnrc_mac_timeout/Makefile b/tests/gnrc_mac_timeout/Makefile
new file mode 100644
index 0000000000..f6d15881f4
--- /dev/null
+++ b/tests/gnrc_mac_timeout/Makefile
@@ -0,0 +1,10 @@
+APPLICATION = gnrc_mac_timeout
+include ../Makefile.tests_common
+
+BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-mega2560 arduino-uno \
+                             chronos nucleo-f030r8 nucleo-f031k6 nucleo-f042k6 \
+                             nucleo-l031k6 nucleo-l053r8 stm32f0discovery waspmote-pro
+
+USEMODULE += gnrc_mac
+
+include $(RIOTBASE)/Makefile.include
diff --git a/tests/gnrc_mac_timeout/README.md b/tests/gnrc_mac_timeout/README.md
new file mode 100644
index 0000000000..aa526aa60c
--- /dev/null
+++ b/tests/gnrc_mac_timeout/README.md
@@ -0,0 +1,14 @@
+Expected result
+===============
+
+This is a test application for using the timeout module of gnrc_mac for setting timeouts.
+
+When everything works as expected, you should see timeouts expired at the time they are set
+to be expired. Also, you should see the status of timeouts are corresponding to their real
+status, i.e., the system should state that a timeout is "running" when it is set and
+awaiting to be expired, and state "not running" when a timeout has expired and not set
+again.
+
+Background
+==========
+Test for verifying the functionalities of the timeout module of gnrc_mac.
diff --git a/tests/gnrc_mac_timeout/main.c b/tests/gnrc_mac_timeout/main.c
new file mode 100644
index 0000000000..1dcad248a2
--- /dev/null
+++ b/tests/gnrc_mac_timeout/main.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 INRIA
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup tests
+ * @{
+ *
+ * @file
+ * @brief    gnrc_mac timeout test application
+ *
+ * @author   Shuguo Zhuo <shuguo.zhuo@inria.fr>
+ *
+ * @}
+ */
+
+#include <stdio.h>
+
+#include "net/gnrc/mac/timeout.h"
+#include "thread.h"
+#include "msg.h"
+#include "xtimer.h"
+
+#define TIMEOUT_COUNT    3
+#define TIMEOUT_1_DURATION    1000
+#define TIMEOUT_2_DURATION    2538
+#define TIMEOUT_3_DURATION    3471
+static gnrc_mac_timeout_t mac_timeout;
+static gnrc_mac_timeout_event_t test_timeouts[TIMEOUT_COUNT];
+static gnrc_mac_timeout_type_t timeout_1;
+static gnrc_mac_timeout_type_t timeout_2;
+static gnrc_mac_timeout_type_t timeout_3;
+static uint32_t start_time;
+
+static char worker_stack[THREAD_STACKSIZE_MAIN];
+
+
+/* This thread will print the drift to stdout once per second */
+void *worker_thread(void *arg)
+{
+    int count = 1;
+
+    (void) arg;
+
+    while (1) {
+        msg_t m;
+        uint32_t now;
+
+        msg_receive(&m);
+        now = xtimer_now_usec() / US_PER_MS;
+
+        if (gnrc_mac_timeout_is_expired(&mac_timeout, timeout_1)) {
+            printf("At %6" PRIu32 " ms received msg %i: timeout_1 (set at %" PRIu32 " ms) expired, "
+                   "supposed to be %" PRIu32 " ms!\n", now, count++, start_time, (TIMEOUT_1_DURATION + start_time));
+        }
+
+        if (gnrc_mac_timeout_is_expired(&mac_timeout, timeout_2)) {
+            printf("At %6" PRIu32 " ms received msg %i: timeout_2 (set at %" PRIu32 " ms) expired, "
+                   "supposed to be %" PRIu32 " ms!\n", now, count++, start_time, (TIMEOUT_2_DURATION + start_time));
+        }
+
+        if (gnrc_mac_timeout_is_expired(&mac_timeout, timeout_3)) {
+            printf("At %6" PRIu32 " ms received msg %i: timeout_3 (set at %" PRIu32 " ms) expired, "
+                   "supposed to be %" PRIu32 " ms!\n", now, count++, start_time, (TIMEOUT_3_DURATION + start_time));
+        }
+
+        if (gnrc_mac_timeout_is_running(&mac_timeout, timeout_1)) {
+            printf("At %6" PRIu32 " ms: timeout_1 is running.\n", now);
+        }
+        else {
+            printf("At %6" PRIu32 " ms: timeout_1 is not running.\n", now);
+        }
+
+        if (gnrc_mac_timeout_is_running(&mac_timeout, timeout_2)) {
+            printf("At %6" PRIu32 " ms: timeout_2 is running.\n", now);
+        }
+        else {
+            printf("At %6" PRIu32 " ms: timeout_2 is not running.\n", now);
+        }
+
+        if (gnrc_mac_timeout_is_running(&mac_timeout, timeout_3)) {
+            printf("At %6" PRIu32 " ms: timeout_3 is running.\n", now);
+        }
+        else {
+            printf("At %6" PRIu32 " ms: timeout_3 is not running.\n", now);
+        }
+    }
+}
+
+int main(void)
+{
+    /* create worker thread */
+    kernel_pid_t pid = thread_create(worker_stack, sizeof(worker_stack),
+                                     THREAD_PRIORITY_MAIN - 1,
+                                     THREAD_CREATE_STACKTEST,
+                                     worker_thread, NULL, "worker");
+
+    timeout_1 = -1;
+    timeout_2 = -2;
+    timeout_3 = -3;
+
+    start_time = xtimer_now_usec() / US_PER_MS;
+    gnrc_mac_init_timeouts(&mac_timeout, test_timeouts, TIMEOUT_COUNT);
+    gnrc_mac_set_timeout(&mac_timeout, timeout_1, TIMEOUT_1_DURATION, pid);
+    gnrc_mac_set_timeout(&mac_timeout, timeout_2, TIMEOUT_2_DURATION, pid);
+    gnrc_mac_set_timeout(&mac_timeout, timeout_3, TIMEOUT_3_DURATION, pid);
+    printf("Testing gnrc_mac timeout module (start time = %" PRIu32 " ms)\n", start_time);
+    printf("Set timeout_1, should be expired at %" PRIu32 " ms)\n", TIMEOUT_1_DURATION + start_time);
+    printf("Set timeout_2, should be expired at %" PRIu32 " ms)\n", TIMEOUT_2_DURATION + start_time);
+    printf("Set timeout_3, should be expired at %" PRIu32 " ms)\n", TIMEOUT_3_DURATION + start_time);
+
+
+    puts("Are the reception times of all 3 msgs close to the supposed values?\n");
+    puts("If yes, the tests were successful");
+}
-- 
GitLab