blob: 3f4167452da765a9e07973764c1d5e6001d1b458 [file] [log] [blame]
Ewout van Bekkum666935d2021-06-21 15:52:50 -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 <mutex>
18
19#include "RTOS.h"
20#include "pw_assert/check.h"
21#include "pw_chrono_embos/system_clock_constants.h"
22#include "pw_interrupt/context.h"
23
24namespace pw::chrono {
25namespace {
26
27// Instead of adding targeted locks to each instance, simply use the global
28// recursive critical section lock. Note it has to be recursive because a timer
29// cannot be started with a time period of 0 and ergo the Invoke* API might
30// have to directly invoke the callback.
31class RecursiveCriticalSectionLock {
32 public:
33 void lock() {
34 OS_IncDI(); // Mask interrupts.
35 OS_SuspendAllTasks(); // Disable task switching.
36 }
37
38 void unlock() {
39 OS_ResumeAllSuspendedTasks(); // Restore task switching.
40 OS_DecRI(); // Restore interrupts.
41 }
42};
43RecursiveCriticalSectionLock recursive_global_timer_lock;
44
45void HandleTimerCallback(void* void_native_system_timer) {
46 // NOTE: embOS invokes all timer callbacks from a single interrupt. Ergo we do
47 // not add a unnecessary grab of the recursive_global_timer_lock here.
48 PW_DCHECK(interrupt::InInterruptContext(),
49 "HandleTimerCallback must be invoked from an interrupt");
50 // TODO(ewout): can we potentially skip this as it's always in an interrupt
51 // handler and the control API is not interrupt safe?
52 std::lock_guard lock(recursive_global_timer_lock);
53
54 backend::NativeSystemTimer& native_type =
55 *static_cast<backend::NativeSystemTimer*>(void_native_system_timer);
56 const SystemClock::duration time_until_deadline =
57 native_type.expiry_deadline - SystemClock::now();
58 if (time_until_deadline <= SystemClock::duration::zero()) {
59 // We have met the deadline, execute the user's callback.
60 native_type.user_callback(native_type.expiry_deadline);
61 return;
62 }
63 const SystemClock::duration period =
64 std::min(pw::chrono::embos::kMaxTimeout, time_until_deadline);
65 OS_SetTimerPeriodEx(&native_type.tcb, static_cast<OS_TIME>(period.count()));
66 OS_StartTimerEx(&native_type.tcb);
67}
68
69constexpr OS_TIME kInvalidPeriod = 0;
70
71} // namespace
72
73SystemTimer::SystemTimer(ExpiryCallback callback)
74 : native_type_{.tcb{},
75 .expiry_deadline = SystemClock::time_point(),
76 .user_callback = std::move(callback)} {
77 OS_CreateTimerEx(
78 &native_type_.tcb, HandleTimerCallback, kInvalidPeriod, &native_type_);
79}
80
81SystemTimer::~SystemTimer() {
82 // Not threadsafe by design.
83 Cancel();
84 OS_DeleteTimerEx(&native_type_.tcb);
85}
86
87void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
88 std::lock_guard lock(recursive_global_timer_lock);
89
90 // Ensure the timer has been cancelled first.
91 Cancel();
92
93 native_type_.expiry_deadline = timestamp;
94 const SystemClock::duration time_until_deadline =
95 timestamp - SystemClock::now();
96
97 // Timers can only be created with a non-zero period, ergo we must immediately
98 // invoke the user's callback if it cannot be deferred by at least a partial
99 // tick.
100 if (time_until_deadline <= SystemClock::duration::zero()) {
101 native_type_.user_callback(timestamp);
102 return;
103 }
104
105 // Schedule the timer as far out as possible. Note that the timeout might be
106 // clamped and it may be rescheduled internally.
107 const SystemClock::duration period =
108 std::min(pw::chrono::embos::kMaxTimeout, time_until_deadline);
109 OS_SetTimerPeriodEx(&native_type_.tcb, static_cast<OS_TIME>(period.count()));
110 OS_RetriggerTimerEx(&native_type_.tcb);
111}
112
113} // namespace pw::chrono