Skip to content

Commit

Permalink
feat: add some l2cap benchmarks
Browse files Browse the repository at this point in the history
Add some convenient benchmarks for l2cap peripheral and central.
  • Loading branch information
lulf committed Feb 26, 2025
1 parent 1fb2e13 commit 477ab38
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 0 deletions.
14 changes: 14 additions & 0 deletions benchmarks/nrf-sdc/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
#runner = "probe-rs run --chip nRF52832_xxAA"
runner = "probe-rs run --chip nRF52833_xxAA"
#runner = "probe-rs run --chip nRF52840_xxAA"

[build]
# Pick ONE of these compilation targets
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi" # Cortex-M3
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)

[env]
DEFMT_LOG = "trouble_host=info,info"
62 changes: 62 additions & 0 deletions benchmarks/nrf-sdc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[package]
name = "trouble-nrf-sdc-tests"
version = "0.1.0"
edition = "2024"
resolver = "2"

[dependencies]
embassy-executor = { version = "0.7", default-features = false, features = ["arch-cortex-m", "executor-thread", "defmt", "executor-interrupt"] }
embassy-time = { version = "0.4", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] }
embassy-nrf = { version = "0.3", default-features = false, features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac", "rt"] }
embassy-futures = "0.1.1"
embassy-sync = { version = "0.6", features = ["defmt"] }
trouble-host = { path = "../../host", default-features = false, features = ["defmt", "l2cap-rx-queue-size-4", "l2cap-rx-packet-pool-size-16", "l2cap-tx-queue-size-4", "central", "peripheral", "scan", "gatt", "controller-host-flow-control"] }

futures = { version = "0.3", default-features = false, features = ["async-await"]}
nrf-sdc = { version = "0.1.0", default-features = false, features = ["defmt", "peripheral", "central"] }
nrf-mpsl = { version = "0.1.0", default-features = false, features = ["defmt", "critical-section-impl"] }
bt-hci = { version = "0.2", default-features = false, features = ["defmt"] }

defmt = "0.3"
defmt-rtt = "0.4.0"

cortex-m = { version = "0.7.6" }
cortex-m-rt = "0.7.0"
panic-probe = { version = "0.3", features = ["print-defmt"] }
static_cell = "2"

[profile.release]
debug = 2

[patch.crates-io]
nrf-sdc = { git = "https://github.com/alexmoon/nrf-sdc.git", rev = "551a95436e999b4290b4a33383aa3d6747b63dd9" }
nrf-mpsl = { git = "https://github.com/alexmoon/nrf-sdc.git", rev = "551a95436e999b4290b4a33383aa3d6747b63dd9" }

#embassy-executor = {path = "../../../embassy/embassy-executor"}
#embassy-nrf = {path = "../../../embassy/embassy-nrf"}
#embassy-sync = {path = "../../../embassy/embassy-sync"}
#embassy-futures = {path = "../../../embassy/embassy-futures"}
#embassy-time = {path = "../../../embassy/embassy-time"}
#embassy-time-driver = {path = "../../../embassy/embassy-time-driver"}
#embassy-embedded-hal = {path = "../../../embassy/embassy-embedded-hal"}
#embassy-hal-internal = {path = "../../../embassy/embassy-hal-internal"}
#nrf-sdc = { path = "../../../nrf-sdc/nrf-sdc" }
#nrf-mpsl = { path = "../../../nrf-sdc/nrf-mpsl" }
#bt-hci = { path = "../../../bt-hci" }

[features]
nrf52832 = [
"embassy-executor/task-arena-size-32768",
"embassy-nrf/nrf52832",
"nrf-sdc/nrf52832",
]
nrf52833 = [
"embassy-executor/task-arena-size-32768",
"embassy-nrf/nrf52833",
"nrf-sdc/nrf52833",
]
nrf52840 = [
"embassy-executor/task-arena-size-65536",
"embassy-nrf/nrf52840",
"nrf-sdc/nrf52840",
]
46 changes: 46 additions & 0 deletions benchmarks/nrf-sdc/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn linker_data() -> &'static [u8] {
#[cfg(feature = "nrf52832")]
return include_bytes!("memory-nrf52832.x");
#[cfg(feature = "nrf52833")]
return include_bytes!("memory-nrf52833.x");
#[cfg(feature = "nrf52840")]
return include_bytes!("memory-nrf52840.x");
#[cfg(not(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840")))]
unimplemented!("must select a target")
}

fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(linker_data())
.unwrap();
println!("cargo:rustc-link-search={}", out.display());

// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");

