aboutsummaryrefslogtreecommitdiff
path: root/embassy-net/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-net/src/lib.rs')
-rw-r--r--embassy-net/src/lib.rs307
1 files changed, 302 insertions, 5 deletions
diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs
index e18e819cb..afe0d6da0 100644
--- a/embassy-net/src/lib.rs
+++ b/embassy-net/src/lib.rs
@@ -9,16 +9,28 @@
9pub(crate) mod fmt; 9pub(crate) mod fmt;
10 10
11pub mod device; 11pub mod device;
12mod stack;
13
14pub use stack::{Config, ConfigStrategy, Stack, StackResources};
15
16#[cfg(feature = "tcp")] 12#[cfg(feature = "tcp")]
17pub mod tcp; 13pub mod tcp;
18
19#[cfg(feature = "udp")] 14#[cfg(feature = "udp")]
20pub mod udp; 15pub mod udp;
21 16
17use core::cell::RefCell;
18use core::future::{poll_fn, Future};
19use core::task::{Context, Poll};
20
21use embassy_sync::waitqueue::WakerRegistration;
22use embassy_time::{Instant, Timer};
23use futures::pin_mut;
24use heapless::Vec;
25#[cfg(feature = "dhcpv4")]
26use smoltcp::iface::SocketHandle;
27use smoltcp::iface::{Interface, InterfaceBuilder, SocketSet, SocketStorage};
28#[cfg(feature = "medium-ethernet")]
29use smoltcp::iface::{Neighbor, NeighborCache, Route, Routes};
30#[cfg(feature = "medium-ethernet")]
31use smoltcp::phy::Medium;
32#[cfg(feature = "dhcpv4")]
33use smoltcp::socket::dhcpv4;
22// smoltcp reexports 34// smoltcp reexports
23pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; 35pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant};
24#[cfg(feature = "medium-ethernet")] 36#[cfg(feature = "medium-ethernet")]
@@ -28,3 +40,288 @@ pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr};
28pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; 40pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr};
29#[cfg(feature = "udp")] 41#[cfg(feature = "udp")]
30pub use smoltcp::{socket::udp::PacketMetadata, wire::IpListenEndpoint}; 42pub use smoltcp::{socket::udp::PacketMetadata, wire::IpListenEndpoint};
43
44use crate::device::{Device, DeviceAdapter, LinkState};
45
46const LOCAL_PORT_MIN: u16 = 1025;
47const LOCAL_PORT_MAX: u16 = 65535;
48
49pub struct StackResources<const ADDR: usize, const SOCK: usize, const NEIGHBOR: usize> {
50 addresses: [IpCidr; ADDR],
51 sockets: [SocketStorage<'static>; SOCK],
52
53 #[cfg(feature = "medium-ethernet")]
54 routes: [Option<(IpCidr, Route)>; 1],
55 #[cfg(feature = "medium-ethernet")]
56 neighbor_cache: [Option<(IpAddress, Neighbor)>; NEIGHBOR],
57}
58
59impl<const ADDR: usize, const SOCK: usize, const NEIGHBOR: usize> StackResources<ADDR, SOCK, NEIGHBOR> {
60 pub fn new() -> Self {
61 Self {
62 addresses: [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32); ADDR],
63 sockets: [SocketStorage::EMPTY; SOCK],
64 #[cfg(feature = "medium-ethernet")]
65 routes: [None; 1],
66 #[cfg(feature = "medium-ethernet")]
67 neighbor_cache: [None; NEIGHBOR],
68 }
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct Config {
74 pub address: Ipv4Cidr,
75 pub gateway: Option<Ipv4Address>,
76 pub dns_servers: Vec<Ipv4Address, 3>,
77}
78
79pub enum ConfigStrategy {
80 Static(Config),
81 #[cfg(feature = "dhcpv4")]
82 Dhcp,
83}
84
85pub struct Stack<D: Device> {
86 pub(crate) socket: RefCell<SocketStack>,
87 inner: RefCell<Inner<D>>,
88}
89
90struct Inner<D: Device> {
91 device: D,
92 link_up: bool,
93 config: Option<Config>,
94 #[cfg(feature = "dhcpv4")]
95 dhcp_socket: Option<SocketHandle>,
96}
97
98pub(crate) struct SocketStack {
99 pub(crate) sockets: SocketSet<'static>,
100 pub(crate) iface: Interface<'static>,
101 pub(crate) waker: WakerRegistration,
102 next_local_port: u16,
103}
104
105impl<D: Device + 'static> Stack<D> {
106 pub fn new<const ADDR: usize, const SOCK: usize, const NEIGH: usize>(
107 mut device: D,
108 config: ConfigStrategy,
109 resources: &'static mut StackResources<ADDR, SOCK, NEIGH>,
110 random_seed: u64,
111 ) -> Self {
112 #[cfg(feature = "medium-ethernet")]
113 let medium = device.capabilities().medium;
114
115 #[cfg(feature = "medium-ethernet")]
116 let ethernet_addr = if medium == Medium::Ethernet {
117 device.ethernet_address()
118 } else {
119 [0, 0, 0, 0, 0, 0]
120 };
121
122 let mut b = InterfaceBuilder::new();
123 b = b.ip_addrs(&mut resources.addresses[..]);
124 b = b.random_seed(random_seed);
125
126 #[cfg(feature = "medium-ethernet")]
127 if medium == Medium::Ethernet {
128 b = b.hardware_addr(HardwareAddress::Ethernet(EthernetAddress(ethernet_addr)));
129 b = b.neighbor_cache(NeighborCache::new(&mut resources.neighbor_cache[..]));
130 b = b.routes(Routes::new(&mut resources.routes[..]));
131 }
132
133 let iface = b.finalize(&mut DeviceAdapter {
134 inner: &mut device,
135 cx: None,
136 });
137
138 let sockets = SocketSet::new(&mut resources.sockets[..]);
139
140 let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
141
142 let mut inner = Inner {
143 device,
144 link_up: false,
145 config: None,
146 #[cfg(feature = "dhcpv4")]
147 dhcp_socket: None,
148 };
149 let mut socket = SocketStack {
150 sockets,
151 iface,
152 waker: WakerRegistration::new(),
153 next_local_port,
154 };
155
156 match config {
157 ConfigStrategy::Static(config) => inner.apply_config(&mut socket, config),
158 #[cfg(feature = "dhcpv4")]
159 ConfigStrategy::Dhcp => {
160 let handle = socket.sockets.add(smoltcp::socket::dhcpv4::Socket::new());
161 inner.dhcp_socket = Some(handle);
162 }
163 }
164
165 Self {
166 socket: RefCell::new(socket),
167 inner: RefCell::new(inner),
168 }
169 }
170
171 fn with<R>(&self, f: impl FnOnce(&SocketStack, &Inner<D>) -> R) -> R {
172 f(&*self.socket.borrow(), &*self.inner.borrow())
173 }
174
175 fn with_mut<R>(&self, f: impl FnOnce(&mut SocketStack, &mut Inner<D>) -> R) -> R {
176 f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut())
177 }
178
179 pub fn ethernet_address(&self) -> [u8; 6] {
180 self.with(|_s, i| i.device.ethernet_address())
181 }
182
183 pub fn is_link_up(&self) -> bool {
184 self.with(|_s, i| i.link_up)
185 }
186
187 pub fn is_config_up(&self) -> bool {
188 self.with(|_s, i| i.config.is_some())
189 }
190
191 pub fn config(&self) -> Option<Config> {
192 self.with(|_s, i| i.config.clone())
193 }
194
195 pub async fn run(&self) -> ! {
196 poll_fn(|cx| {
197 self.with_mut(|s, i| i.poll(cx, s));
198 Poll::<()>::Pending
199 })
200 .await;
201 unreachable!()
202 }
203}
204
205impl SocketStack {
206 #[allow(clippy::absurd_extreme_comparisons)]
207 pub fn get_local_port(&mut self) -> u16 {
208 let res = self.next_local_port;
209 self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 };
210 res
211 }
212}
213
214impl<D: Device + 'static> Inner<D> {
215 fn apply_config(&mut self, s: &mut SocketStack, config: Config) {
216 #[cfg(feature = "medium-ethernet")]
217 let medium = self.device.capabilities().medium;
218
219 debug!("Acquired IP configuration:");
220
221 debug!(" IP address: {}", config.address);
222 self.set_ipv4_addr(s, config.address);
223
224 #[cfg(feature = "medium-ethernet")]
225 if medium == Medium::Ethernet {
226 if let Some(gateway) = config.gateway {
227 debug!(" Default gateway: {}", gateway);
228 s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap();
229 } else {
230 debug!(" Default gateway: None");
231 s.iface.routes_mut().remove_default_ipv4_route();
232 }
233 }
234 for (i, s) in config.dns_servers.iter().enumerate() {
235 debug!(" DNS server {}: {}", i, s);
236 }
237
238 self.config = Some(config)
239 }
240
241 #[allow(unused)] // used only with dhcp
242 fn unapply_config(&mut self, s: &mut SocketStack) {
243 #[cfg(feature = "medium-ethernet")]
244 let medium = self.device.capabilities().medium;
245
246 debug!("Lost IP configuration");
247 self.set_ipv4_addr(s, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0));
248 #[cfg(feature = "medium-ethernet")]
249 if medium == Medium::Ethernet {
250 s.iface.routes_mut().remove_default_ipv4_route();
251 }
252 self.config = None
253 }
254
255 fn set_ipv4_addr(&mut self, s: &mut SocketStack, cidr: Ipv4Cidr) {
256 s.iface.update_ip_addrs(|addrs| {
257 let dest = addrs.iter_mut().next().unwrap();
258 *dest = IpCidr::Ipv4(cidr);
259 });
260 }
261
262 fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
263 s.waker.register(cx.waker());
264
265 let timestamp = instant_to_smoltcp(Instant::now());
266 let mut smoldev = DeviceAdapter {
267 cx: Some(cx),
268 inner: &mut self.device,
269 };
270 if s.iface.poll(timestamp, &mut smoldev, &mut s.sockets).is_err() {
271 // If poll() returns error, it may not be done yet, so poll again later.
272 cx.waker().wake_by_ref();
273 return;
274 }
275
276 // Update link up
277 let old_link_up = self.link_up;
278 self.link_up = self.device.link_state(cx) == LinkState::Up;
279
280 // Print when changed
281 if old_link_up != self.link_up {
282 info!("link_up = {:?}", self.link_up);
283 }
284
285 #[cfg(feature = "dhcpv4")]
286 if let Some(dhcp_handle) = self.dhcp_socket {
287 let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
288
289 if self.link_up {
290 match socket.poll() {
291 None => {}
292 Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s),
293 Some(dhcpv4::Event::Configured(config)) => {
294 let config = Config {
295 address: config.address,
296 gateway: config.router,
297 dns_servers: config.dns_servers,
298 };
299 self.apply_config(s, config)
300 }
301 }
302 } else if old_link_up {
303 socket.reset();
304 self.unapply_config(s);
305 }
306 }
307 //if old_link_up || self.link_up {
308 // self.poll_configurator(timestamp)
309 //}
310
311 if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) {
312 let t = Timer::at(instant_from_smoltcp(poll_at));
313 pin_mut!(t);
314 if t.poll(cx).is_ready() {
315 cx.waker().wake_by_ref();
316 }
317 }
318 }
319}
320
321fn instant_to_smoltcp(instant: Instant) -> SmolInstant {
322 SmolInstant::from_millis(instant.as_millis() as i64)
323}
324
325fn instant_from_smoltcp(instant: SmolInstant) -> Instant {
326 Instant::from_millis(instant.total_millis() as u64)
327}