#![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 = 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 }