feat: ✨ Add time support
This commit is contained in:
parent
33418e79ab
commit
84fbdcc707
|
@ -11,6 +11,7 @@ mod boot;
|
|||
#[cfg(feature = "jtag")]
|
||||
pub mod jtag;
|
||||
pub mod memory;
|
||||
pub mod time;
|
||||
pub mod traps;
|
||||
|
||||
/// Loop forever in sleep mode.
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
//
|
||||
// Copyright (c) 2018-2022 Andre Richter <andre.o.richter@gmail.com>
|
||||
|
||||
//! Architectural timer primitives.
|
||||
//!
|
||||
//! # Orientation
|
||||
//!
|
||||
//! Since arch modules are imported into generic modules using the path attribute, the path of this
|
||||
//! file is:
|
||||
//!
|
||||
//! crate::time::arch_time
|
||||
|
||||
use {
|
||||
crate::warn,
|
||||
aarch64_cpu::{asm::barrier, registers::*},
|
||||
core::{
|
||||
num::{NonZeroU128, NonZeroU32, NonZeroU64},
|
||||
ops::{Add, Div},
|
||||
time::Duration,
|
||||
},
|
||||
tock_registers::interfaces::Readable,
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
// Private Definitions
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap();
|
||||
|
||||
#[derive(Copy, Clone, PartialOrd, PartialEq)]
|
||||
struct GenericTimerCounterValue(u64);
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
// Global instances
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
/// Boot assembly code overwrites this value with the value of CNTFRQ_EL0 before any Rust code is
|
||||
/// executed. This given value here is just a (safe) dummy.
|
||||
#[no_mangle]
|
||||
static ARCH_TIMER_COUNTER_FREQUENCY: NonZeroU32 = NonZeroU32::MIN;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
// Private Code
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
fn arch_timer_counter_frequency() -> NonZeroU32 {
|
||||
// Read volatile is needed here to prevent the compiler from optimizing
|
||||
// ARCH_TIMER_COUNTER_FREQUENCY away.
|
||||
//
|
||||
// This is safe, because all the safety requirements as stated in read_volatile()'s
|
||||
// documentation are fulfilled.
|
||||
unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }
|
||||
}
|
||||
|
||||
impl GenericTimerCounterValue {
|
||||
pub const MAX: Self = GenericTimerCounterValue(u64::MAX);
|
||||
}
|
||||
|
||||
impl Add for GenericTimerCounterValue {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
GenericTimerCounterValue(self.0.wrapping_add(other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GenericTimerCounterValue> for Duration {
|
||||
fn from(counter_value: GenericTimerCounterValue) -> Self {
|
||||
if counter_value.0 == 0 {
|
||||
return Duration::ZERO;
|
||||
}
|
||||
|
||||
let frequency: NonZeroU64 = arch_timer_counter_frequency().into();
|
||||
|
||||
// Div<NonZeroU64> implementation for u64 cannot panic.
|
||||
let secs = counter_value.0.div(frequency);
|
||||
|
||||
// This is safe, because frequency can never be greater than u32::MAX, which means the
|
||||
// largest theoretical value for sub_second_counter_value is (u32::MAX - 1). Therefore,
|
||||
// (sub_second_counter_value * NANOSEC_PER_SEC) cannot overflow an u64.
|
||||
//
|
||||
// The subsequent division ensures the result fits into u32, since the max result is smaller
|
||||
// than NANOSEC_PER_SEC. Therefore, just cast it to u32 using `as`.
|
||||
let sub_second_counter_value = counter_value.0 % frequency;
|
||||
let nanos = unsafe { sub_second_counter_value.unchecked_mul(u64::from(NANOSEC_PER_SEC)) }
|
||||
.div(frequency) as u32;
|
||||
|
||||
Duration::new(secs, nanos)
|
||||
}
|
||||
}
|
||||
|
||||
fn max_duration() -> Duration {
|
||||
Duration::from(GenericTimerCounterValue::MAX)
|
||||
}
|
||||
|
||||
impl TryFrom<Duration> for GenericTimerCounterValue {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(duration: Duration) -> Result<Self, Self::Error> {
|
||||
if duration < resolution() {
|
||||
return Ok(GenericTimerCounterValue(0));
|
||||
}
|
||||
|
||||
if duration > max_duration() {
|
||||
return Err("Conversion error. Duration too big");
|
||||
}
|
||||
|
||||
let frequency: u128 = u32::from(arch_timer_counter_frequency()) as u128;
|
||||
let duration: u128 = duration.as_nanos();
|
||||
|
||||
// This is safe, because frequency can never be greater than u32::MAX, and
|
||||
// (Duration::MAX.as_nanos() * u32::MAX) < u128::MAX.
|
||||
let counter_value =
|
||||
unsafe { duration.unchecked_mul(frequency) }.div(NonZeroU128::from(NANOSEC_PER_SEC));
|
||||
|
||||
// Since we checked above that we are <= max_duration(), just cast to u64.
|
||||
Ok(GenericTimerCounterValue(counter_value as u64))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_cntpct() -> GenericTimerCounterValue {
|
||||
// Prevent that the counter is read ahead of time due to out-of-order execution.
|
||||
barrier::isb(barrier::SY);
|
||||
let cnt = CNTPCT_EL0.get();
|
||||
|
||||
GenericTimerCounterValue(cnt)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
// Public Code
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
/// The timer's resolution.
|
||||
pub fn resolution() -> Duration {
|
||||
Duration::from(GenericTimerCounterValue(1))
|
||||
}
|
||||
|
||||
/// The uptime since power-on of the device.
|
||||
///
|
||||
/// This includes time consumed by firmware and bootloaders.
|
||||
pub fn uptime() -> Duration {
|
||||
read_cntpct().into()
|
||||
}
|
||||
|
||||
/// Spin for a given duration.
|
||||
pub fn spin_for(duration: Duration) {
|
||||
let curr_counter_value = read_cntpct();
|
||||
|
||||
let counter_value_delta: GenericTimerCounterValue = match duration.try_into() {
|
||||
Err(msg) => {
|
||||
warn!("spin_for: {}. Skipping", msg);
|
||||
return;
|
||||
}
|
||||
Ok(val) => val,
|
||||
};
|
||||
let counter_value_target = curr_counter_value + counter_value_delta;
|
||||
|
||||
// Busy wait.
|
||||
//
|
||||
// Read CNTPCT_EL0 directly to avoid the ISB that is part of [`read_cntpct`].
|
||||
while GenericTimerCounterValue(CNTPCT_EL0.get()) < counter_value_target {}
|
||||
}
|
|
@ -6,11 +6,13 @@
|
|||
#![feature(allocator_api)]
|
||||
#![feature(format_args_nl)]
|
||||
#![feature(core_intrinsics)]
|
||||
#![feature(const_option)]
|
||||
#![feature(strict_provenance)]
|
||||
#![feature(stmt_expr_attributes)]
|
||||
#![feature(slice_ptr_get)]
|
||||
#![feature(panic_info_message)]
|
||||
#![feature(nonnull_slice_from_raw_parts)] // stabilised in 1.71 nightly
|
||||
#![feature(unchecked_math)]
|
||||
#![feature(custom_test_frameworks)]
|
||||
#![test_runner(crate::tests::test_runner)]
|
||||
#![reexport_test_harness_main = "test_main"]
|
||||
|
@ -42,6 +44,7 @@ pub mod platform;
|
|||
pub mod qemu;
|
||||
mod sync;
|
||||
pub mod tests;
|
||||
pub mod time;
|
||||
pub mod write_to;
|
||||
|
||||
// The global allocator for DMA-able memory. That is, memory which is tagged
|
||||
|
|
|
@ -36,3 +36,54 @@ pub fn _print(args: core::fmt::Arguments) {
|
|||
let mut buf = [0u8; 2048]; // Increase this buffer size to allow dumping larger panic texts.
|
||||
qemu::semihosting::sys_write0_call(write_to::c_show(&mut buf, args).unwrap());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
/// Prints info text, with a newline.
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($string:expr) => ({
|
||||
let timestamp = $crate::time::time_manager().uptime();
|
||||
|
||||
$crate::macros::_print(format_args_nl!(
|
||||
concat!("[ {:>3}.{:06}] ", $string),
|
||||
timestamp.as_secs(),
|
||||
timestamp.subsec_micros(),
|
||||
));
|
||||
});
|
||||
($format_string:expr, $($arg:tt)*) => ({
|
||||
let timestamp = $crate::time::time_manager().uptime();
|
||||
|
||||
$crate::macros::_print(format_args_nl!(
|
||||
concat!("[ {:>3}.{:06}] ", $format_string),
|
||||
timestamp.as_secs(),
|
||||
timestamp.subsec_micros(),
|
||||
$($arg)*
|
||||
));
|
||||
})
|
||||
}
|
||||
|
||||
/// Prints warning text, with a newline.
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($string:expr) => ({
|
||||
let timestamp = $crate::time::time_manager().uptime();
|
||||
|
||||
$crate::macros::_print(format_args_nl!(
|
||||
concat!("[W {:>3}.{:06}] ", $string),
|
||||
timestamp.as_secs(),
|
||||
timestamp.subsec_micros(),
|
||||
));
|
||||
});
|
||||
($format_string:expr, $($arg:tt)*) => ({
|
||||
let timestamp = $crate::time::time_manager().uptime();
|
||||
|
||||
$crate::macros::_print(format_args_nl!(
|
||||
concat!("[W {:>3}.{:06}] ", $format_string),
|
||||
timestamp.as_secs(),
|
||||
timestamp.subsec_micros(),
|
||||
$($arg)*
|
||||
));
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
//
|
||||
// Copyright (c) 2020-2022 Andre Richter <andre.o.richter@gmail.com>
|
||||
|
||||
//! Timer primitives.
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::arch::aarch64::time as arch_time;
|
||||
|
||||
use core::time::Duration;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
// Public Definitions
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
/// Provides time management functions.
|
||||
pub struct TimeManager;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
// Global instances
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
static TIME_MANAGER: TimeManager = TimeManager::new();
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
// Public Code
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
/// Return a reference to the global TimeManager.
|
||||
pub fn time_manager() -> &'static TimeManager {
|
||||
&TIME_MANAGER
|
||||
}
|
||||
|
||||
impl TimeManager {
|
||||
/// Create an instance.
|
||||
pub const fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue