blob: e646f326a5a972fffc36149425dcce3a34812695 [file] [log] [blame]
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -07001// 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>
Ewout van Bekkum1f43db32021-07-09 12:18:04 -070018#include <mutex>
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -070019
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
26namespace pw::chrono {
27namespace {
28
29using State = backend::NativeSystemTimer::State;
30
Ewout van Bekkum1f43db32021-07-09 12:18:04 -070031// Instead of adding targeted locks to each instance, simply use the global
32// scheduler critical section lock.
33class SchedulerLock {
34 public:
35 static void lock() { vTaskSuspendAll(); }
36 static void unlock() { xTaskResumeAll(); }
37};
38SchedulerLock global_timer_lock;
39
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -070040void HandleTimerCallback(TimerHandle_t timer_handle) {
Ewout van Bekkum1f43db32021-07-09 12:18:04 -070041 // 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);
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -070044
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;
Ewout van Bekkum1f43db32021-07-09 12:18:04 -070067
68 // Release the scheduler lock once we won't modify native_state any further.
69 lock.unlock();
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -070070 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.
Ewout van Bekkumee0b1e02021-08-13 08:27:31 -070075 // Note that this must be > SystemClock::duration::zero() based on the
76 // conditional above.
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -070077 const SystemClock::duration period =
78 std::min(pw::chrono::freertos::kMaxTimeout, time_until_deadline);
79 PW_CHECK_UINT_EQ(
80 xTimerChangePeriod(
81 &native_type.tcb, static_cast<TickType_t>(period.count()), 0),
82 pdPASS,
83 "Timer command queue overflowed");
84 PW_CHECK_UINT_EQ(xTimerStart(&native_type.tcb, 0),
85 pdPASS,
86 "Timer command queue overflowed");
87}
88
Ewout van Bekkumee0b1e02021-08-13 08:27:31 -070089// FreeRTOS requires a timer to have a non-zero period.
90constexpr SystemClock::duration kMinTimerPeriod = SystemClock::duration(1);
91constexpr TickType_t kInvalidPeriod = kMinTimerPeriod.count();
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -070092constexpr UBaseType_t kOneShotMode = pdFALSE; // Do not use auto reload.
93
94} // namespace
95
96#if configUSE_TIMERS != 1
97#error \
98 "Backend requires your FreeRTOS configuration to have configUSE_TIMERS == 1"
99#endif
100
101#if configSUPPORT_STATIC_ALLOCATION != 1
102#error \
103 "Backend requires your FreeRTOS configuration to have configSUPPORT_STATIC_ALLOCATION == 1"
104#endif
105
106SystemTimer::SystemTimer(ExpiryCallback callback)
107 : native_type_{.tcb{},
108 .state = State::kCancelled,
109 .expiry_deadline = SystemClock::time_point(),
110 .user_callback = std::move(callback)} {
111 // Note that timer "creation" is not enqueued through the command queue and
112 // is ergo safe to do before the scheduler is running.
113 const TimerHandle_t handle =
114 xTimerCreateStatic("", // "pw::chrono::SystemTimer",
115 kInvalidPeriod,
116 kOneShotMode,
117 this,
118 HandleTimerCallback,
119 &native_type_.tcb);
120
121 // This should never fail since the pointer provided was not null and it
122 // should return a pointer to the StaticTimer_t.
123 PW_DCHECK_PTR_EQ(handle, &native_type_.tcb);
124}
125
126SystemTimer::~SystemTimer() {
127 Cancel();
128
129 // WARNING: This enqueues the request to delete the timer through a queue, it
130 // does not synchronously delete and disable the timer here! This means that
131 // if the timer is about to expire and the timer service thread is a lower
132 // priority that it may use the native_type_ after it is free'd.
133 PW_CHECK_UINT_EQ(pdPASS,
134 xTimerDelete(&native_type_.tcb, 0),
135 "Timer command queue overflowed");
136
137 // In case the timer is still active as warned above, busy yield loop until it
138 // has been removed. Note that this is safe before the scheduler has been
139 // started because the timer cannot have been added to the queue yet and ergo
140 // it shouldn't attempt to yield.
141 while (xTimerIsTimerActive(&native_type_.tcb)) {
142 taskYIELD();
143 }
144}
145
146void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
147 // The FreeRTOS timer service is always handled by a thread, ergo to ensure
148 // this API is threadsafe we simply disable task switching.
Ewout van Bekkum1f43db32021-07-09 12:18:04 -0700149 std::lock_guard lock(global_timer_lock);
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -0700150
151 // We don't want to call Cancel which would enqueue a stop command instead of
152 // synchronously updating the state. Instead we update the expiry deadline
153 // and update the state where the one shot only fires if the expiry deadline
154 // is exceeded and the callback is executed once.
155 native_type_.expiry_deadline = timestamp;
156
157 // Schedule the timer as far out as possible. Note that the timeout might be
158 // clamped and it may be rescheduled internally.
159 const SystemClock::duration time_until_deadline =
160 timestamp - SystemClock::now();
Ewout van Bekkumee0b1e02021-08-13 08:27:31 -0700161 const SystemClock::duration period = std::clamp(
162 kMinTimerPeriod, time_until_deadline, pw::chrono::freertos::kMaxTimeout);
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -0700163
164 PW_CHECK_UINT_EQ(
165 xTimerChangePeriod(
166 &native_type_.tcb, static_cast<TickType_t>(period.count()), 0),
167 pdPASS,
168 "Timer command queue overflowed");
169
170 // Don't enqueue the start multiple times, schedule it once and let the
171 // callback cancel.
172 if (native_type_.state == State::kCancelled) {
173 PW_CHECK_UINT_EQ(xTimerStart(&native_type_.tcb, 0),
174 pdPASS,
175 "Timer command queue overflowed");
176 native_type_.state = State::kScheduled;
177 }
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -0700178}
179
180void SystemTimer::Cancel() {
181 // The FreeRTOS timer service is always handled by a thread, ergo to ensure
182 // this API is threadsafe we simply disable task switching.
Ewout van Bekkum1f43db32021-07-09 12:18:04 -0700183 std::lock_guard lock(global_timer_lock);
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -0700184
185 // The stop command may not be executed until later in case we're in a
186 // critical section. For this reason update the internal state in case the
187 // callback gets invoked.
188 //
189 // Note that xTimerIsTimerActive cannot be used here as the timer service
190 // daemon may be a lower priority and ergo may still execute the callback
191 // after Cancel() was invoked. This is because a single expired timer may be
192 // processed before the entire command queue is emptied.
193 native_type_.state = State::kCancelled;
194
195 PW_CHECK_UINT_EQ(xTimerStop(&native_type_.tcb, 0),
196 pdPASS,
197 "Timer command queue overflowed");
Ewout van Bekkum4f97fdd2021-06-24 10:37:37 -0700198}
199
200} // namespace pw::chrono