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