231 lines
4.7 KiB
C
231 lines
4.7 KiB
C
/** @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);
|
|
}
|