From b7b2d31c2457ec4ee153e3345a954f7582c40533 Mon Sep 17 00:00:00 2001 From: Andre Richter Date: Fri, 23 Sep 2022 22:38:10 +0200 Subject: [PATCH] Rewrite timer subsystem --- 07_timestamps/README.md | 290 +++++++++++------- 07_timestamps/src/_arch/aarch64/cpu/boot.s | 8 + 07_timestamps/src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- 07_timestamps/src/main.rs | 4 +- 07_timestamps/src/panic_wait.rs | 2 - 07_timestamps/src/print.rs | 8 - 07_timestamps/src/time.rs | 52 +++- 08_hw_debug_JTAG/src/_arch/aarch64/cpu/boot.s | 8 + 08_hw_debug_JTAG/src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- 08_hw_debug_JTAG/src/main.rs | 4 +- 08_hw_debug_JTAG/src/panic_wait.rs | 2 - 08_hw_debug_JTAG/src/print.rs | 8 - 08_hw_debug_JTAG/src/time.rs | 52 +++- 09_privilege_level/README.md | 16 +- .../src/_arch/aarch64/cpu/boot.s | 8 + 09_privilege_level/src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- 09_privilege_level/src/main.rs | 4 +- 09_privilege_level/src/panic_wait.rs | 2 - 09_privilege_level/src/print.rs | 8 - 09_privilege_level/src/time.rs | 52 +++- .../README.md | 16 +- .../src/_arch/aarch64/cpu/boot.s | 8 + .../src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- .../src/main.rs | 4 +- .../src/panic_wait.rs | 2 - .../src/print.rs | 8 - .../src/time.rs | 52 +++- 11_exceptions_part1_groundwork/README.md | 8 +- .../src/_arch/aarch64/cpu/boot.s | 8 + .../src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- 11_exceptions_part1_groundwork/src/main.rs | 4 +- .../src/panic_wait.rs | 2 - 11_exceptions_part1_groundwork/src/print.rs | 8 - 11_exceptions_part1_groundwork/src/time.rs | 52 +++- 12_integrated_testing/README.md | 3 +- .../kernel/src/_arch/aarch64/cpu/boot.s | 8 + .../kernel/src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- 12_integrated_testing/kernel/src/lib.rs | 3 + .../kernel/src/panic_wait.rs | 2 - 12_integrated_testing/kernel/src/print.rs | 8 - 12_integrated_testing/kernel/src/time.rs | 52 +++- .../kernel/tests/01_timer_sanity.rs | 3 +- 13_exceptions_part2_peripheral_IRQs/README.md | 8 +- .../kernel/src/_arch/aarch64/cpu/boot.s | 8 + .../kernel/src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- .../kernel/src/lib.rs | 3 + .../kernel/src/panic_wait.rs | 2 - .../kernel/src/print.rs | 8 - .../kernel/src/time.rs | 52 +++- .../kernel/tests/01_timer_sanity.rs | 3 +- 14_virtual_mem_part2_mmio_remap/README.md | 13 +- .../kernel/src/_arch/aarch64/cpu/boot.s | 8 + .../kernel/src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- .../kernel/src/lib.rs | 3 + .../kernel/src/panic_wait.rs | 2 - .../kernel/src/print.rs | 8 - .../kernel/src/time.rs | 52 +++- .../kernel/tests/01_timer_sanity.rs | 3 +- .../README.md | 16 +- .../kernel/src/_arch/aarch64/cpu/boot.s | 8 + .../kernel/src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- .../kernel/src/lib.rs | 3 + .../kernel/src/panic_wait.rs | 2 - .../kernel/src/print.rs | 8 - .../kernel/src/time.rs | 52 +++- .../kernel/tests/01_timer_sanity.rs | 3 +- .../README.md | 16 +- .../kernel/src/_arch/aarch64/cpu/boot.s | 8 + .../kernel/src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- .../kernel/src/lib.rs | 3 + .../kernel/src/panic_wait.rs | 2 - .../kernel/src/print.rs | 8 - .../kernel/src/time.rs | 52 +++- .../kernel/tests/01_timer_sanity.rs | 3 +- 17_kernel_symbols/README.md | 2 +- .../kernel/src/_arch/aarch64/cpu/boot.s | 8 + .../kernel/src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- 17_kernel_symbols/kernel/src/lib.rs | 3 + 17_kernel_symbols/kernel/src/panic_wait.rs | 2 - 17_kernel_symbols/kernel/src/print.rs | 8 - 17_kernel_symbols/kernel/src/time.rs | 52 +++- .../kernel/tests/01_timer_sanity.rs | 3 +- 18_backtrace/README.md | 6 +- .../kernel/src/_arch/aarch64/cpu/boot.s | 8 + 18_backtrace/kernel/src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- 18_backtrace/kernel/src/lib.rs | 3 + 18_backtrace/kernel/src/panic_wait.rs | 2 - 18_backtrace/kernel/src/print.rs | 8 - 18_backtrace/kernel/src/time.rs | 52 +++- 18_backtrace/kernel/tests/01_timer_sanity.rs | 3 +- 19_kernel_heap/README.md | 25 +- .../kernel/src/_arch/aarch64/cpu/boot.s | 8 + .../kernel/src/_arch/aarch64/time.rs | 187 ++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- .../kernel/src/bsp/raspberrypi/driver.rs | 1 + 19_kernel_heap/kernel/src/lib.rs | 3 + 19_kernel_heap/kernel/src/panic_wait.rs | 2 - 19_kernel_heap/kernel/src/print.rs | 12 - 19_kernel_heap/kernel/src/time.rs | 52 +++- .../kernel/tests/01_timer_sanity.rs | 3 +- X1_JTAG_boot/jtag_boot_rpi3.img | Bin 8712 -> 8656 bytes X1_JTAG_boot/jtag_boot_rpi4.img | Bin 7616 -> 7752 bytes X1_JTAG_boot/src/_arch/aarch64/cpu/boot.s | 8 + X1_JTAG_boot/src/_arch/aarch64/time.rs | 189 +++++++----- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- X1_JTAG_boot/src/main.rs | 3 + X1_JTAG_boot/src/panic_wait.rs | 2 - X1_JTAG_boot/src/print.rs | 8 - X1_JTAG_boot/src/time.rs | 48 ++- 121 files changed, 2538 insertions(+), 1581 deletions(-) diff --git a/07_timestamps/README.md b/07_timestamps/README.md index 1126d9b7..a78e508b 100644 --- a/07_timestamps/README.md +++ b/07_timestamps/README.md @@ -182,7 +182,7 @@ diff -uNr 06_uart_chainloader/src/_arch/aarch64/cpu/boot.s 07_timestamps/src/_ar //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- -@@ -48,35 +37,23 @@ +@@ -48,35 +37,31 @@ // If execution reaches here, it is the boot core. // Initialize DRAM. @@ -220,6 +220,14 @@ diff -uNr 06_uart_chainloader/src/_arch/aarch64/cpu/boot.s 07_timestamps/src/_ar - // Jump to the relocated Rust code. - ADR_ABS x1, _start_rust - br x1 ++ // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. ++ // Abort if the frequency read back as 0. ++ ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs ++ mrs x2, CNTFRQ_EL0 ++ cmp x2, xzr ++ b.eq .L_parking_loop ++ str w2, [x1] ++ + // Jump to Rust code. + b _start_rust @@ -249,7 +257,7 @@ diff -uNr 06_uart_chainloader/src/_arch/aarch64/cpu.rs 07_timestamps/src/_arch/a diff -uNr 06_uart_chainloader/src/_arch/aarch64/time.rs 07_timestamps/src/_arch/aarch64/time.rs --- 06_uart_chainloader/src/_arch/aarch64/time.rs +++ 07_timestamps/src/_arch/aarch64/time.rs -@@ -0,0 +1,121 @@ +@@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2022 Andre Richter @@ -263,114 +271,155 @@ diff -uNr 06_uart_chainloader/src/_arch/aarch64/time.rs 07_timestamps/src/_arch/ +//! +//! crate::time::arch_time + -+use crate::{time, warn}; -+use core::time::Duration; ++use crate::warn; ++use core::{ ++ num::{NonZeroU128, NonZeroU32, NonZeroU64}, ++ ops::{Add, Div}, ++ time::Duration, ++}; +use cortex_a::{asm::barrier, registers::*}; -+use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; ++use tock_registers::interfaces::Readable; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + -+const NS_PER_S: u64 = 1_000_000_000; ++const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); + -+/// ARMv8 Generic Timer. -+struct GenericTimer; ++#[derive(Copy, Clone, PartialOrd, PartialEq)] ++struct GenericTimerCounterValue(u64); + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + -+static TIME_MANAGER: GenericTimer = GenericTimer; ++/// 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 +//-------------------------------------------------------------------------------------------------- + -+impl GenericTimer { -+ #[inline(always)] -+ fn read_cntpct(&self) -> u64 { -+ // Prevent that the counter is read ahead of time due to out-of-order execution. -+ unsafe { barrier::isb(barrier::SY) }; -+ CNTPCT_EL0.get() -+ } ++impl GenericTimerCounterValue { ++ pub const MAX: Self = GenericTimerCounterValue(u64::MAX); +} + -+//-------------------------------------------------------------------------------------------------- -+// Public Code -+//-------------------------------------------------------------------------------------------------- ++impl Add for GenericTimerCounterValue { ++ type Output = Self; + -+/// Return a reference to the time manager. -+pub fn time_manager() -> &'static impl time::interface::TimeManager { -+ &TIME_MANAGER ++ fn add(self, other: Self) -> Self { ++ GenericTimerCounterValue(self.0.wrapping_add(other.0)) ++ } +} + -+//------------------------------------------------------------------------------ -+// OS Interface Code -+//------------------------------------------------------------------------------ ++impl From for Duration { ++ fn from(counter_value: GenericTimerCounterValue) -> Self { ++ if counter_value.0 == 0 { ++ return Duration::ZERO; ++ } + -+impl time::interface::TimeManager for GenericTimer { -+ fn resolution(&self) -> Duration { -+ Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) ++ // 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. ++ let frequency: NonZeroU64 = ++ unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); ++ ++ // Div 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 modulo frequency; ++ let nanos = unsafe { sub_second_counter_value.unchecked_mul(u64::from(NANOSEC_PER_SEC)) } ++ .div(frequency) as u32; ++ ++ Duration::new(secs, nanos) + } ++} + -+ fn uptime(&self) -> Duration { -+ let current_count: u64 = self.read_cntpct() * NS_PER_S; -+ let frq: u64 = CNTFRQ_EL0.get() as u64; ++fn max_duration() -> Duration { ++ Duration::from(GenericTimerCounterValue::MAX) ++} + -+ Duration::from_nanos(current_count / frq) -+ } ++impl TryFrom for GenericTimerCounterValue { ++ type Error = &'static str; + -+ fn spin_for(&self, duration: Duration) { -+ // Instantly return on zero. -+ if duration.as_nanos() == 0 { -+ return; ++ fn try_from(duration: Duration) -> Result { ++ if duration < resolution() { ++ return Ok(GenericTimerCounterValue(0)); + } + -+ // Calculate the register compare value. -+ let frq = CNTFRQ_EL0.get(); -+ let x = match frq.checked_mul(duration.as_nanos() as u64) { -+ #[allow(unused_imports)] -+ None => { -+ warn!("Spin duration too long, skipping"); -+ return; -+ } -+ Some(val) => val, -+ }; -+ let tval = x / NS_PER_S; -+ -+ // Check if it is within supported bounds. -+ let warn: Option<&str> = if tval == 0 { -+ Some("smaller") -+ // The upper 32 bits of CNTP_TVAL_EL0 are reserved. -+ } else if tval > u32::max_value().into() { -+ Some("bigger") -+ } else { -+ None -+ }; -+ -+ #[allow(unused_imports)] -+ if let Some(w) = warn { -+ warn!( -+ "Spin duration {} than architecturally supported, skipping", -+ w -+ ); -+ return; ++ if duration > max_duration() { ++ return Err("Conversion error. Duration too big"); + } + -+ // Set the compare value register. -+ CNTP_TVAL_EL0.set(tval); ++ // This is safe, because all the safety requirements as stated in read_volatile()'s ++ // documentation are fulfilled. ++ let frequency: u128 = ++ unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; ++ let duration: u128 = duration.as_nanos(); + -+ // Kick off the counting. // Disable timer interrupt. -+ CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); ++ // 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)); + -+ // ISTATUS will be '1' when cval ticks have passed. Busy-check it. -+ while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} -+ -+ // Disable counting again. -+ CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); ++ // 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. ++ unsafe { 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 {} ++} diff -uNr 06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs 07_timestamps/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs --- 06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -380,7 +429,7 @@ diff -uNr 06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs 07_times #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::cpu; -+ use crate::{time, time::interface::TimeManager}; ++ use crate::time; + use core::time::Duration; - // Make an educated guess for a good delay value (Sequence described in the BCM2837 @@ -534,7 +583,20 @@ diff -uNr 06_uart_chainloader/src/cpu.rs 07_timestamps/src/cpu.rs diff -uNr 06_uart_chainloader/src/main.rs 07_timestamps/src/main.rs --- 06_uart_chainloader/src/main.rs +++ 07_timestamps/src/main.rs -@@ -121,6 +121,7 @@ +@@ -108,9 +108,12 @@ + + #![allow(clippy::upper_case_acronyms)] + #![feature(asm_const)] ++#![feature(const_option)] + #![feature(format_args_nl)] ++#![feature(nonzero_min_max)] + #![feature(panic_info_message)] + #![feature(trait_alias)] ++#![feature(unchecked_math)] + #![no_main] + #![no_std] + +@@ -121,6 +124,7 @@ mod panic_wait; mod print; mod synchronization; @@ -542,7 +604,7 @@ diff -uNr 06_uart_chainloader/src/main.rs 07_timestamps/src/main.rs /// Early init code. /// -@@ -143,55 +144,38 @@ +@@ -143,55 +147,37 @@ kernel_main() } @@ -558,7 +620,6 @@ diff -uNr 06_uart_chainloader/src/main.rs 07_timestamps/src/main.rs - use console::console; + use core::time::Duration; + use driver::interface::DriverManager; -+ use time::interface::TimeManager; - println!("{}", MINILOAD_LOGO); - println!("{:^37}", bsp::board_name()); @@ -630,12 +691,7 @@ diff -uNr 06_uart_chainloader/src/main.rs 07_timestamps/src/main.rs diff -uNr 06_uart_chainloader/src/panic_wait.rs 07_timestamps/src/panic_wait.rs --- 06_uart_chainloader/src/panic_wait.rs +++ 07_timestamps/src/panic_wait.rs -@@ -42,18 +42,23 @@ - - #[panic_handler] - fn panic(info: &PanicInfo) -> ! { -+ use crate::time::interface::TimeManager; -+ +@@ -45,15 +45,18 @@ // Protect against panic infinite loops if any of the following code panics itself. panic_prevent_reenter(); @@ -659,7 +715,7 @@ diff -uNr 06_uart_chainloader/src/panic_wait.rs 07_timestamps/src/panic_wait.rs diff -uNr 06_uart_chainloader/src/print.rs 07_timestamps/src/print.rs --- 06_uart_chainloader/src/print.rs +++ 07_timestamps/src/print.rs -@@ -34,3 +34,59 @@ +@@ -34,3 +34,51 @@ $crate::print::_print(format_args_nl!($($arg)*)); }) } @@ -668,8 +724,6 @@ diff -uNr 06_uart_chainloader/src/print.rs 07_timestamps/src/print.rs +#[macro_export] +macro_rules! info { + ($string:expr) => ({ -+ use $crate::time::interface::TimeManager; -+ + let timestamp = $crate::time::time_manager().uptime(); + + $crate::print::_print(format_args_nl!( @@ -679,8 +733,6 @@ diff -uNr 06_uart_chainloader/src/print.rs 07_timestamps/src/print.rs + )); + }); + ($format_string:expr, $($arg:tt)*) => ({ -+ use $crate::time::interface::TimeManager; -+ + let timestamp = $crate::time::time_manager().uptime(); + + $crate::print::_print(format_args_nl!( @@ -696,8 +748,6 @@ diff -uNr 06_uart_chainloader/src/print.rs 07_timestamps/src/print.rs +#[macro_export] +macro_rules! warn { + ($string:expr) => ({ -+ use $crate::time::interface::TimeManager; -+ + let timestamp = $crate::time::time_manager().uptime(); + + $crate::print::_print(format_args_nl!( @@ -707,8 +757,6 @@ diff -uNr 06_uart_chainloader/src/print.rs 07_timestamps/src/print.rs + )); + }); + ($format_string:expr, $($arg:tt)*) => ({ -+ use $crate::time::interface::TimeManager; -+ + let timestamp = $crate::time::time_manager().uptime(); + + $crate::print::_print(format_args_nl!( @@ -723,7 +771,7 @@ diff -uNr 06_uart_chainloader/src/print.rs 07_timestamps/src/print.rs diff -uNr 06_uart_chainloader/src/time.rs 07_timestamps/src/time.rs --- 06_uart_chainloader/src/time.rs +++ 07_timestamps/src/time.rs -@@ -0,0 +1,37 @@ +@@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2020-2022 Andre Richter @@ -734,31 +782,51 @@ diff -uNr 06_uart_chainloader/src/time.rs 07_timestamps/src/time.rs +#[path = "_arch/aarch64/time.rs"] +mod arch_time; + ++use core::time::Duration; ++ +//-------------------------------------------------------------------------------------------------- -+// Architectural Public Reexports ++// Public Definitions +//-------------------------------------------------------------------------------------------------- -+pub use arch_time::time_manager; ++ ++/// Provides time management functions. ++pub struct TimeManager; + +//-------------------------------------------------------------------------------------------------- -+// Public Definitions ++// Global instances +//-------------------------------------------------------------------------------------------------- + -+/// Timekeeping interfaces. -+pub mod interface { -+ use core::time::Duration; ++static TIME_MANAGER: TimeManager = TimeManager::new(); + -+ /// Time management functions. -+ pub trait TimeManager { -+ /// The timer's resolution. -+ fn resolution(&self) -> Duration; ++//-------------------------------------------------------------------------------------------------- ++// Public Code ++//-------------------------------------------------------------------------------------------------- + -+ /// The uptime since power-on of the device. -+ /// -+ /// This includes time consumed by firmware and bootloaders. -+ fn uptime(&self) -> Duration; ++/// 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. -+ fn spin_for(&self, duration: Duration); ++ /// Spin for a given duration. ++ pub fn spin_for(&self, duration: Duration) { ++ arch_time::spin_for(duration) + } +} diff --git a/07_timestamps/src/_arch/aarch64/cpu/boot.s b/07_timestamps/src/_arch/aarch64/cpu/boot.s index 1f70169f..aa701fd1 100644 --- a/07_timestamps/src/_arch/aarch64/cpu/boot.s +++ b/07_timestamps/src/_arch/aarch64/cpu/boot.s @@ -52,6 +52,14 @@ _start: ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x2, CNTFRQ_EL0 + cmp x2, xzr + b.eq .L_parking_loop + str w2, [x1] + // Jump to Rust code. b _start_rust diff --git a/07_timestamps/src/_arch/aarch64/time.rs b/07_timestamps/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/07_timestamps/src/_arch/aarch64/time.rs +++ b/07_timestamps/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/07_timestamps/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/07_timestamps/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index 24958eb5..24e537cf 100644 --- a/07_timestamps/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/07_timestamps/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -140,7 +140,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/07_timestamps/src/main.rs b/07_timestamps/src/main.rs index 9ecf3f97..90c39a37 100644 --- a/07_timestamps/src/main.rs +++ b/07_timestamps/src/main.rs @@ -108,9 +108,12 @@ #![allow(clippy::upper_case_acronyms)] #![feature(asm_const)] +#![feature(const_option)] #![feature(format_args_nl)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_main] #![no_std] @@ -148,7 +151,6 @@ unsafe fn kernel_init() -> ! { fn kernel_main() -> ! { use core::time::Duration; use driver::interface::DriverManager; - use time::interface::TimeManager; info!( "{} version {}", diff --git a/07_timestamps/src/panic_wait.rs b/07_timestamps/src/panic_wait.rs index edd83885..ccf54f61 100644 --- a/07_timestamps/src/panic_wait.rs +++ b/07_timestamps/src/panic_wait.rs @@ -42,8 +42,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - // Protect against panic infinite loops if any of the following code panics itself. panic_prevent_reenter(); diff --git a/07_timestamps/src/print.rs b/07_timestamps/src/print.rs index 8705eec0..fe13b334 100644 --- a/07_timestamps/src/print.rs +++ b/07_timestamps/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/07_timestamps/src/time.rs b/07_timestamps/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/07_timestamps/src/time.rs +++ b/07_timestamps/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/08_hw_debug_JTAG/src/_arch/aarch64/cpu/boot.s b/08_hw_debug_JTAG/src/_arch/aarch64/cpu/boot.s index 1f70169f..aa701fd1 100644 --- a/08_hw_debug_JTAG/src/_arch/aarch64/cpu/boot.s +++ b/08_hw_debug_JTAG/src/_arch/aarch64/cpu/boot.s @@ -52,6 +52,14 @@ _start: ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x2, CNTFRQ_EL0 + cmp x2, xzr + b.eq .L_parking_loop + str w2, [x1] + // Jump to Rust code. b _start_rust diff --git a/08_hw_debug_JTAG/src/_arch/aarch64/time.rs b/08_hw_debug_JTAG/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/08_hw_debug_JTAG/src/_arch/aarch64/time.rs +++ b/08_hw_debug_JTAG/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/08_hw_debug_JTAG/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/08_hw_debug_JTAG/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index 24958eb5..24e537cf 100644 --- a/08_hw_debug_JTAG/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/08_hw_debug_JTAG/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -140,7 +140,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/08_hw_debug_JTAG/src/main.rs b/08_hw_debug_JTAG/src/main.rs index 9ecf3f97..90c39a37 100644 --- a/08_hw_debug_JTAG/src/main.rs +++ b/08_hw_debug_JTAG/src/main.rs @@ -108,9 +108,12 @@ #![allow(clippy::upper_case_acronyms)] #![feature(asm_const)] +#![feature(const_option)] #![feature(format_args_nl)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_main] #![no_std] @@ -148,7 +151,6 @@ unsafe fn kernel_init() -> ! { fn kernel_main() -> ! { use core::time::Duration; use driver::interface::DriverManager; - use time::interface::TimeManager; info!( "{} version {}", diff --git a/08_hw_debug_JTAG/src/panic_wait.rs b/08_hw_debug_JTAG/src/panic_wait.rs index edd83885..ccf54f61 100644 --- a/08_hw_debug_JTAG/src/panic_wait.rs +++ b/08_hw_debug_JTAG/src/panic_wait.rs @@ -42,8 +42,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - // Protect against panic infinite loops if any of the following code panics itself. panic_prevent_reenter(); diff --git a/08_hw_debug_JTAG/src/print.rs b/08_hw_debug_JTAG/src/print.rs index 8705eec0..fe13b334 100644 --- a/08_hw_debug_JTAG/src/print.rs +++ b/08_hw_debug_JTAG/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/08_hw_debug_JTAG/src/time.rs b/08_hw_debug_JTAG/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/08_hw_debug_JTAG/src/time.rs +++ b/08_hw_debug_JTAG/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/09_privilege_level/README.md b/09_privilege_level/README.md index dcf0f929..afcfe8f6 100644 --- a/09_privilege_level/README.md +++ b/09_privilege_level/README.md @@ -303,7 +303,7 @@ diff -uNr 08_hw_debug_JTAG/src/_arch/aarch64/cpu/boot.s 09_privilege_level/src/_ // Only proceed on the boot core. Park it otherwise. mrs x1, MPIDR_EL1 and x1, x1, {CONST_CORE_ID_MASK} -@@ -48,11 +53,11 @@ +@@ -48,7 +53,7 @@ // Prepare the jump to Rust code. .L_prepare_rust: @@ -312,6 +312,10 @@ diff -uNr 08_hw_debug_JTAG/src/_arch/aarch64/cpu/boot.s 09_privilege_level/src/_ ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 +@@ -60,7 +65,7 @@ + b.eq .L_parking_loop + str w2, [x1] + - // Jump to Rust code. + // Jump to Rust code. x0 holds the function argument provided to _start_rust(). b _start_rust @@ -498,7 +502,7 @@ diff -uNr 08_hw_debug_JTAG/src/exception.rs 09_privilege_level/src/exception.rs diff -uNr 08_hw_debug_JTAG/src/main.rs 09_privilege_level/src/main.rs --- 08_hw_debug_JTAG/src/main.rs +++ 09_privilege_level/src/main.rs -@@ -118,6 +118,7 @@ +@@ -121,6 +121,7 @@ mod console; mod cpu; mod driver; @@ -506,15 +510,15 @@ diff -uNr 08_hw_debug_JTAG/src/main.rs 09_privilege_level/src/main.rs mod panic_wait; mod print; mod synchronization; -@@ -146,6 +147,7 @@ +@@ -149,6 +150,7 @@ /// The main function running after the early init. fn kernel_main() -> ! { + use console::console; use core::time::Duration; use driver::interface::DriverManager; - use time::interface::TimeManager; -@@ -157,6 +159,12 @@ + +@@ -159,6 +161,12 @@ ); info!("Booting on: {}", bsp::board_name()); @@ -527,7 +531,7 @@ diff -uNr 08_hw_debug_JTAG/src/main.rs 09_privilege_level/src/main.rs info!( "Architectural timer resolution: {} ns", time::time_manager().resolution().as_nanos() -@@ -171,11 +179,15 @@ +@@ -173,11 +181,15 @@ info!(" {}. {}", i + 1, driver.compatible()); } diff --git a/09_privilege_level/src/_arch/aarch64/cpu/boot.s b/09_privilege_level/src/_arch/aarch64/cpu/boot.s index 7576dc14..f6df2123 100644 --- a/09_privilege_level/src/_arch/aarch64/cpu/boot.s +++ b/09_privilege_level/src/_arch/aarch64/cpu/boot.s @@ -57,6 +57,14 @@ _start: ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x2, CNTFRQ_EL0 + cmp x2, xzr + b.eq .L_parking_loop + str w2, [x1] + // Jump to Rust code. x0 holds the function argument provided to _start_rust(). b _start_rust diff --git a/09_privilege_level/src/_arch/aarch64/time.rs b/09_privilege_level/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/09_privilege_level/src/_arch/aarch64/time.rs +++ b/09_privilege_level/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/09_privilege_level/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/09_privilege_level/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index 24958eb5..24e537cf 100644 --- a/09_privilege_level/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/09_privilege_level/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -140,7 +140,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/09_privilege_level/src/main.rs b/09_privilege_level/src/main.rs index 6c1ab310..79a6716e 100644 --- a/09_privilege_level/src/main.rs +++ b/09_privilege_level/src/main.rs @@ -108,9 +108,12 @@ #![allow(clippy::upper_case_acronyms)] #![feature(asm_const)] +#![feature(const_option)] #![feature(format_args_nl)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_main] #![no_std] @@ -150,7 +153,6 @@ fn kernel_main() -> ! { use console::console; use core::time::Duration; use driver::interface::DriverManager; - use time::interface::TimeManager; info!( "{} version {}", diff --git a/09_privilege_level/src/panic_wait.rs b/09_privilege_level/src/panic_wait.rs index edd83885..ccf54f61 100644 --- a/09_privilege_level/src/panic_wait.rs +++ b/09_privilege_level/src/panic_wait.rs @@ -42,8 +42,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - // Protect against panic infinite loops if any of the following code panics itself. panic_prevent_reenter(); diff --git a/09_privilege_level/src/print.rs b/09_privilege_level/src/print.rs index 8705eec0..fe13b334 100644 --- a/09_privilege_level/src/print.rs +++ b/09_privilege_level/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/09_privilege_level/src/time.rs b/09_privilege_level/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/09_privilege_level/src/time.rs +++ b/09_privilege_level/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/10_virtual_mem_part1_identity_mapping/README.md b/10_virtual_mem_part1_identity_mapping/README.md index 8345aef0..f1d22d98 100644 --- a/10_virtual_mem_part1_identity_mapping/README.md +++ b/10_virtual_mem_part1_identity_mapping/README.md @@ -1109,18 +1109,20 @@ diff -uNr 09_privilege_level/src/common.rs 10_virtual_mem_part1_identity_mapping diff -uNr 09_privilege_level/src/main.rs 10_virtual_mem_part1_identity_mapping/src/main.rs --- 09_privilege_level/src/main.rs +++ 10_virtual_mem_part1_identity_mapping/src/main.rs -@@ -107,18 +107,23 @@ +@@ -107,9 +107,12 @@ //! 2. Once finished with architectural setup, the arch code calls `kernel_init()`. #![allow(clippy::upper_case_acronyms)] +#![allow(incomplete_features)] #![feature(asm_const)] + #![feature(const_option)] +#![feature(core_intrinsics)] #![feature(format_args_nl)] +#![feature(int_roundings)] + #![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(trait_alias)] - #![no_main] +@@ -118,10 +121,12 @@ #![no_std] mod bsp; @@ -1133,7 +1135,7 @@ diff -uNr 09_privilege_level/src/main.rs 10_virtual_mem_part1_identity_mapping/s mod panic_wait; mod print; mod synchronization; -@@ -129,9 +134,17 @@ +@@ -132,9 +137,17 @@ /// # Safety /// /// - Only a single core must be active and running this function. @@ -1152,7 +1154,7 @@ diff -uNr 09_privilege_level/src/main.rs 10_virtual_mem_part1_identity_mapping/s for i in bsp::driver::driver_manager().all_device_drivers().iter() { if let Err(x) = i.init() { -@@ -147,7 +160,7 @@ +@@ -150,7 +163,7 @@ /// The main function running after the early init. fn kernel_main() -> ! { @@ -1160,8 +1162,8 @@ diff -uNr 09_privilege_level/src/main.rs 10_virtual_mem_part1_identity_mapping/s + use console::{console, interface::Write}; use core::time::Duration; use driver::interface::DriverManager; - use time::interface::TimeManager; -@@ -159,6 +172,9 @@ + +@@ -161,6 +174,9 @@ ); info!("Booting on: {}", bsp::board_name()); @@ -1171,7 +1173,7 @@ diff -uNr 09_privilege_level/src/main.rs 10_virtual_mem_part1_identity_mapping/s let (_, privilege_level) = exception::current_privilege_level(); info!("Current privilege level: {}", privilege_level); -@@ -182,6 +198,13 @@ +@@ -184,6 +200,13 @@ info!("Timer test, spinning for 1 second"); time::time_manager().spin_for(Duration::from_secs(1)); diff --git a/10_virtual_mem_part1_identity_mapping/src/_arch/aarch64/cpu/boot.s b/10_virtual_mem_part1_identity_mapping/src/_arch/aarch64/cpu/boot.s index 7576dc14..f6df2123 100644 --- a/10_virtual_mem_part1_identity_mapping/src/_arch/aarch64/cpu/boot.s +++ b/10_virtual_mem_part1_identity_mapping/src/_arch/aarch64/cpu/boot.s @@ -57,6 +57,14 @@ _start: ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x2, CNTFRQ_EL0 + cmp x2, xzr + b.eq .L_parking_loop + str w2, [x1] + // Jump to Rust code. x0 holds the function argument provided to _start_rust(). b _start_rust diff --git a/10_virtual_mem_part1_identity_mapping/src/_arch/aarch64/time.rs b/10_virtual_mem_part1_identity_mapping/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/10_virtual_mem_part1_identity_mapping/src/_arch/aarch64/time.rs +++ b/10_virtual_mem_part1_identity_mapping/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index 24958eb5..24e537cf 100644 --- a/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -140,7 +140,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/10_virtual_mem_part1_identity_mapping/src/main.rs b/10_virtual_mem_part1_identity_mapping/src/main.rs index 40b6d13b..e038e093 100644 --- a/10_virtual_mem_part1_identity_mapping/src/main.rs +++ b/10_virtual_mem_part1_identity_mapping/src/main.rs @@ -109,11 +109,14 @@ #![allow(clippy::upper_case_acronyms)] #![allow(incomplete_features)] #![feature(asm_const)] +#![feature(const_option)] #![feature(core_intrinsics)] #![feature(format_args_nl)] #![feature(int_roundings)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_main] #![no_std] @@ -163,7 +166,6 @@ fn kernel_main() -> ! { use console::{console, interface::Write}; use core::time::Duration; use driver::interface::DriverManager; - use time::interface::TimeManager; info!( "{} version {}", diff --git a/10_virtual_mem_part1_identity_mapping/src/panic_wait.rs b/10_virtual_mem_part1_identity_mapping/src/panic_wait.rs index edd83885..ccf54f61 100644 --- a/10_virtual_mem_part1_identity_mapping/src/panic_wait.rs +++ b/10_virtual_mem_part1_identity_mapping/src/panic_wait.rs @@ -42,8 +42,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - // Protect against panic infinite loops if any of the following code panics itself. panic_prevent_reenter(); diff --git a/10_virtual_mem_part1_identity_mapping/src/print.rs b/10_virtual_mem_part1_identity_mapping/src/print.rs index 8705eec0..fe13b334 100644 --- a/10_virtual_mem_part1_identity_mapping/src/print.rs +++ b/10_virtual_mem_part1_identity_mapping/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/10_virtual_mem_part1_identity_mapping/src/time.rs b/10_virtual_mem_part1_identity_mapping/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/10_virtual_mem_part1_identity_mapping/src/time.rs +++ b/10_virtual_mem_part1_identity_mapping/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/11_exceptions_part1_groundwork/README.md b/11_exceptions_part1_groundwork/README.md index 4fc713a5..3e7ca05e 100644 --- a/11_exceptions_part1_groundwork/README.md +++ b/11_exceptions_part1_groundwork/README.md @@ -1024,7 +1024,7 @@ diff -uNr 10_virtual_mem_part1_identity_mapping/src/exception.rs 11_exceptions_p diff -uNr 10_virtual_mem_part1_identity_mapping/src/main.rs 11_exceptions_part1_groundwork/src/main.rs --- 10_virtual_mem_part1_identity_mapping/src/main.rs +++ 11_exceptions_part1_groundwork/src/main.rs -@@ -142,6 +142,8 @@ +@@ -145,6 +145,8 @@ use driver::interface::DriverManager; use memory::mmu::interface::MMU; @@ -1033,7 +1033,7 @@ diff -uNr 10_virtual_mem_part1_identity_mapping/src/main.rs 11_exceptions_part1_ if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() { panic!("MMU: {}", string); } -@@ -160,7 +162,7 @@ +@@ -163,7 +165,7 @@ /// The main function running after the early init. fn kernel_main() -> ! { @@ -1041,8 +1041,8 @@ diff -uNr 10_virtual_mem_part1_identity_mapping/src/main.rs 11_exceptions_part1_ + use console::console; use core::time::Duration; use driver::interface::DriverManager; - use time::interface::TimeManager; -@@ -198,13 +200,28 @@ + +@@ -200,13 +202,28 @@ info!("Timer test, spinning for 1 second"); time::time_manager().spin_for(Duration::from_secs(1)); diff --git a/11_exceptions_part1_groundwork/src/_arch/aarch64/cpu/boot.s b/11_exceptions_part1_groundwork/src/_arch/aarch64/cpu/boot.s index 7576dc14..f6df2123 100644 --- a/11_exceptions_part1_groundwork/src/_arch/aarch64/cpu/boot.s +++ b/11_exceptions_part1_groundwork/src/_arch/aarch64/cpu/boot.s @@ -57,6 +57,14 @@ _start: ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x2, CNTFRQ_EL0 + cmp x2, xzr + b.eq .L_parking_loop + str w2, [x1] + // Jump to Rust code. x0 holds the function argument provided to _start_rust(). b _start_rust diff --git a/11_exceptions_part1_groundwork/src/_arch/aarch64/time.rs b/11_exceptions_part1_groundwork/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/11_exceptions_part1_groundwork/src/_arch/aarch64/time.rs +++ b/11_exceptions_part1_groundwork/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index 24958eb5..24e537cf 100644 --- a/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -140,7 +140,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/11_exceptions_part1_groundwork/src/main.rs b/11_exceptions_part1_groundwork/src/main.rs index 585382a5..8e632fa5 100644 --- a/11_exceptions_part1_groundwork/src/main.rs +++ b/11_exceptions_part1_groundwork/src/main.rs @@ -109,11 +109,14 @@ #![allow(clippy::upper_case_acronyms)] #![allow(incomplete_features)] #![feature(asm_const)] +#![feature(const_option)] #![feature(core_intrinsics)] #![feature(format_args_nl)] #![feature(int_roundings)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_main] #![no_std] @@ -165,7 +168,6 @@ fn kernel_main() -> ! { use console::console; use core::time::Duration; use driver::interface::DriverManager; - use time::interface::TimeManager; info!( "{} version {}", diff --git a/11_exceptions_part1_groundwork/src/panic_wait.rs b/11_exceptions_part1_groundwork/src/panic_wait.rs index edd83885..ccf54f61 100644 --- a/11_exceptions_part1_groundwork/src/panic_wait.rs +++ b/11_exceptions_part1_groundwork/src/panic_wait.rs @@ -42,8 +42,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - // Protect against panic infinite loops if any of the following code panics itself. panic_prevent_reenter(); diff --git a/11_exceptions_part1_groundwork/src/print.rs b/11_exceptions_part1_groundwork/src/print.rs index 8705eec0..fe13b334 100644 --- a/11_exceptions_part1_groundwork/src/print.rs +++ b/11_exceptions_part1_groundwork/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/11_exceptions_part1_groundwork/src/time.rs b/11_exceptions_part1_groundwork/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/11_exceptions_part1_groundwork/src/time.rs +++ b/11_exceptions_part1_groundwork/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/12_integrated_testing/README.md b/12_integrated_testing/README.md index e0d0dd1d..c5d032e8 100644 --- a/12_integrated_testing/README.md +++ b/12_integrated_testing/README.md @@ -622,7 +622,7 @@ your test code into individual chunks. For example, take a look at `tests/01_tim #![test_runner(libkernel::test_runner)] use core::time::Duration; -use libkernel::{bsp, cpu, driver, exception, time, time::interface::TimeManager}; +use libkernel::{bsp, cpu, driver, exception, time}; use test_macros::kernel_test; #[no_mangle] @@ -643,6 +643,7 @@ unsafe fn kernel_init() -> ! { #[kernel_test] fn timer_is_counting() { assert!(time::time_manager().uptime().as_nanos() > 0) + assert!(time::time_manager().resolution().as_nanos() < 100) } /// Timer resolution must be sufficient. diff --git a/12_integrated_testing/kernel/src/_arch/aarch64/cpu/boot.s b/12_integrated_testing/kernel/src/_arch/aarch64/cpu/boot.s index 7576dc14..f6df2123 100644 --- a/12_integrated_testing/kernel/src/_arch/aarch64/cpu/boot.s +++ b/12_integrated_testing/kernel/src/_arch/aarch64/cpu/boot.s @@ -57,6 +57,14 @@ _start: ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x2, CNTFRQ_EL0 + cmp x2, xzr + b.eq .L_parking_loop + str w2, [x1] + // Jump to Rust code. x0 holds the function argument provided to _start_rust(). b _start_rust diff --git a/12_integrated_testing/kernel/src/_arch/aarch64/time.rs b/12_integrated_testing/kernel/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/12_integrated_testing/kernel/src/_arch/aarch64/time.rs +++ b/12_integrated_testing/kernel/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/12_integrated_testing/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/12_integrated_testing/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index 24958eb5..24e537cf 100644 --- a/12_integrated_testing/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/12_integrated_testing/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -140,7 +140,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/12_integrated_testing/kernel/src/lib.rs b/12_integrated_testing/kernel/src/lib.rs index 4a41bbfe..35698c74 100644 --- a/12_integrated_testing/kernel/src/lib.rs +++ b/12_integrated_testing/kernel/src/lib.rs @@ -111,12 +111,15 @@ #![allow(clippy::upper_case_acronyms)] #![allow(incomplete_features)] #![feature(asm_const)] +#![feature(const_option)] #![feature(core_intrinsics)] #![feature(format_args_nl)] #![feature(int_roundings)] #![feature(linkage)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_std] // Testing #![cfg_attr(test, no_main)] diff --git a/12_integrated_testing/kernel/src/panic_wait.rs b/12_integrated_testing/kernel/src/panic_wait.rs index 8bedfded..da779008 100644 --- a/12_integrated_testing/kernel/src/panic_wait.rs +++ b/12_integrated_testing/kernel/src/panic_wait.rs @@ -59,8 +59,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - // Protect against panic infinite loops if any of the following code panics itself. panic_prevent_reenter(); diff --git a/12_integrated_testing/kernel/src/print.rs b/12_integrated_testing/kernel/src/print.rs index 8705eec0..fe13b334 100644 --- a/12_integrated_testing/kernel/src/print.rs +++ b/12_integrated_testing/kernel/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/12_integrated_testing/kernel/src/time.rs b/12_integrated_testing/kernel/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/12_integrated_testing/kernel/src/time.rs +++ b/12_integrated_testing/kernel/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/12_integrated_testing/kernel/tests/01_timer_sanity.rs b/12_integrated_testing/kernel/tests/01_timer_sanity.rs index 390cdee5..a0eb732b 100644 --- a/12_integrated_testing/kernel/tests/01_timer_sanity.rs +++ b/12_integrated_testing/kernel/tests/01_timer_sanity.rs @@ -11,7 +11,7 @@ #![test_runner(libkernel::test_runner)] use core::time::Duration; -use libkernel::{bsp, cpu, driver, exception, time, time::interface::TimeManager}; +use libkernel::{bsp, cpu, driver, exception, time}; use test_macros::kernel_test; #[no_mangle] @@ -37,6 +37,7 @@ fn timer_is_counting() { /// Timer resolution must be sufficient. #[kernel_test] fn timer_resolution_is_sufficient() { + assert!(time::time_manager().resolution().as_nanos() > 0); assert!(time::time_manager().resolution().as_nanos() < 100) } diff --git a/13_exceptions_part2_peripheral_IRQs/README.md b/13_exceptions_part2_peripheral_IRQs/README.md index 77bcc2f2..3b7a8f64 100644 --- a/13_exceptions_part2_peripheral_IRQs/README.md +++ b/13_exceptions_part2_peripheral_IRQs/README.md @@ -2385,7 +2385,7 @@ diff -uNr 12_integrated_testing/kernel/src/exception/asynchronous.rs 13_exceptio diff -uNr 12_integrated_testing/kernel/src/lib.rs 13_exceptions_part2_peripheral_IRQs/kernel/src/lib.rs --- 12_integrated_testing/kernel/src/lib.rs +++ 13_exceptions_part2_peripheral_IRQs/kernel/src/lib.rs -@@ -135,6 +135,7 @@ +@@ -138,6 +138,7 @@ pub mod exception; pub mod memory; pub mod print; @@ -2474,10 +2474,10 @@ diff -uNr 12_integrated_testing/kernel/src/panic_wait.rs 13_exceptions_part2_per use core::panic::PanicInfo; //-------------------------------------------------------------------------------------------------- -@@ -61,6 +61,8 @@ - fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; +@@ -59,6 +59,8 @@ + #[panic_handler] + fn panic(info: &PanicInfo) -> ! { + exception::asynchronous::local_irq_mask(); + // Protect against panic infinite loops if any of the following code panics itself. diff --git a/13_exceptions_part2_peripheral_IRQs/kernel/src/_arch/aarch64/cpu/boot.s b/13_exceptions_part2_peripheral_IRQs/kernel/src/_arch/aarch64/cpu/boot.s index 7576dc14..f6df2123 100644 --- a/13_exceptions_part2_peripheral_IRQs/kernel/src/_arch/aarch64/cpu/boot.s +++ b/13_exceptions_part2_peripheral_IRQs/kernel/src/_arch/aarch64/cpu/boot.s @@ -57,6 +57,14 @@ _start: ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x2, CNTFRQ_EL0 + cmp x2, xzr + b.eq .L_parking_loop + str w2, [x1] + // Jump to Rust code. x0 holds the function argument provided to _start_rust(). b _start_rust diff --git a/13_exceptions_part2_peripheral_IRQs/kernel/src/_arch/aarch64/time.rs b/13_exceptions_part2_peripheral_IRQs/kernel/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/13_exceptions_part2_peripheral_IRQs/kernel/src/_arch/aarch64/time.rs +++ b/13_exceptions_part2_peripheral_IRQs/kernel/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/13_exceptions_part2_peripheral_IRQs/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/13_exceptions_part2_peripheral_IRQs/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index b5603b40..0f3e701f 100644 --- a/13_exceptions_part2_peripheral_IRQs/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/13_exceptions_part2_peripheral_IRQs/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -140,7 +140,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/13_exceptions_part2_peripheral_IRQs/kernel/src/lib.rs b/13_exceptions_part2_peripheral_IRQs/kernel/src/lib.rs index 6c5bc6b3..194e2455 100644 --- a/13_exceptions_part2_peripheral_IRQs/kernel/src/lib.rs +++ b/13_exceptions_part2_peripheral_IRQs/kernel/src/lib.rs @@ -111,12 +111,15 @@ #![allow(clippy::upper_case_acronyms)] #![allow(incomplete_features)] #![feature(asm_const)] +#![feature(const_option)] #![feature(core_intrinsics)] #![feature(format_args_nl)] #![feature(int_roundings)] #![feature(linkage)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_std] // Testing #![cfg_attr(test, no_main)] diff --git a/13_exceptions_part2_peripheral_IRQs/kernel/src/panic_wait.rs b/13_exceptions_part2_peripheral_IRQs/kernel/src/panic_wait.rs index 61831213..ae4651e7 100644 --- a/13_exceptions_part2_peripheral_IRQs/kernel/src/panic_wait.rs +++ b/13_exceptions_part2_peripheral_IRQs/kernel/src/panic_wait.rs @@ -59,8 +59,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - exception::asynchronous::local_irq_mask(); // Protect against panic infinite loops if any of the following code panics itself. diff --git a/13_exceptions_part2_peripheral_IRQs/kernel/src/print.rs b/13_exceptions_part2_peripheral_IRQs/kernel/src/print.rs index 8705eec0..fe13b334 100644 --- a/13_exceptions_part2_peripheral_IRQs/kernel/src/print.rs +++ b/13_exceptions_part2_peripheral_IRQs/kernel/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/13_exceptions_part2_peripheral_IRQs/kernel/src/time.rs b/13_exceptions_part2_peripheral_IRQs/kernel/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/13_exceptions_part2_peripheral_IRQs/kernel/src/time.rs +++ b/13_exceptions_part2_peripheral_IRQs/kernel/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/13_exceptions_part2_peripheral_IRQs/kernel/tests/01_timer_sanity.rs b/13_exceptions_part2_peripheral_IRQs/kernel/tests/01_timer_sanity.rs index 390cdee5..a0eb732b 100644 --- a/13_exceptions_part2_peripheral_IRQs/kernel/tests/01_timer_sanity.rs +++ b/13_exceptions_part2_peripheral_IRQs/kernel/tests/01_timer_sanity.rs @@ -11,7 +11,7 @@ #![test_runner(libkernel::test_runner)] use core::time::Duration; -use libkernel::{bsp, cpu, driver, exception, time, time::interface::TimeManager}; +use libkernel::{bsp, cpu, driver, exception, time}; use test_macros::kernel_test; #[no_mangle] @@ -37,6 +37,7 @@ fn timer_is_counting() { /// Timer resolution must be sufficient. #[kernel_test] fn timer_resolution_is_sufficient() { + assert!(time::time_manager().resolution().as_nanos() > 0); assert!(time::time_manager().resolution().as_nanos() < 100) } diff --git a/14_virtual_mem_part2_mmio_remap/README.md b/14_virtual_mem_part2_mmio_remap/README.md index c6ef13f7..7dc0c7a3 100644 --- a/14_virtual_mem_part2_mmio_remap/README.md +++ b/14_virtual_mem_part2_mmio_remap/README.md @@ -2326,20 +2326,21 @@ diff -uNr 13_exceptions_part2_peripheral_IRQs/kernel/src/exception/asynchronous. diff -uNr 13_exceptions_part2_peripheral_IRQs/kernel/src/lib.rs 14_virtual_mem_part2_mmio_remap/kernel/src/lib.rs --- 13_exceptions_part2_peripheral_IRQs/kernel/src/lib.rs +++ 14_virtual_mem_part2_mmio_remap/kernel/src/lib.rs -@@ -113,9 +113,12 @@ - #![feature(asm_const)] +@@ -114,10 +114,13 @@ + #![feature(const_option)] #![feature(core_intrinsics)] #![feature(format_args_nl)] +#![feature(generic_const_exprs)] #![feature(int_roundings)] +#![feature(is_sorted)] #![feature(linkage)] + #![feature(nonzero_min_max)] #![feature(panic_info_message)] +#![feature(step_trait)] #![feature(trait_alias)] + #![feature(unchecked_math)] #![no_std] - // Testing -@@ -183,6 +186,17 @@ +@@ -186,6 +189,17 @@ use driver::interface::DriverManager; exception::handling_init(); @@ -3821,8 +3822,8 @@ diff -uNr 13_exceptions_part2_peripheral_IRQs/kernel/tests/01_timer_sanity.rs 14 #![test_runner(libkernel::test_runner)] use core::time::Duration; --use libkernel::{bsp, cpu, driver, exception, time, time::interface::TimeManager}; -+use libkernel::{bsp, cpu, driver, exception, memory, time, time::interface::TimeManager}; +-use libkernel::{bsp, cpu, driver, exception, time}; ++use libkernel::{bsp, cpu, driver, exception, memory, time}; use test_macros::kernel_test; #[no_mangle] diff --git a/14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/cpu/boot.s b/14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/cpu/boot.s index 7576dc14..f6df2123 100644 --- a/14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/cpu/boot.s +++ b/14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/cpu/boot.s @@ -57,6 +57,14 @@ _start: ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x2, CNTFRQ_EL0 + cmp x2, xzr + b.eq .L_parking_loop + str w2, [x1] + // Jump to Rust code. x0 holds the function argument provided to _start_rust(). b _start_rust diff --git a/14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/time.rs b/14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/time.rs +++ b/14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/14_virtual_mem_part2_mmio_remap/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/14_virtual_mem_part2_mmio_remap/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index a3361c2c..2281e66a 100644 --- a/14_virtual_mem_part2_mmio_remap/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/14_virtual_mem_part2_mmio_remap/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -144,7 +144,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/14_virtual_mem_part2_mmio_remap/kernel/src/lib.rs b/14_virtual_mem_part2_mmio_remap/kernel/src/lib.rs index c40cb4e7..22fb5fea 100644 --- a/14_virtual_mem_part2_mmio_remap/kernel/src/lib.rs +++ b/14_virtual_mem_part2_mmio_remap/kernel/src/lib.rs @@ -111,15 +111,18 @@ #![allow(clippy::upper_case_acronyms)] #![allow(incomplete_features)] #![feature(asm_const)] +#![feature(const_option)] #![feature(core_intrinsics)] #![feature(format_args_nl)] #![feature(generic_const_exprs)] #![feature(int_roundings)] #![feature(is_sorted)] #![feature(linkage)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(step_trait)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_std] // Testing #![cfg_attr(test, no_main)] diff --git a/14_virtual_mem_part2_mmio_remap/kernel/src/panic_wait.rs b/14_virtual_mem_part2_mmio_remap/kernel/src/panic_wait.rs index 61831213..ae4651e7 100644 --- a/14_virtual_mem_part2_mmio_remap/kernel/src/panic_wait.rs +++ b/14_virtual_mem_part2_mmio_remap/kernel/src/panic_wait.rs @@ -59,8 +59,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - exception::asynchronous::local_irq_mask(); // Protect against panic infinite loops if any of the following code panics itself. diff --git a/14_virtual_mem_part2_mmio_remap/kernel/src/print.rs b/14_virtual_mem_part2_mmio_remap/kernel/src/print.rs index 8705eec0..fe13b334 100644 --- a/14_virtual_mem_part2_mmio_remap/kernel/src/print.rs +++ b/14_virtual_mem_part2_mmio_remap/kernel/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/14_virtual_mem_part2_mmio_remap/kernel/src/time.rs b/14_virtual_mem_part2_mmio_remap/kernel/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/14_virtual_mem_part2_mmio_remap/kernel/src/time.rs +++ b/14_virtual_mem_part2_mmio_remap/kernel/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/14_virtual_mem_part2_mmio_remap/kernel/tests/01_timer_sanity.rs b/14_virtual_mem_part2_mmio_remap/kernel/tests/01_timer_sanity.rs index dc3337e2..dd34b06c 100644 --- a/14_virtual_mem_part2_mmio_remap/kernel/tests/01_timer_sanity.rs +++ b/14_virtual_mem_part2_mmio_remap/kernel/tests/01_timer_sanity.rs @@ -11,7 +11,7 @@ #![test_runner(libkernel::test_runner)] use core::time::Duration; -use libkernel::{bsp, cpu, driver, exception, memory, time, time::interface::TimeManager}; +use libkernel::{bsp, cpu, driver, exception, memory, time}; use test_macros::kernel_test; #[no_mangle] @@ -48,6 +48,7 @@ fn timer_is_counting() { /// Timer resolution must be sufficient. #[kernel_test] fn timer_resolution_is_sufficient() { + assert!(time::time_manager().resolution().as_nanos() > 0); assert!(time::time_manager().resolution().as_nanos() < 100) } diff --git a/15_virtual_mem_part3_precomputed_tables/README.md b/15_virtual_mem_part3_precomputed_tables/README.md index 25c26c0a..a6ebe332 100644 --- a/15_virtual_mem_part3_precomputed_tables/README.md +++ b/15_virtual_mem_part3_precomputed_tables/README.md @@ -841,7 +841,7 @@ diff -uNr 14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/cpu/boot.rs 1 diff -uNr 14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/cpu/boot.s 15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/cpu/boot.s --- 14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/cpu/boot.s +++ 15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/cpu/boot.s -@@ -53,11 +53,14 @@ +@@ -53,19 +53,22 @@ // Prepare the jump to Rust code. .L_prepare_rust: @@ -854,6 +854,18 @@ diff -uNr 14_virtual_mem_part2_mmio_remap/kernel/src/_arch/aarch64/cpu/boot.s 15 + ADR_REL x1, __boot_core_stack_end_exclusive + mov sp, x1 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. +- ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs +- mrs x2, CNTFRQ_EL0 +- cmp x2, xzr ++ ADR_REL x2, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs ++ mrs x3, CNTFRQ_EL0 ++ cmp x3, xzr + b.eq .L_parking_loop +- str w2, [x1] ++ str w3, [x2] + - // Jump to Rust code. x0 holds the function argument provided to _start_rust(). + // Jump to Rust code. x0 and x1 hold the function arguments provided to _start_rust(). b _start_rust @@ -1324,7 +1336,7 @@ diff -uNr 14_virtual_mem_part2_mmio_remap/kernel/src/bsp/raspberrypi/memory/mmu. diff -uNr 14_virtual_mem_part2_mmio_remap/kernel/src/lib.rs 15_virtual_mem_part3_precomputed_tables/kernel/src/lib.rs --- 14_virtual_mem_part2_mmio_remap/kernel/src/lib.rs +++ 15_virtual_mem_part3_precomputed_tables/kernel/src/lib.rs -@@ -186,17 +186,7 @@ +@@ -189,17 +189,7 @@ use driver::interface::DriverManager; exception::handling_init(); diff --git a/15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/cpu/boot.s b/15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/cpu/boot.s index dba8c88e..48c3f8a7 100644 --- a/15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/cpu/boot.s +++ b/15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/cpu/boot.s @@ -60,6 +60,14 @@ _start: ADR_REL x1, __boot_core_stack_end_exclusive mov sp, x1 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x2, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x3, CNTFRQ_EL0 + cmp x3, xzr + b.eq .L_parking_loop + str w3, [x2] + // Jump to Rust code. x0 and x1 hold the function arguments provided to _start_rust(). b _start_rust diff --git a/15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/time.rs b/15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/time.rs +++ b/15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/15_virtual_mem_part3_precomputed_tables/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/15_virtual_mem_part3_precomputed_tables/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index a3361c2c..2281e66a 100644 --- a/15_virtual_mem_part3_precomputed_tables/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/15_virtual_mem_part3_precomputed_tables/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -144,7 +144,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/15_virtual_mem_part3_precomputed_tables/kernel/src/lib.rs b/15_virtual_mem_part3_precomputed_tables/kernel/src/lib.rs index a9577868..26c147d6 100644 --- a/15_virtual_mem_part3_precomputed_tables/kernel/src/lib.rs +++ b/15_virtual_mem_part3_precomputed_tables/kernel/src/lib.rs @@ -111,15 +111,18 @@ #![allow(clippy::upper_case_acronyms)] #![allow(incomplete_features)] #![feature(asm_const)] +#![feature(const_option)] #![feature(core_intrinsics)] #![feature(format_args_nl)] #![feature(generic_const_exprs)] #![feature(int_roundings)] #![feature(is_sorted)] #![feature(linkage)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(step_trait)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_std] // Testing #![cfg_attr(test, no_main)] diff --git a/15_virtual_mem_part3_precomputed_tables/kernel/src/panic_wait.rs b/15_virtual_mem_part3_precomputed_tables/kernel/src/panic_wait.rs index 61831213..ae4651e7 100644 --- a/15_virtual_mem_part3_precomputed_tables/kernel/src/panic_wait.rs +++ b/15_virtual_mem_part3_precomputed_tables/kernel/src/panic_wait.rs @@ -59,8 +59,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - exception::asynchronous::local_irq_mask(); // Protect against panic infinite loops if any of the following code panics itself. diff --git a/15_virtual_mem_part3_precomputed_tables/kernel/src/print.rs b/15_virtual_mem_part3_precomputed_tables/kernel/src/print.rs index 8705eec0..fe13b334 100644 --- a/15_virtual_mem_part3_precomputed_tables/kernel/src/print.rs +++ b/15_virtual_mem_part3_precomputed_tables/kernel/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/15_virtual_mem_part3_precomputed_tables/kernel/src/time.rs b/15_virtual_mem_part3_precomputed_tables/kernel/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/15_virtual_mem_part3_precomputed_tables/kernel/src/time.rs +++ b/15_virtual_mem_part3_precomputed_tables/kernel/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/15_virtual_mem_part3_precomputed_tables/kernel/tests/01_timer_sanity.rs b/15_virtual_mem_part3_precomputed_tables/kernel/tests/01_timer_sanity.rs index d5d76a5c..39899332 100644 --- a/15_virtual_mem_part3_precomputed_tables/kernel/tests/01_timer_sanity.rs +++ b/15_virtual_mem_part3_precomputed_tables/kernel/tests/01_timer_sanity.rs @@ -11,7 +11,7 @@ #![test_runner(libkernel::test_runner)] use core::time::Duration; -use libkernel::{bsp, cpu, driver, exception, memory, time, time::interface::TimeManager}; +use libkernel::{bsp, cpu, driver, exception, memory, time}; use test_macros::kernel_test; #[no_mangle] @@ -38,6 +38,7 @@ fn timer_is_counting() { /// Timer resolution must be sufficient. #[kernel_test] fn timer_resolution_is_sufficient() { + assert!(time::time_manager().resolution().as_nanos() > 0); assert!(time::time_manager().resolution().as_nanos() < 100) } diff --git a/16_virtual_mem_part4_higher_half_kernel/README.md b/16_virtual_mem_part4_higher_half_kernel/README.md index 443c7223..6918f897 100644 --- a/16_virtual_mem_part4_higher_half_kernel/README.md +++ b/16_virtual_mem_part4_higher_half_kernel/README.md @@ -412,7 +412,7 @@ diff -uNr 15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/cpu/b //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- -@@ -56,11 +68,23 @@ +@@ -56,19 +68,31 @@ // Load the base address of the kernel's translation tables. ldr x0, PHYS_KERNEL_TABLES_BASE_ADDR // provided by bsp/__board_name__/memory/mmu.rs @@ -435,6 +435,18 @@ diff -uNr 15_virtual_mem_part3_precomputed_tables/kernel/src/_arch/aarch64/cpu/b + ADR_REL x4, __boot_core_stack_end_exclusive + mov sp, x4 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. +- ADR_REL x2, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs +- mrs x3, CNTFRQ_EL0 +- cmp x3, xzr ++ ADR_REL x5, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs ++ mrs x6, CNTFRQ_EL0 ++ cmp x6, xzr + b.eq .L_parking_loop +- str w3, [x2] ++ str w6, [x5] + - // Jump to Rust code. x0 and x1 hold the function arguments provided to _start_rust(). + // Jump to Rust code. x0, x1 and x2 hold the function arguments provided to _start_rust(). b _start_rust @@ -731,7 +743,7 @@ diff -uNr 15_virtual_mem_part3_precomputed_tables/kernel/src/bsp/raspberrypi/mem diff -uNr 15_virtual_mem_part3_precomputed_tables/kernel/src/lib.rs 16_virtual_mem_part4_higher_half_kernel/kernel/src/lib.rs --- 15_virtual_mem_part3_precomputed_tables/kernel/src/lib.rs +++ 16_virtual_mem_part4_higher_half_kernel/kernel/src/lib.rs -@@ -154,11 +154,6 @@ +@@ -157,11 +157,6 @@ ) } diff --git a/16_virtual_mem_part4_higher_half_kernel/kernel/src/_arch/aarch64/cpu/boot.s b/16_virtual_mem_part4_higher_half_kernel/kernel/src/_arch/aarch64/cpu/boot.s index 8c70d035..bf6e32e4 100644 --- a/16_virtual_mem_part4_higher_half_kernel/kernel/src/_arch/aarch64/cpu/boot.s +++ b/16_virtual_mem_part4_higher_half_kernel/kernel/src/_arch/aarch64/cpu/boot.s @@ -84,6 +84,14 @@ _start: ADR_REL x4, __boot_core_stack_end_exclusive mov sp, x4 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x5, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x6, CNTFRQ_EL0 + cmp x6, xzr + b.eq .L_parking_loop + str w6, [x5] + // Jump to Rust code. x0, x1 and x2 hold the function arguments provided to _start_rust(). b _start_rust diff --git a/16_virtual_mem_part4_higher_half_kernel/kernel/src/_arch/aarch64/time.rs b/16_virtual_mem_part4_higher_half_kernel/kernel/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/16_virtual_mem_part4_higher_half_kernel/kernel/src/_arch/aarch64/time.rs +++ b/16_virtual_mem_part4_higher_half_kernel/kernel/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/16_virtual_mem_part4_higher_half_kernel/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/16_virtual_mem_part4_higher_half_kernel/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index a3361c2c..2281e66a 100644 --- a/16_virtual_mem_part4_higher_half_kernel/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/16_virtual_mem_part4_higher_half_kernel/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -144,7 +144,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/16_virtual_mem_part4_higher_half_kernel/kernel/src/lib.rs b/16_virtual_mem_part4_higher_half_kernel/kernel/src/lib.rs index 1483c5a3..59ef14ac 100644 --- a/16_virtual_mem_part4_higher_half_kernel/kernel/src/lib.rs +++ b/16_virtual_mem_part4_higher_half_kernel/kernel/src/lib.rs @@ -111,15 +111,18 @@ #![allow(clippy::upper_case_acronyms)] #![allow(incomplete_features)] #![feature(asm_const)] +#![feature(const_option)] #![feature(core_intrinsics)] #![feature(format_args_nl)] #![feature(generic_const_exprs)] #![feature(int_roundings)] #![feature(is_sorted)] #![feature(linkage)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(step_trait)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_std] // Testing #![cfg_attr(test, no_main)] diff --git a/16_virtual_mem_part4_higher_half_kernel/kernel/src/panic_wait.rs b/16_virtual_mem_part4_higher_half_kernel/kernel/src/panic_wait.rs index 61831213..ae4651e7 100644 --- a/16_virtual_mem_part4_higher_half_kernel/kernel/src/panic_wait.rs +++ b/16_virtual_mem_part4_higher_half_kernel/kernel/src/panic_wait.rs @@ -59,8 +59,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - exception::asynchronous::local_irq_mask(); // Protect against panic infinite loops if any of the following code panics itself. diff --git a/16_virtual_mem_part4_higher_half_kernel/kernel/src/print.rs b/16_virtual_mem_part4_higher_half_kernel/kernel/src/print.rs index 8705eec0..fe13b334 100644 --- a/16_virtual_mem_part4_higher_half_kernel/kernel/src/print.rs +++ b/16_virtual_mem_part4_higher_half_kernel/kernel/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/16_virtual_mem_part4_higher_half_kernel/kernel/src/time.rs b/16_virtual_mem_part4_higher_half_kernel/kernel/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/16_virtual_mem_part4_higher_half_kernel/kernel/src/time.rs +++ b/16_virtual_mem_part4_higher_half_kernel/kernel/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/16_virtual_mem_part4_higher_half_kernel/kernel/tests/01_timer_sanity.rs b/16_virtual_mem_part4_higher_half_kernel/kernel/tests/01_timer_sanity.rs index d5d76a5c..39899332 100644 --- a/16_virtual_mem_part4_higher_half_kernel/kernel/tests/01_timer_sanity.rs +++ b/16_virtual_mem_part4_higher_half_kernel/kernel/tests/01_timer_sanity.rs @@ -11,7 +11,7 @@ #![test_runner(libkernel::test_runner)] use core::time::Duration; -use libkernel::{bsp, cpu, driver, exception, memory, time, time::interface::TimeManager}; +use libkernel::{bsp, cpu, driver, exception, memory, time}; use test_macros::kernel_test; #[no_mangle] @@ -38,6 +38,7 @@ fn timer_is_counting() { /// Timer resolution must be sufficient. #[kernel_test] fn timer_resolution_is_sufficient() { + assert!(time::time_manager().resolution().as_nanos() > 0); assert!(time::time_manager().resolution().as_nanos() < 100) } diff --git a/17_kernel_symbols/README.md b/17_kernel_symbols/README.md index ace1ed6e..699cfe2c 100644 --- a/17_kernel_symbols/README.md +++ b/17_kernel_symbols/README.md @@ -326,7 +326,7 @@ diff -uNr 16_virtual_mem_part4_higher_half_kernel/kernel/src/bsp/raspberrypi/mem diff -uNr 16_virtual_mem_part4_higher_half_kernel/kernel/src/lib.rs 17_kernel_symbols/kernel/src/lib.rs --- 16_virtual_mem_part4_higher_half_kernel/kernel/src/lib.rs +++ 17_kernel_symbols/kernel/src/lib.rs -@@ -139,6 +139,7 @@ +@@ -142,6 +142,7 @@ pub mod memory; pub mod print; pub mod state; diff --git a/17_kernel_symbols/kernel/src/_arch/aarch64/cpu/boot.s b/17_kernel_symbols/kernel/src/_arch/aarch64/cpu/boot.s index 8c70d035..bf6e32e4 100644 --- a/17_kernel_symbols/kernel/src/_arch/aarch64/cpu/boot.s +++ b/17_kernel_symbols/kernel/src/_arch/aarch64/cpu/boot.s @@ -84,6 +84,14 @@ _start: ADR_REL x4, __boot_core_stack_end_exclusive mov sp, x4 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x5, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x6, CNTFRQ_EL0 + cmp x6, xzr + b.eq .L_parking_loop + str w6, [x5] + // Jump to Rust code. x0, x1 and x2 hold the function arguments provided to _start_rust(). b _start_rust diff --git a/17_kernel_symbols/kernel/src/_arch/aarch64/time.rs b/17_kernel_symbols/kernel/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/17_kernel_symbols/kernel/src/_arch/aarch64/time.rs +++ b/17_kernel_symbols/kernel/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/17_kernel_symbols/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/17_kernel_symbols/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index a3361c2c..2281e66a 100644 --- a/17_kernel_symbols/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/17_kernel_symbols/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -144,7 +144,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/17_kernel_symbols/kernel/src/lib.rs b/17_kernel_symbols/kernel/src/lib.rs index 1f6d11b1..975bad2e 100644 --- a/17_kernel_symbols/kernel/src/lib.rs +++ b/17_kernel_symbols/kernel/src/lib.rs @@ -111,15 +111,18 @@ #![allow(clippy::upper_case_acronyms)] #![allow(incomplete_features)] #![feature(asm_const)] +#![feature(const_option)] #![feature(core_intrinsics)] #![feature(format_args_nl)] #![feature(generic_const_exprs)] #![feature(int_roundings)] #![feature(is_sorted)] #![feature(linkage)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(step_trait)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_std] // Testing #![cfg_attr(test, no_main)] diff --git a/17_kernel_symbols/kernel/src/panic_wait.rs b/17_kernel_symbols/kernel/src/panic_wait.rs index 61831213..ae4651e7 100644 --- a/17_kernel_symbols/kernel/src/panic_wait.rs +++ b/17_kernel_symbols/kernel/src/panic_wait.rs @@ -59,8 +59,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - exception::asynchronous::local_irq_mask(); // Protect against panic infinite loops if any of the following code panics itself. diff --git a/17_kernel_symbols/kernel/src/print.rs b/17_kernel_symbols/kernel/src/print.rs index 8705eec0..fe13b334 100644 --- a/17_kernel_symbols/kernel/src/print.rs +++ b/17_kernel_symbols/kernel/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/17_kernel_symbols/kernel/src/time.rs b/17_kernel_symbols/kernel/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/17_kernel_symbols/kernel/src/time.rs +++ b/17_kernel_symbols/kernel/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/17_kernel_symbols/kernel/tests/01_timer_sanity.rs b/17_kernel_symbols/kernel/tests/01_timer_sanity.rs index d5d76a5c..39899332 100644 --- a/17_kernel_symbols/kernel/tests/01_timer_sanity.rs +++ b/17_kernel_symbols/kernel/tests/01_timer_sanity.rs @@ -11,7 +11,7 @@ #![test_runner(libkernel::test_runner)] use core::time::Duration; -use libkernel::{bsp, cpu, driver, exception, memory, time, time::interface::TimeManager}; +use libkernel::{bsp, cpu, driver, exception, memory, time}; use test_macros::kernel_test; #[no_mangle] @@ -38,6 +38,7 @@ fn timer_is_counting() { /// Timer resolution must be sufficient. #[kernel_test] fn timer_resolution_is_sufficient() { + assert!(time::time_manager().resolution().as_nanos() > 0); assert!(time::time_manager().resolution().as_nanos() < 100) } diff --git a/18_backtrace/README.md b/18_backtrace/README.md index c6417bdd..77222e3a 100644 --- a/18_backtrace/README.md +++ b/18_backtrace/README.md @@ -909,7 +909,7 @@ diff -uNr 17_kernel_symbols/kernel/src/bsp/raspberrypi/memory/mmu.rs 18_backtrac diff -uNr 17_kernel_symbols/kernel/src/lib.rs 18_backtrace/kernel/src/lib.rs --- 17_kernel_symbols/kernel/src/lib.rs +++ 18_backtrace/kernel/src/lib.rs -@@ -130,6 +130,7 @@ +@@ -133,6 +133,7 @@ mod panic_wait; mod synchronization; @@ -972,7 +972,7 @@ diff -uNr 17_kernel_symbols/kernel/src/panic_wait.rs 18_backtrace/kernel/src/pan use core::panic::PanicInfo; //-------------------------------------------------------------------------------------------------- -@@ -75,6 +75,7 @@ +@@ -73,6 +73,7 @@ println!( "[ {:>3}.{:06}] Kernel panic!\n\n\ Panic location:\n File '{}', line {}, column {}\n\n\ @@ -980,7 +980,7 @@ diff -uNr 17_kernel_symbols/kernel/src/panic_wait.rs 18_backtrace/kernel/src/pan {}", timestamp.as_secs(), timestamp.subsec_micros(), -@@ -82,6 +83,7 @@ +@@ -80,6 +81,7 @@ line, column, info.message().unwrap_or(&format_args!("")), diff --git a/18_backtrace/kernel/src/_arch/aarch64/cpu/boot.s b/18_backtrace/kernel/src/_arch/aarch64/cpu/boot.s index 8c70d035..bf6e32e4 100644 --- a/18_backtrace/kernel/src/_arch/aarch64/cpu/boot.s +++ b/18_backtrace/kernel/src/_arch/aarch64/cpu/boot.s @@ -84,6 +84,14 @@ _start: ADR_REL x4, __boot_core_stack_end_exclusive mov sp, x4 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x5, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x6, CNTFRQ_EL0 + cmp x6, xzr + b.eq .L_parking_loop + str w6, [x5] + // Jump to Rust code. x0, x1 and x2 hold the function arguments provided to _start_rust(). b _start_rust diff --git a/18_backtrace/kernel/src/_arch/aarch64/time.rs b/18_backtrace/kernel/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/18_backtrace/kernel/src/_arch/aarch64/time.rs +++ b/18_backtrace/kernel/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/18_backtrace/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/18_backtrace/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index a3361c2c..2281e66a 100644 --- a/18_backtrace/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/18_backtrace/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -144,7 +144,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/18_backtrace/kernel/src/lib.rs b/18_backtrace/kernel/src/lib.rs index 2c13a342..2545b20b 100644 --- a/18_backtrace/kernel/src/lib.rs +++ b/18_backtrace/kernel/src/lib.rs @@ -111,15 +111,18 @@ #![allow(clippy::upper_case_acronyms)] #![allow(incomplete_features)] #![feature(asm_const)] +#![feature(const_option)] #![feature(core_intrinsics)] #![feature(format_args_nl)] #![feature(generic_const_exprs)] #![feature(int_roundings)] #![feature(is_sorted)] #![feature(linkage)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(step_trait)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_std] // Testing #![cfg_attr(test, no_main)] diff --git a/18_backtrace/kernel/src/panic_wait.rs b/18_backtrace/kernel/src/panic_wait.rs index c11ec67e..bc95f77c 100644 --- a/18_backtrace/kernel/src/panic_wait.rs +++ b/18_backtrace/kernel/src/panic_wait.rs @@ -59,8 +59,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - exception::asynchronous::local_irq_mask(); // Protect against panic infinite loops if any of the following code panics itself. diff --git a/18_backtrace/kernel/src/print.rs b/18_backtrace/kernel/src/print.rs index 8705eec0..fe13b334 100644 --- a/18_backtrace/kernel/src/print.rs +++ b/18_backtrace/kernel/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/18_backtrace/kernel/src/time.rs b/18_backtrace/kernel/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/18_backtrace/kernel/src/time.rs +++ b/18_backtrace/kernel/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/18_backtrace/kernel/tests/01_timer_sanity.rs b/18_backtrace/kernel/tests/01_timer_sanity.rs index d5d76a5c..39899332 100644 --- a/18_backtrace/kernel/tests/01_timer_sanity.rs +++ b/18_backtrace/kernel/tests/01_timer_sanity.rs @@ -11,7 +11,7 @@ #![test_runner(libkernel::test_runner)] use core::time::Duration; -use libkernel::{bsp, cpu, driver, exception, memory, time, time::interface::TimeManager}; +use libkernel::{bsp, cpu, driver, exception, memory, time}; use test_macros::kernel_test; #[no_mangle] @@ -38,6 +38,7 @@ fn timer_is_counting() { /// Timer resolution must be sufficient. #[kernel_test] fn timer_resolution_is_sufficient() { + assert!(time::time_manager().resolution().as_nanos() > 0); assert!(time::time_manager().resolution().as_nanos() < 100) } diff --git a/19_kernel_heap/README.md b/19_kernel_heap/README.md index ecb3f8f7..11417c2f 100644 --- a/19_kernel_heap/README.md +++ b/19_kernel_heap/README.md @@ -433,7 +433,7 @@ diff -uNr 18_backtrace/kernel/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs 19 diff -uNr 18_backtrace/kernel/src/bsp/raspberrypi/driver.rs 19_kernel_heap/kernel/src/bsp/raspberrypi/driver.rs --- 18_backtrace/kernel/src/bsp/raspberrypi/driver.rs +++ 19_kernel_heap/kernel/src/bsp/raspberrypi/driver.rs -@@ -11,11 +11,11 @@ +@@ -11,6 +11,7 @@ memory::mmu::MMIODescriptor, synchronization::{interface::ReadWriteEx, InitStateLock}, }; @@ -441,12 +441,7 @@ diff -uNr 18_backtrace/kernel/src/bsp/raspberrypi/driver.rs 19_kernel_heap/kerne use core::{ mem::MaybeUninit, sync::atomic::{AtomicBool, Ordering}, - }; -- - pub use device_driver::IRQNumber; - - //-------------------------------------------------------------------------------------------------- -@@ -24,18 +24,11 @@ +@@ -24,18 +25,11 @@ /// Device Driver Manager type. struct BSPDriverManager { @@ -466,7 +461,7 @@ diff -uNr 18_backtrace/kernel/src/bsp/raspberrypi/driver.rs 19_kernel_heap/kerne // Global instances //-------------------------------------------------------------------------------------------------- -@@ -50,7 +43,7 @@ +@@ -50,7 +44,7 @@ static mut INTERRUPT_CONTROLLER: MaybeUninit = MaybeUninit::uninit(); static BSP_DRIVER_MANAGER: BSPDriverManager = BSPDriverManager { @@ -475,7 +470,7 @@ diff -uNr 18_backtrace/kernel/src/bsp/raspberrypi/driver.rs 19_kernel_heap/kerne init_done: AtomicBool::new(false), }; -@@ -143,9 +136,9 @@ +@@ -143,9 +137,9 @@ unsafe fn register_drivers(&self) { self.device_drivers.write(|drivers| { @@ -488,7 +483,7 @@ diff -uNr 18_backtrace/kernel/src/bsp/raspberrypi/driver.rs 19_kernel_heap/kerne }); } } -@@ -180,9 +173,8 @@ +@@ -180,9 +174,8 @@ Ok(()) } @@ -871,9 +866,9 @@ diff -uNr 18_backtrace/kernel/src/lib.rs 19_kernel_heap/kernel/src/lib.rs #![allow(incomplete_features)] +#![feature(alloc_error_handler)] #![feature(asm_const)] + #![feature(const_option)] #![feature(core_intrinsics)] - #![feature(format_args_nl)] -@@ -127,6 +128,8 @@ +@@ -130,6 +131,8 @@ #![reexport_test_harness_main = "test_main"] #![test_runner(crate::test_runner)] @@ -1269,7 +1264,7 @@ diff -uNr 18_backtrace/kernel/src/memory.rs 19_kernel_heap/kernel/src/memory.rs diff -uNr 18_backtrace/kernel/src/print.rs 19_kernel_heap/kernel/src/print.rs --- 18_backtrace/kernel/src/print.rs +++ 19_kernel_heap/kernel/src/print.rs -@@ -90,3 +90,35 @@ +@@ -82,3 +82,31 @@ )); }) } @@ -1279,8 +1274,6 @@ diff -uNr 18_backtrace/kernel/src/print.rs 19_kernel_heap/kernel/src/print.rs +macro_rules! debug { + ($string:expr) => ({ + if cfg!(feature = "debug_prints") { -+ use $crate::time::interface::TimeManager; -+ + let timestamp = $crate::time::time_manager().uptime(); + + $crate::print::_print(format_args_nl!( @@ -1292,8 +1285,6 @@ diff -uNr 18_backtrace/kernel/src/print.rs 19_kernel_heap/kernel/src/print.rs + }); + ($format_string:expr, $($arg:tt)*) => ({ + if cfg!(feature = "debug_prints") { -+ use $crate::time::interface::TimeManager; -+ + let timestamp = $crate::time::time_manager().uptime(); + + $crate::print::_print(format_args_nl!( diff --git a/19_kernel_heap/kernel/src/_arch/aarch64/cpu/boot.s b/19_kernel_heap/kernel/src/_arch/aarch64/cpu/boot.s index 8c70d035..bf6e32e4 100644 --- a/19_kernel_heap/kernel/src/_arch/aarch64/cpu/boot.s +++ b/19_kernel_heap/kernel/src/_arch/aarch64/cpu/boot.s @@ -84,6 +84,14 @@ _start: ADR_REL x4, __boot_core_stack_end_exclusive mov sp, x4 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x5, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x6, CNTFRQ_EL0 + cmp x6, xzr + b.eq .L_parking_loop + str w6, [x5] + // Jump to Rust code. x0, x1 and x2 hold the function arguments provided to _start_rust(). b _start_rust diff --git a/19_kernel_heap/kernel/src/_arch/aarch64/time.rs b/19_kernel_heap/kernel/src/_arch/aarch64/time.rs index c814219c..255a5a45 100644 --- a/19_kernel_heap/kernel/src/_arch/aarch64/time.rs +++ b/19_kernel_heap/kernel/src/_arch/aarch64/time.rs @@ -11,111 +11,152 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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 {} +} diff --git a/19_kernel_heap/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/19_kernel_heap/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index a3361c2c..2281e66a 100644 --- a/19_kernel_heap/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/19_kernel_heap/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -144,7 +144,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/19_kernel_heap/kernel/src/bsp/raspberrypi/driver.rs b/19_kernel_heap/kernel/src/bsp/raspberrypi/driver.rs index 3eb01f27..5571c392 100644 --- a/19_kernel_heap/kernel/src/bsp/raspberrypi/driver.rs +++ b/19_kernel_heap/kernel/src/bsp/raspberrypi/driver.rs @@ -16,6 +16,7 @@ use core::{ mem::MaybeUninit, sync::atomic::{AtomicBool, Ordering}, }; + pub use device_driver::IRQNumber; //-------------------------------------------------------------------------------------------------- diff --git a/19_kernel_heap/kernel/src/lib.rs b/19_kernel_heap/kernel/src/lib.rs index 20dc3bfd..8e4195e0 100644 --- a/19_kernel_heap/kernel/src/lib.rs +++ b/19_kernel_heap/kernel/src/lib.rs @@ -112,15 +112,18 @@ #![allow(incomplete_features)] #![feature(alloc_error_handler)] #![feature(asm_const)] +#![feature(const_option)] #![feature(core_intrinsics)] #![feature(format_args_nl)] #![feature(generic_const_exprs)] #![feature(int_roundings)] #![feature(is_sorted)] #![feature(linkage)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(step_trait)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_std] // Testing #![cfg_attr(test, no_main)] diff --git a/19_kernel_heap/kernel/src/panic_wait.rs b/19_kernel_heap/kernel/src/panic_wait.rs index c11ec67e..bc95f77c 100644 --- a/19_kernel_heap/kernel/src/panic_wait.rs +++ b/19_kernel_heap/kernel/src/panic_wait.rs @@ -59,8 +59,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - exception::asynchronous::local_irq_mask(); // Protect against panic infinite loops if any of the following code panics itself. diff --git a/19_kernel_heap/kernel/src/print.rs b/19_kernel_heap/kernel/src/print.rs index 5e41ef9f..8d56d2e4 100644 --- a/19_kernel_heap/kernel/src/print.rs +++ b/19_kernel_heap/kernel/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -96,8 +88,6 @@ macro_rules! warn { macro_rules! debug { ($string:expr) => ({ if cfg!(feature = "debug_prints") { - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -109,8 +99,6 @@ macro_rules! debug { }); ($format_string:expr, $($arg:tt)*) => ({ if cfg!(feature = "debug_prints") { - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/19_kernel_heap/kernel/src/time.rs b/19_kernel_heap/kernel/src/time.rs index 6d92b196..a6c0c5b6 100644 --- a/19_kernel_heap/kernel/src/time.rs +++ b/19_kernel_heap/kernel/src/time.rs @@ -8,30 +8,50 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } } diff --git a/19_kernel_heap/kernel/tests/01_timer_sanity.rs b/19_kernel_heap/kernel/tests/01_timer_sanity.rs index d5d76a5c..39899332 100644 --- a/19_kernel_heap/kernel/tests/01_timer_sanity.rs +++ b/19_kernel_heap/kernel/tests/01_timer_sanity.rs @@ -11,7 +11,7 @@ #![test_runner(libkernel::test_runner)] use core::time::Duration; -use libkernel::{bsp, cpu, driver, exception, memory, time, time::interface::TimeManager}; +use libkernel::{bsp, cpu, driver, exception, memory, time}; use test_macros::kernel_test; #[no_mangle] @@ -38,6 +38,7 @@ fn timer_is_counting() { /// Timer resolution must be sufficient. #[kernel_test] fn timer_resolution_is_sufficient() { + assert!(time::time_manager().resolution().as_nanos() > 0); assert!(time::time_manager().resolution().as_nanos() < 100) } diff --git a/X1_JTAG_boot/jtag_boot_rpi3.img b/X1_JTAG_boot/jtag_boot_rpi3.img index 6b717dffa6c5b3ef5507a10f0b1e95bdcea9f241..844015333d4c9c99ddbb059320b89f37229f2328 100755 GIT binary patch delta 2665 zcmah~eQZzu)g%kL;dn`|$*9zvsyFyw0%j>@HxIx)|`;dG_ATB4VLEDaIbi z(ow+q7harPQ4}kk_hc2E2a%4GEU_?&o%cGyuI}3e5X%=^tjrC=yZ$do>0b@-1_8L1 zcmSf~rc+#CK{U?<84Dr9(l6MqO>;(E<>DYZ<5gnfWg;gkK}=l%c?%4CQ{Z$N7n*|VXrfpr)J&pGq9v9V{%$iK1*Rn&ehFgVk?ms+q7H7aXs-8=CRoz z(_3!&C*BzQ+tQ!clpomtALT6`_z!6KBn#05%S0D21>cHd<@r__?>qJmX75N;i)@i6 za}?|!g5J2tJn{mbqbVm@WAsxgxwb@@&nnj34yBE`n{9)P8F9J4i@jHc=o&q5d)o)D zgadI;%x-}GkJI|SkIrY&xf%$aW6=I-@|tZH zlO-(piV%{HykYP3qiXS!tsv(vc7laJoQw;e*a73;E=FZ@qyqzW;CLSfl8Je0Qvvt_ zPOkjKAJ2O`3f~EHL(vGg(mTHjU~P)ENAt0#s08OC-eC76t{ZVZJkJr+t4>627vYv} zDT?ngZGTl7&!i4QEQQRcDTExXVB2wZ`QsPjkiAqLHR}z)}k)&ctqf8ds zh`3nEsB?kOHAzHwQYo=AM`fJMSLJ1F2Fs!=Urz0T%n15RR~qCLN-O0mkWXY$JPb5s z+dLVhTD-}2e(0fxow$)}A*zFx=TUzcOcBtDuEEq{u%v_nIr_DxKDyqjXk+P2X_>I6 zIS5eSi858NP^r6rqd1K%1gi-lO zJYexTATs7Xrj}~lEG|tk@)F{SJV`>CW8>rljf<-7$i?OSZ*x&E{iVz%JX}%U!gs78 z2Ty`!#g8ZzSjYc^{sBb48$?B6WEz^Ll-i5vE390rqo1o*y&uEp%nQDHmCghrki?Ht zOV<^a*?l*=6EZly28n#J5S;*l>(nvt1EbT?WJq$j0T8N+x{lR2GGB{2EXwdt#3mtq)*UAJKEWU@k!~0^17= zbBsY2v5kS&AJXmCwgdd3{qC9?{+Sl957Cq2Hmhx2p0>-)YrT3De~RtW7q)*0ZK1xO z_Jjw5p+3GR91ex6`Q2y3U4zsf425`q@c5C#{7?9SGr_*j-cWc4U(G*#GI$2f<0jqJ z{c_DS>Pu}$?3#^lpxPGn^+MfV)@}S-0%?%HD!o~;RiMf5In;h|^EUoeu&;;T#&?HK zoju*hn@qVl&+2}^FHUNM7WW5woQev~)_wsV*KMqSwN>jj3JnZ&#ywNBv6D!5%Ga&= za-(*tOx>xX^;P-C4Q@RJnmTQx_~9QC9O3zD$-SZ8`Tuf!UUyI{_4DV15JKO3Ds+wy z_I(5UjvakH=csRNInLH=Cb+_92L`)cz0KYJuEx5i`o?4K-rnBrHI20mZhx)6tI6Nw zZuB=Ct3TG=>vp?cr-J@)SNP|y?ohbLH4yH04F*s5REGzSYFcQ8y}IK+VPA@0jEgd_}bY delta 2789 zcmah~eQaCR6+ibqJ9&1$3~rM)X~}cFQ9CK5twTxyVJC*sctveZVMtK5ZekKIjvZcH zSO=M1A&q_^{CJ>J^3Oswwa*kSEbY1=p#}O+OE=8FA2_IIss& z+6y@U;=$7kx?WG`0~HZ_$WdSepj&6~)7NX7s!B}@a0S@gYjDMZgxY+YgL}te#}F=e@eh_YYm*W2Tp|hLr4D-8agHQ zJI~IStbdvJmjgE5hnN0}3lEx~r_tr#*HLl%q3q8&*%j0|=SUmCF^qd8=+4M})ot-aBOc=RDzU-_ZD z6-lyr0_y2soE}-Uq0_6a<()Z-da(u6!pksG@^M}#9-so$({>fhTyfXkxd|0P^?77U zca)r*Oi9(Q@mTdlMTKOI%0i)*-r>|z&>&TLq0V*3p?R~KK7IhJ@MMjOZpQ?sCiPzB z{kn$qw^-pf^ZWnwe!^Y>jpyZ08P$3aODssWgpeUj&BGT*W>Dleir%yAmZDFas&|v` zSN$JR^;luE_-YuU=Wi1I*xY9BX3@K>*G0eX2GN`N$n@D07SdGlR#beAS_o3b*Q)s- zkVj^j^GzjG@%dt@(jM6H2Y>IoUU3p z8^SYRU|BCi;1{7VKF37fMKZfkKyygMJ84u3bu#PY<}7Vx{TwYe z4!;|pgifZJJ}eV2mPa3!N&!pd5SEDtwIX172>C|ulqZibq&4tcPrlI@7xemeW3HTj zjm}jmP=2=R8+_ERw?XCEhI(&?Ls4VPfY}F_-5GpT+_7!L#*O0R9pPO77UjK$`Ub&b z?#iw)`>MBVjY_`Z)9!~QDJhBZWTYpS=oNdU*mF_oKJh*gxBaQnJWdXv(GKG;4PSY>scko%sfK|6H%2QrnFo+LqitXgHZgmUCAgB= z)dU84bChAd0Za3a73S<-)_>_T?Khd%0<$MqE1O*HtE$eKZ>R?Gm2%Kk-|{3wYcX4P zU~k2bV*JRCuI=mAi}6?@Dn2EON@4X~>GfiFGQNKxA=>Sz(_5#iF#mVa&DahxyPD!8 zv+=1j+9hUJvA@i07WMe4H9JX~60#>Eb@#PK=x=*lYbrJnt8-I;v^$0K@%RC8X#e0~Qc6X8){8^^u|Yi2YoPNr3*`HR@E^8LQK!u= mnz%G8qicS%^ShU)l1UVBqW3$Oz^!f(nIPQg^?7B?CH@yERC#&; diff --git a/X1_JTAG_boot/jtag_boot_rpi4.img b/X1_JTAG_boot/jtag_boot_rpi4.img index 195a9fa2897dec9ff58270824d17ee341181bd88..90e8668b1951ee773dc86acaba14c1afe08781fc 100755 GIT binary patch delta 1940 zcmah}eP|PB7=PZo#^n;*U9D;QVeG}?n$(Y^wVzQiZ5=jFQ9^}%F-_9OMr-S?ts<;T zs-Sar(DMYD-C(WE_R4*-E+_L zK40(e_q;c~{_2L;Mxo)BGsl05qT$tzKs^jm&{oFLx2AswR+Z!tN+1t+1KQsioS0J- zyK48(D`*En{T{s!EG4hMwF}BLzZC$fMM9IEDl&Z$ene~@6P#B4LKlFQe+FTe2EjTN zqpXDD&Wi`>eU8y;!5wl6@^K=`i;nKO7A#t9(Z&n}X7(L85j+W>@a}=h z5%=7lnKr^qh8bwHOdks-hrZ%nPD^R*>l4Ai;xUYqMr?D@86OR>^a)*7`Is)lrVEl6 z6y*%V`K3%bNDSBP&XiN`#~UI0*)8j-+7f>3Ef%~dX zX%KZ5foLss#;+9uYr+1vTy8-27HNAEtAKqFhipb=vSP7jdv7hU*EBs>N=HlvZfX5Ng3>nEboH z^&og|DKvFsem!K-Gz`-X!e{6M1TLzHEEm2kQjJ z34;6rNI?8q7ZJ}F6fTkSfK6BANa|>*=^j*x-xn;8hpMrg77#vqt{deFxiP;Evj0WD z6{+q-`YlNR7L9((2!l2k(!Uq!e;?^L4Odc^on-%GwMEV0Ot)+XB{w+wq z8-3h!(fAC7ZD)}FbOCbVft1=?=i3UKISPV00_iEe-`H*C8HW`*wq_4f|R))jSM7% z%rB5ya8?5V{MkErbM%PR-NL^}LcC$OyN4;vj%!%0f@_DNH@9r# zc5L-lRdG9;f^CShm|tWsDaz5kCYI?^3&Gd1Qn9({2k}zLnz$Occ}HM-#cHlA*4@Fa z<{~{^{rkH)iv=&y^-?_$$4y%^hK~OiZFLmu`)dsStf4QI8#8qLpJ;2+&~Xm5b%7L5 zy)kdb!*xMD;1@cq4g5l^%+ z5)Rd^tFNhR_eP`9hN`;iT5q^I9I6l3d+Wlr?KSO@sMqWDbj8Aah(GL!^za>?UOwXK zi|y}N%lF>Z2`PbABk?7aHhdO>9z#1MMYLkNPM)ll(6NsE>(k} z>^3Hl0>+LPL!^J&V$#m~M^lJYi6P-n%MX(ZMH7wA)}Ygt8dLg%4R*(KXJ&&EjW@aX z+HlCHZ9 zd0Iz4$O1rq3+m~}r)|ag+*RA>(FIcAw!=B5H~1-t>MtNq^B@qhHwH7aDY)OlsFb z_M)n~oL5xzo8-BK9wUn80fogB+CkJ}obuX|eqI9Umd29=5rU{IB=*tWIewB&dveZ_ zjiWh_{Z6>vfDwk=pzCuh8 z#tNo>OzST54ls?W3pO+L`3&@as3jyGy2iKMR{8Qo5;wau8=dDS(_5iFh4(%f*dz;> z2oSi;avRtU;-apfR{<`618~lbo8iM`$wxUco)%}9|ifY-~7chcXV;yPn4$aiGJs`lcB`f zE727IvAzR?n~Z#_szIFF3@Pu@&YK4i%e#JFsdPI_X7V5v{<1gJlreBq?_w^KFQ5T6keI4XjT0b8o@I(tH#@d?sfYs(Wa1~`By7_o$ zH!3mgR5W-UF=cm#1l4j&W8|Gc7@#iWfc2__Y!3V#9fuPx-NVLs6d?y9(upW_B1*G} z(rHA=+kKpWk1@0<9!!#Fc}oB?Lj_w3CXy*#ezba}oz=KW0-`0f>K_IaFrUe5m)PzP3;pbiz7nEq&}9T;QXPbJrVZv;>smw7&< zan9t#ElH3z6uLIpY#BYn2yiHkC0P6`#s8FlP()7`{_tw!w&$cBFEs7)R7nTJhdZRB z(RP1JTeN+@zm1)n>ik{dLmf5IqX5-KriUWS^LC_VxZAqAIu1p;q^={<-tgPuy&cjU z$E0^VqDN??q%fm=%rahS8DsbhC1r(IEH=)9v3l{$#!YXP)U9QlOxifk#`?-CBU7GQ zM18AH*j|yO8ro4*9jlVsj~smK&|%5x#NYm3^Q)G-YRml^E1wKjGnU+8$rr3QXT296 zNr!G(6`_5_D*ddqmPBZ>)U|#w*L22^jYPwTy9_Y#J4;P|mgP!BYR4U+4W+J^BfKe% dTlBx0p*GLd3tDu<@>*-r0gLWol&&eO_zP&8Bsc&7 diff --git a/X1_JTAG_boot/src/_arch/aarch64/cpu/boot.s b/X1_JTAG_boot/src/_arch/aarch64/cpu/boot.s index 1f70169f..aa701fd1 100644 --- a/X1_JTAG_boot/src/_arch/aarch64/cpu/boot.s +++ b/X1_JTAG_boot/src/_arch/aarch64/cpu/boot.s @@ -52,6 +52,14 @@ _start: ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 + // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. + // Abort if the frequency read back as 0. + ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs + mrs x2, CNTFRQ_EL0 + cmp x2, xzr + b.eq .L_parking_loop + str w2, [x1] + // Jump to Rust code. b _start_rust diff --git a/X1_JTAG_boot/src/_arch/aarch64/time.rs b/X1_JTAG_boot/src/_arch/aarch64/time.rs index c814219c..3d3a20d7 100644 --- a/X1_JTAG_boot/src/_arch/aarch64/time.rs +++ b/X1_JTAG_boot/src/_arch/aarch64/time.rs @@ -11,111 +11,154 @@ //! //! crate::time::arch_time -use crate::{time, warn}; -use core::time::Duration; +#[cfg(feature = "bsp_rpi3")] +use crate::warn; +use core::{ + num::{NonZeroU128, NonZeroU32, NonZeroU64}, + ops::{Add, Div}, + time::Duration, +}; use cortex_a::{asm::barrier, registers::*}; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::Readable; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- -const NS_PER_S: u64 = 1_000_000_000; +const NANOSEC_PER_SEC: NonZeroU64 = NonZeroU64::new(1_000_000_000).unwrap(); -/// ARMv8 Generic Timer. -struct GenericTimer; +#[derive(Copy, Clone, PartialOrd, PartialEq)] +struct GenericTimerCounterValue(u64); //-------------------------------------------------------------------------------------------------- // Global instances //-------------------------------------------------------------------------------------------------- -static TIME_MANAGER: GenericTimer = GenericTimer; +/// 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 //-------------------------------------------------------------------------------------------------- -impl GenericTimer { - #[inline(always)] - fn read_cntpct(&self) -> u64 { - // Prevent that the counter is read ahead of time due to out-of-order execution. - unsafe { barrier::isb(barrier::SY) }; - CNTPCT_EL0.get() - } +impl GenericTimerCounterValue { + pub const MAX: Self = GenericTimerCounterValue(u64::MAX); } -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- +impl Add for GenericTimerCounterValue { + type Output = Self; -/// Return a reference to the time manager. -pub fn time_manager() -> &'static impl time::interface::TimeManager { - &TIME_MANAGER + fn add(self, other: Self) -> Self { + GenericTimerCounterValue(self.0.wrapping_add(other.0)) + } } -//------------------------------------------------------------------------------ -// OS Interface Code -//------------------------------------------------------------------------------ +impl From for Duration { + fn from(counter_value: GenericTimerCounterValue) -> Self { + if counter_value.0 == 0 { + return Duration::ZERO; + } -impl time::interface::TimeManager for GenericTimer { - fn resolution(&self) -> Duration { - Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + // 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. + let frequency: NonZeroU64 = + unsafe { core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY) }.into(); + + // Div 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 uptime(&self) -> Duration { - let current_count: u64 = self.read_cntpct() * NS_PER_S; - let frq: u64 = CNTFRQ_EL0.get() as u64; +fn max_duration() -> Duration { + Duration::from(GenericTimerCounterValue::MAX) +} - Duration::from_nanos(current_count / frq) - } +impl TryFrom for GenericTimerCounterValue { + type Error = &'static str; - fn spin_for(&self, duration: Duration) { - // Instantly return on zero. - if duration.as_nanos() == 0 { - return; + fn try_from(duration: Duration) -> Result { + if duration < resolution() { + return Ok(GenericTimerCounterValue(0)); } - // Calculate the register compare value. - let frq = CNTFRQ_EL0.get(); - let x = match frq.checked_mul(duration.as_nanos() as u64) { - #[allow(unused_imports)] - None => { - warn!("Spin duration too long, skipping"); - return; - } - Some(val) => val, - }; - let tval = x / NS_PER_S; - - // Check if it is within supported bounds. - let warn: Option<&str> = if tval == 0 { - Some("smaller") - // The upper 32 bits of CNTP_TVAL_EL0 are reserved. - } else if tval > u32::max_value().into() { - Some("bigger") - } else { - None - }; - - #[allow(unused_imports)] - if let Some(w) = warn { - warn!( - "Spin duration {} than architecturally supported, skipping", - w - ); - return; + if duration > max_duration() { + return Err("Conversion error. Duration too big"); } - // Set the compare value register. - CNTP_TVAL_EL0.set(tval); + // This is safe, because all the safety requirements as stated in read_volatile()'s + // documentation are fulfilled. + let frequency: u128 = + unsafe { u32::from(core::ptr::read_volatile(&ARCH_TIMER_COUNTER_FREQUENCY)) as u128 }; + let duration: u128 = duration.as_nanos(); - // Kick off the counting. // Disable timer interrupt. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + // 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)); - // ISTATUS will be '1' when cval ticks have passed. Busy-check it. - while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {} - - // Disable counting again. - CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + // 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. + unsafe { 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. +#[cfg(feature = "bsp_rpi3")] +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 {} +} diff --git a/X1_JTAG_boot/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/X1_JTAG_boot/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index 24958eb5..24e537cf 100644 --- a/X1_JTAG_boot/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/X1_JTAG_boot/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -140,7 +140,7 @@ impl GPIOInner { /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) { - use crate::{time, time::interface::TimeManager}; + use crate::time; use core::time::Duration; // The Linux 2837 GPIO driver waits 1 µs between the steps. diff --git a/X1_JTAG_boot/src/main.rs b/X1_JTAG_boot/src/main.rs index a985c679..a38b1ef5 100644 --- a/X1_JTAG_boot/src/main.rs +++ b/X1_JTAG_boot/src/main.rs @@ -108,9 +108,12 @@ #![allow(clippy::upper_case_acronyms)] #![feature(asm_const)] +#![feature(const_option)] #![feature(format_args_nl)] +#![feature(nonzero_min_max)] #![feature(panic_info_message)] #![feature(trait_alias)] +#![feature(unchecked_math)] #![no_main] #![no_std] diff --git a/X1_JTAG_boot/src/panic_wait.rs b/X1_JTAG_boot/src/panic_wait.rs index edd83885..ccf54f61 100644 --- a/X1_JTAG_boot/src/panic_wait.rs +++ b/X1_JTAG_boot/src/panic_wait.rs @@ -42,8 +42,6 @@ fn panic_prevent_reenter() { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use crate::time::interface::TimeManager; - // Protect against panic infinite loops if any of the following code panics itself. panic_prevent_reenter(); diff --git a/X1_JTAG_boot/src/print.rs b/X1_JTAG_boot/src/print.rs index 8705eec0..fe13b334 100644 --- a/X1_JTAG_boot/src/print.rs +++ b/X1_JTAG_boot/src/print.rs @@ -39,8 +39,6 @@ macro_rules! println { #[macro_export] macro_rules! info { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -50,8 +48,6 @@ macro_rules! info { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -67,8 +63,6 @@ macro_rules! info { #[macro_export] macro_rules! warn { ($string:expr) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( @@ -78,8 +72,6 @@ macro_rules! warn { )); }); ($format_string:expr, $($arg:tt)*) => ({ - use $crate::time::interface::TimeManager; - let timestamp = $crate::time::time_manager().uptime(); $crate::print::_print(format_args_nl!( diff --git a/X1_JTAG_boot/src/time.rs b/X1_JTAG_boot/src/time.rs index 6d92b196..c6b40068 100644 --- a/X1_JTAG_boot/src/time.rs +++ b/X1_JTAG_boot/src/time.rs @@ -8,30 +8,46 @@ #[path = "_arch/aarch64/time.rs"] mod arch_time; +use core::time::Duration; + //-------------------------------------------------------------------------------------------------- -// Architectural Public Reexports +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub use arch_time::time_manager; + +/// Provides time management functions. +pub struct TimeManager; //-------------------------------------------------------------------------------------------------- -// Public Definitions +// Global instances //-------------------------------------------------------------------------------------------------- -/// Timekeeping interfaces. -pub mod interface { - use core::time::Duration; +static TIME_MANAGER: TimeManager = TimeManager::new(); - /// Time management functions. - pub trait TimeManager { - /// The timer's resolution. - fn resolution(&self) -> Duration; +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; +/// 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 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. - fn spin_for(&self, duration: Duration); + /// Spin for a given duration. + #[cfg(feature = "bsp_rpi3")] + pub fn spin_for(&self, duration: Duration) { + arch_time::spin_for(duration) } }