blob: 5d230ad3f1171d514e3867aa91c023a538b2bb1c [file] [log] [blame]
// 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