aboutsummaryrefslogtreecommitdiff
path: root/pw_chrono_embos/system_timer.cc
blob: 52ed96520da408b1a62a34970dee494f724c970e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_chrono/system_timer.h"

#include <algorithm>
#include <mutex>

#include "RTOS.h"
#include "pw_assert/check.h"
#include "pw_chrono_embos/system_clock_constants.h"
#include "pw_interrupt/context.h"

namespace pw::chrono {
namespace {

// Instead of adding targeted locks to each instance, simply use the global
// recursive critical section lock. Note it has to be recursive because a user
// callback may use the Invoke* API which in turn needs to grab the lock.
class RecursiveCriticalSectionLock {
 public:
  void lock() {
    OS_IncDI();            // Mask interrupts.
    OS_SuspendAllTasks();  // Disable task switching.
  }

  void unlock() {
    OS_ResumeAllSuspendedTasks();  // Restore task switching.
    OS_DecRI();                    // Restore interrupts.
  }
};
RecursiveCriticalSectionLock recursive_global_timer_lock;

void HandleTimerCallback(void* void_native_system_timer) {
  PW_DCHECK(interrupt::InInterruptContext(),
            "HandleTimerCallback must be invoked from an interrupt");
  std::lock_guard lock(recursive_global_timer_lock);

  backend::NativeSystemTimer& native_type =
      *static_cast<backend::NativeSystemTimer*>(void_native_system_timer);
  const SystemClock::duration time_until_deadline =
      native_type.expiry_deadline - SystemClock::now();
  if (time_until_deadline <= SystemClock::duration::zero()) {
    // We have met the deadline, execute the user's callback.
    native_type.user_callback(native_type.expiry_deadline);
    return;
  }
  const SystemClock::duration period =
      std::min(pw::chrono::embos::kMaxTimeout, time_until_deadline);
  OS_SetTimerPeriodEx(&native_type.tcb, static_cast<OS_TIME>(period.count()));
  OS_StartTimerEx(&native_type.tcb);
}

// embOS requires a timer to have a non-zero period.
constexpr SystemClock::duration kMinTimerPeriod = SystemClock::duration(1);
constexpr OS_TIME kInvalidPeriod = 0;

}  // namespace

SystemTimer::SystemTimer(ExpiryCallback&& callback)
    : native_type_{.tcb{},
                   .expiry_deadline = SystemClock::time_point(),
                   .user_callback = std::move(callback)} {
  OS_CreateTimerEx(
      &native_type_.tcb, HandleTimerCallback, kInvalidPeriod, &native_type_);
}

SystemTimer::~SystemTimer() {
  // Not threadsafe by design.
  Cancel();
  OS_DeleteTimerEx(&native_type_.tcb);
}

void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
  std::lock_guard lock(recursive_global_timer_lock);

  // Ensure the timer has been cancelled first.
  Cancel();

  native_type_.expiry_deadline = timestamp;
  const SystemClock::duration time_until_deadline =
      timestamp - SystemClock::now();

  // Schedule the timer as far out as possible. Note that the timeout might be
  // clamped and it may be rescheduled internally.
  const SystemClock::duration period = std::clamp(
      kMinTimerPeriod, time_until_deadline, pw::chrono::embos::kMaxTimeout);

  OS_SetTimerPeriodEx(&native_type_.tcb, static_cast<OS_TIME>(period.count()));
  OS_RetriggerTimerEx(&native_type_.tcb);
}

}  // namespace pw::chrono