aboutsummaryrefslogtreecommitdiff
path: root/embassy-net-ppp
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-net-ppp')
-rw-r--r--embassy-net-ppp/Cargo.toml28
-rw-r--r--embassy-net-ppp/README.md19
-rw-r--r--embassy-net-ppp/src/fmt.rs257
-rw-r--r--embassy-net-ppp/src/lib.rs165
4 files changed, 469 insertions, 0 deletions
diff --git a/embassy-net-ppp/Cargo.toml b/embassy-net-ppp/Cargo.toml
new file mode 100644
index 000000000..b2874c683
--- /dev/null
+++ b/embassy-net-ppp/Cargo.toml
@@ -0,0 +1,28 @@
1[package]
2name = "embassy-net-ppp"
3version = "0.1.0"
4description = "embassy-net driver for PPP over Serial"
5keywords = ["embedded", "ppp", "embassy-net", "embedded-hal-async", "ethernet", "async"]
6categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"]
7license = "MIT OR Apache-2.0"
8edition = "2021"
9
10[features]
11defmt = ["dep:defmt", "ppproto/defmt"]
12log = ["dep:log", "ppproto/log"]
13
14[dependencies]
15defmt = { version = "0.3", optional = true }
16log = { version = "0.4.14", optional = true }
17
18embedded-io-async = { version = "0.5.0" }
19embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" }
20embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
21ppproto = { version = "0.1.1"}
22embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
23
24[package.metadata.embassy_docs]
25src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-ppp-v$VERSION/embassy-net-ppp/src/"
26src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-ppp/src/"
27target = "thumbv7em-none-eabi"
28features = ["defmt"]
diff --git a/embassy-net-ppp/README.md b/embassy-net-ppp/README.md
new file mode 100644
index 000000000..58d67395a
--- /dev/null
+++ b/embassy-net-ppp/README.md
@@ -0,0 +1,19 @@
1# `embassy-net-ppp`
2
3[`embassy-net`](https://crates.io/crates/embassy-net) integration for PPP over Serial.
4
5## Interoperability
6
7This crate can run on any executor.
8
9It supports any serial port implementing [`embedded-io-async`](https://crates.io/crates/embedded-io-async).
10
11## License
12
13This work is licensed under either of
14
15- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
16 http://www.apache.org/licenses/LICENSE-2.0)
17- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
18
19at your option.
diff --git a/embassy-net-ppp/src/fmt.rs b/embassy-net-ppp/src/fmt.rs
new file mode 100644
index 000000000..91984bde1
--- /dev/null
+++ b/embassy-net-ppp/src/fmt.rs
@@ -0,0 +1,257 @@
1#![macro_use]
2#![allow(unused_macros)]
3
4use core::fmt::{Debug, Display, LowerHex};
5
6#[cfg(all(feature = "defmt", feature = "log"))]
7compile_error!("You may not enable both `defmt` and `log` features.");
8
9macro_rules! assert {
10 ($($x:tt)*) => {
11 {
12 #[cfg(not(feature = "defmt"))]
13 ::core::assert!($($x)*);
14 #[cfg(feature = "defmt")]
15 ::defmt::assert!($($x)*);
16 }
17 };
18}
19
20macro_rules! assert_eq {
21 ($($x:tt)*) => {
22 {
23 #[cfg(not(feature = "defmt"))]
24 ::core::assert_eq!($($x)*);
25 #[cfg(feature = "defmt")]
26 ::defmt::assert_eq!($($x)*);
27 }
28 };
29}
30
31macro_rules! assert_ne {
32 ($($x:tt)*) => {
33 {
34 #[cfg(not(feature = "defmt"))]
35 ::core::assert_ne!($($x)*);
36 #[cfg(feature = "defmt")]
37 ::defmt::assert_ne!($($x)*);
38 }
39 };
40}
41
42macro_rules! debug_assert {
43 ($($x:tt)*) => {
44 {
45 #[cfg(not(feature = "defmt"))]
46 ::core::debug_assert!($($x)*);
47 #[cfg(feature = "defmt")]
48 ::defmt::debug_assert!($($x)*);
49 }
50 };
51}
52
53macro_rules! debug_assert_eq {
54 ($($x:tt)*) => {
55 {
56 #[cfg(not(feature = "defmt"))]
57 ::core::debug_assert_eq!($($x)*);
58 #[cfg(feature = "defmt")]
59 ::defmt::debug_assert_eq!($($x)*);
60 }
61 };
62}
63
64macro_rules! debug_assert_ne {
65 ($($x:tt)*) => {
66 {
67 #[cfg(not(feature = "defmt"))]
68 ::core::debug_assert_ne!($($x)*);
69 #[cfg(feature = "defmt")]
70 ::defmt::debug_assert_ne!($($x)*);
71 }
72 };
73}
74
75macro_rules! todo {
76 ($($x:tt)*) => {
77 {
78 #[cfg(not(feature = "defmt"))]
79 ::core::todo!($($x)*);
80 #[cfg(feature = "defmt")]
81 ::defmt::todo!($($x)*);
82 }
83 };
84}
85
86#[cfg(not(feature = "defmt"))]
87macro_rules! unreachable {
88 ($($x:tt)*) => {
89 ::core::unreachable!($($x)*)
90 };
91}
92
93#[cfg(feature = "defmt")]
94macro_rules! unreachable {
95 ($($x:tt)*) => {
96 ::defmt::unreachable!($($x)*);
97 };
98}
99
100macro_rules! panic {
101 ($($x:tt)*) => {
102 {
103 #[cfg(not(feature = "defmt"))]
104 ::core::panic!($($x)*);
105 #[cfg(feature = "defmt")]
106 ::defmt::panic!($($x)*);
107 }
108 };
109}
110
111macro_rules! trace {
112 ($s:literal $(, $x:expr)* $(,)?) => {
113 {
114 #[cfg(feature = "log")]
115 ::log::trace!($s $(, $x)*);
116 #[cfg(feature = "defmt")]
117 ::defmt::trace!($s $(, $x)*);
118 #[cfg(not(any(feature = "log", feature="defmt")))]
119 let _ = ($( & $x ),*);
120 }
121 };
122}
123
124macro_rules! debug {
125 ($s:literal $(, $x:expr)* $(,)?) => {
126 {
127 #[cfg(feature = "log")]
128 ::log::debug!($s $(, $x)*);
129 #[cfg(feature = "defmt")]
130 ::defmt::debug!($s $(, $x)*);
131 #[cfg(not(any(feature = "log", feature="defmt")))]
132 let _ = ($( & $x ),*);
133 }
134 };
135}
136
137macro_rules! info {
138 ($s:literal $(, $x:expr)* $(,)?) => {
139 {
140 #[cfg(feature = "log")]
141 ::log::info!($s $(, $x)*);
142 #[cfg(feature = "defmt")]
143 ::defmt::info!($s $(, $x)*);
144 #[cfg(not(any(feature = "log", feature="defmt")))]
145 let _ = ($( & $x ),*);
146 }
147 };
148}
149
150macro_rules! warn {
151 ($s:literal $(, $x:expr)* $(,)?) => {
152 {
153 #[cfg(feature = "log")]
154 ::log::warn!($s $(, $x)*);
155 #[cfg(feature = "defmt")]
156 ::defmt::warn!($s $(, $x)*);
157 #[cfg(not(any(feature = "log", feature="defmt")))]
158 let _ = ($( & $x ),*);
159 }
160 };
161}
162
163macro_rules! error {
164 ($s:literal $(, $x:expr)* $(,)?) => {
165 {
166 #[cfg(feature = "log")]
167 ::log::error!($s $(, $x)*);
168 #[cfg(feature = "defmt")]
169 ::defmt::error!($s $(, $x)*);
170 #[cfg(not(any(feature = "log", feature="defmt")))]
171 let _ = ($( & $x ),*);
172 }
173 };
174}
175
176#[cfg(feature = "defmt")]
177macro_rules! unwrap {
178 ($($x:tt)*) => {
179 ::defmt::unwrap!($($x)*)
180 };
181}
182
183#[cfg(not(feature = "defmt"))]
184macro_rules! unwrap {
185 ($arg:expr) => {
186 match $crate::fmt::Try::into_result($arg) {
187 ::core::result::Result::Ok(t) => t,
188 ::core::result::Result::Err(e) => {
189 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
190 }
191 }
192 };
193 ($arg:expr, $($msg:expr),+ $(,)? ) => {
194 match $crate::fmt::Try::into_result($arg) {
195 ::core::result::Result::Ok(t) => t,
196 ::core::result::Result::Err(e) => {
197 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
198 }
199 }
200 }
201}
202
203#[derive(Debug, Copy, Clone, Eq, PartialEq)]
204pub struct NoneError;
205
206pub trait Try {
207 type Ok;
208 type Error;
209 fn into_result(self) -> Result<Self::Ok, Self::Error>;
210}
211
212impl<T> Try for Option<T> {
213 type Ok = T;
214 type Error = NoneError;
215
216 #[inline]
217 fn into_result(self) -> Result<T, NoneError> {
218 self.ok_or(NoneError)
219 }
220}
221
222impl<T, E> Try for Result<T, E> {
223 type Ok = T;
224 type Error = E;
225
226 #[inline]
227 fn into_result(self) -> Self {
228 self
229 }
230}
231
232pub struct Bytes<'a>(pub &'a [u8]);
233
234impl<'a> Debug for Bytes<'a> {
235 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
236 write!(f, "{:#02x?}", self.0)
237 }
238}
239
240impl<'a> Display for Bytes<'a> {
241 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
242 write!(f, "{:#02x?}", self.0)
243 }
244}
245
246impl<'a> LowerHex for Bytes<'a> {
247 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
248 write!(f, "{:#02x?}", self.0)
249 }
250}
251
252#[cfg(feature = "defmt")]
253impl<'a> defmt::Format for Bytes<'a> {
254 fn format(&self, fmt: defmt::Formatter) {
255 defmt::write!(fmt, "{:02x}", self.0)
256 }
257}
diff --git a/embassy-net-ppp/src/lib.rs b/embassy-net-ppp/src/lib.rs
new file mode 100644
index 000000000..7853e04ab
--- /dev/null
+++ b/embassy-net-ppp/src/lib.rs
@@ -0,0 +1,165 @@
1#![no_std]
2#![warn(missing_docs)]
3#![doc = include_str!("../README.md")]
4
5// must be first
6mod fmt;
7
8use core::convert::Infallible;
9use core::mem::MaybeUninit;
10
11use embassy_futures::select::{select3, Either3};
12use embassy_net_driver_channel as ch;
13use embassy_net_driver_channel::driver::LinkState;
14use embassy_sync::blocking_mutex::raw::NoopRawMutex;
15use embassy_sync::signal::Signal;
16use embedded_io_async::{BufRead, Write, WriteAllError};
17use ppproto::pppos::{BufferFullError, PPPoS, PPPoSAction};
18
19const MTU: usize = 1500;
20
21/// Type alias for the embassy-net driver.
22pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>;
23
24/// Internal state for the embassy-net integration.
25pub struct State<const N_RX: usize, const N_TX: usize> {
26 ch_state: ch::State<MTU, N_RX, N_TX>,
27}
28
29impl<const N_RX: usize, const N_TX: usize> State<N_RX, N_TX> {
30 /// Create a new `State`.
31 pub const fn new() -> Self {
32 Self {
33 ch_state: ch::State::new(),
34 }
35 }
36}
37
38/// Background runner for the driver.
39///
40/// You must call `.run()` in a background task for the driver to operate.
41pub struct Runner<'d, R: BufRead, W: Write> {
42 ch: ch::Runner<'d, MTU>,
43 r: R,
44 w: W,
45}
46
47/// Error returned by [`Runner::run`].
48#[derive(Debug)]
49#[cfg_attr(feature = "defmt", derive(defmt::Format))]
50pub enum RunError<RE, WE> {
51 /// Reading from the serial port failed.
52 Read(RE),
53 /// Writing to the serial port failed.
54 Write(WE),
55 /// Writing to the serial port wrote zero bytes, indicating it can't accept more data.
56 WriteZero,
57 /// Writing to the serial got EOF.
58 Eof,
59}
60
61impl<'d, R: BufRead, W: Write> Runner<'d, R, W> {
62 /// You must call this in a background task for the driver to operate.
63 pub async fn run(mut self) -> Result<Infallible, RunError<R::Error, W::Error>> {
64 let config = ppproto::Config {
65 username: b"myuser",
66 password: b"mypass",
67 };
68 let mut ppp = PPPoS::new(config);
69 ppp.open().unwrap();
70
71 let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split();
72 state_chan.set_link_state(LinkState::Down);
73 let _ondrop = OnDrop::new(|| state_chan.set_link_state(LinkState::Down));
74
75 let mut rx_buf = [0; 2048];
76 let mut tx_buf = [0; 2048];
77
78 let poll_signal: Signal<NoopRawMutex, ()> = Signal::new();
79 poll_signal.signal(());
80
81 loop {
82 let mut poll = false;
83 match select3(self.r.fill_buf(), tx_chan.tx_buf(), poll_signal.wait()).await {
84 Either3::First(r) => {
85 let data = r.map_err(RunError::Read)?;
86 if data.is_empty() {
87 return Err(RunError::Eof);
88 }
89 let n = ppp.consume(data, &mut rx_buf);
90 self.r.consume(n);
91 poll = true;
92 }
93 Either3::Second(pkt) => {
94 match ppp.send(pkt, &mut tx_buf) {
95 Ok(n) => match self.w.write_all(&tx_buf[..n]).await {
96 Ok(()) => {}
97 Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero),
98 Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)),
99 },
100 Err(BufferFullError) => unreachable!(),
101 }
102 tx_chan.tx_done();
103 }
104 Either3::Third(_) => poll = true,
105 }
106
107 if poll {
108 match ppp.poll(&mut tx_buf, &mut rx_buf) {
109 PPPoSAction::None => {}
110 PPPoSAction::Received(rg) => {
111 let pkt = &rx_buf[rg];
112 let buf = rx_chan.rx_buf().await; // TODO: fix possible deadlock
113 buf[..pkt.len()].copy_from_slice(pkt);
114 rx_chan.rx_done(pkt.len());
115
116 poll_signal.signal(());
117 }
118 PPPoSAction::Transmit(n) => {
119 match self.w.write_all(&tx_buf[..n]).await {
120 Ok(()) => {}
121 Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero),
122 Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)),
123 }
124 poll_signal.signal(());
125 }
126 }
127
128 match ppp.status().phase {
129 ppproto::Phase::Open => state_chan.set_link_state(LinkState::Up),
130 _ => state_chan.set_link_state(LinkState::Down),
131 }
132 }
133 }
134 }
135}
136
137/// Create a PPP embassy-net driver instance.
138///
139/// This returns two structs:
140/// - a `Device` that you must pass to the `embassy-net` stack.
141/// - a `Runner`. You must call `.run()` on it in a background task.
142pub fn new<'a, const N_RX: usize, const N_TX: usize, R: BufRead, W: Write>(
143 state: &'a mut State<N_RX, N_TX>,
144 r: R,
145 w: W,
146) -> (Device<'a>, Runner<'a, R, W>) {
147 let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ip);
148 (device, Runner { ch: runner, r, w })
149}
150
151struct OnDrop<F: FnOnce()> {
152 f: MaybeUninit<F>,
153}
154
155impl<F: FnOnce()> OnDrop<F> {
156 fn new(f: F) -> Self {
157 Self { f: MaybeUninit::new(f) }
158 }
159}
160
161impl<F: FnOnce()> Drop for OnDrop<F> {
162 fn drop(&mut self) {
163 unsafe { self.f.as_ptr().read()() }
164 }
165}