From a5aea995a802fea8fc1b3e4b5fe47bd6d1fca2a4 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 23 May 2022 03:50:43 +0200 Subject: WIP embassy-net v2 --- embassy-net/src/stack.rs | 393 +++++++++++++++++++++++++++-------------------- 1 file changed, 226 insertions(+), 167 deletions(-) (limited to 'embassy-net/src/stack.rs') diff --git a/embassy-net/src/stack.rs b/embassy-net/src/stack.rs index 9461f832f..e28370df8 100644 --- a/embassy-net/src/stack.rs +++ b/embassy-net/src/stack.rs @@ -1,13 +1,18 @@ -use core::cell::RefCell; +use core::cell::UnsafeCell; use core::future::Future; use core::task::Context; use core::task::Poll; -use embassy::blocking_mutex::ThreadModeMutex; use embassy::time::{Instant, Timer}; use embassy::waitqueue::WakerRegistration; +use futures::future::poll_fn; use futures::pin_mut; -use smoltcp::iface::InterfaceBuilder; -use smoltcp::iface::SocketStorage; +use heapless::Vec; +#[cfg(feature = "dhcpv4")] +use smoltcp::iface::SocketHandle; +use smoltcp::iface::{Interface, InterfaceBuilder}; +use smoltcp::iface::{SocketSet, SocketStorage}; +#[cfg(feature = "dhcpv4")] +use smoltcp::socket::dhcpv4; use smoltcp::time::Instant as SmolInstant; use smoltcp::wire::{IpCidr, Ipv4Address, Ipv4Cidr}; @@ -18,10 +23,7 @@ use smoltcp::phy::{Device as _, Medium}; #[cfg(feature = "medium-ethernet")] use smoltcp::wire::{EthernetAddress, HardwareAddress, IpAddress}; -use crate::config::Configurator; -use crate::config::Event; use crate::device::{Device, DeviceAdapter, LinkState}; -use crate::{Config, Interface}; const LOCAL_PORT_MIN: u16 = 1025; const LOCAL_PORT_MAX: u16 = 65535; @@ -51,24 +53,144 @@ impl } } -static STACK: ThreadModeMutex>> = ThreadModeMutex::new(RefCell::new(None)); +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Config { + pub address: Ipv4Cidr, + pub gateway: Option, + pub dns_servers: Vec, +} + +pub enum ConfigStrategy { + Static(Config), + #[cfg(feature = "dhcpv4")] + Dhcp, +} -pub(crate) struct Stack { - pub iface: Interface, +pub struct Stack { + pub(crate) socket: UnsafeCell, + inner: UnsafeCell>, +} + +struct Inner { + device: DeviceAdapter, link_up: bool, config: Option, + #[cfg(feature = "dhcpv4")] + dhcp_socket: Option, +} + +pub(crate) struct SocketStack { + pub(crate) sockets: SocketSet<'static>, + pub(crate) iface: Interface<'static>, + pub(crate) waker: WakerRegistration, next_local_port: u16, - configurator: &'static mut dyn Configurator, - waker: WakerRegistration, } -impl Stack { - pub(crate) fn with(f: impl FnOnce(&mut Stack) -> R) -> R { - let mut stack = STACK.borrow().borrow_mut(); - let stack = stack.as_mut().unwrap(); - f(stack) +unsafe impl Send for Stack {} + +impl Stack { + pub fn new( + device: D, + config: ConfigStrategy, + resources: &'static mut StackResources, + random_seed: u64, + ) -> Self { + #[cfg(feature = "medium-ethernet")] + let medium = device.capabilities().medium; + + #[cfg(feature = "medium-ethernet")] + let ethernet_addr = if medium == Medium::Ethernet { + device.ethernet_address() + } else { + [0, 0, 0, 0, 0, 0] + }; + + let mut device = DeviceAdapter::new(device); + + let mut b = InterfaceBuilder::new(); + b = b.ip_addrs(&mut resources.addresses[..]); + b = b.random_seed(random_seed); + + #[cfg(feature = "medium-ethernet")] + if medium == Medium::Ethernet { + b = b.hardware_addr(HardwareAddress::Ethernet(EthernetAddress(ethernet_addr))); + b = b.neighbor_cache(NeighborCache::new(&mut resources.neighbor_cache[..])); + b = b.routes(Routes::new(&mut resources.routes[..])); + } + + let iface = b.finalize(&mut device); + + let sockets = SocketSet::new(&mut resources.sockets[..]); + + let next_local_port = + (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; + + let mut inner = Inner { + device, + link_up: false, + config: None, + #[cfg(feature = "dhcpv4")] + dhcp_socket: None, + }; + let mut socket = SocketStack { + sockets, + iface, + waker: WakerRegistration::new(), + next_local_port, + }; + + match config { + ConfigStrategy::Static(config) => inner.apply_config(&mut socket, config), + #[cfg(feature = "dhcpv4")] + ConfigStrategy::Dhcp => { + let handle = socket.sockets.add(smoltcp::socket::dhcpv4::Socket::new()); + inner.dhcp_socket = Some(handle); + } + } + + Self { + socket: UnsafeCell::new(socket), + inner: UnsafeCell::new(inner), + } + } + + /// SAFETY: must not call reentrantly. + unsafe fn with(&self, f: impl FnOnce(&SocketStack, &Inner) -> R) -> R { + f(&*self.socket.get(), &*self.inner.get()) + } + + /// SAFETY: must not call reentrantly. + unsafe fn with_mut(&self, f: impl FnOnce(&mut SocketStack, &mut Inner) -> R) -> R { + f(&mut *self.socket.get(), &mut *self.inner.get()) + } + + pub fn ethernet_address(&self) -> [u8; 6] { + unsafe { self.with(|_s, i| i.device.device.ethernet_address()) } + } + + pub fn is_link_up(&self) -> bool { + unsafe { self.with(|_s, i| i.link_up) } + } + + pub fn is_config_up(&self) -> bool { + unsafe { self.with(|_s, i| i.config.is_some()) } + } + + pub fn config(&self) -> Option { + unsafe { self.with(|_s, i| i.config.clone()) } + } + + pub async fn run(&self) -> ! { + poll_fn(|cx| { + unsafe { self.with_mut(|s, i| i.poll(cx, s)) } + Poll::<()>::Pending + }) + .await; + unreachable!() } +} +impl SocketStack { #[allow(clippy::absurd_extreme_comparisons)] pub fn get_local_port(&mut self) -> u16 { let res = self.next_local_port; @@ -79,60 +201,68 @@ impl Stack { }; res } +} + +impl Inner { + fn apply_config(&mut self, s: &mut SocketStack, config: Config) { + #[cfg(feature = "medium-ethernet")] + let medium = self.device.capabilities().medium; + + debug!("Acquired IP configuration:"); - pub(crate) fn wake(&mut self) { - self.waker.wake() + debug!(" IP address: {}", config.address); + self.set_ipv4_addr(s, config.address); + + #[cfg(feature = "medium-ethernet")] + if medium == Medium::Ethernet { + if let Some(gateway) = config.gateway { + debug!(" Default gateway: {}", gateway); + s.iface + .routes_mut() + .add_default_ipv4_route(gateway) + .unwrap(); + } else { + debug!(" Default gateway: None"); + s.iface.routes_mut().remove_default_ipv4_route(); + } + } + for (i, s) in config.dns_servers.iter().enumerate() { + debug!(" DNS server {}: {}", i, s); + } + + self.config = Some(config) } - fn poll_configurator(&mut self, timestamp: SmolInstant) { + #[allow(unused)] // used only with dhcp + fn unapply_config(&mut self, s: &mut SocketStack) { #[cfg(feature = "medium-ethernet")] - let medium = self.iface.device().capabilities().medium; - - match self.configurator.poll(&mut self.iface, timestamp) { - Event::NoChange => {} - Event::Configured(config) => { - debug!("Acquired IP configuration:"); - - debug!(" IP address: {}", config.address); - set_ipv4_addr(&mut self.iface, config.address); - - #[cfg(feature = "medium-ethernet")] - if medium == Medium::Ethernet { - if let Some(gateway) = config.gateway { - debug!(" Default gateway: {}", gateway); - self.iface - .routes_mut() - .add_default_ipv4_route(gateway) - .unwrap(); - } else { - debug!(" Default gateway: None"); - self.iface.routes_mut().remove_default_ipv4_route(); - } - } - for (i, s) in config.dns_servers.iter().enumerate() { - debug!(" DNS server {}: {}", i, s); - } + let medium = self.device.capabilities().medium; - self.config = Some(config) - } - Event::Deconfigured => { - debug!("Lost IP configuration"); - set_ipv4_addr(&mut self.iface, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)); - #[cfg(feature = "medium-ethernet")] - if medium == Medium::Ethernet { - self.iface.routes_mut().remove_default_ipv4_route(); - } - self.config = None - } + debug!("Lost IP configuration"); + self.set_ipv4_addr(s, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)); + #[cfg(feature = "medium-ethernet")] + if medium == Medium::Ethernet { + s.iface.routes_mut().remove_default_ipv4_route(); } + self.config = None + } + + fn set_ipv4_addr(&mut self, s: &mut SocketStack, cidr: Ipv4Cidr) { + s.iface.update_ip_addrs(|addrs| { + let dest = addrs.iter_mut().next().unwrap(); + *dest = IpCidr::Ipv4(cidr); + }); } - fn poll(&mut self, cx: &mut Context<'_>) { - self.iface.device_mut().device.register_waker(cx.waker()); - self.waker.register(cx.waker()); + fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { + self.device.device.register_waker(cx.waker()); + s.waker.register(cx.waker()); let timestamp = instant_to_smoltcp(Instant::now()); - if self.iface.poll(timestamp).is_err() { + if s.iface + .poll(timestamp, &mut self.device, &mut s.sockets) + .is_err() + { // If poll() returns error, it may not be done yet, so poll again later. cx.waker().wake_by_ref(); return; @@ -140,18 +270,49 @@ impl Stack { // Update link up let old_link_up = self.link_up; - self.link_up = self.iface.device_mut().device.link_state() == LinkState::Up; + self.link_up = self.device.device.link_state() == LinkState::Up; // Print when changed if old_link_up != self.link_up { info!("link_up = {:?}", self.link_up); } - if old_link_up || self.link_up { - self.poll_configurator(timestamp) + #[cfg(feature = "dhcpv4")] + if let Some(dhcp_handle) = self.dhcp_socket { + let socket = s.sockets.get_mut::(dhcp_handle); + + if self.link_up { + match socket.poll() { + None => {} + Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s), + Some(dhcpv4::Event::Configured(config)) => { + let mut dns_servers = Vec::new(); + for s in &config.dns_servers { + if let Some(addr) = s { + dns_servers.push(addr.clone()).unwrap(); + } + } + + self.apply_config( + s, + Config { + address: config.address, + gateway: config.router, + dns_servers, + }, + ) + } + } + } else if old_link_up { + socket.reset(); + self.unapply_config(s); + } } + //if old_link_up || self.link_up { + // self.poll_configurator(timestamp) + //} - if let Some(poll_at) = self.iface.poll_at(timestamp) { + if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) { let t = Timer::at(instant_from_smoltcp(poll_at)); pin_mut!(t); if t.poll(cx).is_ready() { @@ -161,100 +322,6 @@ impl Stack { } } -fn set_ipv4_addr(iface: &mut Interface, cidr: Ipv4Cidr) { - iface.update_ip_addrs(|addrs| { - let dest = addrs.iter_mut().next().unwrap(); - *dest = IpCidr::Ipv4(cidr); - }); -} - -/// Initialize embassy_net. -/// This function must be called from thread mode. -pub fn init( - device: &'static mut dyn Device, - configurator: &'static mut dyn Configurator, - resources: &'static mut StackResources, -) { - #[cfg(feature = "medium-ethernet")] - let medium = device.capabilities().medium; - - #[cfg(feature = "medium-ethernet")] - let ethernet_addr = if medium == Medium::Ethernet { - device.ethernet_address() - } else { - [0, 0, 0, 0, 0, 0] - }; - - let mut b = InterfaceBuilder::new(DeviceAdapter::new(device), &mut resources.sockets[..]); - b = b.ip_addrs(&mut resources.addresses[..]); - - #[cfg(feature = "medium-ethernet")] - if medium == Medium::Ethernet { - b = b.hardware_addr(HardwareAddress::Ethernet(EthernetAddress(ethernet_addr))); - b = b.neighbor_cache(NeighborCache::new(&mut resources.neighbor_cache[..])); - b = b.routes(Routes::new(&mut resources.routes[..])); - } - - let iface = b.finalize(); - - let local_port = loop { - let mut res = [0u8; 2]; - rand(&mut res); - let port = u16::from_le_bytes(res); - if (LOCAL_PORT_MIN..=LOCAL_PORT_MAX).contains(&port) { - break port; - } - }; - - let stack = Stack { - iface, - link_up: false, - config: None, - configurator, - next_local_port: local_port, - waker: WakerRegistration::new(), - }; - - *STACK.borrow().borrow_mut() = Some(stack); -} - -pub fn ethernet_address() -> [u8; 6] { - STACK - .borrow() - .borrow() - .as_ref() - .unwrap() - .iface - .device() - .device - .ethernet_address() -} - -pub fn is_init() -> bool { - STACK.borrow().borrow().is_some() -} - -pub fn is_link_up() -> bool { - STACK.borrow().borrow().as_ref().unwrap().link_up -} - -pub fn is_config_up() -> bool { - STACK.borrow().borrow().as_ref().unwrap().config.is_some() -} - -pub fn config() -> Option { - STACK.borrow().borrow().as_ref().unwrap().config.clone() -} - -pub async fn run() -> ! { - futures::future::poll_fn(|cx| { - Stack::with(|stack| stack.poll(cx)); - Poll::<()>::Pending - }) - .await; - unreachable!() -} - fn instant_to_smoltcp(instant: Instant) -> SmolInstant { SmolInstant::from_millis(instant.as_millis() as i64) } @@ -262,11 +329,3 @@ fn instant_to_smoltcp(instant: Instant) -> SmolInstant { fn instant_from_smoltcp(instant: SmolInstant) -> Instant { Instant::from_millis(instant.total_millis() as u64) } - -extern "Rust" { - fn _embassy_rand(buf: &mut [u8]); -} - -fn rand(buf: &mut [u8]) { - unsafe { _embassy_rand(buf) } -} -- cgit