You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
7.8 KiB
Rust
264 lines
7.8 KiB
Rust
2 years ago
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||
|
//
|
||
|
// Copyright (c) 2020-2022 Andre Richter <andre.o.richter@gmail.com>
|
||
|
|
||
|
//! Timer primitives.
|
||
|
//!
|
||
|
//! # Resources
|
||
|
//!
|
||
|
//! - <https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust>
|
||
|
//! - <https://doc.rust-lang.org/stable/std/panic/fn.set_hook.html>
|
||
|
|
||
|
#[cfg(target_arch = "aarch64")]
|
||
|
#[path = "_arch/aarch64/time.rs"]
|
||
|
mod arch_time;
|
||
|
|
||
|
use crate::{
|
||
|
driver, exception,
|
||
|
exception::asynchronous::IRQNumber,
|
||
|
synchronization::{interface::Mutex, IRQSafeNullLock},
|
||
|
warn,
|
||
|
};
|
||
|
use alloc::{boxed::Box, vec::Vec};
|
||
|
use core::{
|
||
|
sync::atomic::{AtomicBool, Ordering},
|
||
|
time::Duration,
|
||
|
};
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
// Private Definitions
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
struct Timeout {
|
||
|
due_time: Duration,
|
||
|
period: Option<Duration>,
|
||
|
callback: TimeoutCallback,
|
||
|
}
|
||
|
|
||
|
struct OrderedTimeoutQueue {
|
||
|
// Can be replaced with a BinaryHeap once it's new() becomes const.
|
||
|
inner: Vec<Timeout>,
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
// Public Definitions
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/// The callback type used by timer IRQs.
|
||
|
pub type TimeoutCallback = Box<dyn Fn() + Send>;
|
||
|
|
||
|
/// Provides time management functions.
|
||
|
pub struct TimeManager {
|
||
|
queue: IRQSafeNullLock<OrderedTimeoutQueue>,
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
// Global instances
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
static TIME_MANAGER: TimeManager = TimeManager::new();
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
// Private Code
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
impl Timeout {
|
||
|
pub fn is_periodic(&self) -> bool {
|
||
|
self.period.is_some()
|
||
|
}
|
||
|
|
||
|
pub fn refresh(&mut self) {
|
||
|
if let Some(delay) = self.period {
|
||
|
self.due_time += delay;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl OrderedTimeoutQueue {
|
||
|
pub const fn new() -> Self {
|
||
|
Self { inner: Vec::new() }
|
||
|
}
|
||
|
|
||
|
pub fn push(&mut self, timeout: Timeout) {
|
||
|
self.inner.push(timeout);
|
||
|
|
||
|
// Note reverse compare order so that earliest expiring item is at end of vec. We do this so
|
||
|
// that we can use Vec::pop below to retrieve the item that is next due.
|
||
|
self.inner.sort_by(|a, b| b.due_time.cmp(&a.due_time));
|
||
|
}
|
||
|
|
||
|
pub fn peek_next_due_time(&self) -> Option<Duration> {
|
||
|
let timeout = self.inner.last()?;
|
||
|
|
||
|
Some(timeout.due_time)
|
||
|
}
|
||
|
|
||
|
pub fn pop(&mut self) -> Option<Timeout> {
|
||
|
self.inner.pop()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
// Public Code
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/// Return a reference to the global TimeManager.
|
||
|
pub fn time_manager() -> &'static TimeManager {
|
||
|
&TIME_MANAGER
|
||
|
}
|
||
|
|
||
|
impl TimeManager {
|
||
|
/// Compatibility string.
|
||
|
pub const COMPATIBLE: &'static str = "ARM Architectural Timer";
|
||
|
|
||
|
/// Create an instance.
|
||
|
pub const fn new() -> Self {
|
||
|
Self {
|
||
|
queue: IRQSafeNullLock::new(OrderedTimeoutQueue::new()),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// The timer's resolution.
|
||
|
pub fn resolution(&self) -> Duration {
|
||
|
arch_time::resolution()
|
||
|
}
|
||
|
|
||
|
/// The uptime since power-on of the device.
|
||
|
///
|
||
|
/// This includes time consumed by firmware and bootloaders.
|
||
|
pub fn uptime(&self) -> Duration {
|
||
|
arch_time::uptime()
|
||
|
}
|
||
|
|
||
|
/// Spin for a given duration.
|
||
|
pub fn spin_for(&self, duration: Duration) {
|
||
|
arch_time::spin_for(duration)
|
||
|
}
|
||
|
|
||
|
/// Set a timeout.
|
||
|
fn set_timeout(&self, timeout: Timeout) {
|
||
|
self.queue.lock(|queue| {
|
||
|
queue.push(timeout);
|
||
|
|
||
|
arch_time::set_timeout_irq(queue.peek_next_due_time().unwrap());
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/// Set a one-shot timeout.
|
||
|
pub fn set_timeout_once(&self, delay: Duration, callback: TimeoutCallback) {
|
||
|
let timeout = Timeout {
|
||
|
due_time: self.uptime() + delay,
|
||
|
period: None,
|
||
|
callback,
|
||
|
};
|
||
|
|
||
|
self.set_timeout(timeout);
|
||
|
}
|
||
|
|
||
|
/// Set a periodic timeout.
|
||
|
pub fn set_timeout_periodic(&self, delay: Duration, callback: TimeoutCallback) {
|
||
|
let timeout = Timeout {
|
||
|
due_time: self.uptime() + delay,
|
||
|
period: Some(delay),
|
||
|
callback,
|
||
|
};
|
||
|
|
||
|
self.set_timeout(timeout);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Initialize the timer subsystem.
|
||
|
pub fn init() -> Result<(), &'static str> {
|
||
|
static INIT_DONE: AtomicBool = AtomicBool::new(false);
|
||
|
if INIT_DONE.load(Ordering::Relaxed) {
|
||
|
return Err("Init already done");
|
||
|
}
|
||
|
|
||
|
let timer_descriptor =
|
||
|
driver::DeviceDriverDescriptor::new(time_manager(), None, Some(arch_time::timeout_irq()));
|
||
|
driver::driver_manager().register_driver(timer_descriptor);
|
||
|
|
||
|
INIT_DONE.store(true, Ordering::Relaxed);
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// OS Interface Code
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
impl driver::interface::DeviceDriver for TimeManager {
|
||
|
type IRQNumberType = IRQNumber;
|
||
|
|
||
|
fn compatible(&self) -> &'static str {
|
||
|
Self::COMPATIBLE
|
||
|
}
|
||
|
|
||
|
fn register_and_enable_irq_handler(
|
||
|
&'static self,
|
||
|
irq_number: &Self::IRQNumberType,
|
||
|
) -> Result<(), &'static str> {
|
||
|
use exception::asynchronous::{irq_manager, IRQHandlerDescriptor};
|
||
|
|
||
|
let descriptor = IRQHandlerDescriptor::new(*irq_number, Self::COMPATIBLE, self);
|
||
|
|
||
|
irq_manager().register_handler(descriptor)?;
|
||
|
irq_manager().enable(irq_number);
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl exception::asynchronous::interface::IRQHandler for TimeManager {
|
||
|
fn handle(&self) -> Result<(), &'static str> {
|
||
|
arch_time::conclude_timeout_irq();
|
||
|
|
||
|
let maybe_timeout: Option<Timeout> = self.queue.lock(|queue| {
|
||
|
let next_due_time = queue.peek_next_due_time()?;
|
||
|
if next_due_time > self.uptime() {
|
||
|
return None;
|
||
|
}
|
||
|
|
||
|
let mut timeout = queue.pop().unwrap();
|
||
|
|
||
|
// Refresh as early as possible to prevent drift.
|
||
|
if timeout.is_periodic() {
|
||
|
timeout.refresh();
|
||
|
}
|
||
|
|
||
|
Some(timeout)
|
||
|
});
|
||
|
|
||
|
let timeout = match maybe_timeout {
|
||
|
None => {
|
||
|
warn!("Spurious timeout IRQ");
|
||
|
return Ok(());
|
||
|
}
|
||
|
Some(t) => t,
|
||
|
};
|
||
|
|
||
|
// Important: Call the callback while not holding any lock, because the callback might
|
||
|
// attempt to modify data that is protected by a lock (in particular, the timeout queue
|
||
|
// itself).
|
||
|
(timeout.callback)();
|
||
|
|
||
|
self.queue.lock(|queue| {
|
||
|
if timeout.is_periodic() {
|
||
|
// There might be some overhead involved in the periodic path, because the timeout
|
||
|
// item is first popped from the underlying Vec and then pushed back again. It could
|
||
|
// be faster to keep the item in the queue and find a way to work with a reference
|
||
|
// to it.
|
||
|
//
|
||
|
// We are not going this route on purpose, though. It allows to keep the code simple
|
||
|
// and the focus on the high-level concepts.
|
||
|
queue.push(timeout);
|
||
|
};
|
||
|
|
||
|
if let Some(due_time) = queue.peek_next_due_time() {
|
||
|
arch_time::set_timeout_irq(due_time);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|