Merge pull request #1096 from embassy-rs/net-refactor2
net: remove packet pool
This commit is contained in:
commit
5b65b0e843
@ -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<PacketBuf>;
|
||||
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<Self::TxToken<'_>>;
|
||||
fn link_state(&mut self, cx: &mut Context) -> LinkState;
|
||||
|
||||
fn capabilities(&self) -> phy::DeviceCapabilities;
|
||||
fn ethernet_address(&self) -> [u8; 6];
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Device> 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<Self::TxToken<'_>> {
|
||||
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<PacketBuf> {
|
||||
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<D: Device> {
|
||||
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<R, F>(self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> R;
|
||||
}
|
||||
|
||||
impl<D: Device> DeviceAdapter<D> {
|
||||
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<R, F>(self, len: usize, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> R;
|
||||
}
|
||||
|
||||
impl<D: Device> SmolDevice for DeviceAdapter<D> {
|
||||
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<T::RxToken<'a>> where Self: 'a;
|
||||
type TxToken<'a> = TxTokenAdapter<T::TxToken<'a>> 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<Self::TxToken<'_>> {
|
||||
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>(T)
|
||||
where
|
||||
T: RxToken;
|
||||
|
||||
impl smoltcp::phy::RxToken for RxToken {
|
||||
fn consume<R, F>(mut self, _timestamp: SmolInstant, f: F) -> smoltcp::Result<R>
|
||||
impl<T> phy::RxToken for RxTokenAdapter<T>
|
||||
where
|
||||
T: RxToken,
|
||||
{
|
||||
fn consume<R, F>(self, _timestamp: smoltcp::time::Instant, f: F) -> smoltcp::Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> smoltcp::Result<R>,
|
||||
{
|
||||
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>(T)
|
||||
where
|
||||
T: TxToken;
|
||||
|
||||
impl<'a, D: Device> smoltcp::phy::TxToken for TxToken<'a, D> {
|
||||
fn consume<R, F>(self, _timestamp: SmolInstant, len: usize, f: F) -> smoltcp::Result<R>
|
||||
impl<T> phy::TxToken for TxTokenAdapter<T>
|
||||
where
|
||||
T: TxToken,
|
||||
{
|
||||
fn consume<R, F>(self, _timestamp: smoltcp::time::Instant, len: usize, f: F) -> smoltcp::Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> smoltcp::Result<R>,
|
||||
{
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -8,22 +8,30 @@
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
|
||||
mod device;
|
||||
mod packet_pool;
|
||||
mod stack;
|
||||
|
||||
pub use device::{Device, LinkState};
|
||||
pub use packet_pool::{Packet, PacketBox, PacketBoxExt, PacketBuf, MTU};
|
||||
pub use stack::{Config, ConfigStrategy, Stack, StackResources};
|
||||
|
||||
pub mod device;
|
||||
#[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::phy::{DeviceCapabilities, Medium};
|
||||
pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant};
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
pub use smoltcp::wire::{EthernetAddress, HardwareAddress};
|
||||
@ -32,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<const ADDR: usize, const SOCK: usize, const NEIGHBOR: usize> {
|
||||
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<const ADDR: usize, const SOCK: usize, const NEIGHBOR: usize> StackResources<ADDR, SOCK, NEIGHBOR> {
|
||||
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<Ipv4Address>,
|
||||
pub dns_servers: Vec<Ipv4Address, 3>,
|
||||
}
|
||||
|
||||
pub enum ConfigStrategy {
|
||||
Static(Config),
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
Dhcp,
|
||||
}
|
||||
|
||||
pub struct Stack<D: Device> {
|
||||
pub(crate) socket: RefCell<SocketStack>,
|
||||
inner: RefCell<Inner<D>>,
|
||||
}
|
||||
|
||||
struct Inner<D: Device> {
|
||||
device: D,
|
||||
link_up: bool,
|
||||
config: Option<Config>,
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
dhcp_socket: Option<SocketHandle>,
|
||||
}
|
||||
|
||||
pub(crate) struct SocketStack {
|
||||
pub(crate) sockets: SocketSet<'static>,
|
||||
pub(crate) iface: Interface<'static>,
|
||||
pub(crate) waker: WakerRegistration,
|
||||
next_local_port: u16,
|
||||
}
|
||||
|
||||
impl<D: Device + 'static> Stack<D> {
|
||||
pub fn new<const ADDR: usize, const SOCK: usize, const NEIGH: usize>(
|
||||
mut device: D,
|
||||
config: ConfigStrategy,
|
||||
resources: &'static mut StackResources<ADDR, SOCK, NEIGH>,
|
||||
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<R>(&self, f: impl FnOnce(&SocketStack, &Inner<D>) -> R) -> R {
|
||||
f(&*self.socket.borrow(), &*self.inner.borrow())
|
||||
}
|
||||
|
||||
fn with_mut<R>(&self, f: impl FnOnce(&mut SocketStack, &mut Inner<D>) -> 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<Config> {
|
||||
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<D: Device + 'static> Inner<D> {
|
||||
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::<dhcpv4::Socket>(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)
|
||||
}
|
||||
|
@ -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<PacketPool>;
|
||||
|
||||
#[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<usize>) -> PacketBuf;
|
||||
}
|
||||
|
||||
impl PacketBoxExt for PacketBox {
|
||||
fn slice(self, range: Range<usize>) -> 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<usize>,
|
||||
}
|
||||
|
||||
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()]
|
||||
}
|
||||
}
|
@ -1,302 +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::{Device as _, 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<const ADDR: usize, const SOCK: usize, const NEIGHBOR: usize> {
|
||||
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<const ADDR: usize, const SOCK: usize, const NEIGHBOR: usize> StackResources<ADDR, SOCK, NEIGHBOR> {
|
||||
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<Ipv4Address>,
|
||||
pub dns_servers: Vec<Ipv4Address, 3>,
|
||||
}
|
||||
|
||||
pub enum ConfigStrategy {
|
||||
Static(Config),
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
Dhcp,
|
||||
}
|
||||
|
||||
pub struct Stack<D: Device> {
|
||||
pub(crate) socket: RefCell<SocketStack>,
|
||||
inner: RefCell<Inner<D>>,
|
||||
}
|
||||
|
||||
struct Inner<D: Device> {
|
||||
device: DeviceAdapter<D>,
|
||||
link_up: bool,
|
||||
config: Option<Config>,
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
dhcp_socket: Option<SocketHandle>,
|
||||
}
|
||||
|
||||
pub(crate) struct SocketStack {
|
||||
pub(crate) sockets: SocketSet<'static>,
|
||||
pub(crate) iface: Interface<'static>,
|
||||
pub(crate) waker: WakerRegistration,
|
||||
next_local_port: u16,
|
||||
}
|
||||
|
||||
impl<D: Device + 'static> Stack<D> {
|
||||
pub fn new<const ADDR: usize, const SOCK: usize, const NEIGH: usize>(
|
||||
device: D,
|
||||
config: ConfigStrategy,
|
||||
resources: &'static mut StackResources<ADDR, SOCK, NEIGH>,
|
||||
random_seed: u64,
|
||||
) -> Self {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
let medium = device.capabilities().medium;
|
||||
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
let ethernet_addr = if medium == Medium::Ethernet {
|
||||
device.ethernet_address()
|
||||
} else {
|
||||
[0, 0, 0, 0, 0, 0]
|
||||
};
|
||||
|
||||
let mut device = DeviceAdapter::new(device);
|
||||
|
||||
let mut b = InterfaceBuilder::new();
|
||||
b = b.ip_addrs(&mut resources.addresses[..]);
|
||||
b = b.random_seed(random_seed);
|
||||
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
if medium == Medium::Ethernet {
|
||||
b = b.hardware_addr(HardwareAddress::Ethernet(EthernetAddress(ethernet_addr)));
|
||||
b = b.neighbor_cache(NeighborCache::new(&mut resources.neighbor_cache[..]));
|
||||
b = b.routes(Routes::new(&mut resources.routes[..]));
|
||||
}
|
||||
|
||||
let iface = b.finalize(&mut device);
|
||||
|
||||
let sockets = SocketSet::new(&mut resources.sockets[..]);
|
||||
|
||||
let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
|
||||
|
||||
let mut inner = Inner {
|
||||
device,
|
||||
link_up: false,
|
||||
config: None,
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
dhcp_socket: None,
|
||||
};
|
||||
let mut socket = SocketStack {
|
||||
sockets,
|
||||
iface,
|
||||
waker: WakerRegistration::new(),
|
||||
next_local_port,
|
||||
};
|
||||
|
||||
match config {
|
||||
ConfigStrategy::Static(config) => inner.apply_config(&mut socket, config),
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
ConfigStrategy::Dhcp => {
|
||||
let handle = socket.sockets.add(smoltcp::socket::dhcpv4::Socket::new());
|
||||
inner.dhcp_socket = Some(handle);
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
socket: RefCell::new(socket),
|
||||
inner: RefCell::new(inner),
|
||||
}
|
||||
}
|
||||
|
||||
fn with<R>(&self, f: impl FnOnce(&SocketStack, &Inner<D>) -> R) -> R {
|
||||
f(&*self.socket.borrow(), &*self.inner.borrow())
|
||||
}
|
||||
|
||||
fn with_mut<R>(&self, f: impl FnOnce(&mut SocketStack, &mut Inner<D>) -> 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.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<Config> {
|
||||
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<D: Device + 'static> Inner<D> {
|
||||
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) {
|
||||
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() {
|
||||
// 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.device.link_state() == 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::<dhcpv4::Socket>(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)
|
||||
}
|
@ -8,9 +8,8 @@ use smoltcp::socket::tcp;
|
||||
use smoltcp::time::Duration;
|
||||
use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
|
||||
|
||||
use super::stack::Stack;
|
||||
use crate::stack::SocketStack;
|
||||
use crate::Device;
|
||||
use crate::device::Device;
|
||||
use crate::{SocketStack, Stack};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
|
@ -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))]
|
||||
|
@ -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 = []
|
||||
|
@ -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<const N: usize>([u8; N]);
|
||||
|
||||
pub struct PacketQueue<const TX: usize, const RX: usize> {
|
||||
tx_desc: [TDes; TX],
|
||||
rx_desc: [RDes; RX],
|
||||
tx_buf: [Packet<TX_BUFFER_SIZE>; TX],
|
||||
rx_buf: [Packet<RX_BUFFER_SIZE>; RX],
|
||||
}
|
||||
|
||||
impl<const TX: usize, const RX: usize> PacketQueue<TX, RX> {
|
||||
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<Self::TxToken<'_>> {
|
||||
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<R, F>(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<R, F>(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
|
||||
|
@ -1,21 +0,0 @@
|
||||
use crate::eth::_version::rx_desc::RDesRing;
|
||||
use crate::eth::_version::tx_desc::TDesRing;
|
||||
|
||||
pub struct DescriptorRing<const T: usize, const R: usize> {
|
||||
pub(crate) tx: TDesRing<T>,
|
||||
pub(crate) rx: RDesRing<R>,
|
||||
}
|
||||
|
||||
impl<const T: usize, const R: usize> DescriptorRing<T, R> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
tx: TDesRing::new(),
|
||||
rx: RDesRing::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
self.tx.init();
|
||||
self.rx.init();
|
||||
}
|
||||
}
|
@ -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<Inner<'d, T, TX, RX>>);
|
||||
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<const TX: usize, const RX: usize>(
|
||||
queue: &'d mut PacketQueue<TX, RX>,
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
interrupt: impl Peripheral<P = crate::interrupt::ETH> + 'd,
|
||||
ref_clk: impl Peripheral<P = impl RefClkPin<T>> + 'd,
|
||||
@ -101,8 +91,9 @@ 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);
|
||||
|
||||
unsafe {
|
||||
// Enable the necessary Clocks
|
||||
// NOTE(unsafe) We have exclusive access to the registers
|
||||
#[cfg(eth_v1a)]
|
||||
@ -142,9 +133,6 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T,
|
||||
#[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();
|
||||
@ -219,17 +207,16 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T,
|
||||
];
|
||||
|
||||
let mut this = Self {
|
||||
state,
|
||||
_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),
|
||||
};
|
||||
|
||||
this.state.with(|s| {
|
||||
s.desc_ring.init();
|
||||
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
let mac = ETH.ethernet_mac();
|
||||
@ -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);
|
||||
|
||||
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<PacketBuf> {
|
||||
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<TX, RX>,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, const TX: usize, const RX: usize> Inner<'d, T, TX, RX> {
|
||||
pub fn new(_peri: impl Peripheral<P = T> + '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();
|
||||
|
@ -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<u32>,
|
||||
rdes1: VolatileCell<u32>,
|
||||
rdes2: VolatileCell<u32>,
|
||||
@ -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<const N: usize> {
|
||||
descriptors: [RDes; N],
|
||||
buffers: [Option<PacketBox>; N],
|
||||
read_index: usize,
|
||||
next_tail_index: usize,
|
||||
pub(crate) struct RDesRing<'a> {
|
||||
descriptors: &'a mut [RDes],
|
||||
buffers: &'a mut [Packet<RX_BUFFER_SIZE>],
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<const N: usize> RDesRing<N> {
|
||||
pub const fn new() -> Self {
|
||||
const RDES: RDes = RDes::new();
|
||||
const BUFFERS: Option<PacketBox> = None;
|
||||
impl<'a> RDesRing<'a> {
|
||||
pub(crate) fn new(descriptors: &'a mut [RDes], buffers: &'a mut [Packet<RX_BUFFER_SIZE>]) -> Self {
|
||||
assert!(descriptors.len() > 1);
|
||||
assert!(descriptors.len() == buffers.len());
|
||||
|
||||
Self {
|
||||
descriptors: [RDES; N],
|
||||
buffers: [BUFFERS; N],
|
||||
read_index: 0,
|
||||
next_tail_index: 0,
|
||||
}
|
||||
for (i, entry) in descriptors.iter().enumerate() {
|
||||
entry.setup(descriptors.get(i + 1), buffers[i].0.as_mut_ptr());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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<const N: usize> RDesRing<N> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pop_packet(&mut self) -> Option<PacketBuf> {
|
||||
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);
|
||||
// If packet is invalid, pop it and try again.
|
||||
if !descriptor.valid() {
|
||||
warn!("invalid packet: {:08x}", descriptor.rdes0.get());
|
||||
self.pop_packet();
|
||||
continue;
|
||||
}
|
||||
|
||||
// "Preceding reads and writes cannot be moved past subsequent writes."
|
||||
fence(Ordering::Release);
|
||||
break;
|
||||
}
|
||||
|
||||
self.next_tail_index = (self.next_tail_index + 1) % N;
|
||||
let descriptor = &mut self.descriptors[self.index];
|
||||
let len = descriptor.packet_len();
|
||||
return Some(&mut self.buffers[self.index].0[..len]);
|
||||
}
|
||||
None => {}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
@ -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<u32>,
|
||||
tdes1: VolatileCell<u32>,
|
||||
tdes2: VolatileCell<u32>,
|
||||
@ -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<const N: usize> {
|
||||
descriptors: [TDes; N],
|
||||
buffers: [Option<PacketBuf>; N],
|
||||
next_entry: usize,
|
||||
pub(crate) struct TDesRing<'a> {
|
||||
descriptors: &'a mut [TDes],
|
||||
buffers: &'a mut [Packet<TX_BUFFER_SIZE>],
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<const N: usize> TDesRing<N> {
|
||||
pub const fn new() -> Self {
|
||||
const TDES: TDes = TDes::new();
|
||||
const BUFFERS: Option<PacketBuf> = 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<TX_BUFFER_SIZE>]) -> 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));
|
||||
for (i, entry) in descriptors.iter().enumerate() {
|
||||
entry.setup(descriptors.get(i + 1));
|
||||
}
|
||||
previous = Some(entry);
|
||||
}
|
||||
|
||||
if let Some(entry) = &mut previous {
|
||||
entry.setup(None);
|
||||
}
|
||||
}
|
||||
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)) };
|
||||
Self {
|
||||
descriptors,
|
||||
buffers,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if a TDes is available for use
|
||||
pub(crate) fn available(&self) -> bool {
|
||||
self.descriptors[self.next_entry].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 descriptor = &mut self.descriptors[self.index];
|
||||
if descriptor.available() {
|
||||
Some(&mut self.buffers[self.index].0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
let descriptor = &mut self.descriptors[self.next_entry];
|
||||
/// 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());
|
||||
|
||||
let pkt_len = pkt.len();
|
||||
let address = pkt.as_ptr() as *const u8;
|
||||
|
||||
descriptor.set_buffer1(address);
|
||||
descriptor.set_buffer1_len(pkt_len);
|
||||
|
||||
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<const N: usize> TDesRing<N> {
|
||||
// "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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<u32>,
|
||||
tdes1: VolatileCell<u32>,
|
||||
tdes2: VolatileCell<u32>,
|
||||
@ -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<const N: usize> {
|
||||
td: [TDes; N],
|
||||
buffers: [Option<PacketBuf>; N],
|
||||
tdidx: usize,
|
||||
pub(crate) struct TDesRing<'a> {
|
||||
descriptors: &'a mut [TDes],
|
||||
buffers: &'a mut [Packet<TX_BUFFER_SIZE>],
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<const N: usize> TDesRing<N> {
|
||||
pub const fn new() -> Self {
|
||||
const TDES: TDes = TDes::new();
|
||||
const BUFFERS: Option<PacketBuf> = None;
|
||||
impl<'a> TDesRing<'a> {
|
||||
/// Initialise this TDesRing. Assume TDesRing is corrupt.
|
||||
pub fn new(descriptors: &'a mut [TDes], buffers: &'a mut [Packet<TX_BUFFER_SIZE>]) -> 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<const N: usize> TDesRing<N> {
|
||||
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<const N: usize> TDesRing<N> {
|
||||
/// * rdes2:
|
||||
/// * rdes3: OWN and Status
|
||||
#[repr(C)]
|
||||
struct RDes {
|
||||
pub(crate) struct RDes {
|
||||
rdes0: VolatileCell<u32>,
|
||||
rdes1: VolatileCell<u32>,
|
||||
rdes2: VolatileCell<u32>,
|
||||
@ -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<const N: usize> {
|
||||
rd: [RDes; N],
|
||||
buffers: [Option<PacketBox>; N],
|
||||
read_idx: usize,
|
||||
next_tail_idx: usize,
|
||||
pub(crate) struct RDesRing<'a> {
|
||||
descriptors: &'a mut [RDes],
|
||||
buffers: &'a mut [Packet<RX_BUFFER_SIZE>],
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<const N: usize> RDesRing<N> {
|
||||
pub const fn new() -> Self {
|
||||
const RDES: RDes = RDes::new();
|
||||
const BUFFERS: Option<PacketBox> = None;
|
||||
impl<'a> RDesRing<'a> {
|
||||
pub(crate) fn new(descriptors: &'a mut [RDes], buffers: &'a mut [Packet<RX_BUFFER_SIZE>]) -> 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<PacketBuf> {
|
||||
/// 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);
|
||||
// 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.rdes3.get() & EMAC_RDES3_PKTLEN) as usize;
|
||||
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());
|
||||
|
||||
// "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);
|
||||
}
|
||||
// signal DMA it can try again.
|
||||
// NOTE(unsafe) Atomic write
|
||||
unsafe { ETH.ethernet_dma().dmacrx_dtpr().write(|w| w.0 = 0) }
|
||||
|
||||
self.next_tail_idx = (self.next_tail_idx + 1) % N;
|
||||
// Increment index.
|
||||
self.index += 1;
|
||||
if self.index == self.descriptors.len() {
|
||||
self.index = 0
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
pkt
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DescriptorRing<const T: usize, const R: usize> {
|
||||
pub(crate) tx: TDesRing<T>,
|
||||
pub(crate) rx: RDesRing<R>,
|
||||
}
|
||||
|
||||
impl<const T: usize, const R: usize> DescriptorRing<T, R> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
tx: TDesRing::new(),
|
||||
rx: RDesRing::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
self.tx.init();
|
||||
self.rx.init();
|
||||
}
|
||||
}
|
||||
|
@ -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<Inner<'d, T, TX, RX>>);
|
||||
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<const TX: usize, const RX: usize>(
|
||||
queue: &'d mut PacketQueue<TX, RX>,
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
interrupt: impl Peripheral<P = crate::interrupt::ETH> + 'd,
|
||||
ref_clk: impl Peripheral<P = impl RefClkPin<T>> + 'd,
|
||||
@ -63,8 +55,9 @@ 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);
|
||||
|
||||
unsafe {
|
||||
// Enable the necessary Clocks
|
||||
// NOTE(unsafe) We have exclusive access to the registers
|
||||
critical_section::with(|_| {
|
||||
@ -81,9 +74,6 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T,
|
||||
|
||||
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();
|
||||
@ -173,7 +163,9 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T,
|
||||
];
|
||||
|
||||
let mut this = Self {
|
||||
state,
|
||||
_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,
|
||||
@ -181,9 +173,6 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T,
|
||||
mac_addr,
|
||||
};
|
||||
|
||||
this.state.with(|s| {
|
||||
s.desc_ring.init();
|
||||
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
let mac = ETH.ethernet_mac();
|
||||
@ -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);
|
||||
|
||||
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<PacketBuf> {
|
||||
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<TX, RX>,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance, const TX: usize, const RX: usize> Inner<'d, T, TX, RX> {
|
||||
pub fn new(_peri: impl Peripheral<P = T> + '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();
|
||||
|
@ -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 }
|
||||
|
449
embassy-usb/src/class/cdc_ncm/embassy_net.rs
Normal file
449
embassy-usb/src/class/cdc_ncm/embassy_net.rs
Normal file
@ -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<MTU>; N_RX],
|
||||
tx: [PacketBuf<MTU>; N_TX],
|
||||
inner: MaybeUninit<StateInner<'d, MTU>>,
|
||||
}
|
||||
|
||||
impl<'d, const MTU: usize, const N_RX: usize, const N_TX: usize> State<'d, MTU, N_RX, N_TX> {
|
||||
const NEW_PACKET: PacketBuf<MTU> = 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<MTU>>,
|
||||
tx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf<MTU>>,
|
||||
link_state: Mutex<NoopRawMutex, RefCell<LinkStateState>>,
|
||||
}
|
||||
|
||||
/// 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<MTU>>,
|
||||
rx_usb: Receiver<'d, D>,
|
||||
rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf<MTU>>,
|
||||
link_state: &'d Mutex<NoopRawMutex, RefCell<LinkStateState>>,
|
||||
}
|
||||
|
||||
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<const MTU: usize, const N_RX: usize, const N_TX: usize>(
|
||||
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<const MTU: usize> {
|
||||
len: usize,
|
||||
buf: [u8; MTU],
|
||||
}
|
||||
|
||||
impl<const MTU: usize> PacketBuf<MTU> {
|
||||
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<MTU>>,
|
||||
tx: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf<MTU>>,
|
||||
link_state: &'d Mutex<NoopRawMutex, RefCell<LinkStateState>>,
|
||||
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<Self::TxToken<'_>> {
|
||||
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<MTU>>,
|
||||
}
|
||||
|
||||
impl<'a, const MTU: usize> embassy_net::device::RxToken for RxToken<'a, MTU> {
|
||||
fn consume<R, F>(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<MTU>>,
|
||||
}
|
||||
|
||||
impl<'a, const MTU: usize> embassy_net::device::TxToken for TxToken<'a, MTU> {
|
||||
fn consume<R, F>(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<M, RefCell<State>>,
|
||||
}
|
||||
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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 }
|
||||
|
||||
|
@ -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<T> = 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<Device>) -> ! {
|
||||
async fn net_task(stack: &'static Stack<Device<'static, MTU>>) -> ! {
|
||||
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::<MTU, 4, 4>(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<ThreadModeRawMutex, PacketBuf, 8> = Channel::new();
|
||||
static RX_CHANNEL: Channel<ThreadModeRawMutex, PacketBuf, 8> = 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<PacketBuf> {
|
||||
RX_CHANNEL.try_recv().ok()
|
||||
}
|
||||
|
||||
fn ethernet_address(&self) -> [u8; 6] {
|
||||
self.mac_addr
|
||||
}
|
||||
}
|
||||
|
@ -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" }
|
||||
|
@ -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<T> = 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<Device>) -> ! {
|
||||
async fn net_task(stack: &'static Stack<Device<'static, MTU>>) -> ! {
|
||||
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::<MTU, 4, 4>(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<ThreadModeRawMutex, PacketBuf, 8> = Channel::new();
|
||||
static RX_CHANNEL: Channel<ThreadModeRawMutex, PacketBuf, 8> = 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<PacketBuf> {
|
||||
RX_CHANNEL.try_recv().ok()
|
||||
}
|
||||
|
||||
fn ethernet_address(&self) -> [u8; 6] {
|
||||
self.mac_addr
|
||||
}
|
||||
}
|
||||
|
@ -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<TunTap>,
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
impl TunTapDevice {
|
||||
pub fn new(name: &str) -> io::Result<TunTapDevice> {
|
||||
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<PacketBuf> {
|
||||
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<Self::TxToken<'_>> {
|
||||
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<u8>,
|
||||
}
|
||||
|
||||
impl device::RxToken for RxToken {
|
||||
fn consume<R, F>(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<TunTap>,
|
||||
}
|
||||
|
||||
impl<'a> device::TxToken for TxToken<'a> {
|
||||
fn consume<R, F>(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
|
||||
}
|
||||
}
|
||||
|
@ -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"] }
|
||||
|
||||
|
@ -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<T> = 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<Device>) -> ! {
|
||||
@ -50,9 +51,8 @@ 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()),
|
||||
let device = Ethernet::new(
|
||||
singleton!(PacketQueue::<16, 16>::new()),
|
||||
p.ETH,
|
||||
eth_int,
|
||||
p.PA1,
|
||||
@ -67,8 +67,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
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;
|
||||
|
@ -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"] }
|
||||
|
||||
|
@ -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<T> = 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<Device>) -> ! {
|
||||
@ -51,9 +52,8 @@ 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()),
|
||||
let device = Ethernet::new(
|
||||
singleton!(PacketQueue::<16, 16>::new()),
|
||||
p.ETH,
|
||||
eth_int,
|
||||
p.PA1,
|
||||
@ -68,8 +68,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
GenericSMI,
|
||||
mac_addr,
|
||||
0,
|
||||
)
|
||||
};
|
||||
);
|
||||
|
||||
let config = embassy_net::ConfigStrategy::Dhcp;
|
||||
//let config = embassy_net::ConfigStrategy::Static(embassy_net::Config {
|
||||
|
@ -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<T> = 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<Device>) -> ! {
|
||||
@ -52,9 +53,8 @@ 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()),
|
||||
let device = Ethernet::new(
|
||||
singleton!(PacketQueue::<16, 16>::new()),
|
||||
p.ETH,
|
||||
eth_int,
|
||||
p.PA1,
|
||||
@ -69,8 +69,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
GenericSMI,
|
||||
mac_addr,
|
||||
0,
|
||||
)
|
||||
};
|
||||
);
|
||||
|
||||
let config = embassy_net::ConfigStrategy::Dhcp;
|
||||
//let config = embassy_net::ConfigStrategy::Static(embassy_net::Config {
|
||||
|
@ -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"
|
||||
|
@ -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<T> = 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<Device>) -> ! {
|
||||
async fn net_task(stack: &'static Stack<Device<'static, MTU>>) -> ! {
|
||||
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::<MTU, 4, 4>(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<ThreadModeRawMutex, PacketBuf, 8> = Channel::new();
|
||||
static RX_CHANNEL: Channel<ThreadModeRawMutex, PacketBuf, 8> = 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<PacketBuf> {
|
||||
RX_CHANNEL.try_recv().ok()
|
||||
}
|
||||
|
||||
fn ethernet_address(&self) -> [u8; 6] {
|
||||
self.mac_addr
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user