440 lines
15 KiB
Rust
440 lines
15 KiB
Rust
|
|
#![no_std]
|
|||
|
|
#![no_main]
|
|||
|
|
|
|||
|
|
//! Long-duration TCP soak harness.
|
|||
|
|
//!
|
|||
|
|
//! Four parallel TCP echo listeners on ports 7780-7783, each backed by a
|
|||
|
|
//! 4 KB RX + 4 KB TX socket buffer (2× the per-connection window of
|
|||
|
|
//! `embassy_tcp_echo_irq`, 4× the parallelism). Per-port byte counters
|
|||
|
|
//! plus full diagnostic snapshot every 60 seconds, hourly summary line.
|
|||
|
|
//!
|
|||
|
|
//! Designed for backbone reliability validation — leaks, ring-wedge,
|
|||
|
|
//! cumulative cache-call drift, link-state churn — not for peak throughput.
|
|||
|
|
//! The 192 KB on-chip SRAM does not fit four 32 KB sockets; if you need
|
|||
|
|
//! BDP-saturating windows on a single connection, drop the listener count
|
|||
|
|
//! to one and bump the buffer sizes locally.
|
|||
|
|
//!
|
|||
|
|
//! Run from host:
|
|||
|
|
//!
|
|||
|
|
//! ```bash
|
|||
|
|
//! # Open four parallel iperf-style streams via socat (or use the soak
|
|||
|
|
//! # driver script under `examples/dev/soak_driver.sh`):
|
|||
|
|
//! for p in 7780 7781 7782 7783; do
|
|||
|
|
//! socat -u FILE:/dev/urandom TCP:192.168.0.50:$p &
|
|||
|
|
//! done
|
|||
|
|
//! ```
|
|||
|
|
//!
|
|||
|
|
//! Build:
|
|||
|
|
//! cargo build --no-default-features \
|
|||
|
|
//! --features p4-example,p4-time-driver-irq,embassy-net-tcp \
|
|||
|
|
//! --example embassy_tcp_soak
|
|||
|
|
|
|||
|
|
use core::fmt::{self, Write};
|
|||
|
|
use core::hint::spin_loop;
|
|||
|
|
use core::ptr::{read_volatile, write_volatile};
|
|||
|
|
use core::sync::atomic::{AtomicU32, Ordering};
|
|||
|
|
|
|||
|
|
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 panic_halt as _;
|
|||
|
|
use riscv_rt::entry;
|
|||
|
|
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 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 = 0x504F_414B_5350_4F4B; // "POAKSPOK"
|
|||
|
|
|
|||
|
|
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 SOAK_PORTS: [u16; 4] = [7780, 7781, 7782, 7783];
|
|||
|
|
// 4 KB per direction × 4 listeners = 32 KB CPU-side buffers, the largest
|
|||
|
|
// footprint that fits inside the 192 KB on-chip SRAM next to the existing
|
|||
|
|
// driver code, ring storage, NetStackResources, and stack. Bump only after
|
|||
|
|
// shrinking listener count or trimming the static_cell pools.
|
|||
|
|
const TCP_BUF_SIZE: usize = 4 * 1024;
|
|||
|
|
const STAT_PERIOD_S: u64 = 60;
|
|||
|
|
const HOURLY_TICKS: u64 = 60;
|
|||
|
|
|
|||
|
|
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());
|
|||
|
|
|
|||
|
|
// One TCP socket buffer pair per listener. Plain RAM (CPU-side), not DMA.
|
|||
|
|
static TCP_RX_BUF_0: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
|||
|
|
static TCP_TX_BUF_0: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
|||
|
|
static TCP_RX_BUF_1: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
|||
|
|
static TCP_TX_BUF_1: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
|||
|
|
static TCP_RX_BUF_2: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
|||
|
|
static TCP_TX_BUF_2: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
|||
|
|
static TCP_RX_BUF_3: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
|||
|
|
static TCP_TX_BUF_3: ConstStaticCell<[u8; TCP_BUF_SIZE]> = ConstStaticCell::new([0; TCP_BUF_SIZE]);
|
|||
|
|
|
|||
|
|
// Per-port observability. AtomicU32 (RISC-V 32-bit has no native AtomicU64)
|
|||
|
|
// rolls over every ~4 GB; at saturating 100 Mbps line rate that is ~5.7 min,
|
|||
|
|
// so the printed cumulative total is informational only. The per-tick delta
|
|||
|
|
// (60 s × 12.5 MB/s = 750 MB max) always fits in u32 and the stat task uses
|
|||
|
|
// `wrapping_sub` so the throughput line stays correct across rollovers.
|
|||
|
|
static BYTES_ECHOED: [AtomicU32; 4] = [
|
|||
|
|
AtomicU32::new(0),
|
|||
|
|
AtomicU32::new(0),
|
|||
|
|
AtomicU32::new(0),
|
|||
|
|
AtomicU32::new(0),
|
|||
|
|
];
|
|||
|
|
static CONN_COUNT: [AtomicU32; 4] = [
|
|||
|
|
AtomicU32::new(0),
|
|||
|
|
AtomicU32::new(0),
|
|||
|
|
AtomicU32::new(0),
|
|||
|
|
AtomicU32::new(0),
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
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() -> ! {
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Waits for link-up and applies the post-bringup MAC pokes once, then
|
|||
|
|
/// returns. Listener tasks call this in parallel; idempotent register
|
|||
|
|
/// writes mean the four serialized races are safe.
|
|||
|
|
async fn finish_emac_bringup(stack: Stack<'static>, idx: usize, uart: &mut Uart0) {
|
|||
|
|
if idx == 0 {
|
|||
|
|
let t0 = systimer::now_us();
|
|||
|
|
while !stack.is_link_up() {
|
|||
|
|
Timer::after(Duration::from_millis(100)).await;
|
|||
|
|
}
|
|||
|
|
let _ = writeln!(
|
|||
|
|
uart,
|
|||
|
|
"soak: 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, "soak: filter+FUF+ACS-clear applied");
|
|||
|
|
} else {
|
|||
|
|
while !stack.is_link_up() {
|
|||
|
|
Timer::after(Duration::from_millis(100)).await;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[embassy_executor::task(pool_size = 4)]
|
|||
|
|
async fn tcp_echo_task(
|
|||
|
|
stack: Stack<'static>,
|
|||
|
|
idx: usize,
|
|||
|
|
port: u16,
|
|||
|
|
rx_buf: &'static mut [u8],
|
|||
|
|
tx_buf: &'static mut [u8],
|
|||
|
|
) -> ! {
|
|||
|
|
let mut uart = Uart0;
|
|||
|
|
finish_emac_bringup(stack, idx, &mut uart).await;
|
|||
|
|
|
|||
|
|
let mut socket = TcpSocket::new(stack, rx_buf, tx_buf);
|
|||
|
|
socket.set_timeout(Some(Duration::from_secs(120)));
|
|||
|
|
socket.set_keep_alive(Some(Duration::from_secs(30)));
|
|||
|
|
|
|||
|
|
let _ = writeln!(uart, "soak[{}]: listening on :{}", idx, port);
|
|||
|
|
|
|||
|
|
let mut buf = [0u8; 2048];
|
|||
|
|
loop {
|
|||
|
|
match socket
|
|||
|
|
.accept(IpListenEndpoint { addr: None, port })
|
|||
|
|
.await
|
|||
|
|
{
|
|||
|
|
Ok(()) => {
|
|||
|
|
CONN_COUNT[idx].fetch_add(1, Ordering::Relaxed);
|
|||
|
|
}
|
|||
|
|
Err(e) => {
|
|||
|
|
let _ = writeln!(uart, "soak[{}]: accept err {:?}", idx, e);
|
|||
|
|
socket.abort();
|
|||
|
|
let _ = socket.flush().await;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
loop {
|
|||
|
|
match socket.read(&mut buf).await {
|
|||
|
|
Ok(0) => break,
|
|||
|
|
Ok(n) => {
|
|||
|
|
// Drain the slice across potentially-partial writes so the
|
|||
|
|
// soak run cannot lose bytes when the TX window is small.
|
|||
|
|
let mut wrote = 0usize;
|
|||
|
|
let mut write_err = false;
|
|||
|
|
while wrote < n {
|
|||
|
|
match socket.write(&buf[wrote..n]).await {
|
|||
|
|
Ok(0) => {
|
|||
|
|
write_err = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
Ok(w) => wrote += w,
|
|||
|
|
Err(e) => {
|
|||
|
|
let _ = writeln!(uart, "soak[{}]: write err {:?}", idx, e);
|
|||
|
|
write_err = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if write_err {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
BYTES_ECHOED[idx].fetch_add(n as u32, Ordering::Relaxed);
|
|||
|
|
}
|
|||
|
|
Err(e) => {
|
|||
|
|
let _ = writeln!(uart, "soak[{}]: read err {:?}", idx, e);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
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 tick = 0u64;
|
|||
|
|
let mut prev_bytes = [0u32; 4];
|
|||
|
|
let mut prev_rx = 0u32;
|
|||
|
|
let mut prev_tx = 0u32;
|
|||
|
|
|
|||
|
|
loop {
|
|||
|
|
Timer::after(Duration::from_secs(STAT_PERIOD_S)).await;
|
|||
|
|
tick += 1;
|
|||
|
|
let uptime_s = tick * STAT_PERIOD_S;
|
|||
|
|
|
|||
|
|
let bytes: [u32; 4] = [
|
|||
|
|
BYTES_ECHOED[0].load(Ordering::Relaxed),
|
|||
|
|
BYTES_ECHOED[1].load(Ordering::Relaxed),
|
|||
|
|
BYTES_ECHOED[2].load(Ordering::Relaxed),
|
|||
|
|
BYTES_ECHOED[3].load(Ordering::Relaxed),
|
|||
|
|
];
|
|||
|
|
let conns: [u32; 4] = [
|
|||
|
|
CONN_COUNT[0].load(Ordering::Relaxed),
|
|||
|
|
CONN_COUNT[1].load(Ordering::Relaxed),
|
|||
|
|
CONN_COUNT[2].load(Ordering::Relaxed),
|
|||
|
|
CONN_COUNT[3].load(Ordering::Relaxed),
|
|||
|
|
];
|
|||
|
|
let rx_total = RX_FRAMES.load(Ordering::Relaxed);
|
|||
|
|
let tx_total = TX_FRAMES.load(Ordering::Relaxed);
|
|||
|
|
|
|||
|
|
// wrapping_sub keeps deltas correct across u32 rollover (every ~4 GB
|
|||
|
|
// / ~6 min at line rate).
|
|||
|
|
let delta_bytes: u64 = bytes
|
|||
|
|
.iter()
|
|||
|
|
.zip(prev_bytes.iter())
|
|||
|
|
.map(|(b, p)| b.wrapping_sub(*p) as u64)
|
|||
|
|
.sum();
|
|||
|
|
let throughput_bps = delta_bytes * 8 / STAT_PERIOD_S;
|
|||
|
|
|
|||
|
|
let _ = writeln!(
|
|||
|
|
uart,
|
|||
|
|
"[t={}m] thrpt={} kbps rx_frm={} (+{}) tx_frm={} (+{})",
|
|||
|
|
uptime_s / 60,
|
|||
|
|
throughput_bps / 1000,
|
|||
|
|
rx_total,
|
|||
|
|
rx_total.wrapping_sub(prev_rx),
|
|||
|
|
tx_total,
|
|||
|
|
tx_total.wrapping_sub(prev_tx),
|
|||
|
|
);
|
|||
|
|
for i in 0..4 {
|
|||
|
|
let _ = writeln!(
|
|||
|
|
uart,
|
|||
|
|
" port {}: bytes={} (+{}) conns={}",
|
|||
|
|
SOAK_PORTS[i],
|
|||
|
|
bytes[i],
|
|||
|
|
bytes[i].wrapping_sub(prev_bytes[i]),
|
|||
|
|
conns[i],
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
let runt = RX_RUNT_FRAMES_TOTAL.load(Ordering::Relaxed);
|
|||
|
|
let oversized = RX_OVERSIZED_FRAMES_TOTAL.load(Ordering::Relaxed);
|
|||
|
|
let err = RX_ERROR_FRAMES_TOTAL.load(Ordering::Relaxed);
|
|||
|
|
if runt != 0 || oversized != 0 || err != 0 {
|
|||
|
|
let _ = writeln!(
|
|||
|
|
uart,
|
|||
|
|
" ERRORS: runt={} oversized={} err={}",
|
|||
|
|
runt, oversized, err
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if tick % HOURLY_TICKS == 0 {
|
|||
|
|
let inv_calls = CACHE_INV_CALLS.load(Ordering::Relaxed);
|
|||
|
|
let inv_ticks = CACHE_INV_TICKS.load(Ordering::Relaxed);
|
|||
|
|
let wb_calls = CACHE_WB_CALLS.load(Ordering::Relaxed);
|
|||
|
|
let wb_ticks = CACHE_WB_TICKS.load(Ordering::Relaxed);
|
|||
|
|
let _ = writeln!(
|
|||
|
|
uart,
|
|||
|
|
"=== HOURLY @ t={}h: rx={} tx={} cache_inv={}/{} cache_wb={}/{} ===",
|
|||
|
|
tick / HOURLY_TICKS,
|
|||
|
|
rx_total,
|
|||
|
|
tx_total,
|
|||
|
|
inv_calls,
|
|||
|
|
inv_ticks,
|
|||
|
|
wb_calls,
|
|||
|
|
wb_ticks,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
prev_bytes = bytes;
|
|||
|
|
prev_rx = rx_total;
|
|||
|
|
prev_tx = tx_total;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[entry]
|
|||
|
|
fn main() -> ! {
|
|||
|
|
let mut uart = Uart0;
|
|||
|
|
let _ = writeln!(uart, "\n=== embassy_tcp_soak 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: enter");
|
|||
|
|
let dma_resources = DMA_RESOURCES.take();
|
|||
|
|
let _ = writeln!(uart, "init: DMA_RESOURCES.take() ok");
|
|||
|
|
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 _ = writeln!(uart, "init: NET_RESOURCES.take() ok");
|
|||
|
|
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(tcp_echo_task(
|
|||
|
|
stack,
|
|||
|
|
0,
|
|||
|
|
SOAK_PORTS[0],
|
|||
|
|
TCP_RX_BUF_0.take(),
|
|||
|
|
TCP_TX_BUF_0.take(),
|
|||
|
|
))
|
|||
|
|
.unwrap();
|
|||
|
|
spawner
|
|||
|
|
.spawn(tcp_echo_task(
|
|||
|
|
stack,
|
|||
|
|
1,
|
|||
|
|
SOAK_PORTS[1],
|
|||
|
|
TCP_RX_BUF_1.take(),
|
|||
|
|
TCP_TX_BUF_1.take(),
|
|||
|
|
))
|
|||
|
|
.unwrap();
|
|||
|
|
spawner
|
|||
|
|
.spawn(tcp_echo_task(
|
|||
|
|
stack,
|
|||
|
|
2,
|
|||
|
|
SOAK_PORTS[2],
|
|||
|
|
TCP_RX_BUF_2.take(),
|
|||
|
|
TCP_TX_BUF_2.take(),
|
|||
|
|
))
|
|||
|
|
.unwrap();
|
|||
|
|
spawner
|
|||
|
|
.spawn(tcp_echo_task(
|
|||
|
|
stack,
|
|||
|
|
3,
|
|||
|
|
SOAK_PORTS[3],
|
|||
|
|
TCP_RX_BUF_3.take(),
|
|||
|
|
TCP_TX_BUF_3.take(),
|
|||
|
|
))
|
|||
|
|
.unwrap();
|
|||
|
|
|
|||
|
|
spawner.spawn(stat_task()).unwrap();
|
|||
|
|
let _ = writeln!(uart, "init: 4 listeners + stat_task spawned");
|
|||
|
|
}
|