442 lines
16 KiB
Rust
442 lines
16 KiB
Rust
|
|
#![no_std]
|
||
|
|
#![no_main]
|
||
|
|
|
||
|
|
//! Sustained-throughput stress test on the IRQ-driven path.
|
||
|
|
//!
|
||
|
|
//! Two independent TCP listeners — one purely RX-bound, one purely TX-bound —
|
||
|
|
//! plus a 1-second `stat_task` that prints byte deltas, frame deltas, EMAC
|
||
|
|
//! IRQ deltas, and `dma_recovery_task` activity. The point is to push the
|
||
|
|
//! NIC hard in each direction in isolation and see whether `RU` / abnormal
|
||
|
|
//! recovery actually fires under sustained load (currently AIE is masked
|
||
|
|
//! and recovery lives in a polling task — we want data to decide if that's
|
||
|
|
//! still needed).
|
||
|
|
//!
|
||
|
|
//! ## Wiring
|
||
|
|
//!
|
||
|
|
//! Same Waveshare ESP32-P4-ETH topology as `embassy_tcp_echo_irq`. Static IP
|
||
|
|
//! `192.168.0.50/24`, gateway `192.168.0.1`.
|
||
|
|
//!
|
||
|
|
//! ## Driving load from the host
|
||
|
|
//!
|
||
|
|
//! RX-stress (host → P4):
|
||
|
|
//!
|
||
|
|
//! pv -B 1M /dev/zero | nc 192.168.0.50 7780
|
||
|
|
//!
|
||
|
|
//! TX-stress (P4 → host):
|
||
|
|
//!
|
||
|
|
//! nc 192.168.0.50 7781 | pv -B 1M > /dev/null
|
||
|
|
//!
|
||
|
|
//! Or with iperf3 (any side as server is fine — netcat is enough for
|
||
|
|
//! sustained-rate sanity, iperf3 adds RTT/jitter).
|
||
|
|
|
||
|
|
use core::arch::asm;
|
||
|
|
use core::fmt::{self, Write};
|
||
|
|
use core::hint::spin_loop;
|
||
|
|
use core::ptr::{read_volatile, write_volatile};
|
||
|
|
use core::sync::atomic::{AtomicU32, Ordering};
|
||
|
|
|
||
|
|
// ESP32-P4 HP CPU clock at boot is whatever the ROM bootloader left us at;
|
||
|
|
// no clock-tree init in the bare-metal Rust path. Empirically `mcycle` on
|
||
|
|
// the Andes core ticks at ~40 MHz (XTAL freq) regardless of CPU clock, and
|
||
|
|
// does not halt during wfi — useless for CPU% on its own. `minstret` does
|
||
|
|
// halt during wfi (no instructions retire), so we sample both: mcycle for
|
||
|
|
// wall-clock reference, minstret as the active-cycles signal.
|
||
|
|
|
||
|
|
#[inline(always)]
|
||
|
|
fn mcycle_low() -> u32 {
|
||
|
|
let v: u32;
|
||
|
|
unsafe { asm!("csrr {}, mcycle", out(reg) v, options(nostack, nomem)) };
|
||
|
|
v
|
||
|
|
}
|
||
|
|
|
||
|
|
#[inline(always)]
|
||
|
|
fn minstret_low() -> u32 {
|
||
|
|
let v: u32;
|
||
|
|
unsafe { asm!("csrr {}, minstret", out(reg) v, options(nostack, nomem)) };
|
||
|
|
v
|
||
|
|
}
|
||
|
|
|
||
|
|
use embassy_executor::{Executor, Spawner};
|
||
|
|
use embassy_net::tcp::TcpSocket;
|
||
|
|
use embassy_net::{
|
||
|
|
Config, IpListenEndpoint, Ipv4Address, Ipv4Cidr, Runner as NetRunner, Stack, StackResources,
|
||
|
|
StaticConfigV4,
|
||
|
|
};
|
||
|
|
use embassy_time::{Duration, Timer};
|
||
|
|
use esp_p4_eth::{
|
||
|
|
diag::{
|
||
|
|
CACHE_INV_CALLS, CACHE_INV_TICKS, CACHE_WB_CALLS, CACHE_WB_TICKS, RX_ERROR_FRAMES_TOTAL,
|
||
|
|
RX_FRAMES, RX_OVERSIZED_FRAMES_TOTAL, RX_RUNT_FRAMES_TOTAL, TX_FRAMES,
|
||
|
|
},
|
||
|
|
ethernet_task, new_from_static_resources as emac_new, release_waveshare_phy_reset, systimer,
|
||
|
|
time_driver_irq, Device, StaticDmaResources,
|
||
|
|
};
|
||
|
|
use panic_halt as _;
|
||
|
|
use riscv_rt::entry;
|
||
|
|
use static_cell::{ConstStaticCell, StaticCell};
|
||
|
|
|
||
|
|
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];
|
||
|
|
const NET_SEED: u64 = 0x5A17_2026_57_E5_5E_55;
|
||
|
|
|
||
|
|
const SELF_IP: Ipv4Address = Ipv4Address::new(192, 168, 0, 50);
|
||
|
|
const SELF_PREFIX: u8 = 24;
|
||
|
|
const GATEWAY: Ipv4Address = Ipv4Address::new(192, 168, 0, 1);
|
||
|
|
|
||
|
|
const RX_PORT: u16 = 7780;
|
||
|
|
const TX_PORT: u16 = 7781;
|
||
|
|
// 2 KB matches the existing echo example. Bigger windows are tempting for
|
||
|
|
// higher steady-state throughput but the linker cuts it close to the RAM /
|
||
|
|
// L2-cache boundary at 0x4FF80000. Stay conservative.
|
||
|
|
const TCP_BUF_SIZE: usize = 2048;
|
||
|
|
const APP_RX_CHUNK: usize = 1460;
|
||
|
|
const APP_TX_CHUNK: usize = 1460;
|
||
|
|
|
||
|
|
/// App-level RX byte total (read off the RX socket). 32-bit because riscv32imafc
|
||
|
|
/// has no native 64-bit atomics. Wrap-around at ~4 GB is fine for the 1-second
|
||
|
|
/// deltas the stat task computes; a 30-second run at line rate is ~375 MB.
|
||
|
|
static RX_BYTES: AtomicU32 = AtomicU32::new(0);
|
||
|
|
/// App-level TX byte total (written to the TX socket). Same caveat as RX_BYTES.
|
||
|
|
static TX_BYTES: AtomicU32 = AtomicU32::new(0);
|
||
|
|
/// Number of times `dma_recovery_task` actually had to nudge `RX_POLL_DEMAND`.
|
||
|
|
/// If this stays 0 under multi-MB/s load, the polling recovery loop is
|
||
|
|
/// dead code under `p4-time-driver-irq` and AIE-recovery can be re-enabled
|
||
|
|
/// (or the task removed entirely under that feature).
|
||
|
|
static RBU_RECOVERIES: AtomicU32 = AtomicU32::new(0);
|
||
|
|
|
||
|
|
static EXECUTOR: StaticCell<Executor> = StaticCell::new();
|
||
|
|
#[link_section = ".dma_bss"]
|
||
|
|
static DMA_RESOURCES: ConstStaticCell<StaticDmaResources> =
|
||
|
|
ConstStaticCell::new(StaticDmaResources::new());
|
||
|
|
static NET_RESOURCES: ConstStaticCell<StackResources<6>> =
|
||
|
|
ConstStaticCell::new(StackResources::<6>::new());
|
||
|
|
static RX_SOCK_RX_BUF: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
||
|
|
static RX_SOCK_TX_BUF: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
||
|
|
static TX_SOCK_RX_BUF: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
||
|
|
static TX_SOCK_TX_BUF: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
||
|
|
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
#[embassy_executor::task]
|
||
|
|
async fn net_task(mut runner: NetRunner<'static, Device<'static>>) -> ! {
|
||
|
|
runner.run().await
|
||
|
|
}
|
||
|
|
|
||
|
|
#[embassy_executor::task]
|
||
|
|
async fn dma_recovery_task() -> ! {
|
||
|
|
// Under `p4-time-driver-irq` the recovery loop is empirically dead code
|
||
|
|
// (rbu = 0 over 50 s of sustained 4 Mbps RX + 3 Mbps TX), but we keep
|
||
|
|
// it as belt-and-braces against an unmeasured edge case. 50 ms instead
|
||
|
|
// of 1 ms drops the wake-up overhead from ~10 M instr/s to ~200 K instr/s
|
||
|
|
// (~2.5 % CPU saved at 360 MHz HP CPU) without losing recovery for any
|
||
|
|
// realistic stall: a 50 ms RBU window still wakes well before TCP's
|
||
|
|
// retransmit timer (≥200 ms) would notice anything.
|
||
|
|
loop {
|
||
|
|
Timer::after(Duration::from_millis(50)).await;
|
||
|
|
let dma_status = unsafe { read_volatile(0x5009_9014 as *const u32) };
|
||
|
|
let rps = (dma_status >> 17) & 0b111;
|
||
|
|
if rps == 0 || rps == 4 || dma_status & (1 << 7) != 0 {
|
||
|
|
unsafe {
|
||
|
|
write_volatile(0x5009_9014 as *mut u32, dma_status & 0x0001E7FF);
|
||
|
|
write_volatile(0x5009_9008 as *mut u32, 1);
|
||
|
|
}
|
||
|
|
RBU_RECOVERIES.fetch_add(1, Ordering::Relaxed);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async fn finish_emac_bringup(stack: Stack<'static>, uart: &mut Uart0) {
|
||
|
|
let t0 = systimer::now_us();
|
||
|
|
while !stack.is_link_up() {
|
||
|
|
Timer::after(Duration::from_millis(100)).await;
|
||
|
|
}
|
||
|
|
let _ = writeln!(uart, "app: link up after {} ms", (systimer::now_us() - t0) / 1000);
|
||
|
|
|
||
|
|
unsafe {
|
||
|
|
write_volatile(0x5009_8004 as *mut u32, 0x80000011);
|
||
|
|
let dma_op_mode = 0x5009_9018 as *mut u32;
|
||
|
|
write_volatile(dma_op_mode, read_volatile(dma_op_mode) | (1 << 6));
|
||
|
|
let mac_cfg = 0x5009_8000 as *mut u32;
|
||
|
|
write_volatile(mac_cfg, read_volatile(mac_cfg) & !(1 << 25));
|
||
|
|
}
|
||
|
|
let _ = writeln!(uart, "app: filter+FUF+ACS-clear applied");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[embassy_executor::task]
|
||
|
|
async fn rx_sink_task(stack: Stack<'static>) -> ! {
|
||
|
|
let mut uart = Uart0;
|
||
|
|
finish_emac_bringup(stack, &mut uart).await;
|
||
|
|
let _ = writeln!(uart, "app: RX-sink listening on :{}", RX_PORT);
|
||
|
|
|
||
|
|
let rx_buf: &'static mut [u8] = RX_SOCK_RX_BUF.take();
|
||
|
|
let tx_buf: &'static mut [u8] = RX_SOCK_TX_BUF.take();
|
||
|
|
let mut socket = TcpSocket::new(stack, rx_buf, tx_buf);
|
||
|
|
socket.set_timeout(Some(Duration::from_secs(60)));
|
||
|
|
|
||
|
|
let mut buf = [0u8; APP_RX_CHUNK];
|
||
|
|
let mut conn = 0u32;
|
||
|
|
loop {
|
||
|
|
match socket
|
||
|
|
.accept(IpListenEndpoint { addr: None, port: RX_PORT })
|
||
|
|
.await
|
||
|
|
{
|
||
|
|
Ok(()) => {
|
||
|
|
conn += 1;
|
||
|
|
let _ = writeln!(uart, "rx: conn #{} ACCEPTED from {:?}", conn, socket.remote_endpoint());
|
||
|
|
}
|
||
|
|
Err(e) => {
|
||
|
|
let _ = writeln!(uart, "rx: accept error: {:?}", e);
|
||
|
|
socket.abort();
|
||
|
|
let _ = socket.flush().await;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut total = 0u64;
|
||
|
|
loop {
|
||
|
|
match socket.read(&mut buf).await {
|
||
|
|
Ok(0) => {
|
||
|
|
let _ = writeln!(uart, "rx: conn #{} EOF, total = {} bytes", conn, total);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
Ok(n) => {
|
||
|
|
total += n as u64;
|
||
|
|
RX_BYTES.fetch_add(n as u32, Ordering::Relaxed);
|
||
|
|
}
|
||
|
|
Err(e) => {
|
||
|
|
let _ = writeln!(uart, "rx: read error: {:?}", e);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
socket.close();
|
||
|
|
let _ = socket.flush().await;
|
||
|
|
socket.abort();
|
||
|
|
let _ = socket.flush().await;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[embassy_executor::task]
|
||
|
|
async fn tx_source_task(stack: Stack<'static>) -> ! {
|
||
|
|
let mut uart = Uart0;
|
||
|
|
let _ = writeln!(uart, "app: TX-source will listen on :{}", TX_PORT);
|
||
|
|
|
||
|
|
let rx_buf: &'static mut [u8] = TX_SOCK_RX_BUF.take();
|
||
|
|
let tx_buf: &'static mut [u8] = TX_SOCK_TX_BUF.take();
|
||
|
|
let mut socket = TcpSocket::new(stack, rx_buf, tx_buf);
|
||
|
|
socket.set_timeout(Some(Duration::from_secs(60)));
|
||
|
|
|
||
|
|
// 1460-byte payload pattern (TCP MSS for 1500 MTU minus headers). Fill
|
||
|
|
// with a recognisable byte so a Wireshark capture stays interpretable.
|
||
|
|
let payload = [0xA5u8; APP_TX_CHUNK];
|
||
|
|
|
||
|
|
let mut conn = 0u32;
|
||
|
|
loop {
|
||
|
|
match socket
|
||
|
|
.accept(IpListenEndpoint { addr: None, port: TX_PORT })
|
||
|
|
.await
|
||
|
|
{
|
||
|
|
Ok(()) => {
|
||
|
|
conn += 1;
|
||
|
|
let _ = writeln!(uart, "tx: conn #{} ACCEPTED from {:?}", conn, socket.remote_endpoint());
|
||
|
|
}
|
||
|
|
Err(e) => {
|
||
|
|
let _ = writeln!(uart, "tx: accept error: {:?}", e);
|
||
|
|
socket.abort();
|
||
|
|
let _ = socket.flush().await;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut total = 0u64;
|
||
|
|
loop {
|
||
|
|
match socket.write(&payload).await {
|
||
|
|
Ok(0) => {
|
||
|
|
let _ = writeln!(uart, "tx: write returned 0 — peer closed?");
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
Ok(n) => {
|
||
|
|
total += n as u64;
|
||
|
|
TX_BYTES.fetch_add(n as u32, Ordering::Relaxed);
|
||
|
|
}
|
||
|
|
Err(e) => {
|
||
|
|
let _ = writeln!(uart, "tx: write error: {:?} (sent {} bytes)", e, total);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
let _ = writeln!(uart, "tx: conn #{} done, total = {} bytes", conn, total);
|
||
|
|
socket.close();
|
||
|
|
let _ = socket.flush().await;
|
||
|
|
socket.abort();
|
||
|
|
let _ = socket.flush().await;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[embassy_executor::task]
|
||
|
|
async fn stat_task() -> ! {
|
||
|
|
let mut uart = Uart0;
|
||
|
|
let mut last_rx_bytes = 0u32;
|
||
|
|
let mut last_tx_bytes = 0u32;
|
||
|
|
let mut last_rx_frames = 0u32;
|
||
|
|
let mut last_tx_frames = 0u32;
|
||
|
|
let mut last_irq_rx = 0u32;
|
||
|
|
let mut last_irq_tx = 0u32;
|
||
|
|
let mut last_rbu = 0u32;
|
||
|
|
let mut last_mcycle = mcycle_low();
|
||
|
|
let mut last_minstret = minstret_low();
|
||
|
|
let mut last_inv_ticks = 0u32;
|
||
|
|
let mut last_inv_calls = 0u32;
|
||
|
|
let mut last_wb_ticks = 0u32;
|
||
|
|
let mut last_wb_calls = 0u32;
|
||
|
|
let mut tick = 0u64;
|
||
|
|
|
||
|
|
loop {
|
||
|
|
Timer::after(Duration::from_secs(1)).await;
|
||
|
|
tick += 1;
|
||
|
|
|
||
|
|
let rx_bytes = RX_BYTES.load(Ordering::Relaxed);
|
||
|
|
let tx_bytes = TX_BYTES.load(Ordering::Relaxed);
|
||
|
|
let rx_frames = RX_FRAMES.load(Ordering::Relaxed);
|
||
|
|
let tx_frames = TX_FRAMES.load(Ordering::Relaxed);
|
||
|
|
let irq_rx = time_driver_irq::EMAC_IRQ_RX.load(Ordering::Relaxed);
|
||
|
|
let irq_tx = time_driver_irq::EMAC_IRQ_TX.load(Ordering::Relaxed);
|
||
|
|
let rbu = RBU_RECOVERIES.load(Ordering::Relaxed);
|
||
|
|
let mc = mcycle_low();
|
||
|
|
let mi = minstret_low();
|
||
|
|
let inv_ticks = CACHE_INV_TICKS.load(Ordering::Relaxed);
|
||
|
|
let inv_calls = CACHE_INV_CALLS.load(Ordering::Relaxed);
|
||
|
|
let wb_ticks = CACHE_WB_TICKS.load(Ordering::Relaxed);
|
||
|
|
let wb_calls = CACHE_WB_CALLS.load(Ordering::Relaxed);
|
||
|
|
|
||
|
|
let d_rx_bytes = rx_bytes.wrapping_sub(last_rx_bytes);
|
||
|
|
let d_tx_bytes = tx_bytes.wrapping_sub(last_tx_bytes);
|
||
|
|
let d_rx_frames = rx_frames.wrapping_sub(last_rx_frames);
|
||
|
|
let d_tx_frames = tx_frames.wrapping_sub(last_tx_frames);
|
||
|
|
let d_irq_rx = irq_rx.wrapping_sub(last_irq_rx);
|
||
|
|
let d_irq_tx = irq_tx.wrapping_sub(last_irq_tx);
|
||
|
|
let d_rbu = rbu.wrapping_sub(last_rbu);
|
||
|
|
// mcycle wraps every ~11 s at 40 MHz; one second is fine.
|
||
|
|
let d_mcycle = mc.wrapping_sub(last_mcycle);
|
||
|
|
let d_minstret = mi.wrapping_sub(last_minstret);
|
||
|
|
let d_inv_ticks = inv_ticks.wrapping_sub(last_inv_ticks);
|
||
|
|
let d_inv_calls = inv_calls.wrapping_sub(last_inv_calls);
|
||
|
|
let d_wb_ticks = wb_ticks.wrapping_sub(last_wb_ticks);
|
||
|
|
let d_wb_calls = wb_calls.wrapping_sub(last_wb_calls);
|
||
|
|
|
||
|
|
let rx_kbps = (d_rx_bytes as u64 * 8) / 1000;
|
||
|
|
let tx_kbps = (d_tx_bytes as u64 * 8) / 1000;
|
||
|
|
// SYSTIMER ticks at 16 MHz — divide by 16 for microseconds.
|
||
|
|
let inv_us = d_inv_ticks / 16;
|
||
|
|
let wb_us = d_wb_ticks / 16;
|
||
|
|
|
||
|
|
let _ = writeln!(
|
||
|
|
uart,
|
||
|
|
" t={}s mins={} inv={}us/{}c wb={}us/{}c rx={} kbps ({} fr) tx={} kbps ({} fr) rbu+={} err={}",
|
||
|
|
tick,
|
||
|
|
d_minstret,
|
||
|
|
inv_us,
|
||
|
|
d_inv_calls,
|
||
|
|
wb_us,
|
||
|
|
d_wb_calls,
|
||
|
|
rx_kbps,
|
||
|
|
d_rx_frames,
|
||
|
|
tx_kbps,
|
||
|
|
d_tx_frames,
|
||
|
|
d_rbu,
|
||
|
|
RX_ERROR_FRAMES_TOTAL.load(Ordering::Relaxed),
|
||
|
|
);
|
||
|
|
|
||
|
|
last_rx_bytes = rx_bytes;
|
||
|
|
last_tx_bytes = tx_bytes;
|
||
|
|
last_rx_frames = rx_frames;
|
||
|
|
last_tx_frames = tx_frames;
|
||
|
|
last_irq_rx = irq_rx;
|
||
|
|
last_irq_tx = irq_tx;
|
||
|
|
last_rbu = rbu;
|
||
|
|
last_mcycle = mc;
|
||
|
|
last_minstret = mi;
|
||
|
|
last_inv_ticks = inv_ticks;
|
||
|
|
last_inv_calls = inv_calls;
|
||
|
|
last_wb_ticks = wb_ticks;
|
||
|
|
last_wb_calls = wb_calls;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[entry]
|
||
|
|
fn main() -> ! {
|
||
|
|
let mut uart = Uart0;
|
||
|
|
let _ = writeln!(uart, "\n=== embassy_tcp_stress_irq alive ===");
|
||
|
|
|
||
|
|
time_driver_irq::init();
|
||
|
|
let _ = writeln!(uart, "main: time_driver_irq::init() done");
|
||
|
|
|
||
|
|
release_waveshare_phy_reset();
|
||
|
|
for _ in 0..5_000_000 {
|
||
|
|
spin_loop();
|
||
|
|
}
|
||
|
|
let _ = writeln!(uart, "main: PHY reset deasserted, waited 5M cycles");
|
||
|
|
|
||
|
|
let executor = EXECUTOR.init(Executor::new());
|
||
|
|
executor.run(init);
|
||
|
|
}
|
||
|
|
|
||
|
|
fn init(spawner: Spawner) {
|
||
|
|
let mut uart = Uart0;
|
||
|
|
let _ = writeln!(uart, "init: emac_new...");
|
||
|
|
let dma_resources = DMA_RESOURCES.take();
|
||
|
|
let (device, emac_runner, eth) = emac_new(MAC_ADDR, dma_resources);
|
||
|
|
let _ = writeln!(uart, "init: emac_new returned");
|
||
|
|
|
||
|
|
let config = Config::ipv4_static(StaticConfigV4 {
|
||
|
|
address: Ipv4Cidr::new(SELF_IP, SELF_PREFIX),
|
||
|
|
gateway: Some(GATEWAY),
|
||
|
|
dns_servers: heapless::Vec::new(),
|
||
|
|
});
|
||
|
|
let resources = NET_RESOURCES.take();
|
||
|
|
let (stack, net_runner) = embassy_net::new(device, config, resources, NET_SEED);
|
||
|
|
let _ = writeln!(uart, "init: embassy_net::new returned");
|
||
|
|
|
||
|
|
spawner.spawn(ethernet_task(emac_runner, eth)).unwrap();
|
||
|
|
spawner.spawn(net_task(net_runner)).unwrap();
|
||
|
|
spawner.spawn(dma_recovery_task()).unwrap();
|
||
|
|
spawner.spawn(rx_sink_task(stack)).unwrap();
|
||
|
|
spawner.spawn(tx_source_task(stack)).unwrap();
|
||
|
|
spawner.spawn(stat_task()).unwrap();
|
||
|
|
let _ = writeln!(uart, "init: all tasks spawned (rx:{} tx:{})", RX_PORT, TX_PORT);
|
||
|
|
}
|