blob: 51e48bdc5a64eaec06f8aea33b4b71a8a98298b5 [file]
// Copyright 2025 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.
use core::ptr::NonNull;
use foreign_box::ForeignBox;
use list::{ForeignList, Link};
use pw_time_core::Instant;
use crate::Kernel;
list::define_adapter!(pub TimerCallbackListAdapter<K: Kernel> => Timer<K>::link);
pub trait TimerCallback<K: Kernel> {
fn callback(
&mut self,
kernel: K,
timer: ForeignBox<Timer<K>>,
now: Instant<K::Clock>,
) -> Option<ForeignBox<Timer<K>>>;
}
impl<K, F> TimerCallback<K> for F
where
K: Kernel,
F: FnMut(K, ForeignBox<Timer<K>>, Instant<K::Clock>) -> Option<ForeignBox<Timer<K>>>,
{
fn callback(
&mut self,
kernel: K,
timer: ForeignBox<Timer<K>>,
now: Instant<K::Clock>,
) -> Option<ForeignBox<Timer<K>>> {
self(kernel, timer, now)
}
}
#[allow(dead_code)]
pub struct Timer<K: Kernel> {
deadline: Instant<K::Clock>,
// XXX: make private
pub callback: Option<ForeignBox<dyn TimerCallback<K>>>,
link: Link,
}
impl<K: Kernel> Timer<K> {
#[allow(dead_code)]
#[must_use]
pub fn new(deadline: Instant<K::Clock>, callback: ForeignBox<dyn TimerCallback<K>>) -> Self {
Self {
deadline,
callback: Some(callback),
link: Link::new(),
}
}
pub fn set(&mut self, deadline: Instant<K::Clock>) {
self.deadline = deadline
}
}
#[allow(dead_code)]
pub struct TimerQueue<K: Kernel> {
queue: ForeignList<Timer<K>, TimerCallbackListAdapter<K>>,
}
unsafe impl<K: Kernel> Sync for TimerQueue<K> {}
unsafe impl<K: Kernel> Send for TimerQueue<K> {}
impl<K: Kernel> TimerQueue<K> {
#[must_use]
pub const fn new() -> Self {
Self {
queue: ForeignList::new(),
}
}
pub fn get_next_exipred_callback(
&mut self,
now: Instant<K::Clock>,
) -> Option<ForeignBox<Timer<K>>> {
// TODO - konkers: An optimization opportunity exists here to implement
// a something like `peek_front(|front| front.deadline > now)` which
// would eliminate the popping and re-pushing of the non-expired
// callback.
let callback = self.queue.pop_head()?;
if callback.deadline > now {
self.queue.push_front(callback);
return None;
}
Some(callback)
}
}
#[allow(dead_code)]
pub fn schedule_timer<K: Kernel>(kernel: K, timer: ForeignBox<Timer<K>>) {
let mut timer_queue = kernel.get_timer_queue().lock(kernel);
timer_queue
.queue
.sorted_insert_by_key(timer, |timer| timer.deadline);
}
/// # Safety
///
/// `timer` must point to a valid [`TimerCallback`] object which is in the
/// timer queue.
pub unsafe fn cancel_timer<K: Kernel>(
kernel: K,
timer: NonNull<Timer<K>>,
) -> Option<ForeignBox<Timer<K>>> {
let mut timer_queue = kernel.get_timer_queue().lock(kernel);
unsafe { timer_queue.queue.remove_element(timer) }
}
/// # Safety
///
/// `timer` must point to a valid [`TimerCallback`] object which is in the
/// timer queue.
pub unsafe fn cancel_and_consume_timer<K: Kernel>(kernel: K, timer: NonNull<Timer<K>>) {
if let Some(mut callback) = unsafe { cancel_timer(kernel, timer) } {
if let Some(callback) = callback.callback.take() {
callback.consume();
}
callback.consume();
}
}
#[allow(dead_code)]
pub fn process_queue<K: Kernel>(kernel: K, now: Instant<K::Clock>) {
let mut timer_queue = kernel.get_timer_queue().lock(kernel);
while let Some(mut callback) = timer_queue.get_next_exipred_callback(now) {
// Drop the timer queue lock before processing callbacks. This is
// Safe to do because the callback is already removed from the queue.
drop(timer_queue);
let Some(mut callback_fn) = callback.callback.take() else {
pw_assert::panic!("No callback function found on timer");
};
match callback_fn.callback(kernel, callback, now) {
Some(mut next_callback) => {
next_callback.callback = Some(callback_fn);
schedule_timer(kernel, next_callback);
}
None => {
let _ = callback_fn.consume();
}
}
// Require timer queue lock.
timer_queue = kernel.get_timer_queue().lock(kernel);
}
}