aboutsummaryrefslogtreecommitdiff
path: root/embassy-net-ppp/src/lib.rs
diff options
context:
space:
mode:
authorTyler <[email protected]>2023-09-29 20:02:24 -0600
committerGitHub <[email protected]>2023-09-29 20:02:24 -0600
commit2f9b59c5cf21f1e2761a9ccefdfd86f0edea829c (patch)
tree8964744b4fb753cf98f6f413464106c4d2a72976 /embassy-net-ppp/src/lib.rs
parentce91fb2bfc846570ef543a09396c428d70675245 (diff)
parent95b3d9eb3b3657de3d7bc9c04f8fb83eae901640 (diff)
Merge branch 'main' into issue-1974-add-sai-driver
Diffstat (limited to 'embassy-net-ppp/src/lib.rs')
-rw-r--r--embassy-net-ppp/src/lib.rs185
1 files changed, 185 insertions, 0 deletions
diff --git a/embassy-net-ppp/src/lib.rs b/embassy-net-ppp/src/lib.rs
new file mode 100644
index 000000000..66496ee0a
--- /dev/null
+++ b/embassy-net-ppp/src/lib.rs
@@ -0,0 +1,185 @@
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::{select, Either};
12use embassy_net_driver_channel as ch;
13use embassy_net_driver_channel::driver::LinkState;
14use embedded_io_async::{BufRead, Write, WriteAllError};
15use ppproto::pppos::{BufferFullError, PPPoS, PPPoSAction};
16pub use ppproto::{Config, Ipv4Status};
17
18const MTU: usize = 1500;
19
20/// Type alias for the embassy-net driver.
21pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>;
22
23/// Internal state for the embassy-net integration.
24pub struct State<const N_RX: usize, const N_TX: usize> {
25 ch_state: ch::State<MTU, N_RX, N_TX>,
26}
27
28impl<const N_RX: usize, const N_TX: usize> State<N_RX, N_TX> {
29 /// Create a new `State`.
30 pub const fn new() -> Self {
31 Self {
32 ch_state: ch::State::new(),
33 }
34 }
35}
36
37/// Background runner for the driver.
38///
39/// You must call `.run()` in a background task for the driver to operate.
40pub struct Runner<'d> {
41 ch: ch::Runner<'d, MTU>,
42}
43
44/// Error returned by [`Runner::run`].
45#[derive(Debug)]
46#[cfg_attr(feature = "defmt", derive(defmt::Format))]
47pub enum RunError<E> {
48 /// Reading from the serial port failed.
49 Read(E),
50 /// Writing to the serial port failed.
51 Write(E),
52 /// Writing to the serial port wrote zero bytes, indicating it can't accept more data.
53 WriteZero,
54 /// Writing to the serial got EOF.
55 Eof,
56 /// PPP protocol was terminated by the peer
57 Terminated,
58}
59
60impl<E> From<WriteAllError<E>> for RunError<E> {
61 fn from(value: WriteAllError<E>) -> Self {
62 match value {
63 WriteAllError::Other(e) => Self::Write(e),
64 WriteAllError::WriteZero => Self::WriteZero,
65 }
66 }
67}
68
69impl<'d> Runner<'d> {
70 /// You must call this in a background task for the driver to operate.
71 ///
72 /// If reading/writing to the underlying serial port fails, the link state
73 /// is set to Down and the error is returned.
74 ///
75 /// It is allowed to cancel this function's future (i.e. drop it). This will terminate
76 /// the PPP connection and set the link state to Down.
77 ///
78 /// After this function returns or is canceled, you can call it again to establish
79 /// a new PPP connection.
80 pub async fn run<RW: BufRead + Write>(
81 &mut self,
82 mut rw: RW,
83 config: ppproto::Config<'_>,
84 mut on_ipv4_up: impl FnMut(Ipv4Status),
85 ) -> Result<Infallible, RunError<RW::Error>> {
86 let mut ppp = PPPoS::new(config);
87 ppp.open().unwrap();
88
89 let (state_chan, mut rx_chan, mut tx_chan) = self.ch.borrow_split();
90 state_chan.set_link_state(LinkState::Down);
91 let _ondrop = OnDrop::new(|| state_chan.set_link_state(LinkState::Down));
92
93 let mut rx_buf = [0; 2048];
94 let mut tx_buf = [0; 2048];
95
96 let mut needs_poll = true;
97 let mut was_up = false;
98
99 loop {
100 let rx_fut = async {
101 let buf = rx_chan.rx_buf().await;
102 let rx_data = match needs_poll {
103 true => &[][..],
104 false => match rw.fill_buf().await {
105 Ok(rx_data) if rx_data.len() == 0 => return Err(RunError::Eof),
106 Ok(rx_data) => rx_data,
107 Err(e) => return Err(RunError::Read(e)),
108 },
109 };
110 Ok((buf, rx_data))
111 };
112 let tx_fut = tx_chan.tx_buf();
113 match select(rx_fut, tx_fut).await {
114 Either::First(r) => {
115 needs_poll = false;
116
117 let (buf, rx_data) = r?;
118 let n = ppp.consume(rx_data, &mut rx_buf);
119 rw.consume(n);
120
121 match ppp.poll(&mut tx_buf, &mut rx_buf) {
122 PPPoSAction::None => {}
123 PPPoSAction::Received(rg) => {
124 let pkt = &rx_buf[rg];
125 buf[..pkt.len()].copy_from_slice(pkt);
126 rx_chan.rx_done(pkt.len());
127 }
128 PPPoSAction::Transmit(n) => rw.write_all(&tx_buf[..n]).await?,
129 }
130
131 let status = ppp.status();
132 match status.phase {
133 ppproto::Phase::Dead => {
134 return Err(RunError::Terminated);
135 }
136 ppproto::Phase::Open => {
137 if !was_up {
138 on_ipv4_up(status.ipv4.unwrap());
139 }
140 was_up = true;
141 state_chan.set_link_state(LinkState::Up);
142 }
143 _ => {
144 was_up = false;
145 state_chan.set_link_state(LinkState::Down);
146 }
147 }
148 }
149 Either::Second(pkt) => {
150 match ppp.send(pkt, &mut tx_buf) {
151 Ok(n) => rw.write_all(&tx_buf[..n]).await?,
152 Err(BufferFullError) => unreachable!(),
153 }
154 tx_chan.tx_done();
155 }
156 }
157 }
158 }
159}
160
161/// Create a PPP embassy-net driver instance.
162///
163/// This returns two structs:
164/// - a `Device` that you must pass to the `embassy-net` stack.
165/// - a `Runner`. You must call `.run()` on it in a background task.
166pub fn new<'a, const N_RX: usize, const N_TX: usize>(state: &'a mut State<N_RX, N_TX>) -> (Device<'a>, Runner<'a>) {
167 let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ip);
168 (device, Runner { ch: runner })
169}
170
171struct OnDrop<F: FnOnce()> {
172 f: MaybeUninit<F>,
173}
174
175impl<F: FnOnce()> OnDrop<F> {
176 fn new(f: F) -> Self {
177 Self { f: MaybeUninit::new(f) }
178 }
179}
180
181impl<F: FnOnce()> Drop for OnDrop<F> {
182 fn drop(&mut self) {
183 unsafe { self.f.as_ptr().read()() }
184 }
185}