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