1use crate::error::{EmbeddedError, Result};
6use crate::target;
7use core::sync::atomic::{AtomicU64, Ordering};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
11#[repr(u8)]
12pub enum Priority {
13 Idle = 0,
15 Low = 1,
17 Normal = 2,
19 High = 3,
21 Critical = 4,
23}
24
25impl Priority {
26 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#[derive(Debug, Clone, Copy)]
41pub struct Deadline {
42 pub time_us: u64,
44 pub is_hard: bool,
46}
47
48impl Deadline {
49 pub const fn soft(time_us: u64) -> Self {
51 Self {
52 time_us,
53 is_hard: false,
54 }
55 }
56
57 pub const fn hard(time_us: u64) -> Self {
59 Self {
60 time_us,
61 is_hard: true,
62 }
63 }
64
65 pub fn is_expired(&self, current_us: u64) -> bool {
67 current_us >= self.time_us
68 }
69
70 pub fn remaining_us(&self, current_us: u64) -> u64 {
72 self.time_us.saturating_sub(current_us)
73 }
74}
75
76pub struct RealtimeScheduler {
78 start_cycles: AtomicU64,
79 cycles_per_us: u64,
80}
81
82impl RealtimeScheduler {
83 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 pub fn init(&self) {
97 if let Some(cycles) = target::cycle_count() {
98 self.start_cycles.store(cycles, Ordering::Relaxed);
99 }
100 }
101
102 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 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 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 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#[derive(Debug, Clone)]
154pub struct PeriodicTask {
155 pub period_us: u64,
157 pub budget_us: u64,
159 pub priority: Priority,
161 last_exec_us: Option<u64>,
163}
164
165impl PeriodicTask {
166 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 pub fn is_ready(&self, current_us: u64) -> bool {
178 match self.last_exec_us {
179 None => true,
181 Some(last) => current_us.saturating_sub(last) >= self.period_us,
183 }
184 }
185
186 pub fn mark_executed(&mut self, current_us: u64) {
188 self.last_exec_us = Some(current_us);
189 }
190
191 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#[derive(Debug, Clone, Copy, Default)]
200pub struct TaskStats {
201 pub executions: u64,
203 pub min_exec_us: u64,
205 pub max_exec_us: u64,
207 pub total_exec_us: u64,
209 pub deadline_misses: u64,
211}
212
213impl TaskStats {
214 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 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 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 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
262pub 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 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 pub fn init(&mut self) {
283 self.scheduler.init();
284 }
285
286 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 self.sort_tasks();
308
309 Ok(())
310 }
311
312 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 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 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 pub fn get_stats(&self, task_index: usize) -> Option<&TaskStats> {
354 self.stats.get(task_index)
355 }
356
357 pub fn task_count(&self) -> usize {
359 self.tasks.len()
360 }
361}
362
363pub struct Watchdog {
365 timeout_us: u64,
366 last_feed_us: AtomicU64,
367}
368
369impl Watchdog {
370 pub const fn new(timeout_us: u64) -> Self {
372 Self {
373 timeout_us,
374 last_feed_us: AtomicU64::new(0),
375 }
376 }
377
378 pub fn feed(&self, current_us: u64) {
380 self.last_feed_us.store(current_us, Ordering::Release);
381 }
382
383 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 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}