esp-p4-eth 0.1.0

no_std async Ethernet MAC driver for ESP32-P4 RMII designs (embassy-net compatible)
Documentation
#![no_std]
#![no_main]

use core::fmt::{self, Write};
use core::hint::spin_loop;
use core::ptr::{read_volatile, write_volatile};

use panic_halt as _;
use riscv_rt::entry;
use esp_p4_eth::{
    regs, release_waveshare_phy_reset, Dma, DmaError, Ethernet, StaticDmaResources, BUF_SIZE,
};
use static_cell::ConstStaticCell;

const UART0_BASE: usize = 0x500C_A000;
const UART_FIFO_REG: *mut u32 = UART0_BASE as *mut u32;
const UART_STATUS_REG: *const u32 = (UART0_BASE + 0x1C) as *const u32;
const UART_TXFIFO_CNT_SHIFT: u32 = 16;
const UART_TXFIFO_CNT_MASK: u32 = 0xFF << UART_TXFIFO_CNT_SHIFT;
const UART_TXFIFO_CAPACITY: u32 = 128;

const MAC_ADDR: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01];
const LOOPBACK_POLL_SPINS: usize = 200_000;

static DMA_RESOURCES: ConstStaticCell<StaticDmaResources> =
    ConstStaticCell::new(StaticDmaResources::new());

struct Uart0;

#[derive(Clone, Copy)]
enum Pattern {
    Zeroes,
    Ones,
    Alternating,
    Sequential,
}

impl Uart0 {
    fn write_byte(&mut self, byte: u8) {
        while txfifo_count() >= UART_TXFIFO_CAPACITY {
            spin_loop();
        }

        // SAFETY: `UART_FIFO_REG` is the fixed UART0 TX FIFO MMIO register. Volatile write is
        // required so the compiler preserves the hardware side effect.
        unsafe {
            write_volatile(UART_FIFO_REG, byte as u32);
        }
    }
}

impl Write for Uart0 {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for byte in s.bytes() {
            if byte == b'\n' {
                self.write_byte(b'\r');
            }
            self.write_byte(byte);
        }

        Ok(())
    }
}

#[entry]
fn main() -> ! {
    let mut uart = Uart0;
    let _ = writeln!(uart, "EMAC loopback smoke test");

    release_waveshare_phy_reset();
    for _ in 0..3_000_000 {
        spin_loop();
    }

    match Dma::dma_reset() {
        Ok(()) => {
            let _ = writeln!(uart, "DMA reset OK");
        }
        Err(DmaError::ResetTimeout) => {
            panic!("DMA reset timed out");
        }
    }

    Dma::dma_init();

    let dma_resources = DMA_RESOURCES.take();
    let ethernet = Ethernet::new_from_static_resources(MAC_ADDR, dma_resources);
    let mut ethernet = ethernet;

    ethernet.set_mac_addr(MAC_ADDR);
    enable_mac_loopback();
    start_dma_engines();

    let mut frame = [0u8; BUF_SIZE];
    let mut rx_frame = [0u8; BUF_SIZE];

    run_case(
        &mut uart,
        &mut ethernet,
        &mut frame,
        &mut rx_frame,
        64,
        Pattern::Zeroes,
        0,
    );
    run_case(
        &mut uart,
        &mut ethernet,
        &mut frame,
        &mut rx_frame,
        1514,
        Pattern::Ones,
        1,
    );

    for seq in 0..100u8 {
        let size = 64 + ((usize::from(seq) * 37) % (1514 - 64 + 1));
        let pattern = match seq & 0x03 {
            0 => Pattern::Zeroes,
            1 => Pattern::Ones,
            2 => Pattern::Alternating,
            _ => Pattern::Sequential,
        };

        run_case(
            &mut uart,
            &mut ethernet,
            &mut frame,
            &mut rx_frame,
            size,
            pattern,
            seq.wrapping_add(2),
        );
    }

    let _ = writeln!(uart, "Loopback OK");

    loop {
        spin_loop();
    }
}

fn run_case(
    uart: &mut Uart0,
    ethernet: &mut Ethernet<'_>,
    frame: &mut [u8; BUF_SIZE],
    rx_frame: &mut [u8; BUF_SIZE],
    len: usize,
    pattern: Pattern,
    sequence: u8,
) {
    fill_frame(frame, len, pattern, sequence);
    let tx = &frame[..len];

    ethernet.transmit(tx).expect("TX enqueue failed");
    Dma::demand_rx_poll();

    let rx_len = wait_for_frame(ethernet, rx_frame);
    assert_eq!(len, rx_len);
    assert_eq!(tx, &rx_frame[..rx_len]);

    let _ = writeln!(uart, "loopback ok: len={len} seq={sequence}");
}

fn wait_for_frame(ethernet: &mut Ethernet<'_>, rx_frame: &mut [u8; BUF_SIZE]) -> usize {
    for _ in 0..LOOPBACK_POLL_SPINS {
        if let Some((len, data)) = ethernet.receive() {
            rx_frame[..len].copy_from_slice(&data[..len]);
            ethernet.pop_rx();
            return len;
        }

        spin_loop();
    }

    panic!("loopback receive timeout");
}

fn fill_frame(frame: &mut [u8; BUF_SIZE], len: usize, pattern: Pattern, sequence: u8) {
    assert!(len <= 1514);
    assert!(len >= 64);

    for (i, byte) in frame[..len].iter_mut().enumerate() {
        *byte = match pattern {
            Pattern::Zeroes => 0x00,
            Pattern::Ones => 0xFF,
            Pattern::Alternating => {
                if i & 1 == 0 {
                    0x55
                } else {
                    0xAA
                }
            }
            Pattern::Sequential => i as u8,
        };
    }

    frame[0] = 0x02;
    frame[1] = 0x11;
    frame[2] = 0x22;
    frame[3] = 0x33;
    frame[4] = 0x44;
    frame[5] = 0x55;

    frame[6..12].copy_from_slice(&MAC_ADDR);
    frame[12] = 0x08;
    frame[13] = 0x00;
    frame[14] = sequence;
}

fn enable_mac_loopback() {
    let maccfg = regs::read(regs::mac::MACCFG);
    regs::write(
        regs::mac::MACCFG,
        maccfg | regs::bits::maccfg::LM | regs::bits::maccfg::TE | regs::bits::maccfg::RE,
    );
}

fn start_dma_engines() {
    let op_mode = regs::read(regs::dma::OP_MODE);
    regs::write(
        regs::dma::OP_MODE,
        op_mode | regs::bits::dmaopmode::ST | regs::bits::dmaopmode::SR,
    );
}

fn txfifo_count() -> u32 {
    // SAFETY: `UART_STATUS_REG` is the fixed UART0 status MMIO register. Volatile read is
    // required because the value changes independently from the CPU.
    let status = unsafe { read_volatile(UART_STATUS_REG) };
    (status & UART_TXFIFO_CNT_MASK) >> UART_TXFIFO_CNT_SHIFT
}