From ac74613b5a7be72acd8d5259055963f8b4aba7fd Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 7 Dec 2022 15:55:46 +0100 Subject: [PATCH 1/7] net: remove packet pool. The pool was prone to deadlocks, especially due to having a single pool for rx+tx. If the pool got full with rx'd packets it would deadlock because processing a rx packet requires doing another allocation on the pool, for the possibly tx'd response, before deallocating the rx'd packet. This also allows Device impls to allocate the packet memory in a particular RAM kind, if needed for example to do DMA. The `Device` trait is now token-based, like smoltcp's. In the end, this is better because it allows callers to manage memory however they want (including with a pool if they want to). --- embassy-net/src/device.rs | 163 ++++++++++++++++++--------------- embassy-net/src/lib.rs | 6 +- embassy-net/src/packet_pool.rs | 107 ---------------------- embassy-net/src/stack.rs | 24 +++-- embassy-net/src/tcp.rs | 2 +- 5 files changed, 105 insertions(+), 197 deletions(-) delete mode 100644 embassy-net/src/packet_pool.rs diff --git a/embassy-net/src/device.rs b/embassy-net/src/device.rs index 4bdfd772..5d86ca91 100644 --- a/embassy-net/src/device.rs +++ b/embassy-net/src/device.rs @@ -1,10 +1,7 @@ -use core::task::Waker; +use core::task::Context; -use smoltcp::phy::{Device as SmolDevice, DeviceCapabilities}; -use smoltcp::time::Instant as SmolInstant; - -use crate::packet_pool::PacketBoxExt; -use crate::{Packet, PacketBox, PacketBuf}; +use smoltcp::phy; +pub use smoltcp::phy::{Checksum, ChecksumCapabilities, DeviceCapabilities, Medium}; #[derive(PartialEq, Eq, Clone, Copy)] pub enum LinkState { @@ -13,115 +10,133 @@ pub enum LinkState { } pub trait Device { - fn is_transmit_ready(&mut self) -> bool; - fn transmit(&mut self, pkt: PacketBuf); - fn receive(&mut self) -> Option; + type RxToken<'a>: RxToken + where + Self: 'a; + type TxToken<'a>: TxToken + where + Self: 'a; - fn register_waker(&mut self, waker: &Waker); - fn capabilities(&self) -> DeviceCapabilities; - fn link_state(&mut self) -> LinkState; + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>; + fn transmit(&mut self, cx: &mut Context) -> Option>; + fn link_state(&mut self, cx: &mut Context) -> LinkState; + + fn capabilities(&self) -> phy::DeviceCapabilities; fn ethernet_address(&self) -> [u8; 6]; } impl Device for &mut T { - fn is_transmit_ready(&mut self) -> bool { - T::is_transmit_ready(self) + type RxToken<'a> = T::RxToken<'a> + where + Self: 'a; + type TxToken<'a> = T::TxToken<'a> + where + Self: 'a; + + fn transmit(&mut self, cx: &mut Context) -> Option> { + T::transmit(self, cx) } - fn transmit(&mut self, pkt: PacketBuf) { - T::transmit(self, pkt) + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + T::receive(self, cx) } - fn receive(&mut self) -> Option { - T::receive(self) - } - fn register_waker(&mut self, waker: &Waker) { - T::register_waker(self, waker) - } - fn capabilities(&self) -> DeviceCapabilities { + fn capabilities(&self) -> phy::DeviceCapabilities { T::capabilities(self) } - fn link_state(&mut self) -> LinkState { - T::link_state(self) + fn link_state(&mut self, cx: &mut Context) -> LinkState { + T::link_state(self, cx) } fn ethernet_address(&self) -> [u8; 6] { T::ethernet_address(self) } } -pub struct DeviceAdapter { - pub device: D, - caps: DeviceCapabilities, +/// A token to receive a single network packet. +pub trait RxToken { + /// Consumes the token to receive a single network packet. + /// + /// This method receives a packet and then calls the given closure `f` with the raw + /// packet bytes as argument. + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R; } -impl DeviceAdapter { - pub(crate) fn new(device: D) -> Self { - Self { - caps: device.capabilities(), - device, - } - } +/// A token to transmit a single network packet. +pub trait TxToken { + /// Consumes the token to send a single network packet. + /// + /// This method constructs a transmit buffer of size `len` and calls the passed + /// closure `f` with a mutable reference to that buffer. The closure should construct + /// a valid network packet (e.g. an ethernet packet) in the buffer. When the closure + /// returns, the transmit buffer is sent out. + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R; } -impl SmolDevice for DeviceAdapter { - type RxToken<'a> = RxToken where Self: 'a; - type TxToken<'a> = TxToken<'a, D> where Self: 'a; +/////////////////////////// + +pub(crate) struct DeviceAdapter<'d, 'c, T> +where + T: Device, +{ + // must be Some when actually using this to rx/tx + pub cx: Option<&'d mut Context<'c>>, + pub inner: &'d mut T, +} + +impl<'d, 'c, T> phy::Device for DeviceAdapter<'d, 'c, T> +where + T: Device, +{ + type RxToken<'a> = RxTokenAdapter> where Self: 'a; + type TxToken<'a> = TxTokenAdapter> where Self: 'a; fn receive(&mut self) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - let tx_pkt = PacketBox::new(Packet::new())?; - let rx_pkt = self.device.receive()?; - let rx_token = RxToken { pkt: rx_pkt }; - let tx_token = TxToken { - device: &mut self.device, - pkt: tx_pkt, - }; - - Some((rx_token, tx_token)) + self.inner + .receive(self.cx.as_deref_mut().unwrap()) + .map(|(rx, tx)| (RxTokenAdapter(rx), TxTokenAdapter(tx))) } /// Construct a transmit token. fn transmit(&mut self) -> Option> { - if !self.device.is_transmit_ready() { - return None; - } - - let tx_pkt = PacketBox::new(Packet::new())?; - Some(TxToken { - device: &mut self.device, - pkt: tx_pkt, - }) + self.inner.transmit(self.cx.as_deref_mut().unwrap()).map(TxTokenAdapter) } /// Get a description of device capabilities. - fn capabilities(&self) -> DeviceCapabilities { - self.caps.clone() + fn capabilities(&self) -> phy::DeviceCapabilities { + self.inner.capabilities() } } -pub struct RxToken { - pkt: PacketBuf, -} +pub(crate) struct RxTokenAdapter(T) +where + T: RxToken; -impl smoltcp::phy::RxToken for RxToken { - fn consume(mut self, _timestamp: SmolInstant, f: F) -> smoltcp::Result +impl phy::RxToken for RxTokenAdapter +where + T: RxToken, +{ + fn consume(self, _timestamp: smoltcp::time::Instant, f: F) -> smoltcp::Result where F: FnOnce(&mut [u8]) -> smoltcp::Result, { - f(&mut self.pkt) + self.0.consume(|buf| f(buf)) } } -pub struct TxToken<'a, D: Device> { - device: &'a mut D, - pkt: PacketBox, -} +pub(crate) struct TxTokenAdapter(T) +where + T: TxToken; -impl<'a, D: Device> smoltcp::phy::TxToken for TxToken<'a, D> { - fn consume(self, _timestamp: SmolInstant, len: usize, f: F) -> smoltcp::Result +impl phy::TxToken for TxTokenAdapter +where + T: TxToken, +{ + fn consume(self, _timestamp: smoltcp::time::Instant, len: usize, f: F) -> smoltcp::Result where F: FnOnce(&mut [u8]) -> smoltcp::Result, { - let mut buf = self.pkt.slice(0..len); - let r = f(&mut buf)?; - self.device.transmit(buf); - Ok(r) + self.0.consume(len, |buf| f(buf)) } } diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index edb96984..e18e819c 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -8,12 +8,9 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; -mod device; -mod packet_pool; +pub mod device; mod stack; -pub use device::{Device, LinkState}; -pub use packet_pool::{Packet, PacketBox, PacketBoxExt, PacketBuf, MTU}; pub use stack::{Config, ConfigStrategy, Stack, StackResources}; #[cfg(feature = "tcp")] @@ -23,7 +20,6 @@ pub mod tcp; pub mod udp; // smoltcp reexports -pub use smoltcp::phy::{DeviceCapabilities, Medium}; pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; #[cfg(feature = "medium-ethernet")] pub use smoltcp::wire::{EthernetAddress, HardwareAddress}; diff --git a/embassy-net/src/packet_pool.rs b/embassy-net/src/packet_pool.rs deleted file mode 100644 index cb8a1316..00000000 --- a/embassy-net/src/packet_pool.rs +++ /dev/null @@ -1,107 +0,0 @@ -use core::ops::{Deref, DerefMut, Range}; - -use as_slice::{AsMutSlice, AsSlice}; -use atomic_pool::{pool, Box}; - -pub const MTU: usize = 1516; - -#[cfg(feature = "pool-4")] -pub const PACKET_POOL_SIZE: usize = 4; - -#[cfg(feature = "pool-8")] -pub const PACKET_POOL_SIZE: usize = 8; - -#[cfg(feature = "pool-16")] -pub const PACKET_POOL_SIZE: usize = 16; - -#[cfg(feature = "pool-32")] -pub const PACKET_POOL_SIZE: usize = 32; - -#[cfg(feature = "pool-64")] -pub const PACKET_POOL_SIZE: usize = 64; - -#[cfg(feature = "pool-128")] -pub const PACKET_POOL_SIZE: usize = 128; - -pool!(pub PacketPool: [Packet; PACKET_POOL_SIZE]); -pub type PacketBox = Box; - -#[repr(align(4))] -pub struct Packet(pub [u8; MTU]); - -impl Packet { - pub const fn new() -> Self { - Self([0; MTU]) - } -} - -pub trait PacketBoxExt { - fn slice(self, range: Range) -> PacketBuf; -} - -impl PacketBoxExt for PacketBox { - fn slice(self, range: Range) -> PacketBuf { - PacketBuf { packet: self, range } - } -} - -impl AsSlice for Packet { - type Element = u8; - - fn as_slice(&self) -> &[Self::Element] { - &self.deref()[..] - } -} - -impl AsMutSlice for Packet { - fn as_mut_slice(&mut self) -> &mut [Self::Element] { - &mut self.deref_mut()[..] - } -} - -impl Deref for Packet { - type Target = [u8; MTU]; - - fn deref(&self) -> &[u8; MTU] { - &self.0 - } -} - -impl DerefMut for Packet { - fn deref_mut(&mut self) -> &mut [u8; MTU] { - &mut self.0 - } -} - -pub struct PacketBuf { - packet: PacketBox, - range: Range, -} - -impl AsSlice for PacketBuf { - type Element = u8; - - fn as_slice(&self) -> &[Self::Element] { - &self.packet[self.range.clone()] - } -} - -impl AsMutSlice for PacketBuf { - fn as_mut_slice(&mut self) -> &mut [Self::Element] { - &mut self.packet[self.range.clone()] - } -} - -impl Deref for PacketBuf { - type Target = [u8]; - - fn deref(&self) -> &[u8] { - &self.packet[self.range.clone()] - } -} - -impl DerefMut for PacketBuf { - fn deref_mut(&mut self) -> &mut [u8] { - &mut self.packet[self.range.clone()] - } -} diff --git a/embassy-net/src/stack.rs b/embassy-net/src/stack.rs index 5c4fb044..21316e48 100644 --- a/embassy-net/src/stack.rs +++ b/embassy-net/src/stack.rs @@ -12,7 +12,7 @@ use smoltcp::iface::{Interface, InterfaceBuilder, SocketSet, SocketStorage}; #[cfg(feature = "medium-ethernet")] use smoltcp::iface::{Neighbor, NeighborCache, Route, Routes}; #[cfg(feature = "medium-ethernet")] -use smoltcp::phy::{Device as _, Medium}; +use smoltcp::phy::Medium; #[cfg(feature = "dhcpv4")] use smoltcp::socket::dhcpv4; use smoltcp::time::Instant as SmolInstant; @@ -67,7 +67,7 @@ pub struct Stack { } struct Inner { - device: DeviceAdapter, + device: D, link_up: bool, config: Option, #[cfg(feature = "dhcpv4")] @@ -83,7 +83,7 @@ pub(crate) struct SocketStack { impl Stack { pub fn new( - device: D, + mut device: D, config: ConfigStrategy, resources: &'static mut StackResources, random_seed: u64, @@ -98,8 +98,6 @@ impl Stack { [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); @@ -111,7 +109,10 @@ impl Stack { b = b.routes(Routes::new(&mut resources.routes[..])); } - let iface = b.finalize(&mut device); + let iface = b.finalize(&mut DeviceAdapter { + inner: &mut device, + cx: None, + }); let sockets = SocketSet::new(&mut resources.sockets[..]); @@ -155,7 +156,7 @@ impl Stack { } pub fn ethernet_address(&self) -> [u8; 6] { - self.with(|_s, i| i.device.device.ethernet_address()) + self.with(|_s, i| i.device.ethernet_address()) } pub fn is_link_up(&self) -> bool { @@ -238,11 +239,14 @@ impl Inner { } 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 s.iface.poll(timestamp, &mut self.device, &mut s.sockets).is_err() { + let mut smoldev = DeviceAdapter { + cx: Some(cx), + inner: &mut self.device, + }; + if s.iface.poll(timestamp, &mut smoldev, &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; @@ -250,7 +254,7 @@ impl Inner { // Update link up let old_link_up = self.link_up; - self.link_up = self.device.device.link_state() == LinkState::Up; + self.link_up = self.device.link_state(cx) == LinkState::Up; // Print when changed if old_link_up != self.link_up { diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index 73cf2d4e..0ed4b81e 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -9,8 +9,8 @@ use smoltcp::time::Duration; use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; use super::stack::Stack; +use crate::device::Device; use crate::stack::SocketStack; -use crate::Device; #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] From aaaf5f23a8a3a0df1ad2186802e2afc061a74b72 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 7 Dec 2022 16:02:28 +0100 Subject: [PATCH 2/7] net: move stack into lib.rs --- embassy-net/src/lib.rs | 307 ++++++++++++++++++++++++++++++++++++++- embassy-net/src/stack.rs | 306 -------------------------------------- embassy-net/src/tcp.rs | 3 +- embassy-net/src/udp.rs | 3 +- 4 files changed, 304 insertions(+), 315 deletions(-) delete mode 100644 embassy-net/src/stack.rs diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index e18e819c..afe0d6da 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -9,16 +9,28 @@ pub(crate) mod fmt; pub mod device; -mod stack; - -pub use stack::{Config, ConfigStrategy, Stack, StackResources}; - #[cfg(feature = "tcp")] pub mod tcp; - #[cfg(feature = "udp")] pub mod udp; +use core::cell::RefCell; +use core::future::{poll_fn, Future}; +use core::task::{Context, Poll}; + +use embassy_sync::waitqueue::WakerRegistration; +use embassy_time::{Instant, Timer}; +use futures::pin_mut; +use heapless::Vec; +#[cfg(feature = "dhcpv4")] +use smoltcp::iface::SocketHandle; +use smoltcp::iface::{Interface, InterfaceBuilder, SocketSet, SocketStorage}; +#[cfg(feature = "medium-ethernet")] +use smoltcp::iface::{Neighbor, NeighborCache, Route, Routes}; +#[cfg(feature = "medium-ethernet")] +use smoltcp::phy::Medium; +#[cfg(feature = "dhcpv4")] +use smoltcp::socket::dhcpv4; // smoltcp reexports pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; #[cfg(feature = "medium-ethernet")] @@ -28,3 +40,288 @@ pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr}; pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; #[cfg(feature = "udp")] pub use smoltcp::{socket::udp::PacketMetadata, wire::IpListenEndpoint}; + +use crate::device::{Device, DeviceAdapter, LinkState}; + +const LOCAL_PORT_MIN: u16 = 1025; +const LOCAL_PORT_MAX: u16 = 65535; + +pub struct StackResources { + addresses: [IpCidr; ADDR], + sockets: [SocketStorage<'static>; SOCK], + + #[cfg(feature = "medium-ethernet")] + routes: [Option<(IpCidr, Route)>; 1], + #[cfg(feature = "medium-ethernet")] + neighbor_cache: [Option<(IpAddress, Neighbor)>; NEIGHBOR], +} + +impl StackResources { + pub fn new() -> Self { + Self { + addresses: [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32); ADDR], + sockets: [SocketStorage::EMPTY; SOCK], + #[cfg(feature = "medium-ethernet")] + routes: [None; 1], + #[cfg(feature = "medium-ethernet")] + neighbor_cache: [None; NEIGHBOR], + } + } +} + +#[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 struct Stack { + pub(crate) socket: RefCell, + inner: RefCell>, +} + +struct Inner { + device: D, + 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, +} + +impl Stack { + pub fn new( + mut 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 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 DeviceAdapter { + inner: &mut device, + cx: None, + }); + + 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: RefCell::new(socket), + inner: RefCell::new(inner), + } + } + + fn with(&self, f: impl FnOnce(&SocketStack, &Inner) -> R) -> R { + f(&*self.socket.borrow(), &*self.inner.borrow()) + } + + fn with_mut(&self, f: impl FnOnce(&mut SocketStack, &mut Inner) -> R) -> R { + f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut()) + } + + pub fn ethernet_address(&self) -> [u8; 6] { + self.with(|_s, i| i.device.ethernet_address()) + } + + pub fn is_link_up(&self) -> bool { + self.with(|_s, i| i.link_up) + } + + pub fn is_config_up(&self) -> bool { + self.with(|_s, i| i.config.is_some()) + } + + pub fn config(&self) -> Option { + self.with(|_s, i| i.config.clone()) + } + + pub async fn run(&self) -> ! { + poll_fn(|cx| { + 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; + self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 }; + 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:"); + + 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) + } + + #[allow(unused)] // used only with dhcp + fn unapply_config(&mut self, s: &mut SocketStack) { + #[cfg(feature = "medium-ethernet")] + let medium = self.device.capabilities().medium; + + 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<'_>, s: &mut SocketStack) { + s.waker.register(cx.waker()); + + let timestamp = instant_to_smoltcp(Instant::now()); + let mut smoldev = DeviceAdapter { + cx: Some(cx), + inner: &mut self.device, + }; + if s.iface.poll(timestamp, &mut smoldev, &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; + } + + // Update link up + let old_link_up = self.link_up; + self.link_up = self.device.link_state(cx) == LinkState::Up; + + // Print when changed + if old_link_up != self.link_up { + info!("link_up = {:?}", self.link_up); + } + + #[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 config = Config { + address: config.address, + gateway: config.router, + dns_servers: config.dns_servers, + }; + self.apply_config(s, config) + } + } + } 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) = 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() { + cx.waker().wake_by_ref(); + } + } + } +} + +fn instant_to_smoltcp(instant: Instant) -> SmolInstant { + SmolInstant::from_millis(instant.as_millis() as i64) +} + +fn instant_from_smoltcp(instant: SmolInstant) -> Instant { + Instant::from_millis(instant.total_millis() as u64) +} diff --git a/embassy-net/src/stack.rs b/embassy-net/src/stack.rs deleted file mode 100644 index 21316e48..00000000 --- a/embassy-net/src/stack.rs +++ /dev/null @@ -1,306 +0,0 @@ -use core::cell::RefCell; -use core::future::{poll_fn, Future}; -use core::task::{Context, Poll}; - -use embassy_sync::waitqueue::WakerRegistration; -use embassy_time::{Instant, Timer}; -use futures::pin_mut; -use heapless::Vec; -#[cfg(feature = "dhcpv4")] -use smoltcp::iface::SocketHandle; -use smoltcp::iface::{Interface, InterfaceBuilder, SocketSet, SocketStorage}; -#[cfg(feature = "medium-ethernet")] -use smoltcp::iface::{Neighbor, NeighborCache, Route, Routes}; -#[cfg(feature = "medium-ethernet")] -use smoltcp::phy::Medium; -#[cfg(feature = "dhcpv4")] -use smoltcp::socket::dhcpv4; -use smoltcp::time::Instant as SmolInstant; -#[cfg(feature = "medium-ethernet")] -use smoltcp::wire::{EthernetAddress, HardwareAddress, IpAddress}; -use smoltcp::wire::{IpCidr, Ipv4Address, Ipv4Cidr}; - -use crate::device::{Device, DeviceAdapter, LinkState}; - -const LOCAL_PORT_MIN: u16 = 1025; -const LOCAL_PORT_MAX: u16 = 65535; - -pub struct StackResources { - addresses: [IpCidr; ADDR], - sockets: [SocketStorage<'static>; SOCK], - - #[cfg(feature = "medium-ethernet")] - routes: [Option<(IpCidr, Route)>; 1], - #[cfg(feature = "medium-ethernet")] - neighbor_cache: [Option<(IpAddress, Neighbor)>; NEIGHBOR], -} - -impl StackResources { - pub fn new() -> Self { - Self { - addresses: [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32); ADDR], - sockets: [SocketStorage::EMPTY; SOCK], - #[cfg(feature = "medium-ethernet")] - routes: [None; 1], - #[cfg(feature = "medium-ethernet")] - neighbor_cache: [None; NEIGHBOR], - } - } -} - -#[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 struct Stack { - pub(crate) socket: RefCell, - inner: RefCell>, -} - -struct Inner { - device: D, - 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, -} - -impl Stack { - pub fn new( - mut 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 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 DeviceAdapter { - inner: &mut device, - cx: None, - }); - - 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: RefCell::new(socket), - inner: RefCell::new(inner), - } - } - - fn with(&self, f: impl FnOnce(&SocketStack, &Inner) -> R) -> R { - f(&*self.socket.borrow(), &*self.inner.borrow()) - } - - fn with_mut(&self, f: impl FnOnce(&mut SocketStack, &mut Inner) -> R) -> R { - f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut()) - } - - pub fn ethernet_address(&self) -> [u8; 6] { - self.with(|_s, i| i.device.ethernet_address()) - } - - pub fn is_link_up(&self) -> bool { - self.with(|_s, i| i.link_up) - } - - pub fn is_config_up(&self) -> bool { - self.with(|_s, i| i.config.is_some()) - } - - pub fn config(&self) -> Option { - self.with(|_s, i| i.config.clone()) - } - - pub async fn run(&self) -> ! { - poll_fn(|cx| { - 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; - self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 }; - 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:"); - - 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) - } - - #[allow(unused)] // used only with dhcp - fn unapply_config(&mut self, s: &mut SocketStack) { - #[cfg(feature = "medium-ethernet")] - let medium = self.device.capabilities().medium; - - 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<'_>, s: &mut SocketStack) { - s.waker.register(cx.waker()); - - let timestamp = instant_to_smoltcp(Instant::now()); - let mut smoldev = DeviceAdapter { - cx: Some(cx), - inner: &mut self.device, - }; - if s.iface.poll(timestamp, &mut smoldev, &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; - } - - // Update link up - let old_link_up = self.link_up; - self.link_up = self.device.link_state(cx) == LinkState::Up; - - // Print when changed - if old_link_up != self.link_up { - info!("link_up = {:?}", self.link_up); - } - - #[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 config = Config { - address: config.address, - gateway: config.router, - dns_servers: config.dns_servers, - }; - self.apply_config(s, config) - } - } - } 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) = 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() { - cx.waker().wake_by_ref(); - } - } - } -} - -fn instant_to_smoltcp(instant: Instant) -> SmolInstant { - SmolInstant::from_millis(instant.as_millis() as i64) -} - -fn instant_from_smoltcp(instant: SmolInstant) -> Instant { - Instant::from_millis(instant.total_millis() as u64) -} diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index 0ed4b81e..0dc8da73 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -8,9 +8,8 @@ use smoltcp::socket::tcp; use smoltcp::time::Duration; use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; -use super::stack::Stack; use crate::device::Device; -use crate::stack::SocketStack; +use crate::{SocketStack, Stack}; #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index 4ddad77d..2f5334df 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -7,8 +7,7 @@ use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::socket::udp::{self, PacketMetadata}; use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; -use super::stack::SocketStack; -use crate::{Device, Stack}; +use crate::{Device, SocketStack, Stack}; #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] From e9219405ca04e23b6543fb841fd97df54cf72f94 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 7 Dec 2022 16:03:03 +0100 Subject: [PATCH 3/7] usb/cdc-ncm: add embassy-net Device implementation. --- embassy-usb/Cargo.toml | 1 + embassy-usb/src/class/cdc_ncm/embassy_net.rs | 449 ++++++++++++++++++ .../src/class/{cdc_ncm.rs => cdc_ncm/mod.rs} | 18 + examples/nrf/Cargo.toml | 2 +- examples/nrf/src/bin/usb_ethernet.rs | 139 +----- examples/rp/Cargo.toml | 2 +- examples/rp/src/bin/usb_ethernet.rs | 140 +----- examples/stm32l5/Cargo.toml | 2 +- examples/stm32l5/src/bin/usb_ethernet.rs | 140 +----- 9 files changed, 522 insertions(+), 371 deletions(-) create mode 100644 embassy-usb/src/class/cdc_ncm/embassy_net.rs rename embassy-usb/src/class/{cdc_ncm.rs => cdc_ncm/mod.rs} (94%) diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index b59ba8a2..1e72ce68 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -19,6 +19,7 @@ default = ["usbd-hid"] embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } embassy-sync = { version = "0.1.0", path = "../embassy-sync" } +embassy-net = { version = "0.1.0", path = "../embassy-net", optional = true } defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } diff --git a/embassy-usb/src/class/cdc_ncm/embassy_net.rs b/embassy-usb/src/class/cdc_ncm/embassy_net.rs new file mode 100644 index 00000000..60bbfd8d --- /dev/null +++ b/embassy-usb/src/class/cdc_ncm/embassy_net.rs @@ -0,0 +1,449 @@ +use core::cell::RefCell; +use core::mem::MaybeUninit; +use core::task::Context; + +use embassy_futures::select::{select, Either}; +use embassy_net::device::{Device as DeviceTrait, DeviceCapabilities, LinkState, Medium}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::waitqueue::WakerRegistration; +use embassy_usb_driver::Driver; + +use super::{CdcNcmClass, Receiver, Sender}; + +pub struct State<'d, const MTU: usize, const N_RX: usize, const N_TX: usize> { + rx: [PacketBuf; N_RX], + tx: [PacketBuf; N_TX], + inner: MaybeUninit>, +} + +impl<'d, const MTU: usize, const N_RX: usize, const N_TX: usize> State<'d, MTU, N_RX, N_TX> { + const NEW_PACKET: PacketBuf = PacketBuf::new(); + + pub const fn new() -> Self { + Self { + rx: [Self::NEW_PACKET; N_RX], + tx: [Self::NEW_PACKET; N_TX], + inner: MaybeUninit::uninit(), + } + } +} + +struct StateInner<'d, const MTU: usize> { + rx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + link_state: Mutex>, +} + +/// State of the LinkState +struct LinkStateState { + state: LinkState, + waker: WakerRegistration, +} + +pub struct Runner<'d, D: Driver<'d>, const MTU: usize> { + tx_usb: Sender<'d, D>, + tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + rx_usb: Receiver<'d, D>, + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + link_state: &'d Mutex>, +} + +impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> { + pub async fn run(mut self) -> ! { + let rx_fut = async move { + loop { + trace!("WAITING for connection"); + self.link_state.lock(|s| { + let s = &mut *s.borrow_mut(); + s.state = LinkState::Down; + s.waker.wake(); + }); + + self.rx_usb.wait_connection().await.unwrap(); + + trace!("Connected"); + self.link_state.lock(|s| { + let s = &mut *s.borrow_mut(); + s.state = LinkState::Up; + s.waker.wake(); + }); + + loop { + let p = self.rx_chan.send().await; + match self.rx_usb.read_packet(&mut p.buf).await { + Ok(n) => { + p.len = n; + self.rx_chan.send_done(); + } + Err(e) => { + warn!("error reading packet: {:?}", e); + break; + } + }; + } + } + }; + let tx_fut = async move { + loop { + let p = self.tx_chan.recv().await; + if let Err(e) = self.tx_usb.write_packet(&p.buf[..p.len]).await { + warn!("Failed to TX packet: {:?}", e); + } + self.tx_chan.recv_done(); + } + }; + match select(rx_fut, tx_fut).await { + Either::First(x) => x, + Either::Second(x) => x, + } + } +} + +impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + pub fn into_embassy_net_device( + self, + state: &'d mut State<'d, MTU, N_RX, N_TX>, + ethernet_address: [u8; 6], + ) -> (Runner<'d, D, MTU>, Device<'d, MTU>) { + let (tx_usb, rx_usb) = self.split(); + + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header + caps.medium = Medium::Ethernet; + + let state = state.inner.write(StateInner { + rx: zerocopy_channel::Channel::new(&mut state.rx[..]), + tx: zerocopy_channel::Channel::new(&mut state.tx[..]), + link_state: Mutex::new(RefCell::new(LinkStateState { + state: LinkState::Down, + waker: WakerRegistration::new(), + })), + }); + + let (rx_sender, rx_receiver) = state.rx.split(); + let (tx_sender, tx_receiver) = state.tx.split(); + + ( + Runner { + tx_usb, + tx_chan: tx_receiver, + rx_usb, + rx_chan: rx_sender, + link_state: &state.link_state, + }, + Device { + caps, + ethernet_address, + link_state: &state.link_state, + rx: rx_receiver, + tx: tx_sender, + }, + ) + } +} + +pub struct PacketBuf { + len: usize, + buf: [u8; MTU], +} + +impl PacketBuf { + pub const fn new() -> Self { + Self { len: 0, buf: [0; MTU] } + } +} + +pub struct Device<'d, const MTU: usize> { + rx: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + link_state: &'d Mutex>, + caps: DeviceCapabilities, + ethernet_address: [u8; 6], +} + +impl<'d, const MTU: usize> DeviceTrait for Device<'d, MTU> { + type RxToken<'a> = RxToken<'a, MTU> where Self: 'a ; + type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + if self.rx.poll_recv(cx).is_ready() && self.tx.poll_send(cx).is_ready() { + Some((RxToken { rx: self.rx.borrow() }, TxToken { tx: self.tx.borrow() })) + } else { + None + } + } + + /// Construct a transmit token. + fn transmit(&mut self, cx: &mut Context) -> Option> { + if self.tx.poll_send(cx).is_ready() { + Some(TxToken { tx: self.tx.borrow() }) + } else { + None + } + } + + /// Get a description of device capabilities. + fn capabilities(&self) -> DeviceCapabilities { + self.caps.clone() + } + + fn ethernet_address(&self) -> [u8; 6] { + self.ethernet_address + } + + fn link_state(&mut self, cx: &mut Context) -> LinkState { + self.link_state.lock(|s| { + let s = &mut *s.borrow_mut(); + s.waker.register(cx.waker()); + s.state + }) + } +} + +pub struct RxToken<'a, const MTU: usize> { + rx: zerocopy_channel::Receiver<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net::device::RxToken for RxToken<'a, MTU> { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.rx.try_recv()); + let r = f(&mut pkt.buf[..pkt.len]); + self.rx.recv_done(); + r + } +} + +pub struct TxToken<'a, const MTU: usize> { + tx: zerocopy_channel::Sender<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net::device::TxToken for TxToken<'a, MTU> { + fn consume(mut self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.tx.try_send()); + let r = f(&mut pkt.buf[..len]); + pkt.len = len; + self.tx.send_done(); + r + } +} + +mod zerocopy_channel { + use core::cell::RefCell; + use core::future::poll_fn; + use core::marker::PhantomData; + use core::task::{Context, Poll}; + + use embassy_sync::blocking_mutex::raw::RawMutex; + use embassy_sync::blocking_mutex::Mutex; + use embassy_sync::waitqueue::WakerRegistration; + + pub struct Channel<'a, M: RawMutex, T> { + buf: *mut T, + phantom: PhantomData<&'a mut T>, + state: Mutex>, + } + + impl<'a, M: RawMutex, T> Channel<'a, M, T> { + pub fn new(buf: &'a mut [T]) -> Self { + let len = buf.len(); + assert!(len != 0); + + Self { + buf: buf.as_mut_ptr(), + phantom: PhantomData, + state: Mutex::new(RefCell::new(State { + len, + front: 0, + back: 0, + full: false, + send_waker: WakerRegistration::new(), + recv_waker: WakerRegistration::new(), + })), + } + } + + pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { + (Sender { channel: self }, Receiver { channel: self }) + } + } + + pub struct Sender<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, + } + + impl<'a, M: RawMutex, T> Sender<'a, M, T> { + pub fn borrow(&mut self) -> Sender<'_, M, T> { + Sender { channel: self.channel } + } + + pub fn try_send(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + pub fn poll_send(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.recv_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + pub async fn send(&mut self) -> &mut T { + let i = poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Poll::Ready(i), + None => { + s.recv_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + .await; + unsafe { &mut *self.channel.buf.add(i) } + } + + pub fn send_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().push_done()) + } + } + pub struct Receiver<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, + } + + impl<'a, M: RawMutex, T> Receiver<'a, M, T> { + pub fn borrow(&mut self) -> Receiver<'_, M, T> { + Receiver { channel: self.channel } + } + + pub fn try_recv(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + pub fn poll_recv(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + pub async fn recv(&mut self) -> &mut T { + let i = poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Poll::Ready(i), + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + .await; + unsafe { &mut *self.channel.buf.add(i) } + } + + pub fn recv_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().pop_done()) + } + } + + struct State { + len: usize, + + /// Front index. Always 0..=(N-1) + front: usize, + /// Back index. Always 0..=(N-1). + back: usize, + + /// Used to distinguish "empty" and "full" cases when `front == back`. + /// May only be `true` if `front == back`, always `false` otherwise. + full: bool, + + send_waker: WakerRegistration, + recv_waker: WakerRegistration, + } + + impl State { + fn increment(&self, i: usize) -> usize { + if i + 1 == self.len { + 0 + } else { + i + 1 + } + } + + fn is_full(&self) -> bool { + self.full + } + + fn is_empty(&self) -> bool { + self.front == self.back && !self.full + } + + fn push_index(&mut self) -> Option { + match self.is_full() { + true => None, + false => Some(self.back), + } + } + + fn push_done(&mut self) { + assert!(!self.is_full()); + self.back = self.increment(self.back); + if self.back == self.front { + self.full = true; + } + self.send_waker.wake(); + } + + fn pop_index(&mut self) -> Option { + match self.is_empty() { + true => None, + false => Some(self.front), + } + } + + fn pop_done(&mut self) { + assert!(!self.is_empty()); + self.front = self.increment(self.front); + self.full = false; + self.recv_waker.wake(); + } + } +} diff --git a/embassy-usb/src/class/cdc_ncm.rs b/embassy-usb/src/class/cdc_ncm/mod.rs similarity index 94% rename from embassy-usb/src/class/cdc_ncm.rs rename to embassy-usb/src/class/cdc_ncm/mod.rs index a39b87e9..2ee47f68 100644 --- a/embassy-usb/src/class/cdc_ncm.rs +++ b/embassy-usb/src/class/cdc_ncm/mod.rs @@ -1,3 +1,18 @@ +/// CDC-NCM, aka Ethernet over USB. +/// +/// # Compatibility +/// +/// Windows: NOT supported in Windows 10. Supported in Windows 11. +/// +/// Linux: Well-supported since forever. +/// +/// Android: Support for CDC-NCM is spotty and varies across manufacturers. +/// +/// - On Pixel 4a, it refused to work on Android 11, worked on Android 12. +/// - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), +/// it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. +/// This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 +/// and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 use core::intrinsics::copy_nonoverlapping; use core::mem::{size_of, MaybeUninit}; @@ -6,6 +21,9 @@ use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; use crate::types::*; use crate::Builder; +#[cfg(feature = "embassy-net")] +pub mod embassy_net; + /// This should be used as `device_class` when building the `UsbDevice`. pub const USB_CLASS_CDC: u8 = 0x02; diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index 8b95ac3a..a980a505 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -16,7 +16,7 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"], optional = true } -embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "embassy-net"], optional = true } embedded-io = "0.4.0" embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx126x", "time", "defmt"], optional = true } diff --git a/examples/nrf/src/bin/usb_ethernet.rs b/examples/nrf/src/bin/usb_ethernet.rs index de93a2b4..e5f70452 100644 --- a/examples/nrf/src/bin/usb_ethernet.rs +++ b/examples/nrf/src/bin/usb_ethernet.rs @@ -3,19 +3,16 @@ #![feature(type_alias_impl_trait)] use core::mem; -use core::sync::atomic::{AtomicBool, Ordering}; -use core::task::Waker; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{PacketBox, PacketBoxExt, PacketBuf, Stack, StackResources}; +use embassy_net::{Stack, StackResources}; use embassy_nrf::rng::Rng; use embassy_nrf::usb::{Driver, PowerUsb}; use embassy_nrf::{interrupt, pac, peripherals}; -use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; -use embassy_sync::channel::Channel; -use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; use embassy_usb::{Builder, Config, UsbDevice}; use embedded_io::asynch::Write; use static_cell::StaticCell; @@ -27,56 +24,25 @@ macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) + let (x,) = STATIC_CELL.init(($val,)); + x }}; } +const MTU: usize = 1514; + #[embassy_executor::task] async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { device.run().await } #[embassy_executor::task] -async fn usb_ncm_rx_task(mut class: Receiver<'static, MyDriver>) { - loop { - warn!("WAITING for connection"); - LINK_UP.store(false, Ordering::Relaxed); - - class.wait_connection().await.unwrap(); - - warn!("Connected"); - LINK_UP.store(true, Ordering::Relaxed); - - loop { - let mut p = unwrap!(PacketBox::new(embassy_net::Packet::new())); - let n = match class.read_packet(&mut p[..]).await { - Ok(n) => n, - Err(e) => { - warn!("error reading packet: {:?}", e); - break; - } - }; - - let buf = p.slice(0..n); - if RX_CHANNEL.try_send(buf).is_err() { - warn!("Failed pushing rx'd packet to channel."); - } - } - } +async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { + class.run().await } #[embassy_executor::task] -async fn usb_ncm_tx_task(mut class: Sender<'static, MyDriver>) { - loop { - let pkt = TX_CHANNEL.recv().await; - if let Err(e) = class.write_packet(&pkt[..]).await { - warn!("Failed to TX packet: {:?}", e); - } - } -} - -#[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { +async fn net_task(stack: &'static Stack>) -> ! { stack.run().await } @@ -108,55 +74,32 @@ async fn main(spawner: Spawner) { config.device_sub_class = 0x02; config.device_protocol = 0x01; - struct Resources { - device_descriptor: [u8; 256], - config_descriptor: [u8; 256], - bos_descriptor: [u8; 256], - control_buf: [u8; 128], - serial_state: State<'static>, - } - let res: &mut Resources = singleton!(Resources { - device_descriptor: [0; 256], - config_descriptor: [0; 256], - bos_descriptor: [0; 256], - control_buf: [0; 128], - serial_state: State::new(), - }); - // Create embassy-usb DeviceBuilder using the driver and config. let mut builder = Builder::new( driver, config, - &mut res.device_descriptor, - &mut res.config_descriptor, - &mut res.bos_descriptor, - &mut res.control_buf, + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 128])[..], None, ); - // WARNINGS for Android ethernet tethering: - // - On Pixel 4a, it refused to work on Android 11, worked on Android 12. - // - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), - // it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. - // This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 - // and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 - // Our MAC addr. let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; // Create classes on the builder. - let class = CdcNcmClass::new(&mut builder, &mut res.serial_state, host_mac_addr, 64); + let class = CdcNcmClass::new(&mut builder, singleton!(State::new()), host_mac_addr, 64); // Build the builder. let usb = builder.build(); unwrap!(spawner.spawn(usb_task(usb))); - let (tx, rx) = class.split(); - unwrap!(spawner.spawn(usb_ncm_rx_task(rx))); - unwrap!(spawner.spawn(usb_ncm_tx_task(tx))); + let (runner, device) = class.into_embassy_net_device::(singleton!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); let config = embassy_net::ConfigStrategy::Dhcp; //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { @@ -172,7 +115,6 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - let device = Device { mac_addr: our_mac_addr }; let stack = &*singleton!(Stack::new( device, config, @@ -225,50 +167,3 @@ async fn main(spawner: Spawner) { } } } - -static TX_CHANNEL: Channel = Channel::new(); -static RX_CHANNEL: Channel = Channel::new(); -static LINK_UP: AtomicBool = AtomicBool::new(false); - -struct Device { - mac_addr: [u8; 6], -} - -impl embassy_net::Device for Device { - fn register_waker(&mut self, waker: &Waker) { - // loopy loopy wakey wakey - waker.wake_by_ref() - } - - fn link_state(&mut self) -> embassy_net::LinkState { - match LINK_UP.load(Ordering::Relaxed) { - true => embassy_net::LinkState::Up, - false => embassy_net::LinkState::Down, - } - } - - fn capabilities(&self) -> embassy_net::DeviceCapabilities { - let mut caps = embassy_net::DeviceCapabilities::default(); - caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header - caps.medium = embassy_net::Medium::Ethernet; - caps - } - - fn is_transmit_ready(&mut self) -> bool { - true - } - - fn transmit(&mut self, pkt: PacketBuf) { - if TX_CHANNEL.try_send(pkt).is_err() { - warn!("TX failed") - } - } - - fn receive<'a>(&mut self) -> Option { - RX_CHANNEL.try_recv().ok() - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index dfbc2642..31a08cfb 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -10,7 +10,7 @@ embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["de embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio", "critical-section-impl"] } -embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "embassy-net"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" } diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs index 1057fe7f..d0aec874 100644 --- a/examples/rp/src/bin/usb_ethernet.rs +++ b/examples/rp/src/bin/usb_ethernet.rs @@ -2,18 +2,14 @@ #![no_main] #![feature(type_alias_impl_trait)] -use core::sync::atomic::{AtomicBool, Ordering}; -use core::task::Waker; - use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{PacketBox, PacketBoxExt, PacketBuf, Stack, StackResources}; +use embassy_net::{Stack, StackResources}; use embassy_rp::usb::Driver; use embassy_rp::{interrupt, peripherals}; -use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; -use embassy_sync::channel::Channel; -use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; use embassy_usb::{Builder, Config, UsbDevice}; use embedded_io::asynch::Write; use static_cell::StaticCell; @@ -25,56 +21,25 @@ macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) + let (x,) = STATIC_CELL.init(($val,)); + x }}; } +const MTU: usize = 1514; + #[embassy_executor::task] async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { device.run().await } #[embassy_executor::task] -async fn usb_ncm_rx_task(mut class: Receiver<'static, MyDriver>) { - loop { - warn!("WAITING for connection"); - LINK_UP.store(false, Ordering::Relaxed); - - class.wait_connection().await.unwrap(); - - warn!("Connected"); - LINK_UP.store(true, Ordering::Relaxed); - - loop { - let mut p = unwrap!(PacketBox::new(embassy_net::Packet::new())); - let n = match class.read_packet(&mut p[..]).await { - Ok(n) => n, - Err(e) => { - warn!("error reading packet: {:?}", e); - break; - } - }; - - let buf = p.slice(0..n); - if RX_CHANNEL.try_send(buf).is_err() { - warn!("Failed pushing rx'd packet to channel."); - } - } - } +async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { + class.run().await } #[embassy_executor::task] -async fn usb_ncm_tx_task(mut class: Sender<'static, MyDriver>) { - loop { - let pkt = TX_CHANNEL.recv().await; - if let Err(e) = class.write_packet(&pkt[..]).await { - warn!("Failed to TX packet: {:?}", e); - } - } -} - -#[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { +async fn net_task(stack: &'static Stack>) -> ! { stack.run().await } @@ -100,55 +65,32 @@ async fn main(spawner: Spawner) { config.device_sub_class = 0x02; config.device_protocol = 0x01; - struct Resources { - device_descriptor: [u8; 256], - config_descriptor: [u8; 256], - bos_descriptor: [u8; 256], - control_buf: [u8; 128], - serial_state: State<'static>, - } - let res: &mut Resources = singleton!(Resources { - device_descriptor: [0; 256], - config_descriptor: [0; 256], - bos_descriptor: [0; 256], - control_buf: [0; 128], - serial_state: State::new(), - }); - // Create embassy-usb DeviceBuilder using the driver and config. let mut builder = Builder::new( driver, config, - &mut res.device_descriptor, - &mut res.config_descriptor, - &mut res.bos_descriptor, - &mut res.control_buf, + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 128])[..], None, ); - // WARNINGS for Android ethernet tethering: - // - On Pixel 4a, it refused to work on Android 11, worked on Android 12. - // - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), - // it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. - // This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 - // and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 - // Our MAC addr. let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; // Create classes on the builder. - let class = CdcNcmClass::new(&mut builder, &mut res.serial_state, host_mac_addr, 64); + let class = CdcNcmClass::new(&mut builder, singleton!(State::new()), host_mac_addr, 64); // Build the builder. let usb = builder.build(); unwrap!(spawner.spawn(usb_task(usb))); - let (tx, rx) = class.split(); - unwrap!(spawner.spawn(usb_ncm_rx_task(rx))); - unwrap!(spawner.spawn(usb_ncm_tx_task(tx))); + let (runner, device) = class.into_embassy_net_device::(singleton!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); let config = embassy_net::ConfigStrategy::Dhcp; //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { @@ -161,7 +103,6 @@ async fn main(spawner: Spawner) { let seed = 1234; // guaranteed random, chosen by a fair dice roll // Init network stack - let device = Device { mac_addr: our_mac_addr }; let stack = &*singleton!(Stack::new( device, config, @@ -214,50 +155,3 @@ async fn main(spawner: Spawner) { } } } - -static TX_CHANNEL: Channel = Channel::new(); -static RX_CHANNEL: Channel = Channel::new(); -static LINK_UP: AtomicBool = AtomicBool::new(false); - -struct Device { - mac_addr: [u8; 6], -} - -impl embassy_net::Device for Device { - fn register_waker(&mut self, waker: &Waker) { - // loopy loopy wakey wakey - waker.wake_by_ref() - } - - fn link_state(&mut self) -> embassy_net::LinkState { - match LINK_UP.load(Ordering::Relaxed) { - true => embassy_net::LinkState::Up, - false => embassy_net::LinkState::Down, - } - } - - fn capabilities(&self) -> embassy_net::DeviceCapabilities { - let mut caps = embassy_net::DeviceCapabilities::default(); - caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header - caps.medium = embassy_net::Medium::Ethernet; - caps - } - - fn is_transmit_ready(&mut self) -> bool { - true - } - - fn transmit(&mut self, pkt: PacketBuf) { - if TX_CHANNEL.try_send(pkt).is_err() { - warn!("TX failed") - } - } - - fn receive<'a>(&mut self) -> Option { - RX_CHANNEL.try_recv().ok() - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index 73ad5078..bb851531 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -11,7 +11,7 @@ embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["de embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "unstable-traits", "memory-x"] } -embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "embassy-net"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } usbd-hid = "0.6.0" diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs index 4f36d3f5..b49329ea 100644 --- a/examples/stm32l5/src/bin/usb_ethernet.rs +++ b/examples/stm32l5/src/bin/usb_ethernet.rs @@ -2,20 +2,16 @@ #![no_main] #![feature(type_alias_impl_trait)] -use core::sync::atomic::{AtomicBool, Ordering}; -use core::task::Waker; - use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{PacketBox, PacketBoxExt, PacketBuf, Stack, StackResources}; +use embassy_net::{Stack, StackResources}; use embassy_stm32::rcc::*; use embassy_stm32::rng::Rng; use embassy_stm32::usb::Driver; use embassy_stm32::{interrupt, Config}; -use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; -use embassy_sync::channel::Channel; -use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; use embassy_usb::{Builder, UsbDevice}; use embedded_io::asynch::Write; use rand_core::RngCore; @@ -28,56 +24,25 @@ macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) + let (x,) = STATIC_CELL.init(($val,)); + x }}; } +const MTU: usize = 1514; + #[embassy_executor::task] async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { device.run().await } #[embassy_executor::task] -async fn usb_ncm_rx_task(mut class: Receiver<'static, MyDriver>) { - loop { - warn!("WAITING for connection"); - LINK_UP.store(false, Ordering::Relaxed); - - class.wait_connection().await.unwrap(); - - warn!("Connected"); - LINK_UP.store(true, Ordering::Relaxed); - - loop { - let mut p = unwrap!(PacketBox::new(embassy_net::Packet::new())); - let n = match class.read_packet(&mut p[..]).await { - Ok(n) => n, - Err(e) => { - warn!("error reading packet: {:?}", e); - break; - } - }; - - let buf = p.slice(0..n); - if RX_CHANNEL.try_send(buf).is_err() { - warn!("Failed pushing rx'd packet to channel."); - } - } - } +async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { + class.run().await } #[embassy_executor::task] -async fn usb_ncm_tx_task(mut class: Sender<'static, MyDriver>) { - loop { - let pkt = TX_CHANNEL.recv().await; - if let Err(e) = class.write_packet(&pkt[..]).await { - warn!("Failed to TX packet: {:?}", e); - } - } -} - -#[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { +async fn net_task(stack: &'static Stack>) -> ! { stack.run().await } @@ -106,55 +71,32 @@ async fn main(spawner: Spawner) { config.device_sub_class = 0x02; config.device_protocol = 0x01; - struct Resources { - device_descriptor: [u8; 256], - config_descriptor: [u8; 256], - bos_descriptor: [u8; 256], - control_buf: [u8; 128], - serial_state: State<'static>, - } - let res: &mut Resources = singleton!(Resources { - device_descriptor: [0; 256], - config_descriptor: [0; 256], - bos_descriptor: [0; 256], - control_buf: [0; 128], - serial_state: State::new(), - }); - // Create embassy-usb DeviceBuilder using the driver and config. let mut builder = Builder::new( driver, config, - &mut res.device_descriptor, - &mut res.config_descriptor, - &mut res.bos_descriptor, - &mut res.control_buf, + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 128])[..], None, ); - // WARNINGS for Android ethernet tethering: - // - On Pixel 4a, it refused to work on Android 11, worked on Android 12. - // - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), - // it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. - // This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 - // and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 - // Our MAC addr. let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; // Create classes on the builder. - let class = CdcNcmClass::new(&mut builder, &mut res.serial_state, host_mac_addr, 64); + let class = CdcNcmClass::new(&mut builder, singleton!(State::new()), host_mac_addr, 64); // Build the builder. let usb = builder.build(); unwrap!(spawner.spawn(usb_task(usb))); - let (tx, rx) = class.split(); - unwrap!(spawner.spawn(usb_ncm_rx_task(rx))); - unwrap!(spawner.spawn(usb_ncm_tx_task(tx))); + let (runner, device) = class.into_embassy_net_device::(singleton!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); let config = embassy_net::ConfigStrategy::Dhcp; //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { @@ -168,7 +110,6 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - let device = Device { mac_addr: our_mac_addr }; let stack = &*singleton!(Stack::new( device, config, @@ -221,50 +162,3 @@ async fn main(spawner: Spawner) { } } } - -static TX_CHANNEL: Channel = Channel::new(); -static RX_CHANNEL: Channel = Channel::new(); -static LINK_UP: AtomicBool = AtomicBool::new(false); - -struct Device { - mac_addr: [u8; 6], -} - -impl embassy_net::Device for Device { - fn register_waker(&mut self, waker: &Waker) { - // loopy loopy wakey wakey - waker.wake_by_ref() - } - - fn link_state(&mut self) -> embassy_net::LinkState { - match LINK_UP.load(Ordering::Relaxed) { - true => embassy_net::LinkState::Up, - false => embassy_net::LinkState::Down, - } - } - - fn capabilities(&self) -> embassy_net::DeviceCapabilities { - let mut caps = embassy_net::DeviceCapabilities::default(); - caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header - caps.medium = embassy_net::Medium::Ethernet; - caps - } - - fn is_transmit_ready(&mut self) -> bool { - true - } - - fn transmit(&mut self, pkt: PacketBuf) { - if TX_CHANNEL.try_send(pkt).is_err() { - warn!("TX failed") - } - } - - fn receive<'a>(&mut self) -> Option { - RX_CHANNEL.try_recv().ok() - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} From 8f3065210927b6e92f6d727741189155b2eab91e Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 9 Dec 2022 03:18:45 +0100 Subject: [PATCH 4/7] stm32/eth_v1: update to new embassy-net trait, remove PeripheralMutex. --- embassy-stm32/src/eth/mod.rs | 121 +++++++- embassy-stm32/src/eth/v1/descriptors.rs | 21 -- embassy-stm32/src/eth/v1/mod.rs | 391 ++++++++++-------------- embassy-stm32/src/eth/v1/rx_desc.rs | 210 +++++-------- embassy-stm32/src/eth/v1/tx_desc.rs | 157 +++------- examples/stm32f7/src/bin/eth.rs | 50 +-- 6 files changed, 424 insertions(+), 526 deletions(-) delete mode 100644 embassy-stm32/src/eth/v1/descriptors.rs diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs index 76a3dfab..fd1b4853 100644 --- a/embassy-stm32/src/eth/mod.rs +++ b/embassy-stm32/src/eth/mod.rs @@ -1,14 +1,131 @@ #![macro_use] +#![cfg_attr(not(feature = "embassy-net"), allow(unused))] -#[cfg(feature = "net")] #[cfg_attr(any(eth_v1a, eth_v1b, eth_v1c), path = "v1/mod.rs")] #[cfg_attr(eth_v2, path = "v2/mod.rs")] mod _version; pub mod generic_smi; -#[cfg(feature = "net")] pub use _version::*; +use embassy_sync::waitqueue::AtomicWaker; +#[allow(unused)] +const MTU: usize = 1514; +const TX_BUFFER_SIZE: usize = 1514; +const RX_BUFFER_SIZE: usize = 1536; + +#[repr(C, align(8))] +#[derive(Copy, Clone)] +pub(crate) struct Packet([u8; N]); + +pub struct PacketQueue { + tx_desc: [TDes; TX], + rx_desc: [RDes; RX], + tx_buf: [Packet; TX], + rx_buf: [Packet; RX], +} + +impl PacketQueue { + pub const fn new() -> Self { + const NEW_TDES: TDes = TDes::new(); + const NEW_RDES: RDes = RDes::new(); + Self { + tx_desc: [NEW_TDES; TX], + rx_desc: [NEW_RDES; RX], + tx_buf: [Packet([0; TX_BUFFER_SIZE]); TX], + rx_buf: [Packet([0; RX_BUFFER_SIZE]); RX], + } + } +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +#[cfg(feature = "embassy-net")] +mod embassy_net_impl { + use core::task::Context; + + use embassy_net::device::{Device, DeviceCapabilities, LinkState}; + + use super::*; + + impl<'d, T: Instance, P: PHY> Device for Ethernet<'d, T, P> { + type RxToken<'a> = RxToken<'a, 'd> where Self: 'a; + type TxToken<'a> = TxToken<'a, 'd> where Self: 'a; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + WAKER.register(cx.waker()); + if self.rx.available().is_some() && self.tx.available().is_some() { + Some((RxToken { rx: &mut self.rx }, TxToken { tx: &mut self.tx })) + } else { + None + } + } + + fn transmit(&mut self, cx: &mut Context) -> Option> { + WAKER.register(cx.waker()); + if self.tx.available().is_some() { + Some(TxToken { tx: &mut self.tx }) + } else { + None + } + } + + fn capabilities(&self) -> DeviceCapabilities { + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = MTU; + caps.max_burst_size = Some(self.tx.len()); + caps + } + + fn link_state(&mut self, cx: &mut Context) -> LinkState { + // TODO: wake cx.waker on link state change + cx.waker().wake_by_ref(); + if P::poll_link(self) { + LinkState::Up + } else { + LinkState::Down + } + } + + fn ethernet_address(&self) -> [u8; 6] { + self.mac_addr + } + } + + pub struct RxToken<'a, 'd> { + rx: &'a mut RDesRing<'d>, + } + + impl<'a, 'd> embassy_net::device::RxToken for RxToken<'a, 'd> { + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.rx.available()); + let r = f(pkt); + self.rx.pop_packet(); + r + } + } + + pub struct TxToken<'a, 'd> { + tx: &'a mut TDesRing<'d>, + } + + impl<'a, 'd> embassy_net::device::TxToken for TxToken<'a, 'd> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.tx.available()); + let r = f(&mut pkt[..len]); + self.tx.transmit(len); + r + } + } +} /// Station Management Interface (SMI) on an ethernet PHY /// /// # Safety diff --git a/embassy-stm32/src/eth/v1/descriptors.rs b/embassy-stm32/src/eth/v1/descriptors.rs deleted file mode 100644 index 25f21ce1..00000000 --- a/embassy-stm32/src/eth/v1/descriptors.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::eth::_version::rx_desc::RDesRing; -use crate::eth::_version::tx_desc::TDesRing; - -pub struct DescriptorRing { - pub(crate) tx: TDesRing, - pub(crate) rx: RDesRing, -} - -impl DescriptorRing { - pub const fn new() -> Self { - Self { - tx: TDesRing::new(), - rx: RDesRing::new(), - } - } - - pub fn init(&mut self) { - self.tx.init(); - self.rx.init(); - } -} diff --git a/embassy-stm32/src/eth/v1/mod.rs b/embassy-stm32/src/eth/v1/mod.rs index 38629a93..de36d3da 100644 --- a/embassy-stm32/src/eth/v1/mod.rs +++ b/embassy-stm32/src/eth/v1/mod.rs @@ -1,14 +1,17 @@ // The v1c ethernet driver was ported to embassy from the awesome stm32-eth project (https://github.com/stm32-rs/stm32-eth). -use core::marker::PhantomData; +mod rx_desc; +mod tx_desc; + use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; -use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; +use embassy_cortex_m::interrupt::InterruptExt; use embassy_hal_common::{into_ref, PeripheralRef}; -use embassy_net::{Device, DeviceCapabilities, LinkState, PacketBuf, MTU}; -use embassy_sync::waitqueue::AtomicWaker; +use stm32_metapac::eth::vals::{Apcs, Cr, Dm, DmaomrSr, Fes, Ftf, Ifg, MbProgress, Mw, Pbl, Rsf, St, Tsf}; +pub(crate) use self::rx_desc::{RDes, RDesRing}; +pub(crate) use self::tx_desc::{TDes, TDesRing}; +use super::*; use crate::gpio::sealed::{AFType, Pin as __GpioPin}; use crate::gpio::{AnyPin, Speed}; #[cfg(eth_v1a)] @@ -18,29 +21,16 @@ use crate::pac::SYSCFG; use crate::pac::{ETH, RCC}; use crate::Peripheral; -mod descriptors; -mod rx_desc; -mod tx_desc; +pub struct Ethernet<'d, T: Instance, P: PHY> { + _peri: PeripheralRef<'d, T>, + pub(crate) tx: TDesRing<'d>, + pub(crate) rx: RDesRing<'d>, -use descriptors::DescriptorRing; -use stm32_metapac::eth::vals::{Apcs, Cr, Dm, DmaomrSr, Fes, Ftf, Ifg, MbProgress, Mw, Pbl, Rsf, St, Tsf}; - -use super::*; - -pub struct State<'d, T: Instance, const TX: usize, const RX: usize>(StateStorage>); -impl<'d, T: Instance, const TX: usize, const RX: usize> State<'d, T, TX, RX> { - pub const fn new() -> Self { - Self(StateStorage::new()) - } -} - -pub struct Ethernet<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> { - state: PeripheralMutex<'d, Inner<'d, T, TX, RX>>, pins: [PeripheralRef<'d, AnyPin>; 9], _phy: P, clock_range: Cr, phy_addr: u8, - mac_addr: [u8; 6], + pub(crate) mac_addr: [u8; 6], } #[cfg(eth_v1a)] @@ -82,10 +72,10 @@ macro_rules! config_pins { }; } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, P, TX, RX> { +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { /// safety: the returned instance is not leak-safe - pub unsafe fn new( - state: &'d mut State<'d, T, TX, RX>, + pub fn new( + queue: &'d mut PacketQueue, peri: impl Peripheral

