zephyr/subsys/net/ip/net_timeout.c

139 lines
3.7 KiB
C

/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <net/net_timeout.h>
#include <sys_clock.h>
void net_timeout_set(struct net_timeout *timeout,
uint32_t lifetime,
uint32_t now)
{
uint64_t expire_timeout;
timeout->timer_start = now;
/* Highly unlikely, but a zero timeout isn't correctly handled by the
* standard calculation.
*/
if (lifetime == 0U) {
timeout->wrap_counter = 0;
timeout->timer_timeout = 0;
return;
}
expire_timeout = (uint64_t)MSEC_PER_SEC * (uint64_t)lifetime;
timeout->wrap_counter = expire_timeout /
(uint64_t)NET_TIMEOUT_MAX_VALUE;
timeout->timer_timeout = expire_timeout -
(uint64_t)NET_TIMEOUT_MAX_VALUE *
(uint64_t)timeout->wrap_counter;
/* The implementation requires that the fractional timeout be zero
* only when the timeout has completed, so if the residual is zero
* copy over one timeout from the wrap.
*/
if (timeout->timer_timeout == 0U) {
timeout->timer_timeout = NET_TIMEOUT_MAX_VALUE;
timeout->wrap_counter -= 1;
}
}
int64_t net_timeout_deadline(const struct net_timeout *timeout,
int64_t now)
{
uint64_t start;
uint64_t deadline;
/* Reconstruct the full-precision start time assuming that the full
* precision start time is less than 2^32 ticks in the past.
*/
start = (uint64_t)now;
start -= (uint32_t)now - timeout->timer_start;
/* Offset the start time by the full precision remaining delay. */
deadline = start + timeout->timer_timeout;
deadline += (uint64_t)NET_TIMEOUT_MAX_VALUE
* (uint64_t)timeout->wrap_counter;
return (int64_t)deadline;
}
uint32_t net_timeout_remaining(const struct net_timeout *timeout,
uint32_t now)
{
int64_t ret = timeout->timer_timeout;
ret += timeout->wrap_counter * (uint64_t)NET_TIMEOUT_MAX_VALUE;
ret -= (int64_t)(int32_t)(now - timeout->timer_start);
if (ret <= 0) {
return 0;
}
return (uint32_t)((uint64_t)ret / MSEC_PER_SEC);
}
uint32_t net_timeout_evaluate(struct net_timeout *timeout,
uint32_t now)
{
uint32_t elapsed;
uint32_t last_delay;
int32_t remains;
bool wraps;
/* Time since last evaluation or set. */
elapsed = now - timeout->timer_start;
/* The delay used the last time this was evaluated. */
wraps = (timeout->wrap_counter > 0U);
last_delay = wraps
? NET_TIMEOUT_MAX_VALUE
: timeout->timer_timeout;
/* Time remaining until completion of the last delay. */
remains = (int32_t)(last_delay - elapsed);
/* If the deadline for the next event hasn't been reached yet just
* return the remaining time.
*/
if (remains > 0) {
return (uint32_t)remains;
}
/* Deadline has been reached. If we're not wrapping we've completed
* the last portion of the full timeout, so return zero to indicate
* the timeout has completed.
*/
if (!wraps) {
return 0U;
}
/* There's more to do. We need to update timer_start to correspond to
* now, then reduce the remaining time by the elapsed time. We know
* that's at least NET_TIMEOUT_MAX_VALUE, and can apply the
* reduction by decrementing the wrap count.
*/
timeout->timer_start = now;
elapsed -= NET_TIMEOUT_MAX_VALUE;
timeout->wrap_counter -= 1;
/* The residual elapsed must reduce timer_timeout, which is capped at
* NET_TIMEOUT_MAX_VALUE. But if subtracting would reduce the
* counter to zero or go negative we need to reduce the the wrap
* counter once more and add the residual to the counter, so the
* counter remains positive.
*/
if (timeout->timer_timeout > elapsed) {
timeout->timer_timeout -= elapsed;
} else {
timeout->timer_timeout += NET_TIMEOUT_MAX_VALUE - elapsed;
timeout->wrap_counter -= 1U;
}
return (timeout->wrap_counter == 0U)
? timeout->timer_timeout
: NET_TIMEOUT_MAX_VALUE;
}