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 self.total_exec_us.checked_div(self.executions).unwrap_or(0)
246 }
247
248 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
258pub 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 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 pub fn init(&mut self) {
279 self.scheduler.init();
280 }
281
282 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 self.sort_tasks();
304
305 Ok(())
306 }
307
308 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 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 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 pub fn get_stats(&self, task_index: usize) -> Option<&TaskStats> {
350 self.stats.get(task_index)
351 }
352
353 pub fn task_count(&self) -> usize {
355 self.tasks.len()
356 }
357}
358
359pub struct Watchdog {
361 timeout_us: u64,
362 last_feed_us: AtomicU64,
363}
364
365impl Watchdog {
366 pub const fn new(timeout_us: u64) -> Self {
368 Self {
369 timeout_us,
370 last_feed_us: AtomicU64::new(0),
371 }
372 }
373
374 pub fn feed(&self, current_us: u64) {
376 self.last_feed_us.store(current_us, Ordering::Release);
377 }
378
379 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 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}