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 std::unique_lock lock(global_timer_lock);
44
45 // Because the timer control block, AKA what the timer handle points at, is
46 // the first member of the NativeSystemTimer struct we play a trick to cheaply
47 // get the native handle reference.
48 backend::NativeSystemTimer& native_type =
49 *reinterpret_cast<backend::NativeSystemTimer*>(timer_handle);
50
51 if (native_type.state == State::kCancelled) {
52 // Do nothing, we were invoked while the stop command was in the queue.
53 //
54 // Note that xTimerIsTimerActive cannot be used here. If a timer is started
55 // after it expired, it is executed immediately from the command queue.
56 // Older versions of FreeRTOS failed to mark expired timers as inactive
57 // before executing them in this way. So, if the timer is executed in the
58 // command queue before the stop command is processed, this callback will be
59 // invoked while xTimerIsTimerActive returns true. This was fixed in
60 // https://github.com/FreeRTOS/FreeRTOS-Kernel/pull/305.
61 return;
62 }
63
64 const SystemClock::duration time_until_deadline =
65 native_type.expiry_deadline - SystemClock::now();
66 if (time_until_deadline <= SystemClock::duration::zero()) {
67 // We have met the deadline, cancel the current state and execute the user's
68 // callback. Note we cannot update the state later as the user's callback
69 // may alter the desired state through the Invoke*() API.
70 native_type.state = State::kCancelled;
71
72 // Release the scheduler lock once we won't modify native_state any further.
73 lock.unlock();
74 native_type.user_callback(native_type.expiry_deadline);
75 return;
76 }
77
78 // We haven't met the deadline yet, reschedule as far out as possible.
79 // Note that this must be > SystemClock::duration::zero() based on the
80 // conditional above.
81 const SystemClock::duration period =
82 std::min(pw::chrono::freertos::kMaxTimeout, time_until_deadline);
83 PW_CHECK_UINT_EQ(
84 xTimerChangePeriod(reinterpret_cast<TimerHandle_t>(&native_type.tcb),
85 static_cast<TickType_t>(period.count()),
86 0),
87 pdPASS,
88 "Timer command queue overflowed");
89 PW_CHECK_UINT_EQ(
90 xTimerStart(reinterpret_cast<TimerHandle_t>(&native_type.tcb), 0),
91 pdPASS,
92 "Timer command queue overflowed");
93 }
94
95 // FreeRTOS requires a timer to have a non-zero period.
96 constexpr SystemClock::duration kMinTimerPeriod = SystemClock::duration(1);
97 constexpr TickType_t kInvalidPeriod = kMinTimerPeriod.count();
98 constexpr UBaseType_t kOneShotMode = pdFALSE; // Do not use auto reload.
99
100 } // namespace
101
102 #if configUSE_TIMERS != 1
103 #error \
104 "Backend requires your FreeRTOS configuration to have configUSE_TIMERS == 1"
105 #endif
106
107 #if configSUPPORT_STATIC_ALLOCATION != 1
108 #error \
109 "Backend requires your FreeRTOS configuration to have configSUPPORT_STATIC_ALLOCATION == 1"
110 #endif
111
SystemTimer(ExpiryCallback && callback)112 SystemTimer::SystemTimer(ExpiryCallback&& callback)
113 : native_type_{.tcb{},
114 .state = State::kCancelled,
115 .expiry_deadline = SystemClock::time_point(),
116 .user_callback = std::move(callback)} {
117 // Note that timer "creation" is not enqueued through the command queue and
118 // is ergo safe to do before the scheduler is running.
119 const TimerHandle_t handle =
120 xTimerCreateStatic("", // "pw::chrono::SystemTimer",
121 kInvalidPeriod,
122 kOneShotMode,
123 this,
124 HandleTimerCallback,
125 &native_type_.tcb);
126
127 // This should never fail since the pointer provided was not null and it
128 // should return a pointer to the StaticTimer_t.
129 PW_DCHECK_PTR_EQ(handle, reinterpret_cast<TimerHandle_t>(&native_type_.tcb));
130 }
131
~SystemTimer()132 SystemTimer::~SystemTimer() {
133 Cancel();
134
135 // WARNING: This enqueues the request to delete the timer through a queue, it
136 // does not synchronously delete and disable the timer here! This means that
137 // if the timer is about to expire and the timer service thread is a lower
138 // priority that it may use the native_type_ after it is free'd.
139 PW_CHECK_UINT_EQ(
140 pdPASS,
141 xTimerDelete(reinterpret_cast<TimerHandle_t>(&native_type_.tcb), 0),
142 "Timer command queue overflowed");
143
144 // In case the timer is still active as warned above, busy yield loop until it
145 // has been removed. The active flag is cleared in the StaticTimer_t when the
146 // delete command is processed.
147 //
148 // Note that this is safe before the scheduler has been started because the
149 // timer cannot have been added to the queue yet and ergo it shouldn't attempt
150 // to yield.
151 while (
152 xTimerIsTimerActive(reinterpret_cast<TimerHandle_t>(&native_type_.tcb))) {
153 taskYIELD();
154 }
155 }
156
InvokeAt(SystemClock::time_point timestamp)157 void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
158 // The FreeRTOS timer service is always handled by a thread, ergo to ensure
159 // this API is threadsafe we simply disable task switching.
160 std::lock_guard lock(global_timer_lock);
161
162 // We don't want to call Cancel which would enqueue a stop command instead of
163 // synchronously updating the state. Instead we update the expiry deadline
164 // and update the state where the one shot only fires if the expiry deadline
165 // is exceeded and the callback is executed once.
166 native_type_.expiry_deadline = timestamp;
167
168 // Schedule the timer as far out as possible. Note that the timeout might be
169 // clamped and it may be rescheduled internally.
170 const SystemClock::duration time_until_deadline =
171 timestamp - SystemClock::now();
172 const SystemClock::duration period = std::clamp(
173 kMinTimerPeriod, time_until_deadline, pw::chrono::freertos::kMaxTimeout);
174
175 PW_CHECK_UINT_EQ(
176 xTimerChangePeriod(reinterpret_cast<TimerHandle_t>(&native_type_.tcb),
177 static_cast<TickType_t>(period.count()),
178 0),
179 pdPASS,
180 "Timer command queue overflowed");
181
182 // Don't enqueue the start multiple times, schedule it once and let the
183 // callback cancel.
184 if (native_type_.state == State::kCancelled) {
185 PW_CHECK_UINT_EQ(
186 xTimerStart(reinterpret_cast<TimerHandle_t>(&native_type_.tcb), 0),
187 pdPASS,
188 "Timer command queue overflowed");
189 native_type_.state = State::kScheduled;
190 }
191 }
192
Cancel()193 void SystemTimer::Cancel() {
194 // The FreeRTOS timer service is always handled by a thread, ergo to ensure
195 // this API is threadsafe we simply disable task switching.
196 std::lock_guard lock(global_timer_lock);
197
198 // The stop command may not be executed until later in case we're in a
199 // critical section. For this reason update the internal state in case the
200 // callback gets invoked.
201 //
202 // Note that xTimerIsTimerActive cannot be used here as the timer service
203 // daemon may be a lower priority and ergo may still execute the callback
204 // after Cancel() was invoked. This is because a single expired timer may be
205 // processed before the entire command queue is emptied.
206 native_type_.state = State::kCancelled;
207
208 PW_CHECK_UINT_EQ(
209 xTimerStop(reinterpret_cast<TimerHandle_t>(&native_type_.tcb), 0),
210 pdPASS,
211 "Timer command queue overflowed");
212 }
213
214 } // namespace pw::chrono
215