• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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