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