From 6701606e4c7811082808271b67caa1cdc9387163 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 6 Jun 2023 02:28:00 +0200 Subject: [PATCH] cyw43: add perf HIL test. --- ci.sh | 2 +- tests/perf-server/Cargo.toml | 8 + tests/perf-server/deploy.sh | 11 ++ tests/perf-server/perf-server.service | 16 ++ tests/perf-server/src/main.rs | 90 +++++++++ tests/rp/Cargo.toml | 6 +- tests/rp/src/bin/cyw43-perf.rs | 267 ++++++++++++++++++++++++++ 7 files changed, 398 insertions(+), 2 deletions(-) create mode 100644 tests/perf-server/Cargo.toml create mode 100755 tests/perf-server/deploy.sh create mode 100644 tests/perf-server/perf-server.service create mode 100644 tests/perf-server/src/main.rs create mode 100644 tests/rp/src/bin/cyw43-perf.rs diff --git a/ci.sh b/ci.sh index 3d6e28796..08558a18f 100755 --- a/ci.sh +++ b/ci.sh @@ -3,7 +3,7 @@ set -euo pipefail export RUSTFLAGS=-Dwarnings -export DEFMT_LOG=trace +export DEFMT_LOG=trace,cyw43=info,cyw43_pio=info,smoltcp=info # needed by wifi examples export WIFI_NETWORK=x diff --git a/tests/perf-server/Cargo.toml b/tests/perf-server/Cargo.toml new file mode 100644 index 000000000..532039050 --- /dev/null +++ b/tests/perf-server/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "perf-server" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "0.4.17" +pretty_env_logger = "0.4.0" diff --git a/tests/perf-server/deploy.sh b/tests/perf-server/deploy.sh new file mode 100755 index 000000000..032e99c30 --- /dev/null +++ b/tests/perf-server/deploy.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -euxo pipefail + +HOST=root@192.168.1.3 + +cargo build --release +ssh $HOST -- systemctl stop perf-server +scp target/release/perf-server $HOST:/root +scp perf-server.service $HOST:/etc/systemd/system/ +ssh $HOST -- 'systemctl daemon-reload; systemctl restart perf-server' \ No newline at end of file diff --git a/tests/perf-server/perf-server.service b/tests/perf-server/perf-server.service new file mode 100644 index 000000000..c14c5d16f --- /dev/null +++ b/tests/perf-server/perf-server.service @@ -0,0 +1,16 @@ +[Unit] +Description=perf-server +After=network.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +Restart=always +RestartSec=1 +User=root +ExecStart=/root/perf-server +Environment=RUST_BACKTRACE=1 +Environment=RUST_LOG=info + +[Install] +WantedBy=multi-user.target diff --git a/tests/perf-server/src/main.rs b/tests/perf-server/src/main.rs new file mode 100644 index 000000000..f6e7efc59 --- /dev/null +++ b/tests/perf-server/src/main.rs @@ -0,0 +1,90 @@ +use std::io::{Read, Write}; +use std::net::{TcpListener, TcpStream}; +use std::thread::spawn; +use std::time::Duration; + +use log::info; + +fn main() { + pretty_env_logger::init(); + spawn(|| rx_listen()); + spawn(|| rxtx_listen()); + tx_listen(); +} + +fn tx_listen() { + info!("tx: listening on 0.0.0.0:4321"); + let listener = TcpListener::bind("0.0.0.0:4321").unwrap(); + loop { + let (socket, addr) = listener.accept().unwrap(); + info!("tx: received connection from: {}", addr); + spawn(|| tx_conn(socket)); + } +} + +fn tx_conn(mut socket: TcpStream) { + socket.set_read_timeout(Some(Duration::from_secs(30))).unwrap(); + socket.set_write_timeout(Some(Duration::from_secs(30))).unwrap(); + + let buf = [0; 1024]; + loop { + if let Err(e) = socket.write_all(&buf) { + info!("tx: failed to write to socket; err = {:?}", e); + return; + } + } +} + +fn rx_listen() { + info!("rx: listening on 0.0.0.0:4322"); + let listener = TcpListener::bind("0.0.0.0:4322").unwrap(); + loop { + let (socket, addr) = listener.accept().unwrap(); + info!("rx: received connection from: {}", addr); + spawn(|| rx_conn(socket)); + } +} + +fn rx_conn(mut socket: TcpStream) { + socket.set_read_timeout(Some(Duration::from_secs(30))).unwrap(); + socket.set_write_timeout(Some(Duration::from_secs(30))).unwrap(); + + let mut buf = [0; 1024]; + loop { + if let Err(e) = socket.read_exact(&mut buf) { + info!("rx: failed to read from socket; err = {:?}", e); + return; + } + } +} + +fn rxtx_listen() { + info!("rxtx: listening on 0.0.0.0:4323"); + let listener = TcpListener::bind("0.0.0.0:4323").unwrap(); + loop { + let (socket, addr) = listener.accept().unwrap(); + info!("rxtx: received connection from: {}", addr); + spawn(|| rxtx_conn(socket)); + } +} + +fn rxtx_conn(mut socket: TcpStream) { + socket.set_read_timeout(Some(Duration::from_secs(30))).unwrap(); + socket.set_write_timeout(Some(Duration::from_secs(30))).unwrap(); + + let mut buf = [0; 1024]; + loop { + match socket.read(&mut buf) { + Ok(n) => { + if let Err(e) = socket.write_all(&buf[..n]) { + info!("rxtx: failed to write to socket; err = {:?}", e); + return; + } + } + Err(e) => { + info!("rxtx: failed to read from socket; err = {:?}", e); + return; + } + } + } +} diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index 6bcac373c..fa97fdcc4 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -5,13 +5,16 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -teleprobe-meta = "1" +teleprobe-meta = "1.1" embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt"] } embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "udp", "dhcpv4", "medium-ethernet"] } +cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } +cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } defmt = "0.3.0" defmt-rtt = "0.4" @@ -25,6 +28,7 @@ panic-probe = { version = "0.3.0", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } embedded-io = { version = "0.4.0", features = ["async"] } embedded-storage = { version = "0.3" } +static_cell = { version = "1.1", features = ["nightly"]} [profile.dev] debug = 2 diff --git a/tests/rp/src/bin/cyw43-perf.rs b/tests/rp/src/bin/cyw43-perf.rs new file mode 100644 index 000000000..568f1b826 --- /dev/null +++ b/tests/rp/src/bin/cyw43-perf.rs @@ -0,0 +1,267 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use cyw43_pio::PioSpi; +use defmt::{assert, panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, Ipv4Address, Stack, StackResources}; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; +use embassy_rp::pio::Pio; +use embassy_rp::rom_data; +use embassy_time::{with_timeout, Duration, Timer}; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +teleprobe_meta::timeout!(120); + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + let p = embassy_rp::init(Default::default()); + + // needed for reading the firmware from flash via XIP. + unsafe { + rom_data::flash_exit_xip(); + rom_data::flash_enter_cmd_xip(); + } + + // cyw43 firmware needs to be flashed manually: + // probe-rs-cli download 43439A0.bin --format bin --chip RP2040 --base-address 0x101c0000 + // probe-rs-cli download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x101f8000 + let fw = unsafe { core::slice::from_raw_parts(0x101c0000 as *const u8, 224190) }; + let clm = unsafe { core::slice::from_raw_parts(0x101f8000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + let state = make_static!(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::Dhcp(Default::default()); + //let config = embassy_net::Config::Static(embassy_net::Config { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. + + // Init network stack + let stack = &*make_static!(Stack::new( + net_device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + loop { + match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + Ok(_) => break, + Err(err) => { + panic!("join failed with status={}", err.status); + } + } + } + + info!("Waiting for DHCP up..."); + while stack.config().is_none() { + Timer::after(Duration::from_millis(100)).await; + } + info!("IP addressing up!"); + + let down = test_download(stack).await; + let up = test_upload(stack).await; + let updown = test_upload_download(stack).await; + + assert!(down > TEST_EXPECTED_DOWNLOAD_KBPS); + assert!(up > TEST_EXPECTED_UPLOAD_KBPS); + assert!(updown > TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +// Test-only wifi network, no internet access! +const WIFI_NETWORK: &str = "EmbassyTest"; +const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; + +const TEST_DURATION: usize = 10; +const TEST_EXPECTED_DOWNLOAD_KBPS: usize = 500; +const TEST_EXPECTED_UPLOAD_KBPS: usize = 500; +const TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS: usize = 400; +const RX_BUFFER_SIZE: usize = 4096; +const TX_BUFFER_SIZE: usize = 4096; +const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2); +const DOWNLOAD_PORT: u16 = 4321; +const UPLOAD_PORT: u16 = 4322; +const UPLOAD_DOWNLOAD_PORT: u16 = 4323; + +async fn test_download(stack: &'static Stack>) -> usize { + info!("Testing download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("download: {} kB/s", kbps); + kbps +} + +async fn test_upload(stack: &'static Stack>) -> usize { + info!("Testing upload..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.write(&buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload: {} kB/s", kbps); + kbps +} + +async fn test_upload_download(stack: &'static Stack>) -> usize { + info!("Testing upload+download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let (mut reader, mut writer) = socket.split(); + + let tx_buf = [0; 4096]; + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + let tx_fut = async { + loop { + match writer.write(&tx_buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(_) => {} + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }; + + let rx_fut = async { + loop { + match reader.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }; + + with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut)) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload+download: {} kB/s", kbps); + kbps +}