println!("cargo:rustc-link-arg-bins=--nmagic");
println!("cargo:rustc-link-arg-bins=-Tlink.x");
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
}
6 changes: 6 additions & 0 deletions benchmarks/nrf-sdc/memory-nrf52832.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
MEMORY
{
/* NOTE 1 K = 1 KiBi = 1024 bytes */
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
RAM : ORIGIN = 0x20000000, LENGTH = 64K
}
6 changes: 6 additions & 0 deletions benchmarks/nrf-sdc/memory-nrf52833.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
MEMORY
{
/* NOTE 1 K = 1 KiBi = 1024 bytes */
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
RAM : ORIGIN = 0x20000000, LENGTH = 128K
}
7 changes: 7 additions & 0 deletions benchmarks/nrf-sdc/memory-nrf52840.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
MEMORY
{
/* NOTE 1 K = 1 KiBi = 1024 bytes */
/* These values correspond to the NRF52840 */
FLASH : ORIGIN = 0x00000000, LENGTH = 1024K
RAM : ORIGIN = 0x20000000, LENGTH = 256K
}
134 changes: 134 additions & 0 deletions benchmarks/nrf-sdc/src/bin/l2cap_benchmark_central.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#![no_std]
#![no_main]

use defmt::{info, unwrap};
use embassy_executor::Spawner;
use embassy_futures::join::join;
use embassy_nrf::peripherals::RNG;
use embassy_nrf::{bind_interrupts, rng};
use embassy_time::{Duration, Instant, Timer};
use nrf_sdc::mpsl::MultiprotocolServiceLayer;
use nrf_sdc::{self as sdc, mpsl};
use static_cell::StaticCell;
use trouble_host::prelude::*;

use {defmt_rtt as _, panic_probe as _};

bind_interrupts!(struct Irqs {
RNG => rng::InterruptHandler<RNG>;
EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler;
CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler;
RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler;
TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler;
RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler;
});

#[embassy_executor::task]
async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! {
mpsl.run().await
}

const L2CAP_TXQ: u8 = 4;
const L2CAP_RXQ: u8 = 4;
const L2CAP_MTU: usize = 251;
const CONNECTIONS_MAX: usize = 1;
const L2CAP_CHANNELS_MAX: usize = 1;

fn build_sdc<'d, const N: usize>(
p: nrf_sdc::Peripherals<'d>,
rng: &'d mut rng::Rng<RNG>,
mpsl: &'d MultiprotocolServiceLayer,
mem: &'d mut sdc::Mem<N>,
) -> Result<nrf_sdc::SoftdeviceController<'d>, nrf_sdc::Error> {
sdc::Builder::new()?
.support_scan()?
.support_central()?
.central_count(1)?
.buffer_cfg(L2CAP_MTU as u8, L2CAP_MTU as u8, L2CAP_TXQ, L2CAP_RXQ)?
.build(p, rng, mpsl, mem)
}

#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_nrf::init(Default::default());
let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31);
let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t {
source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8,
rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8,
rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8,
accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16,
skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0,
};
static MPSL: StaticCell<MultiprotocolServiceLayer> = StaticCell::new();
let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg)));
spawner.must_spawn(mpsl_task(&*mpsl));

let sdc_p = sdc::Peripherals::new(
p.PPI_CH17, p.PPI_CH18, p.PPI_CH20, p.PPI_CH21, p.PPI_CH22, p.PPI_CH23, p.PPI_CH24, p.PPI_CH25, p.PPI_CH26,
p.PPI_CH27, p.PPI_CH28, p.PPI_CH29,
);

let mut rng = rng::Rng::new(p.RNG, Irqs);

let mut sdc_mem = sdc::Mem::<16384>::new();
let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem));

Timer::after(Duration::from_millis(200)).await;

let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]);
let mut resources: HostResources<CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU> = HostResources::new();
let stack = trouble_host::new(sdc, &mut resources).set_random_address(address);
let Host {
mut central,
mut runner,
..
} = stack.build();

let target: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
let config = ConnectConfig {
connect_params: Default::default(),
scan_config: ScanConfig {
filter_accept_list: &[(target.kind, &target.addr)],
..Default::default()
},
};

let _ = join(runner.run(), async {
loop {
let conn = unwrap!(central.connect(&config).await);
const PAYLOAD_LEN: usize = 492;
let config = L2capChannelConfig {
flow_policy: CreditFlowPolicy::MinThreshold(4),
initial_credits: Some(8),
mtu: PAYLOAD_LEN as u16,
};
let mut ch1 = unwrap!(L2capChannel::create(&stack, &conn, 0x2349, &config).await);
info!("sending l2cap data");
let mut last = Instant::now();
let mut bytes: u64 = 0;
for i in 0..500 {
let tx = [(i % 255) as u8; PAYLOAD_LEN];
unwrap!(ch1.send::<_, L2CAP_MTU>(&stack, &tx).await);
bytes += PAYLOAD_LEN as u64;
let duration = Instant::now() - last;
if duration.as_secs() > 10 {
info!("throughput: {} bytes/sec", bytes / duration.as_secs());
bytes = 0;
last = Instant::now();
}
}
info!("waiting for echo data");
let mut rx = [0; PAYLOAD_LEN];
for i in 0..500 {
let len = unwrap!(ch1.receive(&stack, &mut rx).await);
assert_eq!(len, rx.len());
assert_eq!(rx, [(i % 255) as u8; PAYLOAD_LEN]);
}

info!("done");
Timer::after(Duration::from_secs(2)).await;
break;
}
})
.await;
}
Loading

0 comments on commit 477ab38

Please sign in to comment.