diff --git a/Cargo.lock b/Cargo.lock index 221890ec3..8b33a067a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2756,6 +2756,7 @@ version = "0.1.0" dependencies = [ "cortex-m", "cortex-m-rt", + "hubris-ptime", "measurement-handoff", "stm32h7", ] @@ -3394,6 +3395,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "drv-stm32h7-startup", + "hubris-ptime", "kern", "ringbuf", "stm32h7", @@ -3451,6 +3453,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "drv-stm32h7-startup", + "hubris-ptime", "kern", "stm32h7", ] @@ -3670,6 +3673,10 @@ dependencies = [ "build-util", ] +[[package]] +name = "hubris-ptime" +version = "0.1.0" + [[package]] name = "hubris-task-names" version = "0.1.0" @@ -3996,6 +4003,7 @@ dependencies = [ "call_rustfmt", "cfg-if", "cortex-m", + "hubris-ptime", "indexmap 1.9.1", "kerncore", "phash", diff --git a/app/gimlet/Cargo.toml b/app/gimlet/Cargo.toml index 00269a76e..d83d29c96 100644 --- a/app/gimlet/Cargo.toml +++ b/app/gimlet/Cargo.toml @@ -18,6 +18,7 @@ ringbuf = { path = "../../lib/ringbuf", optional = true } drv-stm32h7-startup = { path = "../../drv/stm32h7-startup", features = ["h753"] } kern = { path = "../../sys/kern" } +hubris-ptime = { path = "../../sys/ptime" } [build-dependencies] build-util = {path = "../../build/util"} diff --git a/app/gimlet/src/main.rs b/app/gimlet/src/main.rs index e6c5b8ae7..ad139ee64 100644 --- a/app/gimlet/src/main.rs +++ b/app/gimlet/src/main.rs @@ -230,4 +230,10 @@ fn system_init() { .pc3so() .set_bit() }); + + // Enable timing + let ptime = RollingTimer::new_tim5(&p, 200); + // SAFETY: we promise to never use TIM5 again. + let vtable = unsafe { ptime.into_ptimer() }; + hubris_ptime::set_ptime_vtable(vtable); } diff --git a/app/grapefruit/Cargo.toml b/app/grapefruit/Cargo.toml index e4309c951..2237ab4cb 100644 --- a/app/grapefruit/Cargo.toml +++ b/app/grapefruit/Cargo.toml @@ -16,6 +16,7 @@ stm32h7 = { workspace = true, features = ["rt", "stm32h753"] } drv-stm32h7-startup = { path = "../../drv/stm32h7-startup", features = ["h753"] } kern = { path = "../../sys/kern" } +hubris-ptime = { path = "../../sys/ptime" } [build-dependencies] build-util = {path = "../../build/util"} diff --git a/app/grapefruit/src/main.rs b/app/grapefruit/src/main.rs index 33dc8188d..40a34e19a 100644 --- a/app/grapefruit/src/main.rs +++ b/app/grapefruit/src/main.rs @@ -13,7 +13,7 @@ extern crate stm32h7; use stm32h7::stm32h753 as device; -use drv_stm32h7_startup::ClockConfig; +use drv_stm32h7_startup::{ClockConfig, rolling_timer::RollingTimer}; use cortex_m_rt::entry; @@ -443,4 +443,10 @@ fn system_init() { // Turn on the controller. p.FMC.bcr1.modify(|_, w| w.fmcen().set_bit()); + + // Enable timing + let ptime = RollingTimer::new_tim5(&p, 200); + // SAFETY: we promise to never use TIM5 again. + let vtable = unsafe { ptime.into_ptimer() }; + hubris_ptime::set_ptime_vtable(vtable); } diff --git a/drv/stm32h7-startup/Cargo.toml b/drv/stm32h7-startup/Cargo.toml index b2dd64e86..a53c4c53c 100644 --- a/drv/stm32h7-startup/Cargo.toml +++ b/drv/stm32h7-startup/Cargo.toml @@ -8,6 +8,7 @@ cortex-m = { workspace = true } cortex-m-rt = { workspace = true } stm32h7 = { workspace = true } measurement-handoff = { path = "../../lib/measurement-handoff", optional = true } +hubris-ptime = { path = "../../sys/ptime" } [features] h743 = ["stm32h7/stm32h743"] diff --git a/drv/stm32h7-startup/src/lib.rs b/drv/stm32h7-startup/src/lib.rs index 94a09ce22..29ef24ba0 100644 --- a/drv/stm32h7-startup/src/lib.rs +++ b/drv/stm32h7-startup/src/lib.rs @@ -459,5 +459,67 @@ pub mod rolling_timer { } } } + + pub unsafe fn into_ptimer(self) -> &'static hubris_ptime::PTimeVTable { + // this is all a bit silly + use core::sync::atomic::{AtomicU32, Ordering}; + fn tickrate() -> u32 { + 1_000_000 + } + + fn timekeep() { + let tim5 = unsafe { &*device::TIM5::ptr() }; + let counter = tim5.cnt.read().bits(); + let mut period = PERIOD.load(Ordering::Relaxed); + + // Does the parity match? + let parity = (period & 0b1) ^ (counter >> 31); + if parity != 0 { + period += 1; + PERIOD.store(period, Ordering::Relaxed); + } + + // Bit 63 (one bit) is unused. + // Bits 31..63 (32 bits) are filled by PERIOD + // Bits 00..31 (31 bits) are filled by counter + // let upper = (period as u64) << 31; + // let lower = (now_rolling & 0x7FFF_FFFF) as u64; + // let time = upper | lower; + // hubris_ptime::Instant(time) + } + + fn now() -> hubris_ptime::Instant { + let tim5 = unsafe { &*device::TIM5::ptr() }; + let counter = tim5.cnt.read().bits(); + let period = PERIOD.load(Ordering::Relaxed); + + // Bit 63 (one bit) is unused. + // Bits 31..63 (32 bits) are filled by PERIOD + // Bits 00..31 (31 bits) are filled by (counter & 0x7FFF_FFFF) + // + // IF the lowest bit of `period` DOES NOT match the uppermost + // bit of `counter`, then there has been either a half or full + // rollover (0x7FFF_FFFF -> 0x8000_0000, or 0xFFFF_FFFF -> + // 0x0000_0000) not accounted for in `period`, so we add + // 0x8000_0000. + // + // Adapted from embassy-stm32's time driver technique + let upper = (period as u64) << 31; + let lower = (counter ^ ((period & 1) << 31)) as u64; + let time = upper + lower; + hubris_ptime::Instant(time) + } + + static PERIOD: AtomicU32 = AtomicU32::new(0); + static VTABLE: hubris_ptime::PTimeVTable = + hubris_ptime::PTimeVTable { + now, + timekeep, + tickrate, + }; + + core::mem::forget(self); + &VTABLE + } } } diff --git a/sys/kern/Cargo.toml b/sys/kern/Cargo.toml index de9ba7193..41808e7c7 100644 --- a/sys/kern/Cargo.toml +++ b/sys/kern/Cargo.toml @@ -18,6 +18,7 @@ armv8-m-mpu = { path = "../../lib/armv8-m-mpu" } phash = { path = "../../lib/phash" } unwrap-lite = { path = "../../lib/unwrap-lite" } kerncore.path = "../kerncore" +hubris-ptime.path = "../ptime" [build-dependencies] anyhow = { workspace = true } diff --git a/sys/kern/src/arch/arm_m.rs b/sys/kern/src/arch/arm_m.rs index 9b19adcf6..15ec26d85 100644 --- a/sys/kern/src/arch/arm_m.rs +++ b/sys/kern/src/arch/arm_m.rs @@ -1089,6 +1089,9 @@ static TICKS: [AtomicU32; 2] = { #[unsafe(no_mangle)] pub unsafe extern "C" fn SysTick() { crate::profiling::event_timer_isr_enter(); + if let Some(ptimer) = hubris_ptime::ptimer() { + (ptimer.timekeep)(); + } with_task_table(|tasks| { // Load the time before this tick event. let t0 = TICKS[0].load(Ordering::Relaxed); @@ -1229,7 +1232,8 @@ unsafe extern "C" fn pendsv_entry() { // Safety: we're dereferencing the current task pointer, which we're // trusting the rest of this module to maintain correctly. let current = unsafe { - let current = &*current; + let current = &mut *current; + current.account_task_active_time(); usize::from(current.descriptor().index) }; @@ -1646,6 +1650,10 @@ unsafe extern "C" fn handle_fault(task: *mut task::Task) { // ARMv6-M, to reduce complexity, does not distinguish fault causes. let fault = FaultInfo::InvalidOperation(0); + unsafe { + (*task).account_task_active_time(); + } + // We are now going to force a fault on our current task and directly // switch to a task to run. with_task_table(|tasks| { @@ -1854,6 +1862,10 @@ unsafe extern "C" fn handle_fault( arch::asm!("vstm {0}, {{s16-s31}}", in(reg) fpsave); } + unsafe { + (*task).account_task_active_time(); + } + // We are now going to force a fault on our current task and directly // switch to a task to run. (It may be tempting to use PendSV here, // but that won't work on ARMv8-M in the presence of MPU faults on diff --git a/sys/kern/src/syscalls.rs b/sys/kern/src/syscalls.rs index 4208a9525..e77d3fa9f 100644 --- a/sys/kern/src/syscalls.rs +++ b/sys/kern/src/syscalls.rs @@ -71,7 +71,8 @@ pub unsafe extern "C" fn syscall_entry(nr: u32, task: *mut Task) { // Safety: we're trusting the interrupt entry routine to pass us a valid // task pointer. let idx = unsafe { - let t = &*task; + let t = &mut *task; + t.account_task_active_time(); usize::from(t.descriptor().index) }; diff --git a/sys/kern/src/task.rs b/sys/kern/src/task.rs index d891fca97..642232858 100644 --- a/sys/kern/src/task.rs +++ b/sys/kern/src/task.rs @@ -10,6 +10,7 @@ use abi::{ FaultInfo, FaultSource, Generation, ReplyFaultReason, SchedState, TaskId, TaskState, ULease, UsageError, }; +use hubris_ptime::{Duration, Instant}; use zerocopy::{FromBytes, Immutable, KnownLayout}; use crate::descs::{ @@ -21,6 +22,10 @@ use crate::startup::HUBRIS_FAULT_NOTIFICATION; use crate::time::Timestamp; use crate::umem::USlice; +// hate this +#[unsafe(no_mangle)] +pub(crate) static mut PTIME_LAST_SWITCH: Instant = Instant(0); + /// Internal representation of a task. /// /// The fields of this struct are private to this module so that we can maintain @@ -46,6 +51,9 @@ pub struct Task { /// Notification status. notifications: u32, + /// Time active + active: Duration, + /// Pointer to the ROM descriptor used to create this task, so it can be /// restarted. descriptor: &'static TaskDesc, @@ -65,6 +73,8 @@ impl Task { descriptor, + // TODO: Maintain active time across generations? + active: Duration::ZERO, generation: 0, notifications: 0, save: crate::arch::SavedState::default(), @@ -417,6 +427,18 @@ impl Task { crate::arch::set_current_task(self); } } + + pub(crate) fn account_task_active_time(&mut self) { + if let Some(ptimer) = hubris_ptime::ptimer() { + let now = (ptimer.now)(); + let mut old = now; + unsafe { + core::ptr::swap(&mut old, &raw mut PTIME_LAST_SWITCH); + } + let elapsed = now.0 - old.0; + self.active.0 += elapsed; + } + } } /// Interface that must be implemented by the `arch::SavedState` type. This diff --git a/sys/ptime/Cargo.toml b/sys/ptime/Cargo.toml new file mode 100644 index 000000000..a9f93fcbc --- /dev/null +++ b/sys/ptime/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "hubris-ptime" +version = "0.1.0" +edition = "2024" + +[dependencies] + +[features] + +[lib] +test = false +doctest = false +bench = false + +[lints] +workspace = true diff --git a/sys/ptime/src/lib.rs b/sys/ptime/src/lib.rs new file mode 100644 index 000000000..5e938d9dd --- /dev/null +++ b/sys/ptime/src/lib.rs @@ -0,0 +1,48 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Precise time +//! +//! Currently only used for profiling + +#![no_std] + +use core::sync::atomic::{AtomicPtr, Ordering}; + +#[derive(Debug, Clone, Copy)] +pub struct Instant(pub u64); +#[derive(Debug, Clone, Copy)] +pub struct Duration(pub u64); + +impl Duration { + pub const ZERO: Self = Self(0); +} + +pub type NowFunc = fn() -> Instant; +// TODO: do we want this to return an Instant? It's probably a bit cheaper +// than calling timekeep() -> now(), but I'm not sure if we *need* it for +// anything, as systick doesn't do anything with it. +pub type TimeKeepFunc = fn(); +pub type TickRateFunc = fn() -> u32; + +pub struct PTimeVTable { + pub now: NowFunc, + pub timekeep: TimeKeepFunc, + pub tickrate: TickRateFunc, +} + +static PTIME_VTABLE: AtomicPtr = + AtomicPtr::new(core::ptr::null_mut()); + +pub fn set_ptime_vtable(vtable: &'static PTimeVTable) { + let vtable: *const PTimeVTable = vtable; + let vtable: *mut PTimeVTable = vtable.cast_mut(); + PTIME_VTABLE.store(vtable, Ordering::Release) +} + +pub fn ptimer() -> Option<&'static PTimeVTable> { + let vtable: *mut PTimeVTable = PTIME_VTABLE.load(Ordering::Acquire); + let vtable: *const PTimeVTable = vtable.cast_const(); + unsafe { vtable.as_ref() } +}