xref: /aosp_15_r20/external/pigweed/pw_chrono_freertos/system_timer.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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