Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * 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;
}