1 /*
2  * Copyright (c) 2020 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/net/net_timeout.h>
8 #include <zephyr/sys_clock.h>
9 
net_timeout_set(struct net_timeout * timeout,uint32_t lifetime,uint32_t now)10 void net_timeout_set(struct net_timeout *timeout,
11 		     uint32_t lifetime,
12 		     uint32_t now)
13 {
14 	uint64_t expire_timeout;
15 
16 	timeout->timer_start = now;
17 
18 	/* Highly unlikely, but a zero timeout isn't correctly handled by the
19 	 * standard calculation.
20 	 */
21 	if (lifetime == 0U) {
22 		timeout->wrap_counter = 0;
23 		timeout->timer_timeout = 0;
24 		return;
25 	}
26 
27 	expire_timeout = (uint64_t)MSEC_PER_SEC * (uint64_t)lifetime;
28 	timeout->wrap_counter = expire_timeout /
29 		(uint64_t)NET_TIMEOUT_MAX_VALUE;
30 	timeout->timer_timeout = expire_timeout -
31 		(uint64_t)NET_TIMEOUT_MAX_VALUE *
32 		(uint64_t)timeout->wrap_counter;
33 
34 	/* The implementation requires that the fractional timeout be zero
35 	 * only when the timeout has completed, so if the residual is zero
36 	 * copy over one timeout from the wrap.
37 	 */
38 	if (timeout->timer_timeout == 0U) {
39 		timeout->timer_timeout = NET_TIMEOUT_MAX_VALUE;
40 		timeout->wrap_counter -= 1;
41 	}
42 }
43 
net_timeout_deadline(const struct net_timeout * timeout,int64_t now)44 int64_t net_timeout_deadline(const struct net_timeout *timeout,
45 			     int64_t now)
46 {
47 	uint64_t start;
48 	uint64_t deadline;
49 
50 	/* Reconstruct the full-precision start time assuming that the full
51 	 * precision start time is less than 2^32 ticks in the past.
52 	 */
53 	start = (uint64_t)now;
54 	start -= (uint32_t)now - timeout->timer_start;
55 
56 	/* Offset the start time by the full precision remaining delay. */
57 	deadline = start + timeout->timer_timeout;
58 	deadline += (uint64_t)NET_TIMEOUT_MAX_VALUE
59 		* (uint64_t)timeout->wrap_counter;
60 
61 	return (int64_t)deadline;
62 }
63 
net_timeout_remaining(const struct net_timeout * timeout,uint32_t now)64 uint32_t net_timeout_remaining(const struct net_timeout *timeout,
65 			       uint32_t now)
66 {
67 	int64_t ret = timeout->timer_timeout;
68 
69 	ret += timeout->wrap_counter * (uint64_t)NET_TIMEOUT_MAX_VALUE;
70 	ret -= (int64_t)(int32_t)(now - timeout->timer_start);
71 	if (ret <= 0) {
72 		return 0;
73 	}
74 
75 	return (uint32_t)((uint64_t)ret / MSEC_PER_SEC);
76 }
77 
net_timeout_evaluate(struct net_timeout * timeout,uint32_t now)78 uint32_t net_timeout_evaluate(struct net_timeout *timeout,
79 			      uint32_t now)
80 {
81 	uint32_t elapsed;
82 	uint32_t last_delay;
83 	int32_t remains;
84 	bool wraps;
85 
86 	/* Time since last evaluation or set. */
87 	elapsed = now - timeout->timer_start;
88 
89 	/* The delay used the last time this was evaluated. */
90 	wraps = (timeout->wrap_counter > 0U);
91 	last_delay = wraps
92 		? NET_TIMEOUT_MAX_VALUE
93 		: timeout->timer_timeout;
94 
95 	/* Time remaining until completion of the last delay. */
96 	remains = (int32_t)(last_delay - elapsed);
97 
98 	/* If the deadline for the next event hasn't been reached yet just
99 	 * return the remaining time.
100 	 */
101 	if (remains > 0) {
102 		return (uint32_t)remains;
103 	}
104 
105 	/* Deadline has been reached.  If we're not wrapping we've completed
106 	 * the last portion of the full timeout, so return zero to indicate
107 	 * the timeout has completed.
108 	 */
109 	if (!wraps) {
110 		return 0U;
111 	}
112 
113 	/* There's more to do.  We need to update timer_start to correspond to
114 	 * now, then reduce the remaining time by the elapsed time.  We know
115 	 * that's at least NET_TIMEOUT_MAX_VALUE, and can apply the
116 	 * reduction by decrementing the wrap count.
117 	 */
118 	timeout->timer_start = now;
119 	elapsed -= NET_TIMEOUT_MAX_VALUE;
120 	timeout->wrap_counter -= 1;
121 
122 	/* The residual elapsed must reduce timer_timeout, which is capped at
123 	 * NET_TIMEOUT_MAX_VALUE.  But if subtracting would reduce the
124 	 * counter to zero or go negative we need to reduce the the wrap
125 	 * counter once more and add the residual to the counter, so the
126 	 * counter remains positive.
127 	 */
128 	if (timeout->timer_timeout > elapsed) {
129 		timeout->timer_timeout -= elapsed;
130 	} else {
131 		timeout->timer_timeout += NET_TIMEOUT_MAX_VALUE - elapsed;
132 		timeout->wrap_counter -= 1U;
133 	}
134 
135 	return (timeout->wrap_counter == 0U)
136 		? timeout->timer_timeout
137 		: NET_TIMEOUT_MAX_VALUE;
138 }
139