From 94a0963eaf245d743e75594c8ca36ebb7633889d Mon Sep 17 00:00:00 2001 From: James Munns Date: Mon, 20 Jul 2026 09:42:49 +0200 Subject: [PATCH 1/2] Initial proof of concept timer implementation --- Cargo.lock | 1 + app/gimlet/src/main.rs | 6 ++++ app/grapefruit/src/main.rs | 8 ++++- drv/stm32h7-startup/Cargo.toml | 1 + drv/stm32h7-startup/src/lib.rs | 62 ++++++++++++++++++++++++++++++++++ sys/kern/src/arch/arm_m.rs | 14 +++++++- sys/kern/src/lib.rs | 1 + sys/kern/src/ptime.rs | 50 +++++++++++++++++++++++++++ sys/kern/src/syscalls.rs | 3 +- sys/kern/src/task.rs | 21 ++++++++++++ 10 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 sys/kern/src/ptime.rs diff --git a/Cargo.lock b/Cargo.lock index 221890ec3f..82e446f2e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2756,6 +2756,7 @@ version = "0.1.0" dependencies = [ "cortex-m", "cortex-m-rt", + "kern", "measurement-handoff", "stm32h7", ] diff --git a/app/gimlet/src/main.rs b/app/gimlet/src/main.rs index e6c5b8ae72..0c8b053c3c 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() }; + kern::ptime::set_ptime_vtable(vtable); } diff --git a/app/grapefruit/src/main.rs b/app/grapefruit/src/main.rs index 33dc8188d4..91416ef522 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() }; + kern::ptime::set_ptime_vtable(vtable); } diff --git a/drv/stm32h7-startup/Cargo.toml b/drv/stm32h7-startup/Cargo.toml index b2dd64e86e..8b795bf3c9 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 } +kern = { path = "../../sys/kern" } [features] h743 = ["stm32h7/stm32h743"] diff --git a/drv/stm32h7-startup/src/lib.rs b/drv/stm32h7-startup/src/lib.rs index 94a09ce22d..7c2a7c9168 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 kern::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; + // kern::ptime::Instant(time) + } + + fn now() -> kern::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; + kern::ptime::Instant(time) + } + + static PERIOD: AtomicU32 = AtomicU32::new(0); + static VTABLE: kern::ptime::PTimeVTable = + kern::ptime::PTimeVTable { + now, + timekeep, + tickrate, + }; + + core::mem::forget(self); + &VTABLE + } } } diff --git a/sys/kern/src/arch/arm_m.rs b/sys/kern/src/arch/arm_m.rs index 9b19adcf63..f4f74fd29a 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) = crate::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/lib.rs b/sys/kern/src/lib.rs index e94e0c4ef1..836982530b 100644 --- a/sys/kern/src/lib.rs +++ b/sys/kern/src/lib.rs @@ -42,6 +42,7 @@ pub mod fail; pub mod header; pub mod kipc; pub mod profiling; +pub mod ptime; pub mod startup; pub mod syscalls; pub mod task; diff --git a/sys/kern/src/ptime.rs b/sys/kern/src/ptime.rs new file mode 100644 index 0000000000..855e931c80 --- /dev/null +++ b/sys/kern/src/ptime.rs @@ -0,0 +1,50 @@ +// 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 + +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, +} + +// hate this +#[unsafe(no_mangle)] +pub(crate) static mut PTIME_LAST_SWITCH: Instant = Instant(0); + +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() } +} diff --git a/sys/kern/src/syscalls.rs b/sys/kern/src/syscalls.rs index 4208a95253..e77d3fa9fd 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 d891fca97c..507c523bf7 100644 --- a/sys/kern/src/task.rs +++ b/sys/kern/src/task.rs @@ -17,6 +17,7 @@ use crate::descs::{ TaskFlags, }; use crate::err::UserError; +use crate::ptime::Duration; use crate::startup::HUBRIS_FAULT_NOTIFICATION; use crate::time::Timestamp; use crate::umem::USlice; @@ -46,6 +47,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 +69,8 @@ impl Task { descriptor, + // TODO: Maintain active time across generations? + active: Duration::ZERO, generation: 0, notifications: 0, save: crate::arch::SavedState::default(), @@ -417,6 +423,21 @@ impl Task { crate::arch::set_current_task(self); } } + + pub(crate) fn account_task_active_time(&mut self) { + if let Some(ptimer) = crate::ptime::ptimer() { + let now = (ptimer.now)(); + let mut old = now; + unsafe { + core::ptr::swap( + &mut old, + &raw mut crate::ptime::PTIME_LAST_SWITCH, + ); + } + let elapsed = now.0 - old.0; + self.active.0 += elapsed; + } + } } /// Interface that must be implemented by the `arch::SavedState` type. This From ab43f26ef83774fafd9b7bdeec1ab02524c9a64b Mon Sep 17 00:00:00 2001 From: James Munns Date: Mon, 20 Jul 2026 10:59:18 +0200 Subject: [PATCH 2/2] Add new crate to avoid depending on the kernel directly --- Cargo.lock | 9 ++++++++- app/gimlet/Cargo.toml | 1 + app/gimlet/src/main.rs | 2 +- app/grapefruit/Cargo.toml | 1 + app/grapefruit/src/main.rs | 2 +- drv/stm32h7-startup/Cargo.toml | 2 +- drv/stm32h7-startup/src/lib.rs | 12 ++++++------ sys/kern/Cargo.toml | 1 + sys/kern/src/arch/arm_m.rs | 2 +- sys/kern/src/lib.rs | 1 - sys/kern/src/task.rs | 13 +++++++------ sys/ptime/Cargo.toml | 16 ++++++++++++++++ sys/{kern/src/ptime.rs => ptime/src/lib.rs} | 6 ++---- 13 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 sys/ptime/Cargo.toml rename sys/{kern/src/ptime.rs => ptime/src/lib.rs} (93%) diff --git a/Cargo.lock b/Cargo.lock index 82e446f2e5..8b33a067ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2756,7 +2756,7 @@ version = "0.1.0" dependencies = [ "cortex-m", "cortex-m-rt", - "kern", + "hubris-ptime", "measurement-handoff", "stm32h7", ] @@ -3395,6 +3395,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "drv-stm32h7-startup", + "hubris-ptime", "kern", "ringbuf", "stm32h7", @@ -3452,6 +3453,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "drv-stm32h7-startup", + "hubris-ptime", "kern", "stm32h7", ] @@ -3671,6 +3673,10 @@ dependencies = [ "build-util", ] +[[package]] +name = "hubris-ptime" +version = "0.1.0" + [[package]] name = "hubris-task-names" version = "0.1.0" @@ -3997,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 00269a76e8..d83d29c967 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 0c8b053c3c..ad139ee646 100644 --- a/app/gimlet/src/main.rs +++ b/app/gimlet/src/main.rs @@ -235,5 +235,5 @@ fn system_init() { let ptime = RollingTimer::new_tim5(&p, 200); // SAFETY: we promise to never use TIM5 again. let vtable = unsafe { ptime.into_ptimer() }; - kern::ptime::set_ptime_vtable(vtable); + hubris_ptime::set_ptime_vtable(vtable); } diff --git a/app/grapefruit/Cargo.toml b/app/grapefruit/Cargo.toml index e4309c951f..2237ab4cba 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 91416ef522..40a34e19a2 100644 --- a/app/grapefruit/src/main.rs +++ b/app/grapefruit/src/main.rs @@ -448,5 +448,5 @@ fn system_init() { let ptime = RollingTimer::new_tim5(&p, 200); // SAFETY: we promise to never use TIM5 again. let vtable = unsafe { ptime.into_ptimer() }; - kern::ptime::set_ptime_vtable(vtable); + hubris_ptime::set_ptime_vtable(vtable); } diff --git a/drv/stm32h7-startup/Cargo.toml b/drv/stm32h7-startup/Cargo.toml index 8b795bf3c9..a53c4c53c1 100644 --- a/drv/stm32h7-startup/Cargo.toml +++ b/drv/stm32h7-startup/Cargo.toml @@ -8,7 +8,7 @@ cortex-m = { workspace = true } cortex-m-rt = { workspace = true } stm32h7 = { workspace = true } measurement-handoff = { path = "../../lib/measurement-handoff", optional = true } -kern = { path = "../../sys/kern" } +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 7c2a7c9168..29ef24ba02 100644 --- a/drv/stm32h7-startup/src/lib.rs +++ b/drv/stm32h7-startup/src/lib.rs @@ -460,7 +460,7 @@ pub mod rolling_timer { } } - pub unsafe fn into_ptimer(self) -> &'static kern::ptime::PTimeVTable { + 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 { @@ -485,10 +485,10 @@ pub mod rolling_timer { // let upper = (period as u64) << 31; // let lower = (now_rolling & 0x7FFF_FFFF) as u64; // let time = upper | lower; - // kern::ptime::Instant(time) + // hubris_ptime::Instant(time) } - fn now() -> kern::ptime::Instant { + fn now() -> hubris_ptime::Instant { let tim5 = unsafe { &*device::TIM5::ptr() }; let counter = tim5.cnt.read().bits(); let period = PERIOD.load(Ordering::Relaxed); @@ -507,12 +507,12 @@ pub mod rolling_timer { let upper = (period as u64) << 31; let lower = (counter ^ ((period & 1) << 31)) as u64; let time = upper + lower; - kern::ptime::Instant(time) + hubris_ptime::Instant(time) } static PERIOD: AtomicU32 = AtomicU32::new(0); - static VTABLE: kern::ptime::PTimeVTable = - kern::ptime::PTimeVTable { + static VTABLE: hubris_ptime::PTimeVTable = + hubris_ptime::PTimeVTable { now, timekeep, tickrate, diff --git a/sys/kern/Cargo.toml b/sys/kern/Cargo.toml index de9ba7193c..41808e7c71 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 f4f74fd29a..15ec26d854 100644 --- a/sys/kern/src/arch/arm_m.rs +++ b/sys/kern/src/arch/arm_m.rs @@ -1089,7 +1089,7 @@ static TICKS: [AtomicU32; 2] = { #[unsafe(no_mangle)] pub unsafe extern "C" fn SysTick() { crate::profiling::event_timer_isr_enter(); - if let Some(ptimer) = crate::ptime::ptimer() { + if let Some(ptimer) = hubris_ptime::ptimer() { (ptimer.timekeep)(); } with_task_table(|tasks| { diff --git a/sys/kern/src/lib.rs b/sys/kern/src/lib.rs index 836982530b..e94e0c4ef1 100644 --- a/sys/kern/src/lib.rs +++ b/sys/kern/src/lib.rs @@ -42,7 +42,6 @@ pub mod fail; pub mod header; pub mod kipc; pub mod profiling; -pub mod ptime; pub mod startup; pub mod syscalls; pub mod task; diff --git a/sys/kern/src/task.rs b/sys/kern/src/task.rs index 507c523bf7..6422328584 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::{ @@ -17,11 +18,14 @@ use crate::descs::{ TaskFlags, }; use crate::err::UserError; -use crate::ptime::Duration; 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 @@ -425,14 +429,11 @@ impl Task { } pub(crate) fn account_task_active_time(&mut self) { - if let Some(ptimer) = crate::ptime::ptimer() { + if let Some(ptimer) = hubris_ptime::ptimer() { let now = (ptimer.now)(); let mut old = now; unsafe { - core::ptr::swap( - &mut old, - &raw mut crate::ptime::PTIME_LAST_SWITCH, - ); + core::ptr::swap(&mut old, &raw mut PTIME_LAST_SWITCH); } let elapsed = now.0 - old.0; self.active.0 += elapsed; diff --git a/sys/ptime/Cargo.toml b/sys/ptime/Cargo.toml new file mode 100644 index 0000000000..a9f93fcbca --- /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/kern/src/ptime.rs b/sys/ptime/src/lib.rs similarity index 93% rename from sys/kern/src/ptime.rs rename to sys/ptime/src/lib.rs index 855e931c80..5e938d9dd0 100644 --- a/sys/kern/src/ptime.rs +++ b/sys/ptime/src/lib.rs @@ -6,6 +6,8 @@ //! //! Currently only used for profiling +#![no_std] + use core::sync::atomic::{AtomicPtr, Ordering}; #[derive(Debug, Clone, Copy)] @@ -30,10 +32,6 @@ pub struct PTimeVTable { pub tickrate: TickRateFunc, } -// hate this -#[unsafe(no_mangle)] -pub(crate) static mut PTIME_LAST_SWITCH: Instant = Instant(0); - static PTIME_VTABLE: AtomicPtr = AtomicPtr::new(core::ptr::null_mut());