1 // Copyright 2021 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_chrono/system_timer.h"
16
17 #include <algorithm>
18 #include <mutex>
19
20 #include "FreeRTOS.h"
21 #include "pw_assert/check.h"
22 #include "pw_chrono_freertos/system_clock_constants.h"
23 #include "task.h"
24 #include "timers.h"
25
26 namespace pw::chrono {
27 namespace {
28
29 using State = backend::NativeSystemTimer::State;
30
31 // Instead of adding targeted locks to each instance, simply use the global
32 // scheduler critical section lock.
33 class SchedulerLock {
34 public:
lock()35 static void lock() { vTaskSuspendAll(); }
unlock()36 static void unlock() { xTaskResumeAll(); }
37 };
38 SchedulerLock global_timer_lock;
39
HandleTimerCallback(TimerHandle_t timer_handle)40 void HandleTimerCallback(TimerHandle_t timer_handle) {
41 // The FreeRTOS timer service is always handled by a thread, ergo to ensure
42 // this API is threadsafe we simply disable task switching.
43
44 // Because the timer control block, AKA what the timer handle points at, is
45 // the first member of the NativeSystemTimer struct we play a trick to
46 // cheaply get the native handle reference.
47 backend::NativeSystemTimer* native_type =
48 reinterpret_cast<backend::NativeSystemTimer*>(timer_handle);
49
50 // We're only using this control structure so that we can be sure we never
51 // run the callbacks while the lock_guard is in scope. We need to be sure
52 // that all paths through the loop that require a manual unlock() break out of
53 // the loop. Currently, that is only for callbacks, so we don't require any
54 // control structures, but if more functionality is required, we may need a
55 // switch statement following the loop to dispatch to the correct behavior.
56 do {
57 std::lock_guard lock(global_timer_lock);
58
59 if (native_type->state == State::kCancelled) {
60 // Do nothing, we were invoked while the stop command was in the queue.
61 //
62 // Note that xTimerIsTimerActive cannot be used here. If a timer is
63 // started after it expired, it is executed immediately from the command
64 // queue. Older versions of FreeRTOS failed to mark expired timers as
65 // inactive before executing them in this way. So, if the timer is
66 // executed in the command queue before the stop command is processed,
67 // this callback will be invoked while xTimerIsTimerActive returns true.
68 // This was fixed in https://github.com/FreeRTOS/FreeRTOS-Kernel/pull/305.
69 return;
70 }
71
72 const SystemClock::duration time_until_deadline =
73 native_type->expiry_deadline - SystemClock::now();
74 if (time_until_deadline <= SystemClock::duration::zero()) {
75 // We have met the deadline, cancel the current state and execute the
76 // user's callback. Note we cannot update the state later as the user's
77 // callback may alter the desired state through the Invoke*() API.
78 native_type->state = State::kCancelled;
79
80 // Release the scheduler lock once we won't modify native_state any
81 // further.
82 break;
83 }
84
85 // We haven't met the deadline yet, reschedule as far out as possible.
86 // Note that this must be > SystemClock::duration::zero() based on the
87 // conditional above.
88 const SystemClock::duration period =
89 std::min(pw::chrono::freertos::kMaxTimeout, time_until_deadline);
90 PW_CHECK_UINT_EQ(
91 xTimerChangePeriod(reinterpret_cast<TimerHandle_t>(&native_type->tcb),
92 static_cast<TickType_t>(period.count()),
93 0),
94 pdPASS,
95 "Timer command queue overflowed");
96 PW_CHECK_UINT_EQ(
97 xTimerStart(reinterpret_cast<TimerHandle_t>(&native_type->tcb), 0),
98 pdPASS,
99 "Timer command queue overflowed");
100 return;
101 } while (false);
102
103 // Invoke user_callback once the lock is out of scope.
104 native_type->user_callback(native_type->expiry_deadline);
105 }
106
107 // FreeRTOS requires a timer to have a non-zero period.
108 constexpr SystemClock::duration kMinTimerPeriod = SystemClock::duration(1);
109 constexpr TickType_t kInvalidPeriod = kMinTimerPeriod.count();
110 constexpr UBaseType_t kOneShotMode = pdFALSE; // Do not use auto reload.
111
112 } // namespace
113
114 #if configUSE_TIMERS != 1
115 #error \
116 "Backend requires your FreeRTOS configuration to have configUSE_TIMERS == 1"
117 #endif
118
119 #if configSUPPORT_STATIC_ALLOCATION != 1
120 #error \
121 "Backend requires your FreeRTOS configuration to have configSUPPORT_STATIC_ALLOCATION == 1"
122 #endif
123
SystemTimer(ExpiryCallback && callback)124 SystemTimer::SystemTimer(ExpiryCallback&& callback)
125 : native_type_{.tcb{},
126 .state = State::kCancelled,
127 .expiry_deadline = SystemClock::time_point(),
128 .user_callback = std::move(callback)} {
129 // Note that timer "creation" is not enqueued through the command queue and
130 // is ergo safe to do before the scheduler is running.
131 const TimerHandle_t handle =
132 xTimerCreateStatic("", // "pw::chrono::SystemTimer",
133 kInvalidPeriod,
134 kOneShotMode,
135 this,
136 HandleTimerCallback,
137 &native_type_.tcb);
138
139 // This should never fail since the pointer provided was not null and it
140 // should return a pointer to the StaticTimer_t.
141 PW_DCHECK_PTR_EQ(handle, reinterpret_cast<TimerHandle_t>(&native_type_.tcb));
142 }
143
~SystemTimer()144 SystemTimer::~SystemTimer() {
145 Cancel();
146
147 // WARNING: This enqueues the request to delete the timer through a queue, it
148 // does not synchronously delete and disable the timer here! This means that
149 // if the timer is about to expire and the timer service thread is a lower
150 // priority that it may use the native_type_ after it is free'd.
151 PW_CHECK_UINT_EQ(
152 pdPASS,
153 xTimerDelete(reinterpret_cast<TimerHandle_t>(&native_type_.tcb), 0),
154 "Timer command queue overflowed");
155
156 // In case the timer is still active as warned above, busy yield loop until it
157 // has been removed. The active flag is cleared in the StaticTimer_t when the
158 // delete command is processed.
159 //
160 // Note that this is safe before the scheduler has been started because the
161 // timer cannot have been added to the queue yet and ergo it shouldn't attempt
162 // to yield.
163 while (
164 xTimerIsTimerActive(reinterpret_cast<TimerHandle_t>(&native_type_.tcb))) {
165 taskYIELD();
166 }
167 }
168
InvokeAt(SystemClock::time_point timestamp)169 void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
170 // The FreeRTOS timer service is always handled by a thread, ergo to ensure
171 // this API is threadsafe we simply disable task switching.
172 std::lock_guard lock(global_timer_lock);
173
174 // We don't want to call Cancel which would enqueue a stop command instead of
175 // synchronously updating the state. Instead we update the expiry deadline
176 // and update the state where the one shot only fires if the expiry deadline
177 // is exceeded and the callback is executed once.
178 native_type_.expiry_deadline = timestamp;
179
180 // Schedule the timer as far out as possible. Note that the timeout might be
181 // clamped and it may be rescheduled internally.
182 const SystemClock::duration time_until_deadline =
183 timestamp - SystemClock::now();
184 const SystemClock::duration period = std::clamp(
185 kMinTimerPeriod, time_until_deadline, pw::chrono::freertos::kMaxTimeout);
186
187 PW_CHECK_UINT_EQ(
188 xTimerChangePeriod(reinterpret_cast<TimerHandle_t>(&native_type_.tcb),
189 static_cast<TickType_t>(period.count()),
190 0),
191 pdPASS,
192 "Timer command queue overflowed");
193
194 // Don't enqueue the start multiple times, schedule it once and let the
195 // callback cancel.
196 if (native_type_.state == State::kCancelled) {
197 PW_CHECK_UINT_EQ(
198 xTimerStart(reinterpret_cast<TimerHandle_t>(&native_type_.tcb), 0),
199 pdPASS,
200 "Timer command queue overflowed");
201 native_type_.state = State::kScheduled;
202 }
203 }
204
Cancel()205 void SystemTimer::Cancel() {
206 // The FreeRTOS timer service is always handled by a thread, ergo to ensure
207 // this API is threadsafe we simply disable task switching.
208 std::lock_guard lock(global_timer_lock);
209
210 // The stop command may not be executed until later in case we're in a
211 // critical section. For this reason update the internal state in case the
212 // callback gets invoked.
213 //
214 // Note that xTimerIsTimerActive cannot be used here as the timer service
215 // daemon may be a lower priority and ergo may still execute the callback
216 // after Cancel() was invoked. This is because a single expired timer may be
217 // processed before the entire command queue is emptied.
218 native_type_.state = State::kCancelled;
219
220 PW_CHECK_UINT_EQ(
221 xTimerStop(reinterpret_cast<TimerHandle_t>(&native_type_.tcb), 0),
222 pdPASS,
223 "Timer command queue overflowed");
224 }
225
226 } // namespace pw::chrono
227