tests: Bluetooth: make an L2CAP multilink stress test
This test's purpose is to stress the TX data path in the case of multiple connections. Signed-off-by: Jonathan Rico <jonathan.rico@nordicsemi.no>
This commit is contained in:
parent
8af7180531
commit
d3dbf890bf
9 changed files with 661 additions and 0 deletions
|
@ -853,12 +853,24 @@ static bool chan_has_credits(struct bt_l2cap_le_chan *lechan)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__weak void bt_test_l2cap_data_pull_spy(struct bt_conn *conn,
|
||||||
|
struct bt_l2cap_le_chan *lechan,
|
||||||
|
size_t amount,
|
||||||
|
size_t *length)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
struct net_buf *l2cap_data_pull(struct bt_conn *conn,
|
struct net_buf *l2cap_data_pull(struct bt_conn *conn,
|
||||||
size_t amount,
|
size_t amount,
|
||||||
size_t *length)
|
size_t *length)
|
||||||
{
|
{
|
||||||
struct bt_l2cap_le_chan *lechan = get_ready_chan(conn);
|
struct bt_l2cap_le_chan *lechan = get_ready_chan(conn);
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_TESTING)) {
|
||||||
|
/* Allow tests to snoop in */
|
||||||
|
bt_test_l2cap_data_pull_spy(conn, lechan, amount, length);
|
||||||
|
}
|
||||||
|
|
||||||
if (!lechan) {
|
if (!lechan) {
|
||||||
LOG_DBG("no channel conn %p", conn);
|
LOG_DBG("no channel conn %p", conn);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -10,6 +10,7 @@ set -ue
|
||||||
|
|
||||||
source ${ZEPHYR_BASE}/tests/bsim/compile.source
|
source ${ZEPHYR_BASE}/tests/bsim/compile.source
|
||||||
|
|
||||||
|
app=tests/bsim/bluetooth/host/l2cap/many_conns compile
|
||||||
app=tests/bsim/bluetooth/host/l2cap/general compile
|
app=tests/bsim/bluetooth/host/l2cap/general compile
|
||||||
app=tests/bsim/bluetooth/host/l2cap/userdata compile
|
app=tests/bsim/bluetooth/host/l2cap/userdata compile
|
||||||
app=tests/bsim/bluetooth/host/l2cap/stress compile
|
app=tests/bsim/bluetooth/host/l2cap/stress compile
|
||||||
|
|
14
tests/bsim/bluetooth/host/l2cap/many_conns/CMakeLists.txt
Normal file
14
tests/bsim/bluetooth/host/l2cap/many_conns/CMakeLists.txt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
|
||||||
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
|
project(bsim_test_l2cap_stress)
|
||||||
|
|
||||||
|
FILE(GLOB app_sources src/*.c)
|
||||||
|
target_sources(app PRIVATE ${app_sources} )
|
||||||
|
|
||||||
|
zephyr_include_directories(
|
||||||
|
${BSIM_COMPONENTS_PATH}/libUtilv1/src/
|
||||||
|
${BSIM_COMPONENTS_PATH}/libPhyComv1/src/
|
||||||
|
)
|
50
tests/bsim/bluetooth/host/l2cap/many_conns/prj.conf
Normal file
50
tests/bsim/bluetooth/host/l2cap/many_conns/prj.conf
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
CONFIG_BT=y
|
||||||
|
CONFIG_BT_CENTRAL=y
|
||||||
|
CONFIG_BT_PERIPHERAL=y
|
||||||
|
CONFIG_BT_DEVICE_NAME="L2CAP stress test"
|
||||||
|
|
||||||
|
CONFIG_BT_EATT=n
|
||||||
|
CONFIG_BT_L2CAP_ECRED=n
|
||||||
|
|
||||||
|
CONFIG_BT_SMP=y # Next config depends on it
|
||||||
|
CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y
|
||||||
|
|
||||||
|
# Disable auto-initiated procedures so they don't
|
||||||
|
# mess with the test's execution.
|
||||||
|
CONFIG_BT_AUTO_PHY_UPDATE=n
|
||||||
|
CONFIG_BT_AUTO_DATA_LEN_UPDATE=n
|
||||||
|
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n
|
||||||
|
|
||||||
|
# L2CAP MPS
|
||||||
|
# 23+27+27=77 makes exactly three full packets
|
||||||
|
CONFIG_BT_L2CAP_TX_MTU=77
|
||||||
|
|
||||||
|
# Send L2CAP PDUs without any fragmentation.
|
||||||
|
CONFIG_BT_BUF_ACL_TX_SIZE=81
|
||||||
|
|
||||||
|
CONFIG_BT_BUF_ACL_TX_COUNT=4
|
||||||
|
|
||||||
|
# The minimum value for this is
|
||||||
|
# L2AP MPS + L2CAP header (4)
|
||||||
|
CONFIG_BT_BUF_ACL_RX_SIZE=81
|
||||||
|
|
||||||
|
CONFIG_BT_L2CAP_TX_BUF_COUNT=100
|
||||||
|
|
||||||
|
CONFIG_BT_CTLR_DATA_LENGTH_MAX=81
|
||||||
|
CONFIG_BT_CTLR_RX_BUFFERS=10
|
||||||
|
|
||||||
|
CONFIG_BT_MAX_CONN=4
|
||||||
|
|
||||||
|
CONFIG_LOG=y
|
||||||
|
CONFIG_ASSERT=y
|
||||||
|
CONFIG_NET_BUF_POOL_USAGE=y
|
||||||
|
|
||||||
|
# CONFIG_BT_L2CAP_LOG_LEVEL_DBG=y
|
||||||
|
# CONFIG_BT_CONN_LOG_LEVEL_DBG=y
|
||||||
|
CONFIG_LOG_THREAD_ID_PREFIX=y
|
||||||
|
CONFIG_THREAD_NAME=y
|
||||||
|
|
||||||
|
CONFIG_ARCH_POSIX_TRAP_ON_FATAL=y
|
||||||
|
|
||||||
|
# Need this for the `pull` spy fn
|
||||||
|
CONFIG_BT_TESTING=y
|
22
tests/bsim/bluetooth/host/l2cap/many_conns/src/common.c
Normal file
22
tests/bsim/bluetooth/host/l2cap/many_conns/src/common.c
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
extern enum bst_result_t bst_result;
|
||||||
|
|
||||||
|
void test_init(void)
|
||||||
|
{
|
||||||
|
bst_ticker_set_next_tick_absolute(WAIT_TIME);
|
||||||
|
bst_result = In_progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_tick(bs_time_t HW_device_time)
|
||||||
|
{
|
||||||
|
if (bst_result != Passed) {
|
||||||
|
FAIL("test failed (not passed after %i seconds)\n", WAIT_SECONDS);
|
||||||
|
}
|
||||||
|
}
|
57
tests/bsim/bluetooth/host/l2cap/many_conns/src/common.h
Normal file
57
tests/bsim/bluetooth/host/l2cap/many_conns/src/common.h
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Common functions and helpers for L2CAP tests
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <zephyr/types.h>
|
||||||
|
#include <zephyr/sys/util.h>
|
||||||
|
#include <zephyr/sys/byteorder.h>
|
||||||
|
|
||||||
|
#include <zephyr/bluetooth/bluetooth.h>
|
||||||
|
#include <zephyr/bluetooth/hci.h>
|
||||||
|
#include <zephyr/bluetooth/l2cap.h>
|
||||||
|
#include "bs_types.h"
|
||||||
|
#include "bs_tracing.h"
|
||||||
|
#include "bstests.h"
|
||||||
|
#include "bs_pc_backchannel.h"
|
||||||
|
|
||||||
|
extern enum bst_result_t bst_result;
|
||||||
|
|
||||||
|
#define CREATE_FLAG(flag) static atomic_t flag = (atomic_t)false
|
||||||
|
#define SET_FLAG(flag) (void)atomic_set(&flag, (atomic_t)true)
|
||||||
|
#define UNSET_FLAG(flag) (void)atomic_set(&flag, (atomic_t)false)
|
||||||
|
#define TEST_FLAG(flag) (atomic_get(&flag) == (atomic_t)true)
|
||||||
|
#define WAIT_FOR_FLAG_SET(flag) \
|
||||||
|
while (!(bool)atomic_get(&flag)) { \
|
||||||
|
(void)k_sleep(K_MSEC(1)); \
|
||||||
|
}
|
||||||
|
#define WAIT_FOR_FLAG_UNSET(flag) \
|
||||||
|
while ((bool)atomic_get(&flag)) { \
|
||||||
|
(void)k_sleep(K_MSEC(1)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define WAIT_SECONDS 400 /* seconds */
|
||||||
|
#define WAIT_TIME (WAIT_SECONDS * USEC_PER_SEC) /* microseconds*/
|
||||||
|
|
||||||
|
#define FAIL(...) \
|
||||||
|
do { \
|
||||||
|
bst_result = Failed; \
|
||||||
|
bs_trace_error_time_line(__VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define PASS(...) \
|
||||||
|
do { \
|
||||||
|
bst_result = Passed; \
|
||||||
|
bs_trace_info_time(1, __VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define ASSERT(expr, ...) if (!(expr)) {FAIL(__VA_ARGS__); }
|
||||||
|
|
||||||
|
void test_init(void);
|
||||||
|
void test_tick(bs_time_t HW_device_time);
|
471
tests/bsim/bluetooth/host/l2cap/many_conns/src/main.c
Normal file
471
tests/bsim/bluetooth/host/l2cap/many_conns/src/main.c
Normal file
|
@ -0,0 +1,471 @@
|
||||||
|
/* Application main entry point */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Nordic Semiconductor
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bstests.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#define LOG_MODULE_NAME main
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(LOG_MODULE_NAME, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
CREATE_FLAG(is_connected);
|
||||||
|
CREATE_FLAG(flag_l2cap_connected);
|
||||||
|
|
||||||
|
#define NUM_PERIPHERALS CONFIG_BT_MAX_CONN
|
||||||
|
#define L2CAP_CHANS NUM_PERIPHERALS
|
||||||
|
#define SDU_NUM 1
|
||||||
|
#define SDU_LEN 10
|
||||||
|
|
||||||
|
/* Only one SDU per link will be transmitted */
|
||||||
|
NET_BUF_POOL_DEFINE(sdu_tx_pool,
|
||||||
|
CONFIG_BT_MAX_CONN, BT_L2CAP_SDU_BUF_SIZE(SDU_LEN),
|
||||||
|
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
|
||||||
|
|
||||||
|
/* Only one SDU per link will be received at a time */
|
||||||
|
NET_BUF_POOL_DEFINE(sdu_rx_pool,
|
||||||
|
CONFIG_BT_MAX_CONN, BT_L2CAP_SDU_BUF_SIZE(SDU_LEN),
|
||||||
|
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
|
||||||
|
|
||||||
|
static uint8_t tx_data[SDU_LEN];
|
||||||
|
static uint16_t rx_cnt;
|
||||||
|
static uint8_t disconnect_counter;
|
||||||
|
|
||||||
|
struct test_ctx {
|
||||||
|
struct bt_l2cap_le_chan le_chan;
|
||||||
|
size_t tx_left;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct test_ctx contexts[L2CAP_CHANS];
|
||||||
|
|
||||||
|
struct test_ctx *get_ctx(struct bt_l2cap_chan *chan)
|
||||||
|
{
|
||||||
|
struct bt_l2cap_le_chan *le_chan = CONTAINER_OF(chan, struct bt_l2cap_le_chan, chan);
|
||||||
|
struct test_ctx *ctx = CONTAINER_OF(le_chan, struct test_ctx, le_chan);
|
||||||
|
|
||||||
|
ASSERT(ctx >= &contexts[0] &&
|
||||||
|
ctx <= &contexts[L2CAP_CHANS], "memory corruption");
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
int l2cap_chan_send(struct bt_l2cap_chan *chan, uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
LOG_DBG("chan %p conn %u data %p len %d", chan, bt_conn_index(chan->conn), data, len);
|
||||||
|
|
||||||
|
struct net_buf *buf = net_buf_alloc(&sdu_tx_pool, K_NO_WAIT);
|
||||||
|
|
||||||
|
ASSERT(buf, "No more memory\n");
|
||||||
|
|
||||||
|
net_buf_reserve(buf, BT_L2CAP_SDU_CHAN_SEND_RESERVE);
|
||||||
|
net_buf_add_mem(buf, data, len);
|
||||||
|
|
||||||
|
int ret = bt_l2cap_chan_send(chan, buf);
|
||||||
|
|
||||||
|
ASSERT(ret >= 0, "Failed sending: err %d", ret);
|
||||||
|
|
||||||
|
LOG_DBG("sent %d len %d", ret, len);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct net_buf *alloc_buf_cb(struct bt_l2cap_chan *chan)
|
||||||
|
{
|
||||||
|
return net_buf_alloc(&sdu_rx_pool, K_NO_WAIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sent_cb(struct bt_l2cap_chan *chan)
|
||||||
|
{
|
||||||
|
struct test_ctx *ctx = get_ctx(chan);
|
||||||
|
|
||||||
|
LOG_DBG("%p", chan);
|
||||||
|
|
||||||
|
if (ctx->tx_left) {
|
||||||
|
ctx->tx_left--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int recv_cb(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
||||||
|
{
|
||||||
|
LOG_DBG("len %d", buf->len);
|
||||||
|
rx_cnt++;
|
||||||
|
|
||||||
|
/* Verify SDU data matches TX'd data. */
|
||||||
|
int pos = memcmp(buf->data, tx_data, buf->len);
|
||||||
|
|
||||||
|
if (pos != 0) {
|
||||||
|
LOG_ERR("RX data doesn't match TX: pos %d", pos);
|
||||||
|
LOG_HEXDUMP_ERR(buf->data, buf->len, "RX data");
|
||||||
|
LOG_HEXDUMP_INF(tx_data, buf->len, "TX data");
|
||||||
|
|
||||||
|
for (uint16_t p = 0; p < buf->len; p++) {
|
||||||
|
__ASSERT(buf->data[p] == tx_data[p],
|
||||||
|
"Failed rx[%d]=%x != expect[%d]=%x",
|
||||||
|
p, buf->data[p], p, tx_data[p]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void l2cap_chan_connected_cb(struct bt_l2cap_chan *l2cap_chan)
|
||||||
|
{
|
||||||
|
struct bt_l2cap_le_chan *chan =
|
||||||
|
CONTAINER_OF(l2cap_chan, struct bt_l2cap_le_chan, chan);
|
||||||
|
|
||||||
|
SET_FLAG(flag_l2cap_connected);
|
||||||
|
LOG_DBG("%x (tx mtu %d mps %d) (tx mtu %d mps %d)",
|
||||||
|
l2cap_chan,
|
||||||
|
chan->tx.mtu,
|
||||||
|
chan->tx.mps,
|
||||||
|
chan->rx.mtu,
|
||||||
|
chan->rx.mps);
|
||||||
|
}
|
||||||
|
|
||||||
|
void l2cap_chan_disconnected_cb(struct bt_l2cap_chan *chan)
|
||||||
|
{
|
||||||
|
UNSET_FLAG(flag_l2cap_connected);
|
||||||
|
LOG_DBG("%p", chan);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct bt_l2cap_chan_ops ops = {
|
||||||
|
.connected = l2cap_chan_connected_cb,
|
||||||
|
.disconnected = l2cap_chan_disconnected_cb,
|
||||||
|
.alloc_buf = alloc_buf_cb,
|
||||||
|
.recv = recv_cb,
|
||||||
|
.sent = sent_cb,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct test_ctx *alloc_test_context(void)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < L2CAP_CHANS; i++) {
|
||||||
|
struct bt_l2cap_le_chan *le_chan = &contexts[i].le_chan;
|
||||||
|
|
||||||
|
if (le_chan->state != BT_L2CAP_DISCONNECTED) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&contexts[i], 0, sizeof(struct test_ctx));
|
||||||
|
|
||||||
|
return &contexts[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int server_accept_cb(struct bt_conn *conn, struct bt_l2cap_server *server,
|
||||||
|
struct bt_l2cap_chan **chan)
|
||||||
|
{
|
||||||
|
struct test_ctx *ctx = NULL;
|
||||||
|
|
||||||
|
ctx = alloc_test_context();
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct bt_l2cap_le_chan *le_chan = &ctx->le_chan;
|
||||||
|
|
||||||
|
memset(le_chan, 0, sizeof(*le_chan));
|
||||||
|
le_chan->chan.ops = &ops;
|
||||||
|
le_chan->rx.mtu = SDU_LEN;
|
||||||
|
*chan = &le_chan->chan;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct bt_l2cap_server test_l2cap_server = {
|
||||||
|
.accept = server_accept_cb
|
||||||
|
};
|
||||||
|
|
||||||
|
static int l2cap_server_register(bt_security_t sec_level)
|
||||||
|
{
|
||||||
|
test_l2cap_server.psm = 0;
|
||||||
|
test_l2cap_server.sec_level = sec_level;
|
||||||
|
|
||||||
|
int err = bt_l2cap_server_register(&test_l2cap_server);
|
||||||
|
|
||||||
|
ASSERT(err == 0, "Failed to register l2cap server.");
|
||||||
|
|
||||||
|
return test_l2cap_server.psm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void connected(struct bt_conn *conn, uint8_t conn_err)
|
||||||
|
{
|
||||||
|
char addr[BT_ADDR_LE_STR_LEN];
|
||||||
|
|
||||||
|
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
|
||||||
|
|
||||||
|
ASSERT(!conn_err, "Failed to connect to %s (%u)", addr, conn_err);
|
||||||
|
|
||||||
|
LOG_DBG("%s", addr);
|
||||||
|
|
||||||
|
SET_FLAG(is_connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void disconnected(struct bt_conn *conn, uint8_t reason)
|
||||||
|
{
|
||||||
|
char addr[BT_ADDR_LE_STR_LEN];
|
||||||
|
|
||||||
|
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
|
||||||
|
|
||||||
|
LOG_DBG("%p %s (reason 0x%02x)", conn, addr, reason);
|
||||||
|
|
||||||
|
UNSET_FLAG(is_connected);
|
||||||
|
disconnect_counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_CONN_CB_DEFINE(conn_callbacks) = {
|
||||||
|
.connected = connected,
|
||||||
|
.disconnected = disconnected,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void disconnect_device(struct bt_conn *conn, void *data)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
SET_FLAG(is_connected);
|
||||||
|
|
||||||
|
err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
||||||
|
ASSERT(!err, "Failed to initate disconnect (err %d)", err);
|
||||||
|
|
||||||
|
LOG_DBG("Waiting for disconnection...");
|
||||||
|
WAIT_FOR_FLAG_UNSET(is_connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BT_LE_ADV_CONN_OT BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | \
|
||||||
|
BT_LE_ADV_OPT_ONE_TIME, \
|
||||||
|
BT_GAP_ADV_FAST_INT_MIN_2, \
|
||||||
|
BT_GAP_ADV_FAST_INT_MAX_2, NULL)
|
||||||
|
|
||||||
|
static void test_peripheral_main(void)
|
||||||
|
{
|
||||||
|
LOG_DBG("L2CAP CONN LATENCY Peripheral started*");
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* Prepare tx_data */
|
||||||
|
for (size_t i = 0; i < sizeof(tx_data); i++) {
|
||||||
|
tx_data[i] = (uint8_t)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bt_enable(NULL);
|
||||||
|
ASSERT(!err, "Can't enable Bluetooth (err %d)", err);
|
||||||
|
|
||||||
|
LOG_DBG("Peripheral Bluetooth initialized.");
|
||||||
|
LOG_DBG("Connectable advertising...");
|
||||||
|
err = bt_le_adv_start(BT_LE_ADV_CONN_OT, NULL, 0, NULL, 0);
|
||||||
|
ASSERT(!err, "Advertising failed to start (err %d)", err);
|
||||||
|
|
||||||
|
LOG_DBG("Advertising started.");
|
||||||
|
LOG_DBG("Peripheral waiting for connection...");
|
||||||
|
WAIT_FOR_FLAG_SET(is_connected);
|
||||||
|
LOG_DBG("Peripheral Connected.");
|
||||||
|
|
||||||
|
int psm = l2cap_server_register(BT_SECURITY_L1);
|
||||||
|
|
||||||
|
LOG_DBG("Registered server PSM %x", psm);
|
||||||
|
|
||||||
|
LOG_DBG("Peripheral waiting for transfer completion");
|
||||||
|
while (rx_cnt < SDU_NUM) {
|
||||||
|
k_msleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
bt_conn_foreach(BT_CONN_TYPE_LE, disconnect_device, NULL);
|
||||||
|
WAIT_FOR_FLAG_UNSET(is_connected);
|
||||||
|
LOG_INF("Total received: %d", rx_cnt);
|
||||||
|
|
||||||
|
ASSERT(rx_cnt == SDU_NUM, "Did not receive expected no of SDUs\n");
|
||||||
|
|
||||||
|
PASS("L2CAP LATENCY Peripheral passed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
|
||||||
|
struct net_buf_simple *ad)
|
||||||
|
{
|
||||||
|
struct bt_le_conn_param *param;
|
||||||
|
struct bt_conn *conn;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = bt_le_scan_stop();
|
||||||
|
ASSERT(!err, "Stop LE scan failed (err %d)", err);
|
||||||
|
|
||||||
|
char str[BT_ADDR_LE_STR_LEN];
|
||||||
|
|
||||||
|
bt_addr_le_to_str(addr, str, sizeof(str));
|
||||||
|
|
||||||
|
LOG_DBG("Connecting to %s", str);
|
||||||
|
|
||||||
|
param = BT_LE_CONN_PARAM_DEFAULT;
|
||||||
|
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, param, &conn);
|
||||||
|
ASSERT(!err, "Create conn failed (err %d)", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void connect_peripheral(void)
|
||||||
|
{
|
||||||
|
struct bt_le_scan_param scan_param = {
|
||||||
|
.type = BT_LE_SCAN_TYPE_ACTIVE,
|
||||||
|
.options = BT_LE_SCAN_OPT_NONE,
|
||||||
|
.interval = BT_GAP_SCAN_FAST_INTERVAL,
|
||||||
|
.window = BT_GAP_SCAN_FAST_WINDOW,
|
||||||
|
};
|
||||||
|
|
||||||
|
UNSET_FLAG(is_connected);
|
||||||
|
|
||||||
|
int err = bt_le_scan_start(&scan_param, device_found);
|
||||||
|
|
||||||
|
ASSERT(!err, "Scanning failed to start (err %d)\n", err);
|
||||||
|
|
||||||
|
LOG_DBG("Central initiating connection...");
|
||||||
|
WAIT_FOR_FLAG_SET(is_connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void connect_l2cap_channel(struct bt_conn *conn, void *data)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
struct test_ctx *ctx = alloc_test_context();
|
||||||
|
|
||||||
|
ASSERT(ctx, "No more available test contexts\n");
|
||||||
|
|
||||||
|
struct bt_l2cap_le_chan *le_chan = &ctx->le_chan;
|
||||||
|
|
||||||
|
le_chan->chan.ops = &ops;
|
||||||
|
|
||||||
|
UNSET_FLAG(flag_l2cap_connected);
|
||||||
|
|
||||||
|
err = bt_l2cap_chan_connect(conn, &le_chan->chan, 0x0080);
|
||||||
|
ASSERT(!err, "Error connecting l2cap channel (err %d)\n", err);
|
||||||
|
|
||||||
|
WAIT_FOR_FLAG_SET(flag_l2cap_connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define L2CAP_LE_CID_DYN_START 0x0040
|
||||||
|
#define L2CAP_LE_CID_DYN_END 0x007f
|
||||||
|
#define L2CAP_LE_CID_IS_DYN(_cid) \
|
||||||
|
(_cid >= L2CAP_LE_CID_DYN_START && _cid <= L2CAP_LE_CID_DYN_END)
|
||||||
|
|
||||||
|
static bool is_dynamic(struct bt_l2cap_le_chan *lechan)
|
||||||
|
{
|
||||||
|
return L2CAP_LE_CID_IS_DYN(lechan->tx.cid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bt_test_l2cap_data_pull_spy(struct bt_conn *conn,
|
||||||
|
struct bt_l2cap_le_chan *lechan,
|
||||||
|
size_t amount,
|
||||||
|
size_t *length)
|
||||||
|
{
|
||||||
|
static uint32_t last_pull_time;
|
||||||
|
uint32_t uptime = k_uptime_get_32();
|
||||||
|
|
||||||
|
if (lechan == NULL || !is_dynamic(lechan)) {
|
||||||
|
/* only interested in application-generated data */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last_pull_time == 0) {
|
||||||
|
last_pull_time = uptime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(uptime == last_pull_time,
|
||||||
|
"Too much delay servicing ready channels\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_central_main(void)
|
||||||
|
{
|
||||||
|
LOG_DBG("L2CAP CONN LATENCY Central started*");
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* Prepare tx_data */
|
||||||
|
for (size_t i = 0; i < sizeof(tx_data); i++) {
|
||||||
|
tx_data[i] = (uint8_t)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bt_enable(NULL);
|
||||||
|
ASSERT(err == 0, "Can't enable Bluetooth (err %d)\n", err);
|
||||||
|
LOG_DBG("Central Bluetooth initialized.");
|
||||||
|
|
||||||
|
/* Connect all peripherals */
|
||||||
|
for (int i = 0; i < NUM_PERIPHERALS; i++) {
|
||||||
|
connect_peripheral();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connect L2CAP channels */
|
||||||
|
LOG_DBG("Connect L2CAP channels");
|
||||||
|
bt_conn_foreach(BT_CONN_TYPE_LE, connect_l2cap_channel, NULL);
|
||||||
|
|
||||||
|
/* This is to allow the main thread to put PDUs for all the connections
|
||||||
|
* in the TX queues. This way we verify that we service as many
|
||||||
|
* connections as we have resources for.
|
||||||
|
*/
|
||||||
|
k_thread_priority_set(k_current_get(), K_HIGHEST_APPLICATION_THREAD_PRIO);
|
||||||
|
|
||||||
|
/* Send SDU_NUM SDUs to each peripheral */
|
||||||
|
for (int i = 0; i < NUM_PERIPHERALS; i++) {
|
||||||
|
contexts[i].tx_left = SDU_NUM;
|
||||||
|
l2cap_chan_send(&contexts[i].le_chan.chan, tx_data, sizeof(tx_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
k_thread_priority_set(k_current_get(), CONFIG_MAIN_THREAD_PRIORITY);
|
||||||
|
|
||||||
|
LOG_DBG("Wait until all transfers are completed.");
|
||||||
|
int remaining_tx_total;
|
||||||
|
|
||||||
|
/* Assertion that the `pull` callback gets serviced for all connections
|
||||||
|
* at the same time is handled in bt_l2cap_data_pull_spy().
|
||||||
|
*/
|
||||||
|
|
||||||
|
do {
|
||||||
|
k_msleep(100);
|
||||||
|
|
||||||
|
remaining_tx_total = 0;
|
||||||
|
for (int i = 0; i < L2CAP_CHANS; i++) {
|
||||||
|
remaining_tx_total += contexts[i].tx_left;
|
||||||
|
}
|
||||||
|
} while (remaining_tx_total);
|
||||||
|
|
||||||
|
LOG_DBG("Waiting until all peripherals are disconnected..");
|
||||||
|
while (disconnect_counter < NUM_PERIPHERALS) {
|
||||||
|
k_msleep(100);
|
||||||
|
}
|
||||||
|
LOG_DBG("All peripherals disconnected.");
|
||||||
|
|
||||||
|
PASS("L2CAP LATENCY Central passed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct bst_test_instance test_def[] = {
|
||||||
|
{
|
||||||
|
.test_id = "peripheral",
|
||||||
|
.test_descr = "Peripheral L2CAP LATENCY",
|
||||||
|
.test_pre_init_f = test_init,
|
||||||
|
.test_tick_f = test_tick,
|
||||||
|
.test_main_f = test_peripheral_main
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.test_id = "central",
|
||||||
|
.test_descr = "Central L2CAP LATENCY",
|
||||||
|
.test_pre_init_f = test_init,
|
||||||
|
.test_tick_f = test_tick,
|
||||||
|
.test_main_f = test_central_main
|
||||||
|
},
|
||||||
|
BSTEST_END_MARKER
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bst_test_list *test_main_l2cap_stress_install(struct bst_test_list *tests)
|
||||||
|
{
|
||||||
|
return bst_add_tests(tests, test_def);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern struct bst_test_list *test_main_l2cap_stress_install(struct bst_test_list *tests);
|
||||||
|
|
||||||
|
bst_test_install_t test_installers[] = {
|
||||||
|
test_main_l2cap_stress_install,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
bst_main();
|
||||||
|
return 0;
|
||||||
|
}
|
12
tests/bsim/bluetooth/host/l2cap/many_conns/tests_scripts/_compile.sh
Executable file
12
tests/bsim/bluetooth/host/l2cap/many_conns/tests_scripts/_compile.sh
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright 2024 Nordic Semiconductor ASA
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
set -eu
|
||||||
|
: "${ZEPHYR_BASE:?ZEPHYR_BASE must be defined}"
|
||||||
|
|
||||||
|
INCR_BUILD=1
|
||||||
|
source ${ZEPHYR_BASE}/tests/bsim/compile.source
|
||||||
|
|
||||||
|
app="$(guess_test_relpath)" compile
|
||||||
|
|
||||||
|
wait_for_background_jobs
|
22
tests/bsim/bluetooth/host/l2cap/many_conns/tests_scripts/l2cap.sh
Executable file
22
tests/bsim/bluetooth/host/l2cap/many_conns/tests_scripts/l2cap.sh
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright (c) 2024 Nordic Semiconductor
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
source ${ZEPHYR_BASE}/tests/bsim/sh_common.source
|
||||||
|
|
||||||
|
simulation_id="l2cap_many_conns"
|
||||||
|
|
||||||
|
bsim_exe=./bs_${BOARD_TS}_tests_bsim_bluetooth_host_l2cap_many_conns_prj_conf
|
||||||
|
|
||||||
|
cd ${BSIM_OUT_PATH}/bin
|
||||||
|
|
||||||
|
Execute "${bsim_exe}" -s=${simulation_id} -d=0 -testid=central -rs=42
|
||||||
|
|
||||||
|
Execute "${bsim_exe}" -s=${simulation_id} -d=1 -testid=peripheral -rs=1
|
||||||
|
Execute "${bsim_exe}" -s=${simulation_id} -d=2 -testid=peripheral -rs=2
|
||||||
|
Execute "${bsim_exe}" -s=${simulation_id} -d=3 -testid=peripheral -rs=3
|
||||||
|
Execute "${bsim_exe}" -s=${simulation_id} -d=4 -testid=peripheral -rs=4
|
||||||
|
|
||||||
|
Execute ./bs_2G4_phy_v1 -s=${simulation_id} -D=5 -sim_length=10e6 $@
|
||||||
|
|
||||||
|
wait_for_background_jobs
|
Loading…
Add table
Add a link
Reference in a new issue