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        self.total_exec_us.checked_div(self.executions).unwrap_or(0)
246    }
247
248    /// Get deadline miss rate
249    pub fn miss_rate(&self) -> f32 {
250        if self.executions == 0 {
251            0.0
252        } else {
253            self.deadline_misses as f32 / self.executions as f32
254        }
255    }
256}
257
258/// Rate monotonic scheduler
259///
260/// Tasks are assigned priorities based on their periods (shorter period = higher priority)
261pub struct RateMonotonicScheduler<const MAX_TASKS: usize> {
262    tasks: heapless::Vec<PeriodicTask, MAX_TASKS>,
263    stats: heapless::Vec<TaskStats, MAX_TASKS>,
264    scheduler: RealtimeScheduler,
265}
266
267impl<const MAX_TASKS: usize> RateMonotonicScheduler<MAX_TASKS> {
268    /// Create a new rate monotonic scheduler
269    pub const fn new(cpu_freq_mhz: u64) -> Self {
270        Self {
271            tasks: heapless::Vec::new(),
272            stats: heapless::Vec::new(),
273            scheduler: RealtimeScheduler::new(cpu_freq_mhz),
274        }
275    }
276
277    /// Initialize the scheduler
278    pub fn init(&mut self) {
279        self.scheduler.init();
280    }
281
282    /// Add a periodic task
283    ///
284    /// # Errors
285    ///
286    /// Returns error if maximum tasks reached
287    pub fn add_task(&mut self, task: PeriodicTask) -> Result<()> {
288        self.tasks
289            .push(task)
290            .map_err(|_| EmbeddedError::BufferTooSmall {
291                required: 1,
292                available: 0,
293            })?;
294
295        self.stats
296            .push(TaskStats::new())
297            .map_err(|_| EmbeddedError::BufferTooSmall {
298                required: 1,
299                available: 0,
300            })?;
301
302        // Sort tasks by period (rate monotonic scheduling)
303        self.sort_tasks();
304
305        Ok(())
306    }
307
308    /// Sort tasks by period (shortest period first)
309    fn sort_tasks(&mut self) {
310        let len = self.tasks.len();
311
312        for i in 0..len {
313            for j in (i + 1)..len {
314                if self.tasks[j].period_us < self.tasks[i].period_us {
315                    self.tasks.swap(i, j);
316                    self.stats.swap(i, j);
317                }
318            }
319        }
320    }
321
322    /// Schedule and execute ready tasks
323    pub fn schedule(&mut self) -> Result<usize> {
324        let current_us = self.scheduler.elapsed_us();
325        let mut executed: usize = 0;
326
327        for (i, task) in self.tasks.iter_mut().enumerate() {
328            if task.is_ready(current_us) {
329                let start_us = self.scheduler.elapsed_us();
330
331                // Task would be executed here
332                // For now, just mark as executed
333
334                let end_us = self.scheduler.elapsed_us();
335                let exec_us = end_us.saturating_sub(start_us);
336
337                let missed = exec_us > task.budget_us;
338                self.stats[i].record_execution(exec_us, missed);
339
340                task.mark_executed(current_us);
341                executed = executed.saturating_add(1);
342            }
343        }
344
345        Ok(executed)
346    }
347
348    /// Get statistics for a task
349    pub fn get_stats(&self, task_index: usize) -> Option<&TaskStats> {
350        self.stats.get(task_index)
351    }
352
353    /// Get number of tasks
354    pub fn task_count(&self) -> usize {
355        self.tasks.len()
356    }
357}
358
359/// Watchdog timer for deadline monitoring
360pub struct Watchdog {
361    timeout_us: u64,
362    last_feed_us: AtomicU64,
363}
364
365impl Watchdog {
366    /// Create a new watchdog with timeout
367    pub const fn new(timeout_us: u64) -> Self {
368        Self {
369            timeout_us,
370            last_feed_us: AtomicU64::new(0),
371        }
372    }
373
374    /// Feed the watchdog (reset timer)
375    pub fn feed(&self, current_us: u64) {
376        self.last_feed_us.store(current_us, Ordering::Release);
377    }
378
379    /// Check if watchdog has expired
380    pub fn is_expired(&self, current_us: u64) -> bool {
381        let last_feed = self.last_feed_us.load(Ordering::Acquire);
382        current_us.saturating_sub(last_feed) >= self.timeout_us
383    }
384
385    /// Get time until expiry
386    pub fn time_until_expiry(&self, current_us: u64) -> u64 {
387        let last_feed = self.last_feed_us.load(Ordering::Acquire);
388        let elapsed = current_us.saturating_sub(last_feed);
389        self.timeout_us.saturating_sub(elapsed)
390    }
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396
397    #[test]
398    fn test_priority_ordering() {
399        assert!(Priority::Idle < Priority::Low);
400        assert!(Priority::Low < Priority::Normal);
401        assert!(Priority::Normal < Priority::High);
402        assert!(Priority::High < Priority::Critical);
403    }
404
405    #[test]
406    fn test_deadline() {
407        let deadline = Deadline::hard(1000);
408        assert!(!deadline.is_expired(500));
409        assert!(deadline.is_expired(1000));
410        assert_eq!(deadline.remaining_us(500), 500);
411    }
412
413    #[test]
414    fn test_periodic_task() {
415        let mut task = PeriodicTask::new(1000, 100, Priority::Normal);
416        assert!(task.is_ready(0));
417        task.mark_executed(0);
418        assert!(!task.is_ready(500));
419        assert!(task.is_ready(1000));
420    }
421
422    #[test]
423    fn test_task_stats() {
424        let mut stats = TaskStats::new();
425        stats.record_execution(100, false);
426        stats.record_execution(200, false);
427        stats.record_execution(150, true);
428
429        assert_eq!(stats.executions, 3);
430        assert_eq!(stats.min_exec_us, 100);
431        assert_eq!(stats.max_exec_us, 200);
432        assert_eq!(stats.avg_exec_us(), 150);
433        assert_eq!(stats.deadline_misses, 1);
434    }
435
436    #[test]
437    fn test_watchdog() {
438        let watchdog = Watchdog::new(1000);
439        watchdog.feed(0);
440
441        assert!(!watchdog.is_expired(500));
442        assert!(watchdog.is_expired(1000));
443        assert_eq!(watchdog.time_until_expiry(500), 500);
444    }
445}