Skip to main content

ove/
embedded_io_impl.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! `embedded-io` 0.6 sync trait impls.
8//!
9//! Compiled only when the `embedded-io` Cargo feature is enabled.
10//! Each section is further gated by the substrate `has_*` cfg for the
11//! corresponding subsystem.  Provides byte-stream `Read` / `Write`
12//! traits on `ove::Uart`, `ove::Stream`, and `ove::fs::File` so any
13//! `embedded-io`-aware utility (line readers, codec drivers, simple
14//! serial protocols) composes with oveRTOS I/O at zero per-call cost.
15//!
16//! Error mapping into [`embedded_io::ErrorKind`] follows the std::io
17//! mirror: `Timeout` → `TimedOut`, `NoMemory` → `OutOfMemory`,
18//! `NotFound` → `NotFound`, `InvalidParam` / `Inval` → `InvalidInput`,
19//! `Eof` → `Other` (callers should not see this — see EOF semantics
20//! note in the `Read` impls).
21
22use crate::error::Error;
23
24impl embedded_io::Error for Error {
25    fn kind(&self) -> embedded_io::ErrorKind {
26        match self {
27            Error::Timeout => embedded_io::ErrorKind::TimedOut,
28            Error::NoMemory => embedded_io::ErrorKind::OutOfMemory,
29            Error::NotFound => embedded_io::ErrorKind::NotFound,
30            Error::InvalidParam | Error::Inval => embedded_io::ErrorKind::InvalidInput,
31            Error::NetRefused => embedded_io::ErrorKind::ConnectionRefused,
32            Error::NetReset => embedded_io::ErrorKind::ConnectionReset,
33            Error::NetClosed => embedded_io::ErrorKind::ConnectionAborted,
34            Error::NotSupported => embedded_io::ErrorKind::Unsupported,
35            _ => embedded_io::ErrorKind::Other,
36        }
37    }
38}
39
40// ---------------------------------------------------------------------------
41// Uart
42// ---------------------------------------------------------------------------
43
44#[cfg(has_uart)]
45mod uart_impl {
46    use super::*;
47    use crate::Uart;
48
49    impl embedded_io::ErrorType for Uart {
50        type Error = Error;
51    }
52
53    impl embedded_io::Read for Uart {
54        fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
55            // Block effectively forever — `embedded_io::Read` has no
56            // timeout in its contract; consumers wanting a bounded
57            // read should call `Uart::read` directly with a duration.
58            Uart::read(self, buf, core::time::Duration::from_secs(u64::MAX / 2))
59        }
60    }
61
62    impl embedded_io::Write for Uart {
63        fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
64            Uart::write(self, buf, core::time::Duration::from_secs(u64::MAX / 2))
65        }
66
67        fn flush(&mut self) -> Result<(), Self::Error> {
68            Uart::flush(self)
69        }
70    }
71}
72
73// ---------------------------------------------------------------------------
74// Stream<N>
75// ---------------------------------------------------------------------------
76
77#[cfg(has_stream)]
78mod stream_impl {
79    use super::*;
80    use crate::Stream;
81
82    impl<const N: usize> embedded_io::ErrorType for Stream<N> {
83        type Error = Error;
84    }
85
86    impl<const N: usize> embedded_io::Read for Stream<N> {
87        fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
88            // Stream::recv blocks indefinitely until at least one
89            // trigger-count of bytes is available — same forever-
90            // blocking contract as embedded_io::Read.
91            Stream::recv(self, buf)
92        }
93    }
94
95    impl<const N: usize> embedded_io::Write for Stream<N> {
96        fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
97            Stream::send(self, buf)
98        }
99
100        fn flush(&mut self) -> Result<(), Self::Error> {
101            // No explicit flush primitive — streams have no buffered
102            // egress beyond the in-flight ring contents, and the
103            // substrate writes synchronously.
104            Ok(())
105        }
106    }
107}
108
109// ---------------------------------------------------------------------------
110// fs::File
111// ---------------------------------------------------------------------------
112
113#[cfg(has_fs)]
114mod file_impl {
115    use super::*;
116    use crate::fs::File;
117
118    impl embedded_io::ErrorType for File {
119        type Error = Error;
120    }
121
122    impl embedded_io::Read for File {
123        fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
124            // `Ok(0)` from `File::read` already signals EOF — the
125            // substrate semantics match `embedded_io::Read` exactly,
126            // so no translation needed.  `Error::Eof` shouldn't fire
127            // here (substrate maps end-of-file to `Ok(0)`), but if
128            // it ever does our `embedded_io::Error::kind()` impl
129            // maps it to `ErrorKind::Other` — callers should rely on
130            // `Ok(0)` for the EOF signal.
131            File::read(self, buf)
132        }
133    }
134
135    impl embedded_io::Write for File {
136        fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
137            File::write(self, buf)
138        }
139
140        fn flush(&mut self) -> Result<(), Self::Error> {
141            // The substrate doesn't expose an explicit fsync/flush
142            // primitive — writes go out synchronously and the FS
143            // layer (LittleFS / Zephyr nvs-backed FS) handles its own
144            // commit barriers.
145            Ok(())
146        }
147    }
148}