use core::cell::Cell;
use core::marker::PhantomData;
use core::sync::atomic::{AtomicU32, Ordering};
use core::task::Context;
use critical_section::Mutex;
use embassy_net_driver::{
Capabilities, ChecksumCapabilities, Driver, HardwareAddress, LinkState, RxToken, TxToken,
};
use embassy_sync::waitqueue::AtomicWaker;
use crate::emac::{Emac, DEFAULT_BUF, DEFAULT_RX, DEFAULT_TX, SMALL_RX, SMALL_TX};
use crate::interrupt::InterruptStatus;
#[derive(Debug, Clone, Copy, Default)]
pub struct DriverCounters {
pub rx_calls: u32,
pub rx_some: u32,
pub rx_dropped: u32,
pub tx_calls: u32,
pub tx_some: u32,
pub tx_dropped: u32,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct IrqCounters {
pub total: u32,
pub ri: u32,
pub ru: u32,
pub ti: u32,
pub tu: u32,
pub eri: u32,
pub err: u32,
pub last_dmastat: u32,
}
const MAX_FRAME_SIZE: usize = 1600;
const ETH_MTU: usize = 1514;
pub struct EmacDriverState {
rx_waker: AtomicWaker,
tx_waker: AtomicWaker,
link_waker: AtomicWaker,
link_state: Mutex<Cell<LinkState>>,
irq_count: AtomicU32,
irq_ri: AtomicU32,
irq_ru: AtomicU32,
irq_ti: AtomicU32,
irq_tu: AtomicU32,
irq_eri: AtomicU32,
irq_err: AtomicU32,
last_dmastat: AtomicU32,
drv_rx_calls: AtomicU32,
drv_rx_some: AtomicU32,
drv_rx_dropped: AtomicU32,
drv_tx_calls: AtomicU32,
drv_tx_some: AtomicU32,
drv_tx_dropped: AtomicU32,
}
impl Default for EmacDriverState {
fn default() -> Self {
Self::new()
}
}
impl EmacDriverState {
pub const fn new() -> Self {
Self {
rx_waker: AtomicWaker::new(),
tx_waker: AtomicWaker::new(),
link_waker: AtomicWaker::new(),
link_state: Mutex::new(Cell::new(LinkState::Down)),
irq_count: AtomicU32::new(0),
irq_ri: AtomicU32::new(0),
irq_ru: AtomicU32::new(0),
irq_ti: AtomicU32::new(0),
irq_tu: AtomicU32::new(0),
irq_eri: AtomicU32::new(0),
irq_err: AtomicU32::new(0),
last_dmastat: AtomicU32::new(0),
drv_rx_calls: AtomicU32::new(0),
drv_rx_some: AtomicU32::new(0),
drv_rx_dropped: AtomicU32::new(0),
drv_tx_calls: AtomicU32::new(0),
drv_tx_some: AtomicU32::new(0),
drv_tx_dropped: AtomicU32::new(0),
}
}
pub fn irq_counters(&self) -> IrqCounters {
IrqCounters {
total: self.irq_count.load(Ordering::Relaxed),
ri: self.irq_ri.load(Ordering::Relaxed),
ru: self.irq_ru.load(Ordering::Relaxed),
ti: self.irq_ti.load(Ordering::Relaxed),
tu: self.irq_tu.load(Ordering::Relaxed),
eri: self.irq_eri.load(Ordering::Relaxed),
err: self.irq_err.load(Ordering::Relaxed),
last_dmastat: self.last_dmastat.load(Ordering::Relaxed),
}
}
pub fn driver_counters(&self) -> DriverCounters {
DriverCounters {
rx_calls: self.drv_rx_calls.load(Ordering::Relaxed),
rx_some: self.drv_rx_some.load(Ordering::Relaxed),
rx_dropped: self.drv_rx_dropped.load(Ordering::Relaxed),
tx_calls: self.drv_tx_calls.load(Ordering::Relaxed),
tx_some: self.drv_tx_some.load(Ordering::Relaxed),
tx_dropped: self.drv_tx_dropped.load(Ordering::Relaxed),
}
}
pub fn link_state(&self) -> LinkState {
critical_section::with(|cs| self.link_state.borrow(cs).get())
}
pub fn set_link_state(&self, state: LinkState) {
critical_section::with(|cs| self.link_state.borrow(cs).set(state));
self.link_waker.wake();
}
pub fn set_link_up(&self) {
self.set_link_state(LinkState::Up);
}
pub fn set_link_down(&self) {
self.set_link_state(LinkState::Down);
}
pub fn on_interrupt_status(&self, status: InterruptStatus) {
if status.rx_complete || status.rx_buf_unavailable {
self.rx_waker.wake();
}
if status.tx_complete || status.tx_buf_unavailable {
self.tx_waker.wake();
}
if status.has_error() {
self.rx_waker.wake();
self.tx_waker.wake();
}
}
pub fn handle_emac_interrupt(&self) {
let dmastat = crate::regs::dma::BASE + crate::regs::dma::DMASTATUS;
let raw = unsafe { core::ptr::read_volatile(dmastat as *const u32) };
let status = InterruptStatus::from_raw(raw);
unsafe {
core::ptr::write_volatile(
dmastat as *mut u32,
raw & crate::regs::dma::status::ALL_INTERRUPTS,
)
};
self.irq_count.fetch_add(1, Ordering::Relaxed);
self.last_dmastat.store(raw, Ordering::Relaxed);
if status.rx_complete {
self.irq_ri.fetch_add(1, Ordering::Relaxed);
}
if status.rx_buf_unavailable {
self.irq_ru.fetch_add(1, Ordering::Relaxed);
}
if status.tx_complete {
self.irq_ti.fetch_add(1, Ordering::Relaxed);
}
if status.tx_buf_unavailable {
self.irq_tu.fetch_add(1, Ordering::Relaxed);
}
if (raw & crate::regs::dma::status::ERI) != 0 {
self.irq_eri.fetch_add(1, Ordering::Relaxed);
}
if status.has_error() {
self.irq_err.fetch_add(1, Ordering::Relaxed);
}
self.on_interrupt_status(status);
}
}
pub struct EmacDriver<'d, const RX: usize, const TX: usize, const BUF: usize> {
emac: *mut Emac<RX, TX, BUF>,
state: &'d EmacDriverState,
_marker: PhantomData<&'d mut Emac<RX, TX, BUF>>,
}
unsafe impl<const RX: usize, const TX: usize, const BUF: usize> Send for EmacDriver<'_, RX, TX, BUF> where
Emac<RX, TX, BUF>: Send
{
}
impl<'d, const RX: usize, const TX: usize, const BUF: usize> EmacDriver<'d, RX, TX, BUF> {
pub fn new(emac: &'d mut Emac<RX, TX, BUF>, state: &'d EmacDriverState) -> Self {
Self {
emac: emac as *mut _,
state,
_marker: PhantomData,
}
}
pub fn state(&self) -> &EmacDriverState {
self.state
}
pub const fn effective_mtu() -> usize {
let ring_capacity = TX * BUF;
if ring_capacity < ETH_MTU {
ring_capacity
} else {
ETH_MTU
}
}
}
pub type EmacDefaultDriver<'d> = EmacDriver<'d, DEFAULT_RX, DEFAULT_TX, DEFAULT_BUF>;
pub type EmacSmallDriver<'d> = EmacDriver<'d, SMALL_RX, SMALL_TX, DEFAULT_BUF>;
pub struct EmacRxToken<'a, const RX: usize, const TX: usize, const BUF: usize> {
emac: *mut Emac<RX, TX, BUF>,
state: &'a EmacDriverState,
_marker: PhantomData<&'a mut Emac<RX, TX, BUF>>,
}
impl<const RX: usize, const TX: usize, const BUF: usize> RxToken for EmacRxToken<'_, RX, TX, BUF> {
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
let mut buffer = [0u8; MAX_FRAME_SIZE];
let emac = unsafe { &mut *self.emac };
match emac.receive(&mut buffer) {
Ok(Some(n)) => f(&mut buffer[..n]),
Ok(None) | Err(_) => {
self.state.drv_rx_dropped.fetch_add(1, Ordering::Relaxed);
f(&mut [])
}
}
}
}
pub struct EmacTxToken<'a, const RX: usize, const TX: usize, const BUF: usize> {
emac: *mut Emac<RX, TX, BUF>,
state: &'a EmacDriverState,
_marker: PhantomData<&'a mut Emac<RX, TX, BUF>>,
}
impl<const RX: usize, const TX: usize, const BUF: usize> TxToken for EmacTxToken<'_, RX, TX, BUF> {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
let len = len.min(MAX_FRAME_SIZE);
let mut buffer = [0u8; MAX_FRAME_SIZE];
let result = f(&mut buffer[..len]);
let emac = unsafe { &mut *self.emac };
if emac.transmit(&buffer[..len]).is_err() {
self.state.drv_tx_dropped.fetch_add(1, Ordering::Relaxed);
}
result
}
}
impl<const RX: usize, const TX: usize, const BUF: usize> Driver for EmacDriver<'_, RX, TX, BUF> {
type RxToken<'a>
= EmacRxToken<'a, RX, TX, BUF>
where
Self: 'a;
type TxToken<'a>
= EmacTxToken<'a, RX, TX, BUF>
where
Self: 'a;
fn receive(&mut self, cx: &mut Context<'_>) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
self.state.drv_rx_calls.fetch_add(1, Ordering::Relaxed);
let emac = unsafe { &mut *self.emac };
if !emac.rx_available() {
self.state.rx_waker.register(cx.waker());
if !emac.rx_available() {
return None;
}
}
self.state.drv_rx_some.fetch_add(1, Ordering::Relaxed);
Some((
EmacRxToken {
emac: self.emac,
state: self.state,
_marker: PhantomData,
},
EmacTxToken {
emac: self.emac,
state: self.state,
_marker: PhantomData,
},
))
}
fn transmit(&mut self, cx: &mut Context<'_>) -> Option<Self::TxToken<'_>> {
self.state.drv_tx_calls.fetch_add(1, Ordering::Relaxed);
let emac = unsafe { &mut *self.emac };
let mtu = Self::effective_mtu();
if !emac.can_transmit(mtu) {
self.state.tx_waker.register(cx.waker());
if !emac.can_transmit(mtu) {
return None;
}
}
self.state.drv_tx_some.fetch_add(1, Ordering::Relaxed);
Some(EmacTxToken {
emac: self.emac,
state: self.state,
_marker: PhantomData,
})
}
fn link_state(&mut self, cx: &mut Context<'_>) -> LinkState {
self.state.link_waker.register(cx.waker());
self.state.link_state()
}
fn capabilities(&self) -> Capabilities {
let mut caps = Capabilities::default();
caps.max_transmission_unit = Self::effective_mtu();
caps.max_burst_size = Some(1);
caps.checksum = ChecksumCapabilities::default();
caps
}
fn hardware_address(&self) -> HardwareAddress {
let emac = unsafe { &*self.emac };
HardwareAddress::Ethernet(emac.mac_address())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn state_starts_link_down() {
let s = EmacDriverState::new();
assert!(matches!(s.link_state(), LinkState::Down));
}
#[test]
fn state_link_set_up_then_down() {
let s = EmacDriverState::new();
s.set_link_up();
assert!(matches!(s.link_state(), LinkState::Up));
s.set_link_down();
assert!(matches!(s.link_state(), LinkState::Down));
}
#[test]
fn state_static_compatible() {
static STATE: EmacDriverState = EmacDriverState::new();
assert!(matches!(STATE.link_state(), LinkState::Down));
}
fn test_emac() -> Emac<10, 10, 1600> {
use crate::config::{ClkGpio, EmacConfig, RmiiClockConfig, RmiiPins, XtalFreq};
Emac::new(EmacConfig {
clock: RmiiClockConfig::InternalApll {
gpio: ClkGpio::Gpio17,
xtal: XtalFreq::Mhz40,
},
pins: RmiiPins { mdc: 23, mdio: 18 },
})
}
#[test]
fn driver_capabilities_advertise_mtu_and_burst() {
let mut emac = test_emac();
let state = EmacDriverState::new();
let driver = EmacDriver::new(&mut emac, &state);
let caps = driver.capabilities();
assert_eq!(caps.max_transmission_unit, ETH_MTU);
assert_eq!(caps.max_transmission_unit, 1514);
assert_eq!(caps.max_burst_size, Some(1));
}
#[test]
fn effective_mtu_caps_to_ring_capacity() {
assert_eq!(EmacDriver::<10, 10, 1600>::effective_mtu(), ETH_MTU);
assert_eq!(EmacDriver::<4, 4, 1600>::effective_mtu(), ETH_MTU);
assert_eq!(EmacDriver::<2, 2, 512>::effective_mtu(), 1024);
assert_eq!(EmacDriver::<1, 1, 1514>::effective_mtu(), ETH_MTU);
assert_eq!(EmacDriver::<1, 1, 1513>::effective_mtu(), 1513);
}
#[test]
fn driver_hardware_address_reflects_cached_mac() {
let mut emac = test_emac();
{
let state = EmacDriverState::new();
let driver = EmacDriver::new(&mut emac, &state);
let HardwareAddress::Ethernet(mac) = driver.hardware_address() else {
panic!("expected Ethernet hardware address");
};
assert_eq!(mac, [0u8; 6]);
}
let custom = [0xF0, 0x57, 0x8D, 0x01, 0x04, 0xE3];
emac.set_mac_address(custom);
let state = EmacDriverState::new();
let driver = EmacDriver::new(&mut emac, &state);
let HardwareAddress::Ethernet(mac) = driver.hardware_address() else {
panic!("expected Ethernet hardware address");
};
assert_eq!(mac, custom);
}
}