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]

//! Cold-boot hang diagnostic.
//!
//! Mirrors `embassy_tcp_echo_irq.rs`'s opening sequence verbatim
//! (`time_driver_irq::init()` + `release_waveshare_phy_reset()` + 5M cycle
//! wait), then calls `Ethernet::try_new` synchronously and prints the
//! outcome. No embassy executor, no rx/tx — only what's needed to reproduce
//! the suspected `phy.init` hang in isolation.
//!
//! Run two passes for the bisection:
//!   1. Cold: power-cycle the board, flash and monitor this directly.
//!   2. Warm: run `mdio_test.elf` first (espflash --after no-reset --monitor),
//!      kill monitor, then flash this within ~1 s.
//! Compare the `[phy_init]` markers between the two runs.
//!
//! Build:
//!   cargo build --no-default-features \
//!       --features p4-example,p4-time-driver-irq,phy-init-debug \
//!       --example phy_init_diag

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::{
    release_waveshare_phy_reset, systimer, time_driver_irq, Ethernet, StaticDmaResources,
};
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] = [0xE8, 0xF6, 0x0A, 0xE0, 0x93, 0xF5];

#[link_section = ".dma_bss"]
static DMA_RESOURCES: ConstStaticCell<StaticDmaResources> =
    ConstStaticCell::new(StaticDmaResources::new());

struct Uart0;

impl Uart0 {
    fn write_byte(&mut self, byte: u8) {
        while txfifo_count() >= UART_TXFIFO_CAPACITY {
            spin_loop();
        }
        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(())
    }
}

fn txfifo_count() -> u32 {
    let status = unsafe { read_volatile(UART_STATUS_REG) };
    (status & UART_TXFIFO_CNT_MASK) >> UART_TXFIFO_CNT_SHIFT
}

#[entry]
fn main() -> ! {
    let mut uart = Uart0;
    let _ = writeln!(uart, "\n=== phy_init_diag alive ===");

    // Match embassy_tcp_echo_irq exactly: configure SYSTIMER alarm + CLIC
    // routing + mtvec BEFORE the PHY reset deassert. Hypothesis: the IRQ
    // setup may matter for cold-boot phy.init behaviour vs. plain mdio_test.
    time_driver_irq::init();
    let _ = writeln!(uart, "diag: time_driver_irq::init() done, t={}us", systimer::now_us());

    release_waveshare_phy_reset();
    for _ in 0..5_000_000 {
        spin_loop();
    }
    let _ = writeln!(uart, "diag: PHY reset deasserted, waited 5M cycles, t={}us", systimer::now_us());

    let resources = DMA_RESOURCES.take();
    let (tx_desc, rx_desc, tx_buf, rx_buf) = resources.split();
    let _ = writeln!(uart, "diag: DMA resources allocated, t={}us", systimer::now_us());

    let _ = writeln!(uart, "diag: calling Ethernet::try_new ...");
    let t0 = systimer::now_us();
    let result = Ethernet::try_new(MAC_ADDR, tx_desc, rx_desc, tx_buf, rx_buf);
    let elapsed_us = systimer::now_us().wrapping_sub(t0);

    match result {
        Ok(_eth) => {
            let _ = writeln!(uart, "diag: Ethernet::try_new OK in {} us", elapsed_us);
        }
        Err(e) => {
            let _ = writeln!(uart, "diag: Ethernet::try_new ERR after {} us: {:?}", elapsed_us, e);
        }
    }

    // Post-state register dump regardless of outcome — same fields as
    // mdio_test for side-by-side comparison.
    let _ = writeln!(uart, "--- post-call register dump ---");
    let r = |a: usize| unsafe { read_volatile(a as *const u32) };
    let _ = writeln!(uart, "  PERI_CLK_CTRL00  = 0x{:08X}", r(0x500E_6030));
    let _ = writeln!(uart, "  PERI_CLK_CTRL01  = 0x{:08X}", r(0x500E_6034));
    let _ = writeln!(uart, "  LP_HP_CLK_CTRL   = 0x{:08X}", r(0x5011_1040));
    let _ = writeln!(uart, "  SYS_GMAC_CTRL0   = 0x{:08X}", r(0x500E_514C));
    let _ = writeln!(uart, "  MAC_CONFIG       = 0x{:08X}", r(0x5009_8000));
    let _ = writeln!(uart, "  MAC_FRAME_FILTER = 0x{:08X}", r(0x5009_8004));
    let mii_addr = r(0x5009_8010);
    let _ = writeln!(
        uart,
        "  MII_ADDR         = 0x{:08X}  (csr_clk_range bits[5:2] = 0b{:04b})",
        mii_addr,
        (mii_addr >> 2) & 0xF
    );
    let _ = writeln!(uart, "  MII_DATA         = 0x{:08X}", r(0x5009_8014));
    let _ = writeln!(uart, "  MAC_VERSION      = 0x{:08X}", r(0x5009_8020));
    let _ = writeln!(uart, "  DMA_BUS_MODE     = 0x{:08X}", r(0x5009_9000));
    let _ = writeln!(uart, "  DMA_OP_MODE      = 0x{:08X}", r(0x5009_9018));
    let _ = writeln!(uart, "  DMA_STATUS       = 0x{:08X}", r(0x5009_9014));

    let _ = writeln!(uart, "=== phy_init_diag done ===");
    loop {
        spin_loop();
    }
}