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)]
160pub struct PeriodicTask {
161 pub period_us: u64,
163 pub budget_us: u64,
165 pub priority: Priority,
167 last_exec_us: Option<u64>,
169 run: Option<fn()>,
175}
176
177impl PeriodicTask {
178 pub const fn new(period_us: u64, budget_us: u64, priority: Priority) -> Self {
185 Self {
186 period_us,
187 budget_us,
188 priority,
189 last_exec_us: None,
190 run: None,
191 }
192 }
193
194 pub const fn with_runner(
201 period_us: u64,
202 budget_us: u64,
203 priority: Priority,
204 run: fn(),
205 ) -> Self {
206 Self {
207 period_us,
208 budget_us,
209 priority,
210 last_exec_us: None,
211 run: Some(run),
212 }
213 }
214
215 pub fn runner(&self) -> Option<fn()> {
217 self.run
218 }
219
220 pub fn is_ready(&self, current_us: u64) -> bool {
222 match self.last_exec_us {
223 None => true,
225 Some(last) => current_us.saturating_sub(last) >= self.period_us,
227 }
228 }
229
230 pub fn mark_executed(&mut self, current_us: u64) {
232 self.last_exec_us = Some(current_us);
233 }
234
235 pub fn next_deadline(&self) -> Deadline {
237 let last = self.last_exec_us.unwrap_or(0);
238 Deadline::hard(last + self.period_us + self.budget_us)
239 }
240}
241
242#[derive(Debug, Clone, Copy, Default)]
244pub struct TaskStats {
245 pub executions: u64,
247 pub min_exec_us: u64,
249 pub max_exec_us: u64,
251 pub total_exec_us: u64,
253 pub deadline_misses: u64,
255}
256
257impl TaskStats {
258 pub const fn new() -> Self {
260 Self {
261 executions: 0,
262 min_exec_us: u64::MAX,
263 max_exec_us: 0,
264 total_exec_us: 0,
265 deadline_misses: 0,
266 }
267 }
268
269 pub fn record_execution(&mut self, exec_us: u64, missed_deadline: bool) {
271 self.executions = self.executions.saturating_add(1);
272 self.total_exec_us = self.total_exec_us.saturating_add(exec_us);
273
274 if exec_us < self.min_exec_us {
275 self.min_exec_us = exec_us;
276 }
277
278 if exec_us > self.max_exec_us {
279 self.max_exec_us = exec_us;
280 }
281
282 if missed_deadline {
283 self.deadline_misses = self.deadline_misses.saturating_add(1);
284 }
285 }
286
287 pub fn avg_exec_us(&self) -> u64 {
289 self.total_exec_us.checked_div(self.executions).unwrap_or(0)
290 }
291
292 pub fn miss_rate(&self) -> f32 {
294 if self.executions == 0 {
295 0.0
296 } else {
297 self.deadline_misses as f32 / self.executions as f32
298 }
299 }
300}
301
302pub struct RateMonotonicScheduler<const MAX_TASKS: usize> {
306 tasks: heapless::Vec<PeriodicTask, MAX_TASKS>,
307 stats: heapless::Vec<TaskStats, MAX_TASKS>,
308 scheduler: RealtimeScheduler,
309}
310
311impl<const MAX_TASKS: usize> RateMonotonicScheduler<MAX_TASKS> {
312 pub const fn new(cpu_freq_mhz: u64) -> Self {
314 Self {
315 tasks: heapless::Vec::new(),
316 stats: heapless::Vec::new(),
317 scheduler: RealtimeScheduler::new(cpu_freq_mhz),
318 }
319 }
320
321 pub fn init(&mut self) {
323 self.scheduler.init();
324 }
325
326 pub fn add_task(&mut self, task: PeriodicTask) -> Result<()> {
332 self.tasks
333 .push(task)
334 .map_err(|_| EmbeddedError::BufferTooSmall {
335 required: 1,
336 available: 0,
337 })?;
338
339 self.stats
340 .push(TaskStats::new())
341 .map_err(|_| EmbeddedError::BufferTooSmall {
342 required: 1,
343 available: 0,
344 })?;
345
346 self.sort_tasks();
348
349 Ok(())
350 }
351
352 fn sort_tasks(&mut self) {
354 let len = self.tasks.len();
355
356 for i in 0..len {
357 for j in (i + 1)..len {
358 if self.tasks[j].period_us < self.tasks[i].period_us {
359 self.tasks.swap(i, j);
360 self.stats.swap(i, j);
361 }
362 }
363 }
364 }
365
366 pub fn schedule(&mut self) -> Result<usize> {
374 let current_us = self.scheduler.elapsed_us();
375 let mut executed: usize = 0;
376
377 for i in 0..self.tasks.len() {
378 if !self.tasks[i].is_ready(current_us) {
379 continue;
380 }
381
382 let start_us = self.scheduler.elapsed_us();
383 if let Some(run) = self.tasks[i].run {
384 run();
386 }
387 let end_us = self.scheduler.elapsed_us();
388 let exec_us = end_us.saturating_sub(start_us);
389
390 let missed = exec_us > self.tasks[i].budget_us;
391 self.stats[i].record_execution(exec_us, missed);
392
393 self.tasks[i].mark_executed(current_us);
394 executed = executed.saturating_add(1);
395 }
396
397 Ok(executed)
398 }
399
400 pub fn schedule_with<F>(&mut self, mut dispatch: F) -> Result<usize>
410 where
411 F: FnMut(usize),
412 {
413 let current_us = self.scheduler.elapsed_us();
414 let mut executed: usize = 0;
415
416 for i in 0..self.tasks.len() {
417 if !self.tasks[i].is_ready(current_us) {
418 continue;
419 }
420
421 let start_us = self.scheduler.elapsed_us();
422 dispatch(i);
423 let end_us = self.scheduler.elapsed_us();
424 let exec_us = end_us.saturating_sub(start_us);
425
426 let missed = exec_us > self.tasks[i].budget_us;
427 self.stats[i].record_execution(exec_us, missed);
428
429 self.tasks[i].mark_executed(current_us);
430 executed = executed.saturating_add(1);
431 }
432
433 Ok(executed)
434 }
435
436 pub fn get_stats(&self, task_index: usize) -> Option<&TaskStats> {
438 self.stats.get(task_index)
439 }
440
441 pub fn task_count(&self) -> usize {
443 self.tasks.len()
444 }
445}
446
447pub struct Watchdog {
449 timeout_us: u64,
450 last_feed_us: AtomicU64,
451}
452
453impl Watchdog {
454 pub const fn new(timeout_us: u64) -> Self {
456 Self {
457 timeout_us,
458 last_feed_us: AtomicU64::new(0),
459 }
460 }
461
462 pub fn feed(&self, current_us: u64) {
464 self.last_feed_us.store(current_us, Ordering::Release);
465 }
466
467 pub fn is_expired(&self, current_us: u64) -> bool {
469 let last_feed = self.last_feed_us.load(Ordering::Acquire);
470 current_us.saturating_sub(last_feed) >= self.timeout_us
471 }
472
473 pub fn time_until_expiry(&self, current_us: u64) -> u64 {
475 let last_feed = self.last_feed_us.load(Ordering::Acquire);
476 let elapsed = current_us.saturating_sub(last_feed);
477 self.timeout_us.saturating_sub(elapsed)
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484
485 #[test]
486 fn test_priority_ordering() {
487 assert!(Priority::Idle < Priority::Low);
488 assert!(Priority::Low < Priority::Normal);
489 assert!(Priority::Normal < Priority::High);
490 assert!(Priority::High < Priority::Critical);
491 }
492
493 #[test]
494 fn test_deadline() {
495 let deadline = Deadline::hard(1000);
496 assert!(!deadline.is_expired(500));
497 assert!(deadline.is_expired(1000));
498 assert_eq!(deadline.remaining_us(500), 500);
499 }
500
501 #[test]
502 fn test_periodic_task() {
503 let mut task = PeriodicTask::new(1000, 100, Priority::Normal);
504 assert!(task.is_ready(0));
505 task.mark_executed(0);
506 assert!(!task.is_ready(500));
507 assert!(task.is_ready(1000));
508 }
509
510 #[test]
511 fn test_task_stats() {
512 let mut stats = TaskStats::new();
513 stats.record_execution(100, false);
514 stats.record_execution(200, false);
515 stats.record_execution(150, true);
516
517 assert_eq!(stats.executions, 3);
518 assert_eq!(stats.min_exec_us, 100);
519 assert_eq!(stats.max_exec_us, 200);
520 assert_eq!(stats.avg_exec_us(), 150);
521 assert_eq!(stats.deadline_misses, 1);
522 }
523
524 #[test]
525 fn test_schedule_runs_attached_runner() {
526 use core::sync::atomic::AtomicUsize;
527
528 static RUN_COUNT: AtomicUsize = AtomicUsize::new(0);
529 fn body() {
530 RUN_COUNT.fetch_add(1, Ordering::Relaxed);
531 }
532
533 let mut scheduler = RateMonotonicScheduler::<4>::new(1);
534 scheduler.init();
535 scheduler
536 .add_task(PeriodicTask::with_runner(1000, 100, Priority::Normal, body))
537 .expect("add_task failed");
538
539 let executed = scheduler.schedule().expect("schedule failed");
540 assert_eq!(executed, 1, "ready task should be scheduled");
541 assert_eq!(
542 RUN_COUNT.load(Ordering::Relaxed),
543 1,
544 "attached runner must actually execute"
545 );
546
547 let stats = scheduler.get_stats(0).expect("stats should exist");
548 assert_eq!(stats.executions, 1);
549 }
550
551 #[test]
552 fn test_schedule_with_closure_executes() {
553 let mut scheduler = RateMonotonicScheduler::<4>::new(1);
554 scheduler.init();
555 scheduler
556 .add_task(PeriodicTask::new(1000, 100, Priority::Normal))
557 .expect("add_task failed");
558
559 let mut counter = 0usize;
560 let executed = scheduler
561 .schedule_with(|_idx| {
562 counter += 1;
563 })
564 .expect("schedule_with failed");
565
566 assert_eq!(executed, 1);
567 assert_eq!(counter, 1, "dispatch closure must run for the ready task");
568 }
569
570 #[test]
571 fn test_watchdog() {
572 let watchdog = Watchdog::new(1000);
573 watchdog.feed(0);
574
575 assert!(!watchdog.is_expired(500));
576 assert!(watchdog.is_expired(1000));
577 assert_eq!(watchdog.time_until_expiry(500), 500);
578 }
579}