Skip to main content

ax_task/runtime/
clock.rs

1//! Scheduler-clock and physical monotonic-clock ABI.
2
3/// Largest value in Linux's signed `ktime_t` domain.
4pub const KTIME_MAX_NANOS: u64 = i64::MAX as u64;
5
6/// One finite sample of the runtime monotonic clock.
7#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
8#[repr(transparent)]
9pub struct MonotonicInstant(u64);
10
11impl MonotonicInstant {
12    /// Validates one sample against the signed `ktime_t` domain.
13    pub const fn from_nanos(now_ns: u64) -> Option<Self> {
14        if now_ns <= KTIME_MAX_NANOS {
15            Some(Self(now_ns))
16        } else {
17            None
18        }
19    }
20
21    /// Returns the absolute sample in nanoseconds.
22    pub const fn as_nanos(self) -> u64 {
23        self.0
24    }
25
26    /// Reports whether an absolute monotonic deadline has elapsed.
27    pub const fn reached(self, deadline: MonotonicDeadline) -> bool {
28        self.0 >= deadline.0
29    }
30
31    /// Adds a relative timeout with Linux `ktime_add_safe()` saturation.
32    pub fn deadline_after(self, timeout: core::time::Duration) -> MonotonicDeadline {
33        let timeout_ns = timeout.as_nanos();
34        let sum = self.0 as u128 + timeout_ns;
35        if sum >= KTIME_MAX_NANOS as u128 {
36            // Linux `ktime_add_safe()` calls `ktime_set(KTIME_SEC_MAX, 0)`;
37            // `ktime_set()` then clamps that boundary to `KTIME_MAX`.
38            MonotonicDeadline(KTIME_MAX_NANOS)
39        } else {
40            MonotonicDeadline(sum as u64)
41        }
42    }
43}
44
45/// Absolute finite deadline measured by the runtime's monotonic clock.
46#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
47#[repr(transparent)]
48pub struct MonotonicDeadline(u64);
49
50impl MonotonicDeadline {
51    /// The monotonic clock origin, which is necessarily already due once the
52    /// clock has advanced.
53    pub const ORIGIN: Self = Self(0);
54
55    /// Creates a representable physical clockevent deadline.
56    ///
57    /// Zero is a valid already-due deadline. Absence is represented only by
58    /// `Option::None`; like Linux `ktime_t`, `KTIME_MAX` remains a finite value.
59    pub const fn from_nanos(deadline_ns: u64) -> Option<Self> {
60        if deadline_ns <= KTIME_MAX_NANOS {
61            Some(Self(deadline_ns))
62        } else {
63            None
64        }
65    }
66
67    /// Converts a duration-valued absolute timestamp using Linux
68    /// `timespec64_to_ktime()` saturation.
69    pub fn from_duration(deadline: core::time::Duration) -> Self {
70        const NANOS_PER_SECOND: u64 = 1_000_000_000;
71        const KTIME_SEC_MAX: u64 = KTIME_MAX_NANOS / NANOS_PER_SECOND;
72
73        if deadline.as_secs() >= KTIME_SEC_MAX {
74            return Self(KTIME_MAX_NANOS);
75        }
76        Self(deadline.as_secs() * NANOS_PER_SECOND + u64::from(deadline.subsec_nanos()))
77    }
78
79    /// Returns the absolute deadline in nanoseconds.
80    pub const fn as_nanos(self) -> u64 {
81        self.0
82    }
83}
84
85/// One Linux-style runqueue-clock observation for a target CPU.
86///
87/// `clock` is the corrected `sched_clock_cpu()` value. `hardirq_time_ns` is
88/// present only when the runtime enables Linux-style IRQ time accounting and
89/// can publish the target CPU's cumulative interrupt time coherently with that
90/// clock. The runqueue owner must treat absence like Linux built without
91/// `CONFIG_IRQ_TIME_ACCOUNTING`: task time advances by the full clock delta and
92/// IRQ PELT remains disabled.
93#[derive(Clone, Copy, Debug, Eq, PartialEq)]
94#[repr(C)]
95pub struct RqClockSample {
96    clock: crate::sched::SchedulerTimestamp,
97    hardirq_time_ns: Option<u64>,
98    frequency_capacity: u32,
99    cpu_capacity: u32,
100}
101
102impl RqClockSample {
103    /// Creates one coherent runqueue-clock observation.
104    pub const fn new(clock: crate::sched::SchedulerTimestamp, hardirq_time_ns: u64) -> Self {
105        Self {
106            clock,
107            hardirq_time_ns: Some(hardirq_time_ns),
108            frequency_capacity: 1_024,
109            cpu_capacity: 1_024,
110        }
111    }
112
113    /// Creates a sample from a runtime without IRQ time accounting authority.
114    pub const fn without_irq_time_accounting(clock: crate::sched::SchedulerTimestamp) -> Self {
115        Self {
116            clock,
117            hardirq_time_ns: None,
118            frequency_capacity: 1_024,
119            cpu_capacity: 1_024,
120        }
121    }
122
123    /// Adds Linux scheduler-capacity scaling for the sampled CPU.
124    ///
125    /// Both scales use `SCHED_CAPACITY_SCALE == 1024`. The current ArceOS
126    /// runtime uses the full-capacity default on fixed-frequency homogeneous
127    /// systems; a platform with frequency invariance can publish narrower
128    /// values through this constructor.
129    pub const fn with_capacity_scales(
130        clock: crate::sched::SchedulerTimestamp,
131        hardirq_time_ns: u64,
132        frequency_capacity: u32,
133        cpu_capacity: u32,
134    ) -> Option<Self> {
135        if frequency_capacity == 0
136            || frequency_capacity > 1_024
137            || cpu_capacity == 0
138            || cpu_capacity > 1_024
139        {
140            return None;
141        }
142        Some(Self {
143            clock,
144            hardirq_time_ns: Some(hardirq_time_ns),
145            frequency_capacity,
146            cpu_capacity,
147        })
148    }
149
150    /// Returns the corrected scheduler-clock value.
151    pub const fn clock(self) -> crate::sched::SchedulerTimestamp {
152        self.clock
153    }
154
155    /// Returns cumulative hard-interrupt time for the target CPU.
156    pub const fn hardirq_time_ns(self) -> Option<u64> {
157        self.hardirq_time_ns
158    }
159
160    /// Returns Linux `arch_scale_freq_capacity()` units.
161    pub const fn frequency_capacity(self) -> u32 {
162        self.frequency_capacity
163    }
164
165    /// Returns Linux `arch_scale_cpu_capacity()` units.
166    pub const fn cpu_capacity(self) -> u32 {
167        self.cpu_capacity
168    }
169}
170
171/// One generation-ordered publication from a CPU's scheduler owner.
172#[derive(Clone, Copy, Debug, Eq, PartialEq)]
173pub struct SchedulerDeadlineUpdate {
174    generation: u64,
175    deadline: Option<MonotonicDeadline>,
176}
177
178impl SchedulerDeadlineUpdate {
179    /// Creates one publication after the owner has committed its local state.
180    ///
181    /// Generation zero is reserved for an uninitialized consumer.
182    pub const fn try_new(generation: u64, deadline: Option<MonotonicDeadline>) -> Option<Self> {
183        if generation == 0 {
184            None
185        } else {
186            Some(Self {
187                generation,
188                deadline,
189            })
190        }
191    }
192
193    /// Returns the monotonically increasing per-CPU publication generation.
194    pub const fn generation(self) -> u64 {
195        self.generation
196    }
197
198    /// Returns the next scheduler-owned physical deadline, if one exists.
199    pub const fn deadline(self) -> Option<MonotonicDeadline> {
200        self.deadline
201    }
202}
203
204/// Owner-local update for the current scheduling class's hrtick.
205///
206/// Linux keeps this relative request in the runqueue owner until scheduler
207/// exit, where the local hrtimer base converts it to a physical deadline.
208#[derive(Clone, Copy, Debug, Eq, PartialEq)]
209pub enum SchedulerRuntimeDeadline {
210    Disarmed,
211    Due,
212    After(core::time::Duration),
213}
214
215#[cfg(test)]
216mod monotonic_time_tests {
217    use super::*;
218
219    #[test]
220    fn monotonic_time_matches_linux_ktime_boundaries() {
221        let deadline = MonotonicDeadline::from_nanos(0).unwrap();
222        assert_eq!(deadline, MonotonicDeadline::ORIGIN);
223        assert!(MonotonicInstant::from_nanos(1).unwrap().reached(deadline));
224        assert!(MonotonicInstant::from_nanos(KTIME_MAX_NANOS - 1).is_some());
225        assert!(MonotonicDeadline::from_nanos(KTIME_MAX_NANOS - 1).is_some());
226        assert!(MonotonicInstant::from_nanos(KTIME_MAX_NANOS).is_some());
227        assert_eq!(
228            MonotonicDeadline::from_nanos(KTIME_MAX_NANOS),
229            Some(MonotonicDeadline(KTIME_MAX_NANOS))
230        );
231        assert!(MonotonicDeadline::from_nanos(KTIME_MAX_NANOS + 1).is_none());
232        assert_eq!(
233            MonotonicDeadline::from_duration(core::time::Duration::MAX),
234            MonotonicDeadline::from_nanos(KTIME_MAX_NANOS).unwrap()
235        );
236        let now = MonotonicInstant::from_nanos(KTIME_MAX_NANOS - 2).unwrap();
237        assert_eq!(
238            now.deadline_after(core::time::Duration::from_nanos(2)),
239            MonotonicDeadline::from_nanos(KTIME_MAX_NANOS).unwrap()
240        );
241    }
242}