esp-p4-eth/examples/dev/mdio_test.rs
2026-10-05 01:16:55 +08:00

440 lines
17 KiB
Rust

#![no_std]
#![no_main]
//! Minimal MDIO bring-up test.
//!
//! Bypasses `Ethernet::new_from_static_resources` (known to mis-behave due to
//! large stack frames) by calling `Ethernet::try_new` directly with a static
//! DMA resource block split into the four `&mut` slices it expects. After
//! init, scans every Clause 22 PHY address and dumps the standard register
//! block for the first live PHY. If our clock-tree fixes (no
//! `PAD_EMAC_REF_CLK_EN`, no `HP_PAD_EMAC_TX/RX_CLK_EN`, no
//! `FORCE_NORST_EMAC`) actually work, the IP101GRI at addr=1 should answer
//! with non-zero BMSR/PHYIDR.
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::{
diag::CPU_DESC0_SNAPSHOT, mdio_read, regs, release_waveshare_phy_reset, Dma, Ethernet,
StaticDmaResources, ANAR, ANLPAR, BMCR, BMSR, PHYIDR1, PHYIDR2,
};
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];
// `ConstStaticCell` builds the value in `.bss` at compile time so we never copy
// the ~12 KB `StaticDmaResources` struct through a stack frame — that copy is
// what hangs `StaticCell::init(StaticDmaResources::new())` on this board.
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
}
fn delay_cycles(spins: usize) {
for _ in 0..spins {
spin_loop();
}
}
#[entry]
fn main() -> ! {
let mut uart = Uart0;
let _ = writeln!(uart, "\r\n=== mdio_test alive ===");
release_waveshare_phy_reset();
delay_cycles(3_000_000);
let _ = writeln!(uart, "phy reset released");
let resources = DMA_RESOURCES.take();
let (tx_desc, rx_desc, tx_buf, rx_buf) = resources.split();
let _ = writeln!(uart, "DMA resources allocated");
let mut ethernet = match Ethernet::try_new(MAC_ADDR, tx_desc, rx_desc, tx_buf, rx_buf) {
Ok(eth) => {
let _ = writeln!(uart, "Ethernet::try_new OK");
eth
}
Err(e) => {
let _ = writeln!(uart, "Ethernet::try_new ERR: {:?}", e);
loop {
spin_loop();
}
}
};
// Some chips need MAC enabled for MDIO transactions to gate properly.
match ethernet.start() {
Ok(()) => {
let _ = writeln!(uart, "ethernet.start() OK");
}
Err(e) => {
let _ = writeln!(uart, "ethernet.start() ERR: {:?}", e);
}
}
delay_cycles(2_000_000);
// Control test: dump descriptor[0] state both via memory (after invalidate)
// and via cached read, plus the snapshot of what CPU just wrote in reset().
// If THIS run shows non-zero memory, then embassy_dhcp's setup is somehow
// breaking writeback. If it ALSO shows zeros, the writeback path is broken
// for both, and we have to look elsewhere for why mdio_test "works".
{
let base = regs::read(regs::dma::RX_DESC_LIST);
let mem = unsafe { Dma::peek_rdes(base) };
let cached = unsafe { Dma::peek_rdes_cached(base) };
use core::sync::atomic::Ordering;
let _ = writeln!(uart, "--- DESCRIPTOR[0] PEEK CONTROL ---");
let _ = writeln!(uart, " RX_DESC_LIST register = 0x{:08X}", base);
let _ = writeln!(
uart,
" CPU snapshot : rdes0=0x{:08X} rdes1=0x{:08X} buf=0x{:08X} next=0x{:08X}",
CPU_DESC0_SNAPSHOT[0].load(Ordering::Relaxed),
CPU_DESC0_SNAPSHOT[1].load(Ordering::Relaxed),
CPU_DESC0_SNAPSHOT[2].load(Ordering::Relaxed),
CPU_DESC0_SNAPSHOT[3].load(Ordering::Relaxed),
);
let _ = writeln!(
uart,
" cached (no inv): rdes0=0x{:08X} rdes1=0x{:08X} buf=0x{:08X} next=0x{:08X}",
cached[0], cached[1], cached[2], cached[3],
);
let _ = writeln!(
uart,
" mem (after inv): rdes0=0x{:08X} rdes1=0x{:08X} buf=0x{:08X} next=0x{:08X}",
mem[0], mem[1], mem[2], mem[3],
);
}
// Full post-init register dump before we touch MDIO. This is the side-by-
// side reference for the IDF baseline state captured after Link Up:
// IDF: MAC_CONFIG=0xCC0C MAC_FRAME_FILTER=0x10 MII_ADDR=0x0940
// DMA_BUS_MODE=0x06022080 DMA_OP_MODE=0x2006 DMA_STATUS=0x00660000
// PERI_CLK_CTRL00=0x2800D05D PERI_CLK_CTRL01=0x00000601
// LP_HP_CLK_CTRL=0x1FFF9FFD SYS_GMAC_CTRL0=0x000000D0
let _ = writeln!(uart, "--- post-init register dump ---");
let r = |a: usize| unsafe { read_volatile(a as *const u32) };
// GPIO routing for MDC/MDIO/REF_CLK — must match IDF baseline exactly.
// IDF: IO_MUX_GPIO31=0x1802, IO_MUX_GPIO52=0x1A02, IO_MUX_GPIO50=0x3A02,
// GPIO31_OUT_CFG=0x100, GPIO52_OUT_CFG=0x100,
// GPIO_FUNC107_IN_SEL=0x3E (unrouted), GPIO_FUNC108_IN_SEL=0xB4
// (= matrix bit 0x80 | pin 52), GPIO_FUNC109_IN_SEL=0x3E (unrouted),
// GPIO_ENABLE0=0x80000000 (bit 31 = MDC OE),
// GPIO_ENABLE1=0x00180000 (bit 19 = RESET51 OE, bit 20 = MDIO52 OE).
let _ = writeln!(uart, " IO_MUX_GPIO31 = 0x{:08X}", r(0x500E_1004 + 31 * 4));
let _ = writeln!(uart, " IO_MUX_GPIO52 = 0x{:08X}", r(0x500E_1004 + 52 * 4));
let _ = writeln!(uart, " IO_MUX_GPIO50 = 0x{:08X}", r(0x500E_1004 + 50 * 4));
// Real P4 GPIO matrix base: OUT_SEL = 0x558, IN_SEL = 0x158.
let _ = writeln!(uart, " GPIO31_OUT_CFG = 0x{:08X}", r(0x500E_0558 + 31 * 4));
let _ = writeln!(uart, " GPIO52_OUT_CFG = 0x{:08X}", r(0x500E_0558 + 52 * 4));
let _ = writeln!(uart, " FUNC107_IN_SEL = 0x{:08X}", r(0x500E_0158 + 107 * 4));
let _ = writeln!(uart, " FUNC108_IN_SEL = 0x{:08X}", r(0x500E_0158 + 108 * 4));
let _ = writeln!(uart, " FUNC109_IN_SEL = 0x{:08X}", r(0x500E_0158 + 109 * 4));
let _ = writeln!(uart, " GPIO_ENABLE0 = 0x{:08X}", r(0x500E_0020));
let _ = writeln!(uart, " GPIO_ENABLE1 = 0x{:08X}", r(0x500E_002C));
let _ = writeln!(uart, " GPIO_OUT0 = 0x{:08X}", r(0x500E_0004));
let _ = writeln!(uart, " GPIO_OUT1 = 0x{:08X}", r(0x500E_0010));
let _ = writeln!(uart, " IO_MUX_GPIO51 = 0x{:08X}", r(0x500E_1004 + 51 * 4));
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));
// Force csr_clock_range to 0b0000 (= "AHB 60-100 MHz" = highest MDC
// divider). This produces the slowest legal MDC regardless of actual AHB
// frequency. If MDC was too fast (because actual AHB is ~80-90 MHz but
// mac_init programmed the divider for AHB=40 MHz), MDIO transactions get
// mistimed and PHY rejects them. Set this to match the IDF baseline value
// (bits[5:2] = 0b0000) before scanning.
unsafe {
let mii_addr = 0x5009_8010 as *mut u32;
let v = read_volatile(mii_addr);
let v_new = v & !(0xF << 2);
write_volatile(mii_addr, v_new);
let _ = writeln!(
uart,
" MII_ADDR forced csr_clk=0 -> 0x{:08X}",
read_volatile(mii_addr as *const u32)
);
}
// Probe a single MDIO read against the strap address (PHY=1, BMSR) and
// dump MII_ADDR + MII_DATA before/after to catch a stuck-BUSY case.
let _ = writeln!(uart, "--- single MDIO probe (PHY=1 BMSR) ---");
let pre_addr = r(0x5009_8010);
let pre_data = r(0x5009_8014);
let _ = writeln!(
uart,
" pre-read MII_ADDR=0x{:08X} MII_DATA=0x{:08X}",
pre_addr, pre_data
);
let v = mdio_read(1, BMSR).unwrap_or(0xDEAD);
let post_addr = r(0x5009_8010);
let post_data = r(0x5009_8014);
let _ = writeln!(
uart,
" post-read MII_ADDR=0x{:08X} MII_DATA=0x{:08X} return=0x{:04X}",
post_addr, post_data, v
);
let _ = writeln!(uart, "--- MDIO scan (32 addresses) ---");
let mut found: Option<u8> = None;
for addr in 0u8..32 {
let v = mdio_read(addr, BMSR).unwrap_or(0xDEAD);
let alive = v != 0x0000 && v != 0xFFFF && v != 0xDEAD;
if alive {
let _ = writeln!(uart, " addr={:>2} BMSR=0x{:04X} <- LIVE", addr, v);
if found.is_none() {
found = Some(addr);
}
}
}
let phy_addr = match found {
Some(addr) => {
let _ = writeln!(uart, "PHY found at addr {}", addr);
addr
}
None => {
let _ = writeln!(uart, "NO live PHY on the bus (all addresses returned 0x0000)");
loop {
spin_loop();
}
}
};
let id1 = mdio_read(phy_addr, PHYIDR1).unwrap_or(0xDEAD);
let id2 = mdio_read(phy_addr, PHYIDR2).unwrap_or(0xDEAD);
let bmcr = mdio_read(phy_addr, BMCR).unwrap_or(0xDEAD);
let bmsr = mdio_read(phy_addr, BMSR).unwrap_or(0xDEAD);
let anar = mdio_read(phy_addr, ANAR).unwrap_or(0xDEAD);
let anlpar = mdio_read(phy_addr, ANLPAR).unwrap_or(0xDEAD);
let _ = writeln!(uart, "PHY@{}: ID1=0x{:04X} ID2=0x{:04X}", phy_addr, id1, id2);
let _ = writeln!(
uart,
" BMCR=0x{:04X} BMSR=0x{:04X} ANAR=0x{:04X} ANLPAR=0x{:04X}",
bmcr, bmsr, anar, anlpar
);
// For IP101GRI, PHYIDR1 = 0x0243, PHYIDR2 high bits = 0x0C50 family.
if id1 == 0x0243 {
let _ = writeln!(uart, "PHY ID matches IP101 family (0x0243). MDIO IS WORKING.");
}
// Poll BMSR for ~15 seconds, print every change. Auto-neg with a working
// partner takes ~3 sec on 100M. Looking for link (bit 2) and an_done
// (bit 5) to both go high, plus ANLPAR to populate.
let _ = writeln!(uart, "--- waiting for auto-neg (poll BMSR up to 15s) ---");
let mut last_bmsr = bmsr;
let mut linked = false;
for tick in 0..150u32 {
let v = mdio_read(phy_addr, BMSR).unwrap_or(0xDEAD);
if v != last_bmsr {
let lp = mdio_read(phy_addr, ANLPAR).unwrap_or(0xDEAD);
let _ = writeln!(
uart,
"[t={:>3}] BMSR 0x{:04X} -> 0x{:04X} link={} an_done={} ANLPAR=0x{:04X}",
tick * 100,
last_bmsr,
v,
(v >> 2) & 1,
(v >> 5) & 1,
lp
);
last_bmsr = v;
}
if (v >> 2) & 1 != 0 && (v >> 5) & 1 != 0 {
linked = true;
let _ = writeln!(uart, "[t={:>3}ms] LINK UP", tick * 100);
break;
}
// ~100ms delay; ROM runs CPU at low MHz so 200_000 spins ≈ 100ms.
delay_cycles(200_000);
}
if !linked {
let _ = writeln!(uart, "auto-neg did NOT complete in 15s. Final state:");
} else {
let _ = writeln!(uart, "Final post-link state:");
}
let bmcr2 = mdio_read(phy_addr, BMCR).unwrap_or(0xDEAD);
let bmsr2 = mdio_read(phy_addr, BMSR).unwrap_or(0xDEAD);
let anar2 = mdio_read(phy_addr, ANAR).unwrap_or(0xDEAD);
let anlpar2 = mdio_read(phy_addr, ANLPAR).unwrap_or(0xDEAD);
let _ = writeln!(
uart,
" BMCR=0x{:04X} BMSR=0x{:04X} ANAR=0x{:04X} ANLPAR=0x{:04X}",
bmcr2, bmsr2, anar2, anlpar2
);
let _ = writeln!(
uart,
" decoded: link={} an_done={} 100M={} duplex_full={}",
(bmsr2 >> 2) & 1,
(bmsr2 >> 5) & 1,
((anar2 & anlpar2) >> 7) & 0x3, // bits 7,8 = 100M half/full ability
((anar2 & anlpar2) >> 6) & 0x3, // bits 6 (10full) | 8 (100full)
);
// IP101 specific: register 17 (0x11) = SPECIFIC_STATUS, bits[10:8] = link mode.
let ip101_status = mdio_read(phy_addr, 0x11).unwrap_or(0xDEAD);
let _ = writeln!(uart, " IP101 SPECIFIC_STATUS (reg 0x11) = 0x{:04X}", ip101_status);
if !linked {
let _ = writeln!(uart, "=== no link, skipping TX test ===");
loop {
spin_loop();
}
}
// Align MAC speed/duplex with what the PHY negotiated. Without this the
// RMII reference clock divider stays at the default (10M) while the PHY
// is at 100M → MAC TX bytes get clocked at the wrong rate.
match ethernet.phy_negotiate() {
Ok((speed, duplex)) => {
let s = match speed {
esp_p4_eth::Speed::Mbps10 => "10M",
esp_p4_eth::Speed::Mbps100 => "100M",
};
let d = match duplex {
esp_p4_eth::Duplex::Half => "half",
esp_p4_eth::Duplex::Full => "full",
};
let _ = writeln!(uart, "phy_negotiate OK: speed={} duplex={}", s, d);
}
Err(e) => {
let _ = writeln!(uart, "phy_negotiate ERR: {:?}", e);
}
}
// --- TX test: send a gratuitous ARP request (broadcast) and watch RX ---
// Source MAC = our MAC_ADDR (board strap). Sender IP = 169.254.144.50.
// Target IP = 169.254.144.33 (Windows APIPA on Realtek port). If user is
// on a real LAN, change these to match the local subnet. Either way the
// frame is broadcast and triggers ARP-cache updates on the PC, which
// should reply if it has the matching IP.
const SENDER_IP: [u8; 4] = [169, 254, 144, 50];
const TARGET_IP: [u8; 4] = [169, 254, 144, 33];
let mut arp = [0u8; 64];
build_arp_request(&mut arp, MAC_ADDR, SENDER_IP, TARGET_IP);
let _ = writeln!(
uart,
"ARP who-has {}.{}.{}.{} tell {}.{}.{}.{}",
TARGET_IP[0], TARGET_IP[1], TARGET_IP[2], TARGET_IP[3],
SENDER_IP[0], SENDER_IP[1], SENDER_IP[2], SENDER_IP[3]
);
match ethernet.transmit(&arp) {
Ok(()) => {
let _ = writeln!(uart, "TX OK (60-byte ARP request sent)");
}
Err(e) => {
let _ = writeln!(uart, "TX ERR: {:?}", e);
}
}
// Poll RX for ~5 seconds. Print every frame received.
let _ = writeln!(uart, "--- listening for RX frames (5s) ---");
let mut rx_count = 0u32;
for _ in 0..50u32 {
esp_p4_eth::Dma::demand_rx_poll();
if let Some((len, data)) = ethernet.receive() {
rx_count += 1;
let _ = writeln!(
uart,
"RX #{}: len={} dst={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} src={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} ethertype=0x{:02x}{:02x}",
rx_count,
len,
data[0], data[1], data[2], data[3], data[4], data[5],
data[6], data[7], data[8], data[9], data[10], data[11],
data[12], data[13]
);
ethernet.pop_rx();
}
delay_cycles(200_000); // ~100ms
}
let _ = writeln!(uart, "RX total: {} frames in 5s", rx_count);
if rx_count == 0 {
let _ = writeln!(uart, "(no frames seen — check cable, IP, or ARP cache)");
}
let _ = writeln!(uart, "=== mdio_test done ===");
loop {
spin_loop();
}
}
const BROADCAST_MAC: [u8; 6] = [0xFF; 6];
fn build_arp_request(
frame: &mut [u8; 64],
source_mac: [u8; 6],
sender_ip: [u8; 4],
target_ip: [u8; 4],
) {
frame.fill(0);
frame[0..6].copy_from_slice(&BROADCAST_MAC);
frame[6..12].copy_from_slice(&source_mac);
frame[12..14].copy_from_slice(&0x0806u16.to_be_bytes()); // ARP ethertype
frame[14..16].copy_from_slice(&0x0001u16.to_be_bytes()); // hw type = Ethernet
frame[16..18].copy_from_slice(&0x0800u16.to_be_bytes()); // proto = IPv4
frame[18] = 6; // hw len
frame[19] = 4; // proto len
frame[20..22].copy_from_slice(&0x0001u16.to_be_bytes()); // op = request
frame[22..28].copy_from_slice(&source_mac);
frame[28..32].copy_from_slice(&sender_ip);
// target MAC zeros (unknown)
frame[38..42].copy_from_slice(&target_ip);
}