+ 'd, interrupt: impl Peripheral

+ 'd, ref_clk: impl Peripheral

> + 'd, @@ -101,134 +91,131 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, mac_addr: [u8; 6], phy_addr: u8, ) -> Self { - into_ref!(interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + into_ref!(peri, interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - // Enable the necessary Clocks - // NOTE(unsafe) We have exclusive access to the registers - #[cfg(eth_v1a)] - critical_section::with(|_| { - RCC.apb2enr().modify(|w| w.set_afioen(true)); + unsafe { + // Enable the necessary Clocks + // NOTE(unsafe) We have exclusive access to the registers + #[cfg(eth_v1a)] + critical_section::with(|_| { + RCC.apb2enr().modify(|w| w.set_afioen(true)); - // Select RMII (Reduced Media Independent Interface) - // Must be done prior to enabling peripheral clock - AFIO.mapr().modify(|w| w.set_mii_rmii_sel(true)); + // Select RMII (Reduced Media Independent Interface) + // Must be done prior to enabling peripheral clock + AFIO.mapr().modify(|w| w.set_mii_rmii_sel(true)); - RCC.ahbenr().modify(|w| { - w.set_ethen(true); - w.set_ethtxen(true); - w.set_ethrxen(true); - }); - }); - - #[cfg(any(eth_v1b, eth_v1c))] - critical_section::with(|_| { - RCC.apb2enr().modify(|w| w.set_syscfgen(true)); - RCC.ahb1enr().modify(|w| { - w.set_ethen(true); - w.set_ethtxen(true); - w.set_ethrxen(true); + RCC.ahbenr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); }); - // RMII (Reduced Media Independent Interface) - SYSCFG.pmc().modify(|w| w.set_mii_rmii_sel(true)); - }); + #[cfg(any(eth_v1b, eth_v1c))] + critical_section::with(|_| { + RCC.apb2enr().modify(|w| w.set_syscfgen(true)); + RCC.ahb1enr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); - #[cfg(eth_v1a)] - { - config_in_pins!(ref_clk, rx_d0, rx_d1); - config_af_pins!(mdio, mdc, tx_d0, tx_d1, tx_en); - } + // RMII (Reduced Media Independent Interface) + SYSCFG.pmc().modify(|w| w.set_mii_rmii_sel(true)); + }); - #[cfg(any(eth_v1b, eth_v1c))] - config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - - // NOTE(unsafe) We are ourselves not leak-safe. - let state = PeripheralMutex::new(interrupt, &mut state.0, || Inner::new(peri)); - - // NOTE(unsafe) We have exclusive access to the registers - let dma = ETH.ethernet_dma(); - let mac = ETH.ethernet_mac(); - - // Reset and wait - dma.dmabmr().modify(|w| w.set_sr(true)); - while dma.dmabmr().read().sr() {} - - mac.maccr().modify(|w| { - w.set_ifg(Ifg::IFG96); // inter frame gap 96 bit times - w.set_apcs(Apcs::STRIP); // automatic padding and crc stripping - w.set_fes(Fes::FES100); // fast ethernet speed - w.set_dm(Dm::FULLDUPLEX); // full duplex - // TODO: Carrier sense ? ECRSFD - }); - - // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, - // so the LR write must happen after the HR write. - mac.maca0hr() - .modify(|w| w.set_maca0h(u16::from(mac_addr[4]) | (u16::from(mac_addr[5]) << 8))); - mac.maca0lr().write(|w| { - w.set_maca0l( - u32::from(mac_addr[0]) - | (u32::from(mac_addr[1]) << 8) - | (u32::from(mac_addr[2]) << 16) - | (u32::from(mac_addr[3]) << 24), - ) - }); - - // pause time - mac.macfcr().modify(|w| w.set_pt(0x100)); - - // Transfer and Forward, Receive and Forward - dma.dmaomr().modify(|w| { - w.set_tsf(Tsf::STOREFORWARD); - w.set_rsf(Rsf::STOREFORWARD); - }); - - dma.dmabmr().modify(|w| { - w.set_pbl(Pbl::PBL32) // programmable burst length - 32 ? - }); - - // TODO MTU size setting not found for v1 ethernet, check if correct - - // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called - let hclk = crate::rcc::get_freqs().ahb1; - let hclk_mhz = hclk.0 / 1_000_000; - - // Set the MDC clock frequency in the range 1MHz - 2.5MHz - let clock_range = match hclk_mhz { - 0..=24 => panic!("Invalid HCLK frequency - should be at least 25 MHz."), - 25..=34 => Cr::CR_20_35, // Divide by 16 - 35..=59 => Cr::CR_35_60, // Divide by 26 - 60..=99 => Cr::CR_60_100, // Divide by 42 - 100..=149 => Cr::CR_100_150, // Divide by 62 - 150..=216 => Cr::CR_150_168, // Divide by 102 - _ => { - panic!("HCLK results in MDC clock > 2.5MHz even for the highest CSR clock divider") + #[cfg(eth_v1a)] + { + config_in_pins!(ref_clk, rx_d0, rx_d1); + config_af_pins!(mdio, mdc, tx_d0, tx_d1, tx_en); } - }; - let pins = [ - ref_clk.map_into(), - mdio.map_into(), - mdc.map_into(), - crs.map_into(), - rx_d0.map_into(), - rx_d1.map_into(), - tx_d0.map_into(), - tx_d1.map_into(), - tx_en.map_into(), - ]; + #[cfg(any(eth_v1b, eth_v1c))] + config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - let mut this = Self { - state, - pins, - _phy: phy, - clock_range, - phy_addr, - mac_addr, - }; + // NOTE(unsafe) We have exclusive access to the registers + let dma = ETH.ethernet_dma(); + let mac = ETH.ethernet_mac(); - this.state.with(|s| { - s.desc_ring.init(); + // Reset and wait + dma.dmabmr().modify(|w| w.set_sr(true)); + while dma.dmabmr().read().sr() {} + + mac.maccr().modify(|w| { + w.set_ifg(Ifg::IFG96); // inter frame gap 96 bit times + w.set_apcs(Apcs::STRIP); // automatic padding and crc stripping + w.set_fes(Fes::FES100); // fast ethernet speed + w.set_dm(Dm::FULLDUPLEX); // full duplex + // TODO: Carrier sense ? ECRSFD + }); + + // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, + // so the LR write must happen after the HR write. + mac.maca0hr() + .modify(|w| w.set_maca0h(u16::from(mac_addr[4]) | (u16::from(mac_addr[5]) << 8))); + mac.maca0lr().write(|w| { + w.set_maca0l( + u32::from(mac_addr[0]) + | (u32::from(mac_addr[1]) << 8) + | (u32::from(mac_addr[2]) << 16) + | (u32::from(mac_addr[3]) << 24), + ) + }); + + // pause time + mac.macfcr().modify(|w| w.set_pt(0x100)); + + // Transfer and Forward, Receive and Forward + dma.dmaomr().modify(|w| { + w.set_tsf(Tsf::STOREFORWARD); + w.set_rsf(Rsf::STOREFORWARD); + }); + + dma.dmabmr().modify(|w| { + w.set_pbl(Pbl::PBL32) // programmable burst length - 32 ? + }); + + // TODO MTU size setting not found for v1 ethernet, check if correct + + // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called + let hclk = crate::rcc::get_freqs().ahb1; + let hclk_mhz = hclk.0 / 1_000_000; + + // Set the MDC clock frequency in the range 1MHz - 2.5MHz + let clock_range = match hclk_mhz { + 0..=24 => panic!("Invalid HCLK frequency - should be at least 25 MHz."), + 25..=34 => Cr::CR_20_35, // Divide by 16 + 35..=59 => Cr::CR_35_60, // Divide by 26 + 60..=99 => Cr::CR_60_100, // Divide by 42 + 100..=149 => Cr::CR_100_150, // Divide by 62 + 150..=216 => Cr::CR_150_168, // Divide by 102 + _ => { + panic!("HCLK results in MDC clock > 2.5MHz even for the highest CSR clock divider") + } + }; + + let pins = [ + ref_clk.map_into(), + mdio.map_into(), + mdc.map_into(), + crs.map_into(), + rx_d0.map_into(), + rx_d1.map_into(), + tx_d0.map_into(), + tx_d1.map_into(), + tx_en.map_into(), + ]; + + let mut this = Self { + _peri: peri, + pins, + _phy: phy, + clock_range, + phy_addr, + mac_addr, + tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf), + rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf), + }; fence(Ordering::SeqCst); @@ -245,23 +232,45 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, w.set_sr(DmaomrSr::STARTED); // start receiving channel }); + this.rx.demand_poll(); + // Enable interrupts dma.dmaier().modify(|w| { w.set_nise(true); w.set_rie(true); w.set_tie(true); }); - }); - P::phy_reset(&mut this); - P::phy_init(&mut this); - this + P::phy_reset(&mut this); + P::phy_init(&mut this); + + interrupt.set_handler(Self::on_interrupt); + interrupt.enable(); + + this + } + } + + fn on_interrupt(_cx: *mut ()) { + WAKER.wake(); + + // TODO: Check and clear more flags + unsafe { + let dma = ETH.ethernet_dma(); + + dma.dmasr().modify(|w| { + w.set_ts(true); + w.set_rs(true); + w.set_nis(true); + }); + // Delay two peripheral's clock + dma.dmasr().read(); + dma.dmasr().read(); + } } } -unsafe impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> StationManagement - for Ethernet<'d, T, P, TX, RX> -{ +unsafe impl<'d, T: Instance, P: PHY> StationManagement for Ethernet<'d, T, P> { fn smi_read(&mut self, reg: u8) -> u16 { // NOTE(unsafe) These registers aren't used in the interrupt and we have `&mut self` unsafe { @@ -297,44 +306,7 @@ unsafe impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> StationMa } } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Device for Ethernet<'d, T, P, TX, RX> { - fn is_transmit_ready(&mut self) -> bool { - self.state.with(|s| s.desc_ring.tx.available()) - } - - fn transmit(&mut self, pkt: PacketBuf) { - self.state.with(|s| unwrap!(s.desc_ring.tx.transmit(pkt))); - } - - fn receive(&mut self) -> Option { - self.state.with(|s| s.desc_ring.rx.pop_packet()) - } - - fn register_waker(&mut self, waker: &Waker) { - WAKER.register(waker); - } - - fn capabilities(&self) -> DeviceCapabilities { - let mut caps = DeviceCapabilities::default(); - caps.max_transmission_unit = MTU; - caps.max_burst_size = Some(TX.min(RX)); - caps - } - - fn link_state(&mut self) -> LinkState { - if P::poll_link(self) { - LinkState::Up - } else { - LinkState::Down - } - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} - -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Drop for Ethernet<'d, T, P, TX, RX> { +impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { fn drop(&mut self) { // NOTE(unsafe) We have `&mut self` and the interrupt doesn't use this registers unsafe { @@ -361,46 +333,3 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Drop for Etherne }) } } - -//---------------------------------------------------------------------- - -struct Inner<'d, T: Instance, const TX: usize, const RX: usize> { - _peri: PhantomData<&'d mut T>, - desc_ring: DescriptorRing, -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> Inner<'d, T, TX, RX> { - pub fn new(_peri: impl Peripheral

+ 'd) -> Self { - Self { - _peri: PhantomData, - desc_ring: DescriptorRing::new(), - } - } -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> PeripheralState for Inner<'d, T, TX, RX> { - type Interrupt = crate::interrupt::ETH; - - fn on_interrupt(&mut self) { - unwrap!(self.desc_ring.tx.on_interrupt()); - self.desc_ring.rx.on_interrupt(); - - WAKER.wake(); - - // TODO: Check and clear more flags - unsafe { - let dma = ETH.ethernet_dma(); - - dma.dmasr().modify(|w| { - w.set_ts(true); - w.set_rs(true); - w.set_nis(true); - }); - // Delay two peripheral's clock - dma.dmasr().read(); - dma.dmasr().read(); - } - } -} - -static WAKER: AtomicWaker = AtomicWaker::new(); diff --git a/embassy-stm32/src/eth/v1/rx_desc.rs b/embassy-stm32/src/eth/v1/rx_desc.rs index d482590a..8b8566d9 100644 --- a/embassy-stm32/src/eth/v1/rx_desc.rs +++ b/embassy-stm32/src/eth/v1/rx_desc.rs @@ -1,9 +1,9 @@ use core::sync::atomic::{compiler_fence, fence, Ordering}; -use embassy_net::{Packet, PacketBox, PacketBoxExt, PacketBuf}; -use stm32_metapac::eth::vals::{DmaomrSr, Rpd, Rps}; +use stm32_metapac::eth::vals::{Rpd, Rps}; use vcell::VolatileCell; +use crate::eth::RX_BUFFER_SIZE; use crate::pac::ETH; mod rx_consts { @@ -28,6 +28,8 @@ mod rx_consts { use rx_consts::*; +use super::Packet; + /// Receive Descriptor representation /// /// * rdes0: OWN and Status @@ -35,7 +37,7 @@ use rx_consts::*; /// * rdes2: data buffer address /// * rdes3: next descriptor address #[repr(C)] -struct RDes { +pub(crate) struct RDes { rdes0: VolatileCell, rdes1: VolatileCell, rdes2: VolatileCell, @@ -54,7 +56,7 @@ impl RDes { /// Return true if this RDes is acceptable to us #[inline(always)] - pub fn valid(&self) -> bool { + fn valid(&self) -> bool { // Write-back descriptor is valid if: // // Contains first buffer of packet AND contains last buf of @@ -64,15 +66,16 @@ impl RDes { /// Return true if this RDes is not currently owned by the DMA #[inline(always)] - pub fn available(&self) -> bool { + fn available(&self) -> bool { self.rdes0.get() & RXDESC_0_OWN == 0 // Owned by us } /// Configures the reception buffer address and length and passed descriptor ownership to the DMA #[inline(always)] - pub fn set_ready(&mut self, buf_addr: u32, buf_len: usize) { - self.rdes1.set(self.rdes1.get() | (buf_len as u32) & RXDESC_1_RBS_MASK); - self.rdes2.set(buf_addr); + fn set_ready(&self, buf: *mut u8) { + self.rdes1 + .set(self.rdes1.get() | (RX_BUFFER_SIZE as u32) & RXDESC_1_RBS_MASK); + self.rdes2.set(buf as u32); // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::Release); @@ -88,12 +91,12 @@ impl RDes { // points to next descriptor (RCH) #[inline(always)] - fn set_buffer2(&mut self, buffer: *const u8) { + fn set_buffer2(&self, buffer: *const u8) { self.rdes3.set(buffer as u32); } #[inline(always)] - fn set_end_of_ring(&mut self) { + fn set_end_of_ring(&self) { self.rdes1.set(self.rdes1.get() | RXDESC_1_RER); } @@ -102,7 +105,7 @@ impl RDes { ((self.rdes0.get() >> RXDESC_0_FL_SHIFT) & RXDESC_0_FL_MASK) as usize } - pub fn setup(&mut self, next: Option<&Self>) { + fn setup(&self, next: Option<&Self>, buf: *mut u8) { // Defer this initialization to this function, so we can have `RingEntry` on bss. self.rdes1.set(self.rdes1.get() | RXDESC_1_RCH); @@ -113,8 +116,11 @@ impl RDes { self.set_end_of_ring(); } } + + self.set_ready(buf); } } + /// Running state of the `RxRing` #[derive(PartialEq, Eq, Debug)] pub enum RunningState { @@ -123,116 +129,42 @@ pub enum RunningState { Running, } -impl RunningState { - /// whether self equals to `RunningState::Running` - pub fn is_running(&self) -> bool { - *self == RunningState::Running - } -} - /// Rx ring of descriptors and packets -/// -/// This ring has three major locations that work in lock-step. The DMA will never write to the tail -/// index, so the `read_index` must never pass the tail index. The `next_tail_index` is always 1 -/// slot ahead of the real tail index, and it must never pass the `read_index` or it could overwrite -/// a packet still to be passed to the application. -/// -/// nt can't pass r (no alloc) -/// +---+---+---+---+ Read ok +---+---+---+---+ No Read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation ok +---+---+---+---+ +---+---+---+---+ -/// ^ ^t ^t ^ ^t ^ -/// |r |r |r -/// |nt |nt |nt -/// -/// -/// +---+---+---+---+ Read ok +---+---+---+---+ Can't read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation fail +---+---+---+---+ Allocation ok +---+---+---+---+ -/// ^ ^t ^ ^t ^ ^ ^ ^t -/// |r | |r | | |r -/// |nt |nt |nt -/// -pub(crate) struct RDesRing { - descriptors: [RDes; N], - buffers: [Option; N], - read_index: usize, - next_tail_index: usize, +pub(crate) struct RDesRing<'a> { + descriptors: &'a mut [RDes], + buffers: &'a mut [Packet], + index: usize, } -impl RDesRing { - pub const fn new() -> Self { - const RDES: RDes = RDes::new(); - const BUFFERS: Option = None; +impl<'a> RDesRing<'a> { + pub(crate) fn new(descriptors: &'a mut [RDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 1); + assert!(descriptors.len() == buffers.len()); - Self { - descriptors: [RDES; N], - buffers: [BUFFERS; N], - read_index: 0, - next_tail_index: 0, - } - } - - pub(crate) fn init(&mut self) { - assert!(N > 1); - let mut last_index = 0; - for (index, buf) in self.buffers.iter_mut().enumerate() { - let pkt = match PacketBox::new(Packet::new()) { - Some(p) => p, - None => { - if index == 0 { - panic!("Could not allocate at least one buffer for Ethernet receiving"); - } else { - break; - } - } - }; - self.descriptors[index].set_ready(pkt.as_ptr() as u32, pkt.len()); - *buf = Some(pkt); - last_index = index; - } - self.next_tail_index = (last_index + 1) % N; - - // not sure if this is supposed to span all of the descriptor or just those that contain buffers - { - let mut previous: Option<&mut RDes> = None; - for entry in self.descriptors.iter_mut() { - if let Some(prev) = &mut previous { - prev.setup(Some(entry)); - } - previous = Some(entry); - } - - if let Some(entry) = &mut previous { - entry.setup(None); - } + for (i, entry) in descriptors.iter().enumerate() { + entry.setup(descriptors.get(i + 1), buffers[i].0.as_mut_ptr()); } - // Register txdescriptor start + // Register rx descriptor start // NOTE (unsafe) Used for atomic writes unsafe { ETH.ethernet_dma() .dmardlar() - .write(|w| w.0 = &self.descriptors as *const _ as u32); + .write(|w| w.0 = descriptors.as_ptr() as u32); }; // We already have fences in `set_owned`, which is called in `setup` - // Start receive - unsafe { ETH.ethernet_dma().dmaomr().modify(|w| w.set_sr(DmaomrSr::STARTED)) }; - - self.demand_poll(); + Self { + descriptors, + buffers, + index: 0, + } } - fn demand_poll(&self) { + pub(crate) fn demand_poll(&self) { unsafe { ETH.ethernet_dma().dmarpdr().write(|w| w.set_rpd(Rpd::POLL)) }; } - pub(crate) fn on_interrupt(&mut self) { - // XXX: Do we need to do anything here ? Maybe we should try to advance the tail ptr, but it - // would soon hit the read ptr anyway, and we will wake smoltcp's stack on the interrupt - // which should try to pop a packet... - } - /// Get current `RunningState` fn running_state(&self) -> RunningState { match unsafe { ETH.ethernet_dma().dmasr().read().rps() } { @@ -252,52 +184,52 @@ impl RDesRing { } } - pub(crate) fn pop_packet(&mut self) -> Option { - if !self.running_state().is_running() { + /// Get a received packet if any, or None. + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { + if self.running_state() != RunningState::Running { self.demand_poll(); } + // Not sure if the contents of the write buffer on the M7 can affects reads, so we are using // a DMB here just in case, it also serves as a hint to the compiler that we're syncing the // buffer (I think .-.) fence(Ordering::SeqCst); - let read_available = self.descriptors[self.read_index].available(); - let tail_index = (self.next_tail_index + N - 1) % N; - - let pkt = if read_available && self.read_index != tail_index { - let pkt = self.buffers[self.read_index].take(); - let len = self.descriptors[self.read_index].packet_len(); - - assert!(pkt.is_some()); - let valid = self.descriptors[self.read_index].valid(); - - self.read_index = (self.read_index + 1) % N; - if valid { - pkt.map(|p| p.slice(0..len)) - } else { - None + // We might have to process many packets, in case some have been rx'd but are invalid. + loop { + let descriptor = &mut self.descriptors[self.index]; + if !descriptor.available() { + return None; } - } else { - None - }; - // Try to advance the tail_index - if self.next_tail_index != self.read_index { - match PacketBox::new(Packet::new()) { - Some(b) => { - let addr = b.as_ptr() as u32; - let buffer_len = b.len(); - self.buffers[self.next_tail_index].replace(b); - self.descriptors[self.next_tail_index].set_ready(addr, buffer_len); - - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::Release); - - self.next_tail_index = (self.next_tail_index + 1) % N; - } - None => {} + // If packet is invalid, pop it and try again. + if !descriptor.valid() { + warn!("invalid packet: {:08x}", descriptor.rdes0.get()); + self.pop_packet(); + continue; } + + break; + } + + let descriptor = &mut self.descriptors[self.index]; + let len = descriptor.packet_len(); + return Some(&mut self.buffers[self.index].0[..len]); + } + + /// Pop the packet previously returned by `available`. + pub(crate) fn pop_packet(&mut self) { + let descriptor = &mut self.descriptors[self.index]; + assert!(descriptor.available()); + + self.descriptors[self.index].set_ready(self.buffers[self.index].0.as_mut_ptr()); + + self.demand_poll(); + + // Increment index. + self.index += 1; + if self.index == self.descriptors.len() { + self.index = 0 } - pkt } } diff --git a/embassy-stm32/src/eth/v1/tx_desc.rs b/embassy-stm32/src/eth/v1/tx_desc.rs index f2889b55..0e63c544 100644 --- a/embassy-stm32/src/eth/v1/tx_desc.rs +++ b/embassy-stm32/src/eth/v1/tx_desc.rs @@ -1,20 +1,10 @@ use core::sync::atomic::{compiler_fence, fence, Ordering}; -use embassy_net::PacketBuf; -use stm32_metapac::eth::vals::St; use vcell::VolatileCell; +use crate::eth::TX_BUFFER_SIZE; use crate::pac::ETH; -#[non_exhaustive] -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - NoBufferAvailable, - // TODO: Break down this error into several others - TransmissionError, -} - /// Transmit and Receive Descriptor fields #[allow(dead_code)] mod tx_consts { @@ -37,6 +27,8 @@ mod tx_consts { } use tx_consts::*; +use super::Packet; + /// Transmit Descriptor representation /// /// * tdes0: control @@ -44,7 +36,7 @@ use tx_consts::*; /// * tdes2: data buffer address /// * tdes3: next descriptor address #[repr(C)] -struct TDes { +pub(crate) struct TDes { tdes0: VolatileCell, tdes1: VolatileCell, tdes2: VolatileCell, @@ -62,7 +54,7 @@ impl TDes { } /// Return true if this TDes is not currently owned by the DMA - pub fn available(&self) -> bool { + fn available(&self) -> bool { (self.tdes0.get() & TXDESC_0_OWN) == 0 } @@ -79,26 +71,26 @@ impl TDes { fence(Ordering::SeqCst); } - fn set_buffer1(&mut self, buffer: *const u8) { + fn set_buffer1(&self, buffer: *const u8) { self.tdes2.set(buffer as u32); } - fn set_buffer1_len(&mut self, len: usize) { + fn set_buffer1_len(&self, len: usize) { self.tdes1 .set((self.tdes1.get() & !TXDESC_1_TBS_MASK) | ((len as u32) << TXDESC_1_TBS_SHIFT)); } // points to next descriptor (RCH) - fn set_buffer2(&mut self, buffer: *const u8) { + fn set_buffer2(&self, buffer: *const u8) { self.tdes3.set(buffer as u32); } - fn set_end_of_ring(&mut self) { + fn set_end_of_ring(&self) { self.tdes0.set(self.tdes0.get() | TXDESC_0_TER); } // set up as a part fo the ring buffer - configures the tdes - pub fn setup(&mut self, next: Option<&Self>) { + fn setup(&self, next: Option<&Self>) { // Defer this initialization to this function, so we can have `RingEntry` on bss. self.tdes0.set(TXDESC_0_TCH | TXDESC_0_IOC | TXDESC_0_FS | TXDESC_0_LS); match next { @@ -111,85 +103,58 @@ impl TDes { } } -pub(crate) struct TDesRing { - descriptors: [TDes; N], - buffers: [Option; N], - next_entry: usize, +pub(crate) struct TDesRing<'a> { + descriptors: &'a mut [TDes], + buffers: &'a mut [Packet], + index: usize, } -impl TDesRing { - pub const fn new() -> Self { - const TDES: TDes = TDes::new(); - const BUFFERS: Option = None; - - Self { - descriptors: [TDES; N], - buffers: [BUFFERS; N], - next_entry: 0, - } - } - +impl<'a> TDesRing<'a> { /// Initialise this TDesRing. Assume TDesRing is corrupt - /// - /// The current memory address of the buffers inside this TDesRing - /// will be stored in the descriptors, so ensure the TDesRing is - /// not moved after initialisation. - pub(crate) fn init(&mut self) { - assert!(N > 0); + pub(crate) fn new(descriptors: &'a mut [TDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 0); + assert!(descriptors.len() == buffers.len()); - { - let mut previous: Option<&mut TDes> = None; - for entry in self.descriptors.iter_mut() { - if let Some(prev) = &mut previous { - prev.setup(Some(entry)); - } - previous = Some(entry); - } - - if let Some(entry) = &mut previous { - entry.setup(None); - } + for (i, entry) in descriptors.iter().enumerate() { + entry.setup(descriptors.get(i + 1)); } - self.next_entry = 0; // Register txdescriptor start // NOTE (unsafe) Used for atomic writes unsafe { ETH.ethernet_dma() .dmatdlar() - .write(|w| w.0 = &self.descriptors as *const _ as u32); + .write(|w| w.0 = descriptors.as_ptr() as u32); } - // "Preceding reads and writes cannot be moved past subsequent writes." - #[cfg(feature = "fence")] - fence(Ordering::Release); - - // We don't need a compiler fence here because all interactions with `Descriptor` are - // volatiles - - // Start transmission - unsafe { ETH.ethernet_dma().dmaomr().modify(|w| w.set_st(St::STARTED)) }; - } - - /// Return true if a TDes is available for use - pub(crate) fn available(&self) -> bool { - self.descriptors[self.next_entry].available() - } - - pub(crate) fn transmit(&mut self, pkt: PacketBuf) -> Result<(), Error> { - if !self.available() { - return Err(Error::NoBufferAvailable); + Self { + descriptors, + buffers, + index: 0, } + } - let descriptor = &mut self.descriptors[self.next_entry]; + pub(crate) fn len(&self) -> usize { + self.descriptors.len() + } - let pkt_len = pkt.len(); - let address = pkt.as_ptr() as *const u8; + /// Return the next available packet buffer for transmitting, or None + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { + let descriptor = &mut self.descriptors[self.index]; + if descriptor.available() { + Some(&mut self.buffers[self.index].0) + } else { + None + } + } - descriptor.set_buffer1(address); - descriptor.set_buffer1_len(pkt_len); + /// Transmit the packet written in a buffer returned by `available`. + pub(crate) fn transmit(&mut self, len: usize) { + let descriptor = &mut self.descriptors[self.index]; + assert!(descriptor.available()); - self.buffers[self.next_entry].replace(pkt); + descriptor.set_buffer1(self.buffers[self.index].0.as_ptr()); + descriptor.set_buffer1_len(len); descriptor.set_owned(); @@ -198,36 +163,12 @@ impl TDesRing { // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::Release); - // Move the tail pointer (TPR) to the next descriptor - self.next_entry = (self.next_entry + 1) % N; - + // Move the index to the next descriptor + self.index += 1; + if self.index == self.descriptors.len() { + self.index = 0 + } // Request the DMA engine to poll the latest tx descriptor unsafe { ETH.ethernet_dma().dmatpdr().modify(|w| w.0 = 1) } - Ok(()) - } - - pub(crate) fn on_interrupt(&mut self) -> Result<(), Error> { - let previous = (self.next_entry + N - 1) % N; - let td = &self.descriptors[previous]; - - // DMB to ensure that we are reading an updated value, probably not needed at the hardware - // level, but this is also a hint to the compiler that we're syncing on the buffer. - fence(Ordering::SeqCst); - - let tdes0 = td.tdes0.get(); - - if tdes0 & TXDESC_0_OWN != 0 { - // Transmission isn't done yet, probably a receive interrupt that fired this - return Ok(()); - } - - // Release the buffer - self.buffers[previous].take(); - - if tdes0 & TXDESC_0_ES != 0 { - Err(Error::TransmissionError) - } else { - Ok(()) - } } } diff --git a/examples/stm32f7/src/bin/eth.rs b/examples/stm32f7/src/bin/eth.rs index 5202edf6..224cc202 100644 --- a/examples/stm32f7/src/bin/eth.rs +++ b/examples/stm32f7/src/bin/eth.rs @@ -7,7 +7,7 @@ use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv4Address, Stack, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, State}; +use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::mhz; @@ -22,11 +22,12 @@ macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) + let (x,) = STATIC_CELL.init(($val,)); + x }}; } -type Device = Ethernet<'static, ETH, GenericSMI, 4, 4>; +type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] async fn net_task(stack: &'static Stack) -> ! { @@ -50,25 +51,23 @@ async fn main(spawner: Spawner) -> ! { let eth_int = interrupt::take!(ETH); let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; - let device = unsafe { - Ethernet::new( - singleton!(State::new()), - p.ETH, - eth_int, - p.PA1, - p.PA2, - p.PC1, - p.PA7, - p.PC4, - p.PC5, - p.PG13, - p.PB13, - p.PG11, - GenericSMI, - mac_addr, - 0, - ) - }; + let device = Ethernet::new( + singleton!(PacketQueue::<16, 16>::new()), + p.ETH, + eth_int, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI, + mac_addr, + 0, + ); let config = embassy_net::ConfigStrategy::Dhcp; //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { @@ -91,8 +90,8 @@ async fn main(spawner: Spawner) -> ! { info!("Network task initialized"); // Then we can use it! - let mut rx_buffer = [0; 1024]; - let mut tx_buffer = [0; 1024]; + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; loop { let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); @@ -107,8 +106,9 @@ async fn main(spawner: Spawner) -> ! { continue; } info!("connected!"); + let buf = [0; 1024]; loop { - let r = socket.write_all(b"Hello\n").await; + let r = socket.write_all(&buf).await; if let Err(e) = r { info!("write error: {:?}", e); return; From 3005ee0178af46ef8a2b7f8b02f4aea69ed9202b Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 12 Dec 2022 02:04:33 +0100 Subject: [PATCH 5/7] stm32/eth_v2: update to new embassy-net trait, remove PeripheralMutex. --- embassy-stm32/src/eth/v2/descriptors.rs | 333 +++++++--------------- embassy-stm32/src/eth/v2/mod.rs | 357 ++++++++++-------------- examples/stm32h7/src/bin/eth.rs | 43 ++- examples/stm32h7/src/bin/eth_client.rs | 43 ++- 4 files changed, 289 insertions(+), 487 deletions(-) diff --git a/embassy-stm32/src/eth/v2/descriptors.rs b/embassy-stm32/src/eth/v2/descriptors.rs index c6c06a9c..2426596f 100644 --- a/embassy-stm32/src/eth/v2/descriptors.rs +++ b/embassy-stm32/src/eth/v2/descriptors.rs @@ -1,19 +1,10 @@ use core::sync::atomic::{fence, Ordering}; -use embassy_net::{Packet, PacketBox, PacketBoxExt, PacketBuf}; use vcell::VolatileCell; +use crate::eth::{Packet, RX_BUFFER_SIZE, TX_BUFFER_SIZE}; use crate::pac::ETH; -#[non_exhaustive] -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - NoBufferAvailable, - // TODO: Break down this error into several others - TransmissionError, -} - /// Transmit and Receive Descriptor fields #[allow(dead_code)] mod emac_consts { @@ -41,7 +32,7 @@ use emac_consts::*; /// * tdes2: buffer lengths /// * tdes3: control and payload/frame length #[repr(C)] -struct TDes { +pub(crate) struct TDes { tdes0: VolatileCell, tdes1: VolatileCell, tdes2: VolatileCell, @@ -59,41 +50,26 @@ impl TDes { } /// Return true if this TDes is not currently owned by the DMA - pub fn available(&self) -> bool { + fn available(&self) -> bool { self.tdes3.get() & EMAC_DES3_OWN == 0 } } -pub(crate) struct TDesRing { - td: [TDes; N], - buffers: [Option; N], - tdidx: usize, +pub(crate) struct TDesRing<'a> { + descriptors: &'a mut [TDes], + buffers: &'a mut [Packet], + index: usize, } -impl TDesRing { - pub const fn new() -> Self { - const TDES: TDes = TDes::new(); - const BUFFERS: Option = None; +impl<'a> TDesRing<'a> { + /// Initialise this TDesRing. Assume TDesRing is corrupt. + pub fn new(descriptors: &'a mut [TDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 0); + assert!(descriptors.len() == buffers.len()); - Self { - td: [TDES; N], - buffers: [BUFFERS; N], - tdidx: 0, - } - } - - /// Initialise this TDesRing. Assume TDesRing is corrupt - /// - /// The current memory address of the buffers inside this TDesRing - /// will be stored in the descriptors, so ensure the TDesRing is - /// not moved after initialisation. - pub(crate) fn init(&mut self) { - assert!(N > 0); - - for td in self.td.iter_mut() { + for td in descriptors.iter_mut() { *td = TDes::new(); } - self.tdidx = 0; // Initialize the pointers in the DMA engine. (There will be a memory barrier later // before the DMA engine is enabled.) @@ -101,80 +77,60 @@ impl TDesRing { unsafe { let dma = ETH.ethernet_dma(); - dma.dmactx_dlar().write(|w| w.0 = &self.td as *const _ as u32); - dma.dmactx_rlr().write(|w| w.set_tdrl((N as u16) - 1)); - dma.dmactx_dtpr().write(|w| w.0 = &self.td[0] as *const _ as u32); + dma.dmactx_dlar().write(|w| w.0 = descriptors.as_mut_ptr() as u32); + dma.dmactx_rlr().write(|w| w.set_tdrl((descriptors.len() as u16) - 1)); + dma.dmactx_dtpr().write(|w| w.0 = 0); + } + + Self { + descriptors, + buffers, + index: 0, } } - /// Return true if a TDes is available for use - pub(crate) fn available(&self) -> bool { - self.td[self.tdidx].available() + pub(crate) fn len(&self) -> usize { + self.descriptors.len() } - pub(crate) fn transmit(&mut self, pkt: PacketBuf) -> Result<(), Error> { - if !self.available() { - return Err(Error::NoBufferAvailable); + /// Return the next available packet buffer for transmitting, or None + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { + let d = &mut self.descriptors[self.index]; + if d.available() { + Some(&mut self.buffers[self.index].0) + } else { + None } - let x = self.tdidx; - let td = &mut self.td[x]; + } - let pkt_len = pkt.len(); - assert!(pkt_len as u32 <= EMAC_TDES2_B1L); - let address = pkt.as_ptr() as u32; + /// Transmit the packet written in a buffer returned by `available`. + pub(crate) fn transmit(&mut self, len: usize) { + let td = &mut self.descriptors[self.index]; + assert!(td.available()); + assert!(len as u32 <= EMAC_TDES2_B1L); // Read format - td.tdes0.set(address); - td.tdes2.set(pkt_len as u32 & EMAC_TDES2_B1L | EMAC_TDES2_IOC); + td.tdes0.set(self.buffers[self.index].0.as_ptr() as u32); + td.tdes2.set(len as u32 & EMAC_TDES2_B1L | EMAC_TDES2_IOC); // FD: Contains first buffer of packet // LD: Contains last buffer of packet // Give the DMA engine ownership td.tdes3.set(EMAC_DES3_FD | EMAC_DES3_LD | EMAC_DES3_OWN); - self.buffers[x].replace(pkt); - // Ensure changes to the descriptor are committed before DMA engine sees tail pointer store. // This will generate an DMB instruction. // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::Release); - // Move the tail pointer (TPR) to the next descriptor - let x = (x + 1) % N; + self.index = self.index + 1; + if self.index == self.descriptors.len() { + self.index = 0; + } + + // signal DMA it can try again. // NOTE(unsafe) Atomic write - unsafe { - ETH.ethernet_dma() - .dmactx_dtpr() - .write(|w| w.0 = &self.td[x] as *const _ as u32); - } - self.tdidx = x; - Ok(()) - } - - pub(crate) fn on_interrupt(&mut self) -> Result<(), Error> { - let previous = (self.tdidx + N - 1) % N; - let td = &self.td[previous]; - - // DMB to ensure that we are reading an updated value, probably not needed at the hardware - // level, but this is also a hint to the compiler that we're syncing on the buffer. - fence(Ordering::SeqCst); - - let tdes3 = td.tdes3.get(); - - if tdes3 & EMAC_DES3_OWN != 0 { - // Transmission isn't done yet, probably a receive interrupt that fired this - return Ok(()); - } - assert!(tdes3 & EMAC_DES3_CTXT == 0); - - // Release the buffer - self.buffers[previous].take(); - - if tdes3 & EMAC_DES3_ES != 0 { - Err(Error::TransmissionError) - } else { - Ok(()) - } + unsafe { ETH.ethernet_dma().dmactx_dtpr().write(|w| w.0 = 0) } } } @@ -185,7 +141,7 @@ impl TDesRing { /// * rdes2: /// * rdes3: OWN and Status #[repr(C)] -struct RDes { +pub(crate) struct RDes { rdes0: VolatileCell, rdes1: VolatileCell, rdes2: VolatileCell, @@ -204,7 +160,7 @@ impl RDes { /// Return true if this RDes is acceptable to us #[inline(always)] - pub fn valid(&self) -> bool { + fn valid(&self) -> bool { // Write-back descriptor is valid if: // // Contains first buffer of packet AND contains last buf of @@ -215,177 +171,96 @@ impl RDes { /// Return true if this RDes is not currently owned by the DMA #[inline(always)] - pub fn available(&self) -> bool { + fn available(&self) -> bool { self.rdes3.get() & EMAC_DES3_OWN == 0 // Owned by us } #[inline(always)] - pub fn set_ready(&mut self, buf_addr: u32) { - self.rdes0.set(buf_addr); + fn set_ready(&mut self, buf: *mut u8) { + self.rdes0.set(buf as u32); self.rdes3.set(EMAC_RDES3_BUF1V | EMAC_RDES3_IOC | EMAC_DES3_OWN); } } /// Rx ring of descriptors and packets -/// -/// This ring has three major locations that work in lock-step. The DMA will never write to the tail -/// index, so the `read_index` must never pass the tail index. The `next_tail_index` is always 1 -/// slot ahead of the real tail index, and it must never pass the `read_index` or it could overwrite -/// a packet still to be passed to the application. -/// -/// nt can't pass r (no alloc) -/// +---+---+---+---+ Read ok +---+---+---+---+ No Read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation ok +---+---+---+---+ +---+---+---+---+ -/// ^ ^t ^t ^ ^t ^ -/// |r |r |r -/// |nt |nt |nt -/// -/// -/// +---+---+---+---+ Read ok +---+---+---+---+ Can't read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation fail +---+---+---+---+ Allocation ok +---+---+---+---+ -/// ^ ^t ^ ^t ^ ^ ^ ^t -/// |r | |r | | |r -/// |nt |nt |nt -/// -pub(crate) struct RDesRing { - rd: [RDes; N], - buffers: [Option; N], - read_idx: usize, - next_tail_idx: usize, +pub(crate) struct RDesRing<'a> { + descriptors: &'a mut [RDes], + buffers: &'a mut [Packet], + index: usize, } -impl RDesRing { - pub const fn new() -> Self { - const RDES: RDes = RDes::new(); - const BUFFERS: Option = None; +impl<'a> RDesRing<'a> { + pub(crate) fn new(descriptors: &'a mut [RDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 1); + assert!(descriptors.len() == buffers.len()); - Self { - rd: [RDES; N], - buffers: [BUFFERS; N], - read_idx: 0, - next_tail_idx: 0, - } - } - - pub(crate) fn init(&mut self) { - assert!(N > 1); - - for desc in self.rd.iter_mut() { + for (i, desc) in descriptors.iter_mut().enumerate() { *desc = RDes::new(); + desc.set_ready(buffers[i].0.as_mut_ptr()); } - let mut last_index = 0; - for (index, buf) in self.buffers.iter_mut().enumerate() { - let pkt = match PacketBox::new(Packet::new()) { - Some(p) => p, - None => { - if index == 0 { - panic!("Could not allocate at least one buffer for Ethernet receiving"); - } else { - break; - } - } - }; - let addr = pkt.as_ptr() as u32; - *buf = Some(pkt); - self.rd[index].set_ready(addr); - last_index = index; - } - self.next_tail_idx = (last_index + 1) % N; - unsafe { let dma = ETH.ethernet_dma(); - dma.dmacrx_dlar().write(|w| w.0 = self.rd.as_ptr() as u32); - dma.dmacrx_rlr().write(|w| w.set_rdrl((N as u16) - 1)); + dma.dmacrx_dlar().write(|w| w.0 = descriptors.as_mut_ptr() as u32); + dma.dmacrx_rlr().write(|w| w.set_rdrl((descriptors.len() as u16) - 1)); + dma.dmacrx_dtpr().write(|w| w.0 = 0); + } - // We manage to allocate all buffers, set the index to the last one, that means - // that the DMA won't consider the last one as ready, because it (unfortunately) - // stops at the tail ptr and wraps at the end of the ring, which means that we - // can't tell it to stop after the last buffer. - let tail_ptr = &self.rd[last_index] as *const _ as u32; - fence(Ordering::Release); - - dma.dmacrx_dtpr().write(|w| w.0 = tail_ptr); + Self { + descriptors, + buffers, + index: 0, } } - pub(crate) fn on_interrupt(&mut self) { - // XXX: Do we need to do anything here ? Maybe we should try to advance the tail ptr, but it - // would soon hit the read ptr anyway, and we will wake smoltcp's stack on the interrupt - // which should try to pop a packet... - } - - pub(crate) fn pop_packet(&mut self) -> Option { + /// Get a received packet if any, or None. + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { // Not sure if the contents of the write buffer on the M7 can affects reads, so we are using // a DMB here just in case, it also serves as a hint to the compiler that we're syncing the // buffer (I think .-.) fence(Ordering::SeqCst); - let read_available = self.rd[self.read_idx].available(); - let tail_index = (self.next_tail_idx + N - 1) % N; - - let pkt = if read_available && self.read_idx != tail_index { - let pkt = self.buffers[self.read_idx].take(); - let len = (self.rd[self.read_idx].rdes3.get() & EMAC_RDES3_PKTLEN) as usize; - - assert!(pkt.is_some()); - let valid = self.rd[self.read_idx].valid(); - - self.read_idx = (self.read_idx + 1) % N; - if valid { - pkt.map(|p| p.slice(0..len)) - } else { - None + // We might have to process many packets, in case some have been rx'd but are invalid. + loop { + let descriptor = &mut self.descriptors[self.index]; + if !descriptor.available() { + return None; } - } else { - None - }; - // Try to advance the tail_idx - if self.next_tail_idx != self.read_idx { - match PacketBox::new(Packet::new()) { - Some(b) => { - let addr = b.as_ptr() as u32; - self.buffers[self.next_tail_idx].replace(b); - self.rd[self.next_tail_idx].set_ready(addr); - - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::Release); - - // NOTE(unsafe) atomic write - unsafe { - ETH.ethernet_dma() - .dmacrx_dtpr() - .write(|w| w.0 = &self.rd[self.next_tail_idx] as *const _ as u32); - } - - self.next_tail_idx = (self.next_tail_idx + 1) % N; - } - None => {} + // If packet is invalid, pop it and try again. + if !descriptor.valid() { + warn!("invalid packet: {:08x}", descriptor.rdes0.get()); + self.pop_packet(); + continue; } + + break; } - pkt + + let descriptor = &mut self.descriptors[self.index]; + let len = (descriptor.rdes3.get() & EMAC_RDES3_PKTLEN) as usize; + return Some(&mut self.buffers[self.index].0[..len]); } -} -pub struct DescriptorRing { - pub(crate) tx: TDesRing, - pub(crate) rx: RDesRing, -} + /// Pop the packet previously returned by `available`. + pub(crate) fn pop_packet(&mut self) { + let descriptor = &mut self.descriptors[self.index]; + assert!(descriptor.available()); -impl DescriptorRing { - pub const fn new() -> Self { - Self { - tx: TDesRing::new(), - rx: RDesRing::new(), + self.descriptors[self.index].set_ready(self.buffers[self.index].0.as_mut_ptr()); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::Release); + + // signal DMA it can try again. + // NOTE(unsafe) Atomic write + unsafe { ETH.ethernet_dma().dmacrx_dtpr().write(|w| w.0 = 0) } + + // Increment index. + self.index += 1; + if self.index == self.descriptors.len() { + self.index = 0 } } - - pub fn init(&mut self) { - self.tx.init(); - self.rx.init(); - } } diff --git a/embassy-stm32/src/eth/v2/mod.rs b/embassy-stm32/src/eth/v2/mod.rs index 5b76d1e7..fcb4a296 100644 --- a/embassy-stm32/src/eth/v2/mod.rs +++ b/embassy-stm32/src/eth/v2/mod.rs @@ -1,35 +1,28 @@ -use core::marker::PhantomData; +mod descriptors; + use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; -use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; +use embassy_cortex_m::interrupt::InterruptExt; use embassy_hal_common::{into_ref, PeripheralRef}; -use embassy_net::{Device, DeviceCapabilities, LinkState, PacketBuf, MTU}; -use embassy_sync::waitqueue::AtomicWaker; +pub(crate) use self::descriptors::{RDes, RDesRing, TDes, TDesRing}; +use super::*; use crate::gpio::sealed::{AFType, Pin as _}; use crate::gpio::{AnyPin, Speed}; use crate::pac::{ETH, RCC, SYSCFG}; use crate::Peripheral; -mod descriptors; -use descriptors::DescriptorRing; +const MTU: usize = 1514; // 14 Ethernet header + 1500 IP packet -use super::*; - -pub struct State<'d, T: Instance, const TX: usize, const RX: usize>(StateStorage>); -impl<'d, T: Instance, const TX: usize, const RX: usize> State<'d, T, TX, RX> { - pub const fn new() -> Self { - Self(StateStorage::new()) - } -} -pub struct Ethernet<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> { - state: PeripheralMutex<'d, Inner<'d, T, TX, RX>>, +pub struct Ethernet<'d, T: Instance, P: PHY> { + _peri: PeripheralRef<'d, T>, + pub(crate) tx: TDesRing<'d>, + pub(crate) rx: RDesRing<'d>, pins: [PeripheralRef<'d, AnyPin>; 9], _phy: P, clock_range: u8, phy_addr: u8, - mac_addr: [u8; 6], + pub(crate) mac_addr: [u8; 6], } macro_rules! config_pins { @@ -44,10 +37,9 @@ macro_rules! config_pins { }; } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, P, TX, RX> { - /// safety: the returned instance is not leak-safe - pub unsafe fn new( - state: &'d mut State<'d, T, TX, RX>, +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { + pub fn new( + queue: &'d mut PacketQueue, peri: impl Peripheral

+ 'd, interrupt: impl Peripheral

+ 'd, ref_clk: impl Peripheral

> + 'd, @@ -63,126 +55,123 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, mac_addr: [u8; 6], phy_addr: u8, ) -> Self { - into_ref!(interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + into_ref!(peri, interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - // Enable the necessary Clocks - // NOTE(unsafe) We have exclusive access to the registers - critical_section::with(|_| { - RCC.apb4enr().modify(|w| w.set_syscfgen(true)); - RCC.ahb1enr().modify(|w| { - w.set_eth1macen(true); - w.set_eth1txen(true); - w.set_eth1rxen(true); + unsafe { + // Enable the necessary Clocks + // NOTE(unsafe) We have exclusive access to the registers + critical_section::with(|_| { + RCC.apb4enr().modify(|w| w.set_syscfgen(true)); + RCC.ahb1enr().modify(|w| { + w.set_eth1macen(true); + w.set_eth1txen(true); + w.set_eth1rxen(true); + }); + + // RMII + SYSCFG.pmcr().modify(|w| w.set_epis(0b100)); }); - // RMII - SYSCFG.pmcr().modify(|w| w.set_epis(0b100)); - }); + config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + // NOTE(unsafe) We have exclusive access to the registers + let dma = ETH.ethernet_dma(); + let mac = ETH.ethernet_mac(); + let mtl = ETH.ethernet_mtl(); - // NOTE(unsafe) We are ourselves not leak-safe. - let state = PeripheralMutex::new(interrupt, &mut state.0, || Inner::new(peri)); + // Reset and wait + dma.dmamr().modify(|w| w.set_swr(true)); + while dma.dmamr().read().swr() {} - // NOTE(unsafe) We have exclusive access to the registers - let dma = ETH.ethernet_dma(); - let mac = ETH.ethernet_mac(); - let mtl = ETH.ethernet_mtl(); + mac.maccr().modify(|w| { + w.set_ipg(0b000); // 96 bit times + w.set_acs(true); + w.set_fes(true); + w.set_dm(true); + // TODO: Carrier sense ? ECRSFD + }); - // Reset and wait - dma.dmamr().modify(|w| w.set_swr(true)); - while dma.dmamr().read().swr() {} + // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, + // so the LR write must happen after the HR write. + mac.maca0hr() + .modify(|w| w.set_addrhi(u16::from(mac_addr[4]) | (u16::from(mac_addr[5]) << 8))); + mac.maca0lr().write(|w| { + w.set_addrlo( + u32::from(mac_addr[0]) + | (u32::from(mac_addr[1]) << 8) + | (u32::from(mac_addr[2]) << 16) + | (u32::from(mac_addr[3]) << 24), + ) + }); - mac.maccr().modify(|w| { - w.set_ipg(0b000); // 96 bit times - w.set_acs(true); - w.set_fes(true); - w.set_dm(true); - // TODO: Carrier sense ? ECRSFD - }); + mac.macqtx_fcr().modify(|w| w.set_pt(0x100)); - // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, - // so the LR write must happen after the HR write. - mac.maca0hr() - .modify(|w| w.set_addrhi(u16::from(mac_addr[4]) | (u16::from(mac_addr[5]) << 8))); - mac.maca0lr().write(|w| { - w.set_addrlo( - u32::from(mac_addr[0]) - | (u32::from(mac_addr[1]) << 8) - | (u32::from(mac_addr[2]) << 16) - | (u32::from(mac_addr[3]) << 24), - ) - }); + // disable all MMC RX interrupts + mac.mmc_rx_interrupt_mask().write(|w| { + w.set_rxcrcerpim(true); + w.set_rxalgnerpim(true); + w.set_rxucgpim(true); + w.set_rxlpiuscim(true); + w.set_rxlpitrcim(true) + }); - mac.macqtx_fcr().modify(|w| w.set_pt(0x100)); + // disable all MMC TX interrupts + mac.mmc_tx_interrupt_mask().write(|w| { + w.set_txscolgpim(true); + w.set_txmcolgpim(true); + w.set_txgpktim(true); + w.set_txlpiuscim(true); + w.set_txlpitrcim(true); + }); - // disable all MMC RX interrupts - mac.mmc_rx_interrupt_mask().write(|w| { - w.set_rxcrcerpim(true); - w.set_rxalgnerpim(true); - w.set_rxucgpim(true); - w.set_rxlpiuscim(true); - w.set_rxlpitrcim(true) - }); + mtl.mtlrx_qomr().modify(|w| w.set_rsf(true)); + mtl.mtltx_qomr().modify(|w| w.set_tsf(true)); - // disable all MMC TX interrupts - mac.mmc_tx_interrupt_mask().write(|w| { - w.set_txscolgpim(true); - w.set_txmcolgpim(true); - w.set_txgpktim(true); - w.set_txlpiuscim(true); - w.set_txlpitrcim(true); - }); + dma.dmactx_cr().modify(|w| w.set_txpbl(1)); // 32 ? + dma.dmacrx_cr().modify(|w| { + w.set_rxpbl(1); // 32 ? + w.set_rbsz(MTU as u16); + }); - mtl.mtlrx_qomr().modify(|w| w.set_rsf(true)); - mtl.mtltx_qomr().modify(|w| w.set_tsf(true)); + // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called + let hclk = crate::rcc::get_freqs().ahb1; + let hclk_mhz = hclk.0 / 1_000_000; - dma.dmactx_cr().modify(|w| w.set_txpbl(1)); // 32 ? - dma.dmacrx_cr().modify(|w| { - w.set_rxpbl(1); // 32 ? - w.set_rbsz(MTU as u16); - }); + // Set the MDC clock frequency in the range 1MHz - 2.5MHz + let clock_range = match hclk_mhz { + 0..=34 => 2, // Divide by 16 + 35..=59 => 3, // Divide by 26 + 60..=99 => 0, // Divide by 42 + 100..=149 => 1, // Divide by 62 + 150..=249 => 4, // Divide by 102 + 250..=310 => 5, // Divide by 124 + _ => { + panic!("HCLK results in MDC clock > 2.5MHz even for the highest CSR clock divider") + } + }; - // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called - let hclk = crate::rcc::get_freqs().ahb1; - let hclk_mhz = hclk.0 / 1_000_000; + let pins = [ + ref_clk.map_into(), + mdio.map_into(), + mdc.map_into(), + crs.map_into(), + rx_d0.map_into(), + rx_d1.map_into(), + tx_d0.map_into(), + tx_d1.map_into(), + tx_en.map_into(), + ]; - // Set the MDC clock frequency in the range 1MHz - 2.5MHz - let clock_range = match hclk_mhz { - 0..=34 => 2, // Divide by 16 - 35..=59 => 3, // Divide by 26 - 60..=99 => 0, // Divide by 42 - 100..=149 => 1, // Divide by 62 - 150..=249 => 4, // Divide by 102 - 250..=310 => 5, // Divide by 124 - _ => { - panic!("HCLK results in MDC clock > 2.5MHz even for the highest CSR clock divider") - } - }; - - let pins = [ - ref_clk.map_into(), - mdio.map_into(), - mdc.map_into(), - crs.map_into(), - rx_d0.map_into(), - rx_d1.map_into(), - tx_d0.map_into(), - tx_d1.map_into(), - tx_en.map_into(), - ]; - - let mut this = Self { - state, - pins, - _phy: phy, - clock_range, - phy_addr, - mac_addr, - }; - - this.state.with(|s| { - s.desc_ring.init(); + let mut this = Self { + _peri: peri, + tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf), + rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf), + pins, + _phy: phy, + clock_range, + phy_addr, + mac_addr, + }; fence(Ordering::SeqCst); @@ -205,17 +194,37 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, w.set_rie(true); w.set_tie(true); }); - }); - P::phy_reset(&mut this); - P::phy_init(&mut this); - this + P::phy_reset(&mut this); + P::phy_init(&mut this); + + interrupt.set_handler(Self::on_interrupt); + interrupt.enable(); + + this + } + } + + fn on_interrupt(_cx: *mut ()) { + WAKER.wake(); + + // TODO: Check and clear more flags + unsafe { + let dma = ETH.ethernet_dma(); + + dma.dmacsr().modify(|w| { + w.set_ti(true); + w.set_ri(true); + w.set_nis(true); + }); + // Delay two peripheral's clock + dma.dmacsr().read(); + dma.dmacsr().read(); + } } } -unsafe impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> StationManagement - for Ethernet<'d, T, P, TX, RX> -{ +unsafe impl<'d, T: Instance, P: PHY> StationManagement for Ethernet<'d, T, P> { fn smi_read(&mut self, reg: u8) -> u16 { // NOTE(unsafe) These registers aren't used in the interrupt and we have `&mut self` unsafe { @@ -251,44 +260,7 @@ unsafe impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> StationMa } } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Device for Ethernet<'d, T, P, TX, RX> { - fn is_transmit_ready(&mut self) -> bool { - self.state.with(|s| s.desc_ring.tx.available()) - } - - fn transmit(&mut self, pkt: PacketBuf) { - self.state.with(|s| unwrap!(s.desc_ring.tx.transmit(pkt))); - } - - fn receive(&mut self) -> Option { - self.state.with(|s| s.desc_ring.rx.pop_packet()) - } - - fn register_waker(&mut self, waker: &Waker) { - WAKER.register(waker); - } - - fn capabilities(&self) -> DeviceCapabilities { - let mut caps = DeviceCapabilities::default(); - caps.max_transmission_unit = MTU; - caps.max_burst_size = Some(TX.min(RX)); - caps - } - - fn link_state(&mut self) -> LinkState { - if P::poll_link(self) { - LinkState::Up - } else { - LinkState::Down - } - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} - -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Drop for Ethernet<'d, T, P, TX, RX> { +impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { fn drop(&mut self) { // NOTE(unsafe) We have `&mut self` and the interrupt doesn't use this registers unsafe { @@ -325,46 +297,3 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Drop for Etherne }) } } - -//---------------------------------------------------------------------- - -struct Inner<'d, T: Instance, const TX: usize, const RX: usize> { - _peri: PhantomData<&'d mut T>, - desc_ring: DescriptorRing, -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> Inner<'d, T, TX, RX> { - pub fn new(_peri: impl Peripheral

+ 'd) -> Self { - Self { - _peri: PhantomData, - desc_ring: DescriptorRing::new(), - } - } -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> PeripheralState for Inner<'d, T, TX, RX> { - type Interrupt = crate::interrupt::ETH; - - fn on_interrupt(&mut self) { - unwrap!(self.desc_ring.tx.on_interrupt()); - self.desc_ring.rx.on_interrupt(); - - WAKER.wake(); - - // TODO: Check and clear more flags - unsafe { - let dma = ETH.ethernet_dma(); - - dma.dmacsr().modify(|w| { - w.set_ti(true); - w.set_ri(true); - w.set_nis(true); - }); - // Delay two peripheral's clock - dma.dmacsr().read(); - dma.dmacsr().read(); - } - } -} - -static WAKER: AtomicWaker = AtomicWaker::new(); diff --git a/examples/stm32h7/src/bin/eth.rs b/examples/stm32h7/src/bin/eth.rs index 4ccc0b5e..551325ca 100644 --- a/examples/stm32h7/src/bin/eth.rs +++ b/examples/stm32h7/src/bin/eth.rs @@ -7,7 +7,7 @@ use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv4Address, Stack, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, State}; +use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::mhz; @@ -22,11 +22,12 @@ macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) + let (x,) = STATIC_CELL.init(($val,)); + x }}; } -type Device = Ethernet<'static, ETH, GenericSMI, 4, 4>; +type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] async fn net_task(stack: &'static Stack) -> ! { @@ -51,25 +52,23 @@ async fn main(spawner: Spawner) -> ! { let eth_int = interrupt::take!(ETH); let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; - let device = unsafe { - Ethernet::new( - singleton!(State::new()), - p.ETH, - eth_int, - p.PA1, - p.PA2, - p.PC1, - p.PA7, - p.PC4, - p.PC5, - p.PG13, - p.PB13, - p.PG11, - GenericSMI, - mac_addr, - 0, - ) - }; + let device = Ethernet::new( + singleton!(PacketQueue::<16, 16>::new()), + p.ETH, + eth_int, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI, + mac_addr, + 0, + ); let config = embassy_net::ConfigStrategy::Dhcp; //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { diff --git a/examples/stm32h7/src/bin/eth_client.rs b/examples/stm32h7/src/bin/eth_client.rs index 64fd8414..61a08ae1 100644 --- a/examples/stm32h7/src/bin/eth_client.rs +++ b/examples/stm32h7/src/bin/eth_client.rs @@ -7,7 +7,7 @@ use embassy_executor::Spawner; use embassy_net::tcp::client::{TcpClient, TcpClientState}; use embassy_net::{Stack, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, State}; +use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::mhz; @@ -23,11 +23,12 @@ macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) + let (x,) = STATIC_CELL.init(($val,)); + x }}; } -type Device = Ethernet<'static, ETH, GenericSMI, 4, 4>; +type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] async fn net_task(stack: &'static Stack) -> ! { @@ -52,25 +53,23 @@ async fn main(spawner: Spawner) -> ! { let eth_int = interrupt::take!(ETH); let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; - let device = unsafe { - Ethernet::new( - singleton!(State::new()), - p.ETH, - eth_int, - p.PA1, - p.PA2, - p.PC1, - p.PA7, - p.PC4, - p.PC5, - p.PG13, - p.PB13, - p.PG11, - GenericSMI, - mac_addr, - 0, - ) - }; + let device = Ethernet::new( + singleton!(PacketQueue::<16, 16>::new()), + p.ETH, + eth_int, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI, + mac_addr, + 0, + ); let config = embassy_net::ConfigStrategy::Dhcp; //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { From 5eae295c8ad8da529490b89f914f2cca07ae6972 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 13 Dec 2022 16:14:50 +0100 Subject: [PATCH 6/7] stm32: rename feature `net` to `embassy-net`. The eth code is always built and available, but has no own API (other than the embassy-net Device impl) to rx/tx packets. We could add this API in the future, so the feature only means "embassy-net support". --- embassy-stm32/Cargo.toml | 1 - examples/stm32f7/Cargo.toml | 2 +- examples/stm32h7/Cargo.toml | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index b7f718c5..0c491ee4 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -77,7 +77,6 @@ stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", default-features [features] defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-executor/defmt", "embassy-embedded-hal/defmt", "embassy-hal-common/defmt", "embedded-io?/defmt", "embassy-usb-driver?/defmt"] sdmmc-rs = ["embedded-sdmmc"] -net = ["embassy-net" ] memory-x = ["stm32-metapac/memory-x"] subghz = [] exti = [] diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index f4d674cd..afdf8700 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "net", "stm32f767zi", "unstable-pac", "time-driver-any", "exti"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "embassy-net", "stm32f767zi", "unstable-pac", "time-driver-any", "exti"] } embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } embedded-io = { version = "0.4.0", features = ["async"] } diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index 11ce3505..5acf0035 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "net", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "embassy-net", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] } embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16", "unstable-traits"] } embedded-io = { version = "0.4.0", features = ["async"] } From 790e4e1594d455d06c7303a628172244e78af0da Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 13 Dec 2022 16:18:16 +0100 Subject: [PATCH 7/7] examples/std: update to new embassy-net trait. --- examples/std/src/tuntap.rs | 113 +++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/examples/std/src/tuntap.rs b/examples/std/src/tuntap.rs index a0cace7f..bb3e194c 100644 --- a/examples/std/src/tuntap.rs +++ b/examples/std/src/tuntap.rs @@ -1,8 +1,10 @@ use std::io; use std::io::{Read, Write}; use std::os::unix::io::{AsRawFd, RawFd}; +use std::task::Context; use async_io::Async; +use embassy_net::device::{self, Device, DeviceCapabilities, LinkState}; use log::*; pub const SIOCGIFMTU: libc::c_ulong = 0x8921; @@ -125,54 +127,35 @@ impl io::Write for TunTap { pub struct TunTapDevice { device: Async, - waker: Option, } impl TunTapDevice { pub fn new(name: &str) -> io::Result { Ok(Self { device: Async::new(TunTap::new(name)?)?, - waker: None, }) } } -use core::task::Waker; -use std::task::Context; - -use embassy_net::{Device, DeviceCapabilities, LinkState, Packet, PacketBox, PacketBoxExt, PacketBuf}; - impl Device for TunTapDevice { - fn is_transmit_ready(&mut self) -> bool { - true - } + type RxToken<'a> = RxToken where Self: 'a; + type TxToken<'a> = TxToken<'a> where Self: 'a; - fn transmit(&mut self, pkt: PacketBuf) { - // todo handle WouldBlock - match self.device.get_mut().write(&pkt) { - Ok(_) => {} - Err(e) if e.kind() == io::ErrorKind::WouldBlock => { - info!("transmit WouldBlock"); - } - Err(e) => panic!("transmit error: {:?}", e), - } - } - - fn receive(&mut self) -> Option { - let mut pkt = PacketBox::new(Packet::new()).unwrap(); + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let mut buf = vec![0; self.device.get_ref().mtu]; loop { - match self.device.get_mut().read(&mut pkt[..]) { + match self.device.get_mut().read(&mut buf) { Ok(n) => { - return Some(pkt.slice(0..n)); + buf.truncate(n); + return Some(( + RxToken { buffer: buf }, + TxToken { + device: &mut self.device, + }, + )); } Err(e) if e.kind() == io::ErrorKind::WouldBlock => { - let ready = if let Some(w) = self.waker.as_ref() { - let mut cx = Context::from_waker(w); - self.device.poll_readable(&mut cx).is_ready() - } else { - false - }; - if !ready { + if !self.device.poll_readable(cx).is_ready() { return None; } } @@ -181,28 +164,10 @@ impl Device for TunTapDevice { } } - fn register_waker(&mut self, w: &Waker) { - match self.waker { - // Optimization: If both the old and new Wakers wake the same task, we can simply - // keep the old waker, skipping the clone. (In most executor implementations, - // cloning a waker is somewhat expensive, comparable to cloning an Arc). - Some(ref w2) if (w2.will_wake(w)) => {} - _ => { - // clone the new waker and store it - if let Some(old_waker) = core::mem::replace(&mut self.waker, Some(w.clone())) { - // We had a waker registered for another task. Wake it, so the other task can - // reregister itself if it's still interested. - // - // If two tasks are waiting on the same thing concurrently, this will cause them - // to wake each other in a loop fighting over this WakerRegistration. This wastes - // CPU but things will still work. - // - // If the user wants to have two tasks waiting on the same thing they should use - // a more appropriate primitive that can store multiple wakers. - old_waker.wake() - } - } - } + fn transmit(&mut self, _cx: &mut Context) -> Option> { + Some(TxToken { + device: &mut self.device, + }) } fn capabilities(&self) -> DeviceCapabilities { @@ -211,7 +176,7 @@ impl Device for TunTapDevice { caps } - fn link_state(&mut self) -> LinkState { + fn link_state(&mut self, _cx: &mut Context) -> LinkState { LinkState::Up } @@ -219,3 +184,41 @@ impl Device for TunTapDevice { [0x02, 0x03, 0x04, 0x05, 0x06, 0x07] } } + +#[doc(hidden)] +pub struct RxToken { + buffer: Vec, +} + +impl device::RxToken for RxToken { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + f(&mut self.buffer) + } +} + +#[doc(hidden)] +pub struct TxToken<'a> { + device: &'a mut Async, +} + +impl<'a> device::TxToken for TxToken<'a> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut buffer = vec![0; len]; + let result = f(&mut buffer); + + // todo handle WouldBlock with async + match self.device.get_mut().write(&buffer) { + Ok(_) => {} + Err(e) if e.kind() == io::ErrorKind::WouldBlock => info!("transmit WouldBlock"), + Err(e) => panic!("transmit error: {:?}", e), + } + + result + } +}