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}