|  | /** @file | 
|  | * @brief Trickle timer library | 
|  | * | 
|  | * This implements Trickle timer as specified in RFC 6206 | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Copyright (c) 2016 Intel Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <logging/log.h> | 
|  | LOG_MODULE_REGISTER(net_trickle, CONFIG_NET_TRICKLE_LOG_LEVEL); | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <sys/util.h> | 
|  | #include <random/rand32.h> | 
|  |  | 
|  | #include <net/net_core.h> | 
|  | #include <net/trickle.h> | 
|  |  | 
|  | #define TICK_MAX ~0 | 
|  |  | 
|  | static void trickle_timeout(struct k_work *work); | 
|  |  | 
|  | static inline bool is_suppression_disabled(struct net_trickle *trickle) | 
|  | { | 
|  | return trickle->k == NET_TRICKLE_INFINITE_REDUNDANCY; | 
|  | } | 
|  |  | 
|  | static inline bool is_tx_allowed(struct net_trickle *trickle) | 
|  | { | 
|  | return is_suppression_disabled(trickle) || | 
|  | (trickle->c < trickle->k); | 
|  | } | 
|  |  | 
|  | static inline uint32_t get_end(struct net_trickle *trickle) | 
|  | { | 
|  | return trickle->Istart + trickle->I; | 
|  | } | 
|  |  | 
|  | /* Returns a random time point t in [I/2 , I) */ | 
|  | static uint32_t get_t(uint32_t I) | 
|  | { | 
|  | I >>= 1; | 
|  |  | 
|  | NET_DBG("[%d, %d)", I, I << 1); | 
|  |  | 
|  | return I + (sys_rand32_get() % I); | 
|  | } | 
|  |  | 
|  | static void double_interval_timeout(struct k_work *work) | 
|  | { | 
|  | struct net_trickle *trickle = CONTAINER_OF(work, | 
|  | struct net_trickle, | 
|  | timer); | 
|  | uint32_t rand_time; | 
|  | uint32_t last_end = get_end(trickle); | 
|  |  | 
|  | trickle->c = 0U; | 
|  |  | 
|  | NET_DBG("now %u (was at %u)", k_uptime_get_32(), last_end); | 
|  |  | 
|  | /* Check if we need to double the interval */ | 
|  | if (trickle->I <= (trickle->Imax_abs >> 1)) { | 
|  | /* Double if I <= Imax/2 */ | 
|  | trickle->I <<= 1; | 
|  |  | 
|  | NET_DBG("double I %u", trickle->I); | 
|  | } else { | 
|  | trickle->I = trickle->Imax_abs; | 
|  |  | 
|  | NET_DBG("I %u", trickle->I); | 
|  | } | 
|  |  | 
|  | /* Random t in [I/2, I) */ | 
|  | rand_time = get_t(trickle->I); | 
|  |  | 
|  | NET_DBG("doubling time %u", rand_time); | 
|  |  | 
|  | trickle->Istart = k_uptime_get_32() + rand_time; | 
|  | k_delayed_work_init(&trickle->timer, trickle_timeout); | 
|  | k_delayed_work_submit(&trickle->timer, K_MSEC(rand_time)); | 
|  |  | 
|  | NET_DBG("last end %u new end %u for %u I %u", | 
|  | last_end, get_end(trickle), trickle->Istart, trickle->I); | 
|  | } | 
|  |  | 
|  | static inline void reschedule(struct net_trickle *trickle) | 
|  | { | 
|  | uint32_t now = k_uptime_get_32(); | 
|  | uint32_t diff = get_end(trickle) - now; | 
|  |  | 
|  | NET_DBG("now %d end in %d", now, diff); | 
|  |  | 
|  | /* Did the clock wrap */ | 
|  | if ((int32_t)diff < 0) { | 
|  | diff = 0U; | 
|  | NET_DBG("Clock wrap"); | 
|  | } | 
|  |  | 
|  | k_delayed_work_init(&trickle->timer, double_interval_timeout); | 
|  | k_delayed_work_submit(&trickle->timer, K_MSEC(diff)); | 
|  | } | 
|  |  | 
|  | static void trickle_timeout(struct k_work *work) | 
|  | { | 
|  | struct net_trickle *trickle = CONTAINER_OF(work, | 
|  | struct net_trickle, | 
|  | timer); | 
|  |  | 
|  | NET_DBG("Trickle timeout at %d", k_uptime_get_32()); | 
|  |  | 
|  | if (trickle->cb) { | 
|  | NET_DBG("TX ok %d c(%u) < k(%u)", | 
|  | is_tx_allowed(trickle), trickle->c, trickle->k); | 
|  |  | 
|  | trickle->cb(trickle, is_tx_allowed(trickle), | 
|  | trickle->user_data); | 
|  | } | 
|  |  | 
|  | if (net_trickle_is_running(trickle)) { | 
|  | reschedule(trickle); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void setup_new_interval(struct net_trickle *trickle) | 
|  | { | 
|  | uint32_t t; | 
|  |  | 
|  | trickle->c = 0U; | 
|  |  | 
|  | t = get_t(trickle->I); | 
|  |  | 
|  | trickle->Istart = k_uptime_get_32(); | 
|  |  | 
|  | k_delayed_work_submit(&trickle->timer, K_MSEC(t)); | 
|  |  | 
|  | NET_DBG("new interval at %d ends %d t %d I %d", | 
|  | trickle->Istart, | 
|  | get_end(trickle), | 
|  | t, | 
|  | trickle->I); | 
|  | } | 
|  |  | 
|  | #define CHECK_IMIN(Imin) \ | 
|  | ((Imin < 2) || (Imin > (TICK_MAX >> 1))) | 
|  |  | 
|  | int net_trickle_create(struct net_trickle *trickle, | 
|  | uint32_t Imin, | 
|  | uint8_t Imax, | 
|  | uint8_t k) | 
|  | { | 
|  | NET_ASSERT(trickle && Imax > 0 && k > 0 && !CHECK_IMIN(Imin)); | 
|  |  | 
|  | (void)memset(trickle, 0, sizeof(struct net_trickle)); | 
|  |  | 
|  | trickle->Imin = Imin; | 
|  | trickle->Imax = Imax; | 
|  | trickle->Imax_abs = Imin << Imax; | 
|  | trickle->k = k; | 
|  |  | 
|  | NET_ASSERT(trickle->Imax_abs); | 
|  |  | 
|  | NET_DBG("Imin %d Imax %u k %u Imax_abs %d", | 
|  | trickle->Imin, trickle->Imax, trickle->k, | 
|  | trickle->Imax_abs); | 
|  |  | 
|  | k_delayed_work_init(&trickle->timer, trickle_timeout); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int net_trickle_start(struct net_trickle *trickle, | 
|  | net_trickle_cb_t cb, | 
|  | void *user_data) | 
|  | { | 
|  | NET_ASSERT(trickle && cb); | 
|  |  | 
|  | trickle->cb = cb; | 
|  | trickle->user_data = user_data; | 
|  |  | 
|  | /* Random I in [Imin , Imax] */ | 
|  | trickle->I = trickle->Imin + | 
|  | (sys_rand32_get() % (trickle->Imax_abs - trickle->Imin + 1)); | 
|  |  | 
|  | setup_new_interval(trickle); | 
|  |  | 
|  | NET_DBG("start %d end %d in [%d , %d)", | 
|  | trickle->Istart, get_end(trickle), | 
|  | trickle->I >> 1, trickle->I); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int net_trickle_stop(struct net_trickle *trickle) | 
|  | { | 
|  | NET_ASSERT(trickle); | 
|  |  | 
|  | k_delayed_work_cancel(&trickle->timer); | 
|  |  | 
|  | trickle->I = 0U; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void net_trickle_consistency(struct net_trickle *trickle) | 
|  | { | 
|  | NET_ASSERT(trickle); | 
|  |  | 
|  | if (trickle->c < 0xFF) { | 
|  | trickle->c++; | 
|  | } | 
|  |  | 
|  | NET_DBG("consistency %u", trickle->c); | 
|  | } | 
|  |  | 
|  | void net_trickle_inconsistency(struct net_trickle *trickle) | 
|  | { | 
|  | NET_ASSERT(trickle); | 
|  |  | 
|  | if (trickle->I != trickle->Imin) { | 
|  | NET_DBG("inconsistency"); | 
|  |  | 
|  | trickle->I = trickle->Imin; | 
|  | } | 
|  |  | 
|  | setup_new_interval(trickle); | 
|  | } |