// 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 #include #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(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(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(period.count())); OS_RetriggerTimerEx(&native_type_.tcb); } } // namespace pw::chrono