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 "RTOS.h"
21 #include "pw_assert/check.h"
22 #include "pw_chrono_embos/system_clock_constants.h"
23 #include "pw_interrupt/context.h"
24
25 namespace pw::chrono {
26 namespace {
27
28 // Instead of adding targeted locks to each instance, simply use the global
29 // recursive critical section lock. Note it has to be recursive because a user
30 // callback may use the Invoke* API which in turn needs to grab the lock.
31 class RecursiveCriticalSectionLock {
32 public:
lock()33 void lock() {
34 OS_IncDI(); // Mask interrupts.
35 OS_SuspendAllTasks(); // Disable task switching.
36 }
37
unlock()38 void unlock() {
39 OS_ResumeAllSuspendedTasks(); // Restore task switching.
40 OS_DecRI(); // Restore interrupts.
41 }
42 };
43 RecursiveCriticalSectionLock recursive_global_timer_lock;
44
HandleTimerCallback(void * void_native_system_timer)45 void HandleTimerCallback(void* void_native_system_timer) {
46 PW_DCHECK(interrupt::InInterruptContext(),
47 "HandleTimerCallback must be invoked from an interrupt");
48 std::lock_guard lock(recursive_global_timer_lock);
49
50 backend::NativeSystemTimer& native_type =
51 *static_cast<backend::NativeSystemTimer*>(void_native_system_timer);
52 const SystemClock::duration time_until_deadline =
53 native_type.expiry_deadline - SystemClock::now();
54 if (time_until_deadline <= SystemClock::duration::zero()) {
55 // We have met the deadline, execute the user's callback.
56 native_type.user_callback(native_type.expiry_deadline);
57 return;
58 }
59 const SystemClock::duration period =
60 std::min(pw::chrono::embos::kMaxTimeout, time_until_deadline);
61 OS_SetTimerPeriodEx(&native_type.tcb, static_cast<OS_TIME>(period.count()));
62 OS_StartTimerEx(&native_type.tcb);
63 }
64
65 // embOS requires a timer to have a non-zero period.
66 constexpr SystemClock::duration kMinTimerPeriod = SystemClock::duration(1);
67 constexpr OS_TIME kInvalidPeriod = 0;
68
69 } // namespace
70
SystemTimer(ExpiryCallback && callback)71 SystemTimer::SystemTimer(ExpiryCallback&& callback)
72 : native_type_{.tcb{},
73 .expiry_deadline = SystemClock::time_point(),
74 .user_callback = std::move(callback)} {
75 OS_CreateTimerEx(
76 &native_type_.tcb, HandleTimerCallback, kInvalidPeriod, &native_type_);
77 }
78
~SystemTimer()79 SystemTimer::~SystemTimer() {
80 // Not threadsafe by design.
81 Cancel();
82 OS_DeleteTimerEx(&native_type_.tcb);
83 }
84
InvokeAt(SystemClock::time_point timestamp)85 void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
86 std::lock_guard lock(recursive_global_timer_lock);
87
88 // Ensure the timer has been cancelled first.
89 Cancel();
90
91 native_type_.expiry_deadline = timestamp;
92 const SystemClock::duration time_until_deadline =
93 timestamp - SystemClock::now();
94
95 // Schedule the timer as far out as possible. Note that the timeout might be
96 // clamped and it may be rescheduled internally.
97 const SystemClock::duration period = std::clamp(
98 kMinTimerPeriod, time_until_deadline, pw::chrono::embos::kMaxTimeout);
99
100 OS_SetTimerPeriodEx(&native_type_.tcb, static_cast<OS_TIME>(period.count()));
101 OS_RetriggerTimerEx(&native_type_.tcb);
102 }
103
104 } // namespace pw::chrono
105