Skip to main content

ax_task/future/
poll.rs

1use core::{future::poll_fn, task::Poll};
2
3use axpoll::{IoEvents, Pollable};
4
5use super::{Interrupted, PollIoError, TaskResult};
6use crate::current;
7
8/// A helper to wrap a synchronous non-blocking I/O function into an
9/// asynchronous function.
10///
11/// # Arguments
12///
13/// * `pollable`: The pollable object to register for I/O events.
14/// * `events`: The I/O events to wait for.
15/// * `non_blocking`: If true, the function returns the caller's would-block error
16///   immediately when the I/O operation would block.
17/// * `f`: The synchronous non-blocking I/O function to be wrapped. It should
18///   return an error recognized by [`PollIoError::is_would_block`] when the
19///   operation would block.
20pub async fn poll_io<P, F, T, E>(
21    pollable: &P,
22    events: IoEvents,
23    non_blocking: bool,
24    mut f: F,
25) -> Result<T, E>
26where
27    P: Pollable,
28    F: FnMut() -> Result<T, E>,
29    E: PollIoError,
30{
31    let curr = current();
32    poll_fn(move |cx| {
33        match f() {
34            Ok(value) => return Poll::Ready(Ok(value)),
35            Err(error) if error.is_would_block() => {}
36            Err(e) => return Poll::Ready(Err(e)),
37        }
38
39        // Register before the post-registration retry. A non-blocking
40        // connect(2) returns EINPROGRESS; the caller then uses epoll to wait
41        // for EPOLLOUT. If we skip registration for non-blocking callers, the
42        // TCP stack has no waker to call when the handshake finishes.
43        pollable.register(cx, events);
44
45        match f() {
46            Ok(value) => Poll::Ready(Ok(value)),
47            Err(error) if error.is_would_block() && non_blocking => Poll::Ready(Err(error)),
48            Err(error) if error.is_would_block() => {
49                if curr.poll_interrupt(cx).is_ready() {
50                    Poll::Ready(Err(E::interrupted(Interrupted)))
51                } else {
52                    Poll::Pending
53                }
54            }
55            Err(e) => Poll::Ready(Err(e)),
56        }
57    })
58    .await
59}
60
61/// Registers a waker for the given domain-scoped IRQ id.
62///
63/// This is a generic bridge for IRQ-driven async wakeups. Calling
64/// `PollSet::wake` directly from an IRQ hook is unsafe: it takes a
65/// `SpinNoIrq` mutex AND allocates (`Inner::new()` replacement when
66/// there is an existing waiter), which can deadlock against the task
67/// the IRQ preempted and triggers the slab from interrupt context.
68///
69/// The IRQ hook here does only what is safe in interrupt context:
70/// flip a per-IRQ pending bit and `notify_one` a [`crate::WaitQueue`].
71/// `WaitQueue::notify_one` just pops from a `VecDeque` under a
72/// `SpinNoIrq` (no allocation, deadlock-free because IRQs are
73/// already disabled in the holding paths) and re-queues the drain
74/// task. The drain task runs in normal task context and is the only
75/// place that ever calls `PollSet::wake`.
76pub fn register_irq_waker(irq: ax_hal::irq::IrqId, waker: &core::task::Waker) -> TaskResult {
77    use alloc::{collections::BTreeMap, sync::Arc};
78    use core::sync::atomic::{AtomicBool, Ordering};
79
80    use axpoll::PollSet;
81
82    use crate::{IrqNotify, sync::SpinLock};
83
84    static IRQ_NOTIFY: IrqNotify = IrqNotify::new();
85    static DRAIN_SPAWNED: AtomicBool = AtomicBool::new(false);
86    static IRQ_STATE: SpinLock<BTreeMap<ax_hal::irq::IrqId, IrqPollState>> =
87        SpinLock::new(BTreeMap::new());
88
89    struct IrqPollState {
90        pending: bool,
91        installed: bool,
92        poll: Arc<PollSet>,
93    }
94
95    fn irq_waker_handler(ctx: ax_hal::irq::IrqContext) -> ax_hal::irq::IrqReturn {
96        // Runs in IRQ context with interrupts off. Only mark an already
97        // registered slot and notify the drain task. The map entry is created
98        // during task-context registration, so this path does not allocate.
99        if let Some(state) = IRQ_STATE.lock_irqsave().get_mut(&ctx.irq) {
100            state.pending = true;
101            IRQ_NOTIFY.notify_irq();
102            ax_hal::irq::IrqReturn::Handled
103        } else {
104            ax_hal::irq::IrqReturn::Unhandled
105        }
106    }
107
108    fn ensure_drain_spawned() {
109        if DRAIN_SPAWNED
110            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
111            .is_err()
112        {
113            return;
114        }
115        crate::spawn_raw(
116            || {
117                loop {
118                    IRQ_NOTIFY.wait();
119
120                    // Snapshot the entries that need waking under the
121                    // map lock, then drop the lock before invoking
122                    // `wake` (which can allocate and re-enter the
123                    // scheduler).
124                    let mut to_wake: alloc::vec::Vec<Arc<PollSet>> = alloc::vec::Vec::new();
125                    {
126                        let mut map = IRQ_STATE.lock_irqsave();
127                        for state in map.values_mut() {
128                            if state.pending {
129                                state.pending = false;
130                                to_wake.push(state.poll.clone());
131                            }
132                        }
133                    }
134                    for set in to_wake {
135                        unsafe { set.wake(axpoll::IoEvents::all()) };
136                    }
137                }
138            },
139            alloc::string::String::from("irq_waker_drain"),
140            0x4000,
141        );
142    }
143
144    ensure_drain_spawned();
145
146    let (poll, should_install) = {
147        let mut map = IRQ_STATE.lock_irqsave();
148        let state = map.entry(irq).or_insert_with(|| IrqPollState {
149            pending: false,
150            installed: false,
151            poll: Arc::new(PollSet::new()),
152        });
153        if state.installed {
154            (state.poll.clone(), false)
155        } else {
156            state.installed = true;
157            (state.poll.clone(), true)
158        }
159    };
160    unsafe { poll.register(waker, axpoll::IoEvents::all()) };
161
162    if should_install {
163        ax_hal::irq::request_shared_irq(irq, irq_waker_handler)?;
164    }
165
166    ax_hal::irq::set_enable(irq, true)?;
167    Ok(())
168}
169
170/// Registers a waker for a temporary legacy numeric IRQ.
171pub fn register_legacy_irq_waker(irq: usize, waker: &core::task::Waker) -> TaskResult {
172    let irq = ax_hal::irq::try_legacy_irq(irq)?;
173    register_irq_waker(irq, waker)
174}