105 lines
3.6 KiB
C++
105 lines
3.6 KiB
C++
// 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
|