Skip to main content

web_admin/
io.rs

1use core::{
2    future::Future,
3    pin::pin,
4    task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
5};
6use strat9_syscall::call;
7
8const EAGAIN: usize = 11;
9
10pub struct Strat9Runtime;
11
12/// Implements noop raw waker.
13fn noop_raw_waker() -> RawWaker {
14    /// Implements noop.
15    fn noop(_: *const ()) {}
16    /// Implements clone.
17    fn clone(_: *const ()) -> RawWaker {
18        noop_raw_waker()
19    }
20    static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, noop, noop, noop);
21    RawWaker::new(core::ptr::null(), &VTABLE)
22}
23
24// ---------------------------------------------------------------------------
25// Error type compatible with embedded-io
26// ---------------------------------------------------------------------------
27
28#[derive(Debug)]
29pub struct IoError(pub usize);
30
31impl core::fmt::Display for IoError {
32    /// Implements fmt.
33    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
34        write!(f, "IoError({})", self.0)
35    }
36}
37
38impl core::error::Error for IoError {}
39
40impl embedded_io_async::Error for IoError {
41    /// Implements kind.
42    fn kind(&self) -> embedded_io_async::ErrorKind {
43        embedded_io_async::ErrorKind::Other
44    }
45}
46
47// ---------------------------------------------------------------------------
48// Read / Write halves operating on a raw fd
49// ---------------------------------------------------------------------------
50
51pub struct TcpReadHalf {
52    fd: usize,
53}
54
55pub struct TcpWriteHalf {
56    fd: usize,
57}
58
59impl embedded_io_async::ErrorType for TcpReadHalf {
60    type Error = IoError;
61}
62
63impl embedded_io_async::ErrorType for TcpWriteHalf {
64    type Error = IoError;
65}
66
67impl embedded_io_async::Read for TcpReadHalf {
68    /// Implements read.
69    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, IoError> {
70        let mut eagain_spins = 0usize;
71        loop {
72            match call::read(self.fd, buf) {
73                Ok(n) => return Ok(n),
74                Err(e) if e.to_errno() == EAGAIN => {
75                    eagain_spins = eagain_spins.saturating_add(1);
76                    if eagain_spins % 32 == 0 {
77                        crate::net::sleep_ms(1);
78                    }
79                    let _ = call::sched_yield();
80                    continue;
81                }
82                Err(e) => return Err(IoError(e.to_errno())),
83            }
84        }
85    }
86}
87
88impl embedded_io_async::Write for TcpWriteHalf {
89    /// Implements write.
90    async fn write(&mut self, buf: &[u8]) -> Result<usize, IoError> {
91        let mut eagain_spins = 0usize;
92        loop {
93            match call::write(self.fd, buf) {
94                Ok(n) => return Ok(n),
95                Err(e) if e.to_errno() == EAGAIN => {
96                    eagain_spins = eagain_spins.saturating_add(1);
97                    if eagain_spins % 32 == 0 {
98                        crate::net::sleep_ms(1);
99                    }
100                    let _ = call::sched_yield();
101                    continue;
102                }
103                Err(e) => return Err(IoError(e.to_errno())),
104            }
105        }
106    }
107
108    /// Implements flush.
109    async fn flush(&mut self) -> Result<(), IoError> {
110        Ok(())
111    }
112}
113
114// ---------------------------------------------------------------------------
115// Socket adapter for picoserve
116// ---------------------------------------------------------------------------
117
118pub struct TcpSocket {
119    fd: usize,
120}
121
122impl TcpSocket {
123    /// Creates a new instance.
124    pub fn new(fd: usize) -> Self {
125        Self { fd }
126    }
127}
128
129impl picoserve::io::Socket<Strat9Runtime> for TcpSocket {
130    type Error = IoError;
131    type ReadHalf<'a> = TcpReadHalf;
132    type WriteHalf<'a> = TcpWriteHalf;
133
134    /// Implements split.
135    fn split(&mut self) -> (TcpReadHalf, TcpWriteHalf) {
136        (TcpReadHalf { fd: self.fd }, TcpWriteHalf { fd: self.fd })
137    }
138
139    async fn abort<T: picoserve::time::Timer<Strat9Runtime>>(
140        self,
141        _timeouts: &picoserve::Timeouts,
142        _timer: &mut T,
143    ) -> Result<(), picoserve::Error<IoError>> {
144        let _ = call::close(self.fd);
145        Ok(())
146    }
147
148    async fn shutdown<T: picoserve::time::Timer<Strat9Runtime>>(
149        self,
150        _timeouts: &picoserve::Timeouts,
151        _timer: &mut T,
152    ) -> Result<(), picoserve::Error<IoError>> {
153        let _ = call::close(self.fd);
154        Ok(())
155    }
156}
157
158// ---------------------------------------------------------------------------
159// Timer adapter for picoserve
160// ---------------------------------------------------------------------------
161
162pub struct Strat9Timer;
163
164impl picoserve::time::Timer<Strat9Runtime> for Strat9Timer {
165    /// Implements delay.
166    async fn delay(&self, duration: picoserve::time::Duration) {
167        crate::net::sleep_ms(duration.as_millis());
168    }
169
170    async fn run_with_timeout<F: Future>(
171        &self,
172        duration: picoserve::time::Duration,
173        future: F,
174    ) -> Result<F::Output, picoserve::time::TimeoutError> {
175        let timeout_ns = duration.as_millis().saturating_mul(1_000_000);
176        let start = crate::net::clock_gettime_ns();
177        let deadline = start.saturating_add(timeout_ns);
178        let mut fut = pin!(future);
179
180        // SAFETY: no-op raw waker is valid for manual cooperative polling.
181        let waker = unsafe { Waker::from_raw(noop_raw_waker()) };
182        let mut cx = Context::from_waker(&waker);
183
184        loop {
185            match fut.as_mut().poll(&mut cx) {
186                Poll::Ready(v) => return Ok(v),
187                Poll::Pending => {
188                    if crate::net::clock_gettime_ns() >= deadline {
189                        return Err(picoserve::time::TimeoutError);
190                    }
191                    let _ = call::sched_yield();
192                }
193            }
194        }
195    }
196}