uefi_async/nano_alloc/
executor.rs

1use crate::{init_clock_freq, tick};
2use crate::nano_alloc::TaskNode;
3use core::task::{Context, Poll, Waker};
4
5/// A simple round-robin executor that manages a linked-list of tasks.
6///
7/// The executor polls tasks based on their scheduled `next_run_time`
8/// and supports dynamic task addition.
9pub struct Executor<'curr, 'next> {
10    /// The head of the task linked-list.
11    head: Option<&'next mut TaskNode<'curr, 'next>>,
12    /// The hardware clock frequency, used to normalize task intervals.
13    pub freq: u64,
14}
15
16impl<'curr, 'next> Executor<'curr, 'next> {
17    /// Initializes a new executor and calculates the hardware frequency.
18    #[inline]
19    pub fn new() -> Self { Self{ head: None, freq: init_clock_freq() } }
20
21    /// Adds a task node to the executor.
22    ///
23    /// This method converts the node's frequency into a tick interval
24    /// and pushes the node to the front of the linked-list.
25    #[inline]
26    pub fn add(&mut self, node: &'next mut TaskNode<'curr, 'next>) -> &mut Self {
27        // convert frequency (Hz) to interval (ticks)
28        node.interval = self.freq.checked_div(node.interval).unwrap_or(0);
29        // push to the front of the list
30        node.next = self.head.take();
31        self.head = Some(node);
32        self
33    }
34
35    /// Enters an infinite loop, continuously ticking the executor.
36    #[inline(always)]
37    pub fn run_forever(&mut self) {
38        let mut cx = Self::init_step();
39        loop { self.run_step(tick(), &mut cx) }
40    }
41
42    /// Initializes a no-op [`Context`] required for polling futures.
43    #[inline(always)]
44    pub fn init_step() -> Context<'curr> { Context::from_waker(&Waker::noop()) }
45
46    /// Performs a single pass over all registered tasks.
47    ///
48    /// For each node:
49    /// 1. Checks if the current `time` has reached `next_run_time`.
50    /// 2. Polls the future.
51    /// 3. If `Ready`, the node is removed from the list.
52    /// 4. If `Pending`, the `next_run_time` is updated by the node's interval.
53    #[inline(always)]
54    pub fn run_step(&mut self, time: u64, cx: &mut Context) {
55        let mut cursor = &mut self.head;
56
57        // traverse the task list and poll each async task
58        while let Some(node) = cursor {
59            if time >= node.next_run_time {
60                match node.future.as_mut().poll(cx) {
61                    // remove it from the list by bridging the pointer when async finished.
62                    // it might miss the next one, but it's not a problem for polling.
63                    Poll::Ready(_) => *cursor = node.next.take(),
64                    // schedule next execution time
65                    Poll::Pending => node.next_run_time = time + node.interval,
66                }
67            }
68
69            // Re-borrow: not time to run yet, skip to next node
70            cursor = match { cursor } {
71                Some(node) => &mut node.next,
72                None => break,
73            };
74        }
75    }
76}