Skip to main content

oxigdal_embedded/
realtime.rs

1//! Real-time scheduling and deadline management
2//!
3//! Provides utilities for real-time constrained operations in embedded systems
4
5use crate::error::{EmbeddedError, Result};
6use crate::target;
7use core::sync::atomic::{AtomicU64, Ordering};
8
9/// Real-time priority levels
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
11#[repr(u8)]
12pub enum Priority {
13    /// Idle priority (lowest)
14    Idle = 0,
15    /// Low priority
16    Low = 1,
17    /// Normal priority
18    Normal = 2,
19    /// High priority
20    High = 3,
21    /// Critical priority (highest)
22    Critical = 4,
23}
24
25impl Priority {
26    /// Get priority from u8
27    pub const fn from_u8(value: u8) -> Option<Self> {
28        match value {
29            0 => Some(Self::Idle),
30            1 => Some(Self::Low),
31            2 => Some(Self::Normal),
32            3 => Some(Self::High),
33            4 => Some(Self::Critical),
34            _ => None,
35        }
36    }
37}
38
39/// Deadline specification
40#[derive(Debug, Clone, Copy)]
41pub struct Deadline {
42    /// Deadline time in microseconds
43    pub time_us: u64,
44    /// Is this a hard deadline (must be met)?
45    pub is_hard: bool,
46}
47
48impl Deadline {
49    /// Create a new soft deadline
50    pub const fn soft(time_us: u64) -> Self {
51        Self {
52            time_us,
53            is_hard: false,
54        }
55    }
56
57    /// Create a new hard deadline
58    pub const fn hard(time_us: u64) -> Self {
59        Self {
60            time_us,
61            is_hard: true,
62        }
63    }
64
65    /// Check if deadline is expired
66    pub fn is_expired(&self, current_us: u64) -> bool {
67        current_us >= self.time_us
68    }
69
70    /// Get remaining time in microseconds
71    pub fn remaining_us(&self, current_us: u64) -> u64 {
72        self.time_us.saturating_sub(current_us)
73    }
74}
75
76/// Real-time scheduler
77pub struct RealtimeScheduler {
78    start_cycles: AtomicU64,
79    cycles_per_us: u64,
80}
81
82impl RealtimeScheduler {
83    /// Create a new real-time scheduler
84    ///
85    /// # Arguments
86    ///
87    /// * `cpu_freq_mhz` - CPU frequency in MHz
88    pub const fn new(cpu_freq_mhz: u64) -> Self {
89        Self {
90            start_cycles: AtomicU64::new(0),
91            cycles_per_us: cpu_freq_mhz,
92        }
93    }
94
95    /// Initialize the scheduler (record start time)
96    pub fn init(&self) {
97        if let Some(cycles) = target::cycle_count() {
98            self.start_cycles.store(cycles, Ordering::Relaxed);
99        }
100    }
101
102    /// Get elapsed time in microseconds since init
103    pub fn elapsed_us(&self) -> u64 {
104        match target::cycle_count() {
105            Some(current) => {
106                let start = self.start_cycles.load(Ordering::Relaxed);
107                let elapsed_cycles = current.saturating_sub(start);
108                elapsed_cycles / self.cycles_per_us
109            }
110            None => 0,
111        }
112    }
113
114    /// Execute a function with a deadline
115    ///
116    /// # Errors
117    ///
118    /// Returns `DeadlineMissed` if the deadline is exceeded
119    pub fn execute_with_deadline<F, T>(&self, deadline: Deadline, f: F) -> Result<T>
120    where
121        F: FnOnce() -> T,
122    {
123        let start_us = self.elapsed_us();
124        let result = f();
125        let end_us = self.elapsed_us();
126
127        let elapsed = end_us.saturating_sub(start_us);
128
129        if deadline.is_hard && elapsed > deadline.time_us {
130            return Err(EmbeddedError::DeadlineMissed {
131                actual_us: elapsed,
132                deadline_us: deadline.time_us,
133            });
134        }
135
136        Ok(result)
137    }
138
139    /// Check if deadline can be met
140    pub fn can_meet_deadline(&self, deadline: &Deadline) -> bool {
141        let current_us = self.elapsed_us();
142        !deadline.is_expired(current_us)
143    }
144
145    /// Get time until deadline
146    pub fn time_until_deadline(&self, deadline: &Deadline) -> u64 {
147        let current_us = self.elapsed_us();
148        deadline.remaining_us(current_us)
149    }
150}
151
152/// Periodic task specification
153#[derive(Debug, Clone)]
154pub struct PeriodicTask {
155    /// Period in microseconds
156    pub period_us: u64,
157    /// Execution time budget in microseconds
158    pub budget_us: u64,
159    /// Priority
160    pub priority: Priority,
161    /// Last execution time (None if never executed)
162    last_exec_us: Option<u64>,
163}
164
165impl PeriodicTask {
166    /// Create a new periodic task
167    pub const fn new(period_us: u64, budget_us: u64, priority: Priority) -> Self {
168        Self {
169            period_us,
170            budget_us,
171            priority,
172            last_exec_us: None,
173        }
174    }
175
176    /// Check if task is ready to execute
177    pub fn is_ready(&self, current_us: u64) -> bool {
178        match self.last_exec_us {
179            // First execution: task is always ready
180            None => true,
181            // Subsequent executions: check if period has elapsed
182            Some(last) => current_us.saturating_sub(last) >= self.period_us,
183        }
184    }
185
186    /// Mark task as executed
187    pub fn mark_executed(&mut self, current_us: u64) {
188        self.last_exec_us = Some(current_us);
189    }
190
191    /// Get deadline for next execution
192    pub fn next_deadline(&self) -> Deadline {
193        let last = self.last_exec_us.unwrap_or(0);
194        Deadline::hard(last + self.period_us + self.budget_us)
195    }
196}
197
198/// Task timing statistics
199#[derive(Debug, Clone, Copy, Default)]
200pub struct TaskStats {
201    /// Total executions
202    pub executions: u64,
203    /// Minimum execution time (microseconds)
204    pub min_exec_us: u64,
205    /// Maximum execution time (microseconds)
206    pub max_exec_us: u64,
207    /// Total execution time (microseconds)
208    pub total_exec_us: u64,
209    /// Number of deadline misses
210    pub deadline_misses: u64,
211}
212
213impl TaskStats {
214    /// Create new task statistics
215    pub const fn new() -> Self {
216        Self {
217            executions: 0,
218            min_exec_us: u64::MAX,
219            max_exec_us: 0,
220            total_exec_us: 0,
221            deadline_misses: 0,
222        }
223    }
224
225    /// Record an execution
226    pub fn record_execution(&mut self, exec_us: u64, missed_deadline: bool) {
227        self.executions = self.executions.saturating_add(1);
228        self.total_exec_us = self.total_exec_us.saturating_add(exec_us);
229
230        if exec_us < self.min_exec_us {
231            self.min_exec_us = exec_us;
232        }
233
234        if exec_us > self.max_exec_us {
235            self.max_exec_us = exec_us;
236        }
237
238        if missed_deadline {
239            self.deadline_misses = self.deadline_misses.saturating_add(1);
240        }
241    }
242
243    /// Get average execution time
244    pub fn avg_exec_us(&self) -> u64 {
245        if self.executions == 0 {
246            0
247        } else {
248            self.total_exec_us / self.executions
249        }
250    }
251
252    /// Get deadline miss rate
253    pub fn miss_rate(&self) -> f32 {
254        if self.executions == 0 {
255            0.0
256        } else {
257            self.deadline_misses as f32 / self.executions as f32
258        }
259    }
260}
261
262/// Rate monotonic scheduler
263///
264/// Tasks are assigned priorities based on their periods (shorter period = higher priority)
265pub struct RateMonotonicScheduler<const MAX_TASKS: usize> {
266    tasks: heapless::Vec<PeriodicTask, MAX_TASKS>,
267    stats: heapless::Vec<TaskStats, MAX_TASKS>,
268    scheduler: RealtimeScheduler,
269}
270
271impl<const MAX_TASKS: usize> RateMonotonicScheduler<MAX_TASKS> {
272    /// Create a new rate monotonic scheduler
273    pub const fn new(cpu_freq_mhz: u64) -> Self {
274        Self {
275            tasks: heapless::Vec::new(),
276            stats: heapless::Vec::new(),
277            scheduler: RealtimeScheduler::new(cpu_freq_mhz),
278        }
279    }
280
281    /// Initialize the scheduler
282    pub fn init(&mut self) {
283        self.scheduler.init();
284    }
285
286    /// Add a periodic task
287    ///
288    /// # Errors
289    ///
290    /// Returns error if maximum tasks reached
291    pub fn add_task(&mut self, task: PeriodicTask) -> Result<()> {
292        self.tasks
293            .push(task)
294            .map_err(|_| EmbeddedError::BufferTooSmall {
295                required: 1,
296                available: 0,
297            })?;
298
299        self.stats
300            .push(TaskStats::new())
301            .map_err(|_| EmbeddedError::BufferTooSmall {
302                required: 1,
303                available: 0,
304            })?;
305
306        // Sort tasks by period (rate monotonic scheduling)
307        self.sort_tasks();
308
309        Ok(())
310    }
311
312    /// Sort tasks by period (shortest period first)
313    fn sort_tasks(&mut self) {
314        let len = self.tasks.len();
315
316        for i in 0..len {
317            for j in (i + 1)..len {
318                if self.tasks[j].period_us < self.tasks[i].period_us {
319                    self.tasks.swap(i, j);
320                    self.stats.swap(i, j);
321                }
322            }
323        }
324    }
325
326    /// Schedule and execute ready tasks
327    pub fn schedule(&mut self) -> Result<usize> {
328        let current_us = self.scheduler.elapsed_us();
329        let mut executed: usize = 0;
330
331        for (i, task) in self.tasks.iter_mut().enumerate() {
332            if task.is_ready(current_us) {
333                let start_us = self.scheduler.elapsed_us();
334
335                // Task would be executed here
336                // For now, just mark as executed
337
338                let end_us = self.scheduler.elapsed_us();
339                let exec_us = end_us.saturating_sub(start_us);
340
341                let missed = exec_us > task.budget_us;
342                self.stats[i].record_execution(exec_us, missed);
343
344                task.mark_executed(current_us);
345                executed = executed.saturating_add(1);
346            }
347        }
348
349        Ok(executed)
350    }
351
352    /// Get statistics for a task
353    pub fn get_stats(&self, task_index: usize) -> Option<&TaskStats> {
354        self.stats.get(task_index)
355    }
356
357    /// Get number of tasks
358    pub fn task_count(&self) -> usize {
359        self.tasks.len()
360    }
361}
362
363/// Watchdog timer for deadline monitoring
364pub struct Watchdog {
365    timeout_us: u64,
366    last_feed_us: AtomicU64,
367}
368
369impl Watchdog {
370    /// Create a new watchdog with timeout
371    pub const fn new(timeout_us: u64) -> Self {
372        Self {
373            timeout_us,
374            last_feed_us: AtomicU64::new(0),
375        }
376    }
377
378    /// Feed the watchdog (reset timer)
379    pub fn feed(&self, current_us: u64) {
380        self.last_feed_us.store(current_us, Ordering::Release);
381    }
382
383    /// Check if watchdog has expired
384    pub fn is_expired(&self, current_us: u64) -> bool {
385        let last_feed = self.last_feed_us.load(Ordering::Acquire);
386        current_us.saturating_sub(last_feed) >= self.timeout_us
387    }
388
389    /// Get time until expiry
390    pub fn time_until_expiry(&self, current_us: u64) -> u64 {
391        let last_feed = self.last_feed_us.load(Ordering::Acquire);
392        let elapsed = current_us.saturating_sub(last_feed);
393        self.timeout_us.saturating_sub(elapsed)
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400
401    #[test]
402    fn test_priority_ordering() {
403        assert!(Priority::Idle < Priority::Low);
404        assert!(Priority::Low < Priority::Normal);
405        assert!(Priority::Normal < Priority::High);
406        assert!(Priority::High < Priority::Critical);
407    }
408
409    #[test]
410    fn test_deadline() {
411        let deadline = Deadline::hard(1000);
412        assert!(!deadline.is_expired(500));
413        assert!(deadline.is_expired(1000));
414        assert_eq!(deadline.remaining_us(500), 500);
415    }
416
417    #[test]
418    fn test_periodic_task() {
419        let mut task = PeriodicTask::new(1000, 100, Priority::Normal);
420        assert!(task.is_ready(0));
421        task.mark_executed(0);
422        assert!(!task.is_ready(500));
423        assert!(task.is_ready(1000));
424    }
425
426    #[test]
427    fn test_task_stats() {
428        let mut stats = TaskStats::new();
429        stats.record_execution(100, false);
430        stats.record_execution(200, false);
431        stats.record_execution(150, true);
432
433        assert_eq!(stats.executions, 3);
434        assert_eq!(stats.min_exec_us, 100);
435        assert_eq!(stats.max_exec_us, 200);
436        assert_eq!(stats.avg_exec_us(), 150);
437        assert_eq!(stats.deadline_misses, 1);
438    }
439
440    #[test]
441    fn test_watchdog() {
442        let watchdog = Watchdog::new(1000);
443        watchdog.feed(0);
444
445        assert!(!watchdog.is_expired(500));
446        assert!(watchdog.is_expired(1000));
447        assert_eq!(watchdog.time_until_expiry(500), 500);
448    }
449}