tests: Bluetooth: Add multi-central robustness test
This test's purpose is to verify we (as a peripheral) don't leak resources when communicating with multiple centrals that connect and disconnect intermittently. Signed-off-by: Jonathan Rico <jonathan.rico@nordicsemi.no>
This commit is contained in:
parent
406d17c3c5
commit
11b3aa1f1f
10 changed files with 697 additions and 0 deletions
|
@ -11,6 +11,7 @@ set -ue
|
|||
source ${ZEPHYR_BASE}/tests/bsim/compile.source
|
||||
|
||||
app=tests/bsim/bluetooth/host/l2cap/many_conns compile
|
||||
app=tests/bsim/bluetooth/host/l2cap/multilink_peripheral compile
|
||||
app=tests/bsim/bluetooth/host/l2cap/general compile
|
||||
app=tests/bsim/bluetooth/host/l2cap/userdata compile
|
||||
app=tests/bsim/bluetooth/host/l2cap/stress compile
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
|
||||
project(multilink_peripheral)
|
||||
|
||||
add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/common/testlib testlib)
|
||||
target_link_libraries(app PRIVATE testlib)
|
||||
|
||||
# This contains babblesim-specific helpers, e.g. device synchronization.
|
||||
add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit)
|
||||
target_link_libraries(app PRIVATE babblekit)
|
||||
|
||||
zephyr_include_directories(
|
||||
${BSIM_COMPONENTS_PATH}/libUtilv1/src/
|
||||
${BSIM_COMPONENTS_PATH}/libPhyComv1/src/
|
||||
)
|
||||
|
||||
target_sources(app PRIVATE
|
||||
src/main.c
|
||||
src/dut.c
|
||||
src/central.c
|
||||
)
|
18
tests/bsim/bluetooth/host/l2cap/multilink_peripheral/Kconfig
Normal file
18
tests/bsim/bluetooth/host/l2cap/multilink_peripheral/Kconfig
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Kconfig options for the test
|
||||
#
|
||||
# Only used as single point for log level configuration.
|
||||
# Can be extended with any new kconfig, really.
|
||||
#
|
||||
# Copyright (c) 2024 Nordic Semiconductor ASA
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
menu "Test configuration"
|
||||
|
||||
module = APP
|
||||
module-str = app
|
||||
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
|
||||
endmenu
|
||||
|
||||
source "Kconfig.zephyr"
|
|
@ -0,0 +1,42 @@
|
|||
#### Logs, debug, etc options
|
||||
|
||||
CONFIG_ASSERT=y
|
||||
|
||||
# More debug
|
||||
CONFIG_LOG=y
|
||||
CONFIG_THREAD_NAME=y
|
||||
CONFIG_LOG_THREAD_ID_PREFIX=y
|
||||
|
||||
CONFIG_LOG_FUNC_NAME_PREFIX_DBG=y
|
||||
CONFIG_LOG_FUNC_NAME_PREFIX_INF=y
|
||||
CONFIG_LOG_FUNC_NAME_PREFIX_WRN=y
|
||||
CONFIG_LOG_FUNC_NAME_PREFIX_ERR=y
|
||||
|
||||
# CONFIG_APP_LOG_LEVEL_DBG=y
|
||||
# CONFIG_BT_L2CAP_LOG_LEVEL_DBG=y
|
||||
# CONFIG_BT_CONN_LOG_LEVEL_DBG=y
|
||||
# CONFIG_BT_HCI_DRIVER_LOG_LEVEL_DBG=y
|
||||
|
||||
CONFIG_ARCH_POSIX_TRAP_ON_FATAL=y
|
||||
|
||||
#### Test-specific options
|
||||
|
||||
CONFIG_BT=y
|
||||
CONFIG_BT_PERIPHERAL=y
|
||||
CONFIG_BT_CENTRAL=y
|
||||
|
||||
# Dependency of testlib/adv and testlib/scan.
|
||||
CONFIG_BT_EXT_ADV=y
|
||||
CONFIG_BT_DEVICE_NAME="dee-yu-tee"
|
||||
|
||||
# Dynamic channel depends on SMP
|
||||
CONFIG_BT_SMP=y
|
||||
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
|
||||
|
||||
CONFIG_BT_MAX_CONN=2
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/bluetooth/att.h>
|
||||
#include <zephyr/bluetooth/l2cap.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include "testlib/scan.h"
|
||||
#include "testlib/conn.h"
|
||||
|
||||
#include "babblekit/flags.h"
|
||||
#include "babblekit/sync.h"
|
||||
#include "babblekit/testcase.h"
|
||||
|
||||
/* local includes */
|
||||
#include "data.h"
|
||||
|
||||
LOG_MODULE_REGISTER(central, CONFIG_APP_LOG_LEVEL);
|
||||
|
||||
static struct bt_l2cap_le_chan le_chan;
|
||||
|
||||
static void sent_cb(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
TEST_FAIL("Tester should not send data");
|
||||
}
|
||||
|
||||
static int recv_cb(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
||||
{
|
||||
LOG_DBG("received %d bytes", buf->len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void l2cap_chan_connected_cb(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
LOG_DBG("%p", chan);
|
||||
}
|
||||
|
||||
static void l2cap_chan_disconnected_cb(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
LOG_DBG("%p", chan);
|
||||
}
|
||||
|
||||
static int server_accept_cb(struct bt_conn *conn, struct bt_l2cap_server *server,
|
||||
struct bt_l2cap_chan **chan)
|
||||
{
|
||||
static struct bt_l2cap_chan_ops ops = {
|
||||
.connected = l2cap_chan_connected_cb,
|
||||
.disconnected = l2cap_chan_disconnected_cb,
|
||||
.recv = recv_cb,
|
||||
.sent = sent_cb,
|
||||
};
|
||||
|
||||
memset(&le_chan, 0, sizeof(le_chan));
|
||||
le_chan.chan.ops = &ops;
|
||||
*chan = &le_chan.chan;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l2cap_server_register(bt_security_t sec_level)
|
||||
{
|
||||
static struct bt_l2cap_server test_l2cap_server = {.accept = server_accept_cb};
|
||||
|
||||
test_l2cap_server.psm = 0;
|
||||
test_l2cap_server.sec_level = sec_level;
|
||||
|
||||
int err = bt_l2cap_server_register(&test_l2cap_server);
|
||||
|
||||
TEST_ASSERT(err == 0, "Failed to register l2cap server (err %d)", err);
|
||||
|
||||
return test_l2cap_server.psm;
|
||||
}
|
||||
|
||||
static void acl_connected(struct bt_conn *conn, uint8_t err)
|
||||
{
|
||||
char addr[BT_ADDR_LE_STR_LEN];
|
||||
|
||||
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to connect to %s (0x%02x)", addr, err);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DBG("Connected to %s", addr);
|
||||
}
|
||||
|
||||
static void acl_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("Disconnected from %s (reason 0x%02x)", addr, reason);
|
||||
}
|
||||
|
||||
/* Read the comments on `entrypoint_dut()` first. */
|
||||
void entrypoint_central(void)
|
||||
{
|
||||
int err;
|
||||
struct bt_conn *conn = NULL;
|
||||
bt_addr_le_t dut;
|
||||
static struct bt_conn_cb central_cb = {
|
||||
.connected = acl_connected,
|
||||
.disconnected = acl_disconnected,
|
||||
};
|
||||
|
||||
/* Mark test as in progress. */
|
||||
TEST_START("central");
|
||||
|
||||
/* Initialize Bluetooth */
|
||||
err = bt_conn_cb_register(¢ral_cb);
|
||||
TEST_ASSERT(err == 0, "Can't register callbacks (err %d)", err);
|
||||
|
||||
err = bt_enable(NULL);
|
||||
TEST_ASSERT(err == 0, "Can't enable Bluetooth (err %d)", err);
|
||||
|
||||
LOG_DBG("Bluetooth initialized");
|
||||
|
||||
int psm = l2cap_server_register(BT_SECURITY_L1);
|
||||
|
||||
LOG_DBG("Registered server PSM %x", psm);
|
||||
|
||||
/* The device address will not change. Scan only once in order to reduce
|
||||
* test time.
|
||||
*/
|
||||
err = bt_testlib_scan_find_name(&dut, DUT_NAME);
|
||||
TEST_ASSERT(!err, "Failed to start scan (err %d)", err);
|
||||
|
||||
/* DUT will terminate all devices when it's done. Mark the device as
|
||||
* "passed" so bsim doesn't return a nonzero err code when the
|
||||
* termination happens.
|
||||
*/
|
||||
TEST_PASS("central");
|
||||
|
||||
while (true) {
|
||||
/* Create a connection using that address */
|
||||
err = bt_testlib_connect(&dut, &conn);
|
||||
TEST_ASSERT(!err, "Failed to initiate connection (err %d)", err);
|
||||
|
||||
LOG_DBG("Connected");
|
||||
|
||||
/* Receive in the background */
|
||||
k_sleep(K_MSEC(1000));
|
||||
|
||||
/* Disconnect and destroy connection object */
|
||||
err = bt_testlib_disconnect(&conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
||||
TEST_ASSERT(!err, "Failed to disconnect (err %d)", err);
|
||||
|
||||
LOG_DBG("Disconnected");
|
||||
|
||||
/* Simulate the central going in and out of range. In the real world, it is unlikely
|
||||
* to drop a connection and re-establish it after only a few milliseconds.
|
||||
*/
|
||||
k_sleep(K_MSEC(200));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_TESTS_BSIM_BLUETOOTH_HOST_L2CAP_MULTILINK_PERIPHERAL_SRC_DATA_H_
|
||||
#define ZEPHYR_TESTS_BSIM_BLUETOOTH_HOST_L2CAP_MULTILINK_PERIPHERAL_SRC_DATA_H_
|
||||
|
||||
#define SDU_NUM 200 /* Number of SDU (L2CAP buffers) to send */
|
||||
#define SDU_LEN 20 /* Length in bytes of said SDUs */
|
||||
#define DUT_NAME CONFIG_BT_DEVICE_NAME /* name to advertise with */
|
||||
|
||||
#endif /* ZEPHYR_TESTS_BSIM_BLUETOOTH_HOST_L2CAP_MULTILINK_PERIPHERAL_SRC_DATA_H_ */
|
338
tests/bsim/bluetooth/host/l2cap/multilink_peripheral/src/dut.c
Normal file
338
tests/bsim/bluetooth/host/l2cap/multilink_peripheral/src/dut.c
Normal file
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/bluetooth/l2cap.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include "babblekit/flags.h"
|
||||
#include "babblekit/testcase.h"
|
||||
|
||||
/* local includes */
|
||||
#include "data.h"
|
||||
|
||||
LOG_MODULE_REGISTER(dut, CONFIG_APP_LOG_LEVEL);
|
||||
|
||||
static DEFINE_FLAG(ADVERTISING);
|
||||
|
||||
static void sdu_destroy(struct net_buf *buf)
|
||||
{
|
||||
LOG_DBG("%p", buf);
|
||||
|
||||
net_buf_destroy(buf);
|
||||
}
|
||||
|
||||
/* Only one SDU per link will be transmitted at a time */
|
||||
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, sdu_destroy);
|
||||
|
||||
static uint8_t tx_data[SDU_LEN];
|
||||
|
||||
struct test_ctx {
|
||||
bt_addr_le_t peer;
|
||||
struct bt_l2cap_le_chan le_chan;
|
||||
size_t sdu_count; /* the number of SDUs that have been transferred until now */
|
||||
};
|
||||
|
||||
static struct test_ctx contexts[CONFIG_BT_MAX_CONN];
|
||||
|
||||
static int send_data_over_l2cap(struct bt_l2cap_chan *chan, uint8_t *data, size_t len)
|
||||
{
|
||||
char addr[BT_ADDR_LE_STR_LEN];
|
||||
|
||||
bt_addr_le_to_str(bt_conn_get_dst(chan->conn), addr, sizeof(addr));
|
||||
|
||||
LOG_DBG("[%s] chan %p data %p len %d", addr, chan, data, len);
|
||||
|
||||
struct net_buf *buf = net_buf_alloc(&sdu_tx_pool, K_NO_WAIT);
|
||||
|
||||
if (buf == NULL) {
|
||||
TEST_FAIL("No more memory");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
TEST_ASSERT(ret == 0, "Failed sending: err %d", ret);
|
||||
LOG_DBG("queued SDU", len);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void resume_sending_until_done(struct test_ctx *ctx)
|
||||
{
|
||||
struct bt_l2cap_chan *chan = &ctx->le_chan.chan;
|
||||
|
||||
TEST_ASSERT(ctx->le_chan.state == BT_L2CAP_CONNECTED,
|
||||
"attempting to send on disconnected channel (%p)", chan);
|
||||
|
||||
LOG_DBG("%p, transmitted %d SDUs", chan, ctx->sdu_count);
|
||||
|
||||
if (ctx->sdu_count < SDU_NUM) {
|
||||
send_data_over_l2cap(chan, tx_data, sizeof(tx_data));
|
||||
} else {
|
||||
char addr[BT_ADDR_LE_STR_LEN];
|
||||
|
||||
bt_addr_le_to_str(bt_conn_get_dst(chan->conn), addr, sizeof(addr));
|
||||
|
||||
LOG_DBG("[%s] Done sending", addr);
|
||||
}
|
||||
}
|
||||
|
||||
static struct test_ctx *get_ctx_from_chan(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);
|
||||
|
||||
TEST_ASSERT(PART_OF_ARRAY(contexts, ctx), "memory corruption");
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static void sent_cb(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
struct test_ctx *ctx = get_ctx_from_chan(chan);
|
||||
|
||||
LOG_DBG("%p", chan);
|
||||
|
||||
ctx->sdu_count++;
|
||||
|
||||
resume_sending_until_done(ctx);
|
||||
}
|
||||
|
||||
static void l2cap_chan_connected_cb(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
struct bt_l2cap_le_chan *le_chan = CONTAINER_OF(chan, struct bt_l2cap_le_chan, chan);
|
||||
|
||||
LOG_DBG("%p (tx mtu %d mps %d) (tx mtu %d mps %d)", chan, le_chan->tx.mtu, le_chan->tx.mps,
|
||||
le_chan->rx.mtu, le_chan->rx.mps);
|
||||
|
||||
LOG_DBG("initiating SDU transfer");
|
||||
resume_sending_until_done(get_ctx_from_chan(chan));
|
||||
}
|
||||
|
||||
static void l2cap_chan_disconnected_cb(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
LOG_DBG("%p", chan);
|
||||
}
|
||||
|
||||
static int recv_cb(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
||||
{
|
||||
TEST_FAIL("DUT should not receive data");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int connect_l2cap_channel(struct bt_conn *conn, struct bt_l2cap_le_chan *le_chan)
|
||||
{
|
||||
static struct bt_l2cap_chan_ops ops = {
|
||||
.connected = l2cap_chan_connected_cb,
|
||||
.disconnected = l2cap_chan_disconnected_cb,
|
||||
.recv = recv_cb,
|
||||
.sent = sent_cb,
|
||||
};
|
||||
|
||||
memset(le_chan, 0, sizeof(*le_chan));
|
||||
le_chan->chan.ops = &ops;
|
||||
|
||||
return bt_l2cap_chan_connect(conn, &le_chan->chan, 0x0080);
|
||||
}
|
||||
|
||||
static bool addr_in_use(bt_addr_le_t *address)
|
||||
{
|
||||
return !bt_addr_le_eq(address, BT_ADDR_LE_ANY);
|
||||
}
|
||||
|
||||
static struct test_ctx *alloc_ctx(void)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(contexts); i++) {
|
||||
struct test_ctx *context = &contexts[i];
|
||||
struct bt_l2cap_le_chan *le_chan = &context->le_chan;
|
||||
|
||||
if (le_chan->state != BT_L2CAP_DISCONNECTED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (addr_in_use(&context->peer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
memset(context, 0, sizeof(struct test_ctx));
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct test_ctx *get_ctx_from_address(const bt_addr_le_t *address)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(contexts); i++) {
|
||||
struct test_ctx *context = &contexts[i];
|
||||
|
||||
if (bt_addr_le_eq(address, &context->peer)) {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void acl_connected(struct bt_conn *conn, uint8_t err)
|
||||
{
|
||||
const bt_addr_le_t *central = bt_conn_get_dst(conn);
|
||||
char addr[BT_ADDR_LE_STR_LEN];
|
||||
struct test_ctx *ctx;
|
||||
int ret;
|
||||
|
||||
bt_addr_le_to_str(central, addr, sizeof(addr));
|
||||
|
||||
TEST_ASSERT(err == 0, "Failed to connect to %s (0x%02x)", addr, err);
|
||||
|
||||
UNSET_FLAG(ADVERTISING);
|
||||
|
||||
LOG_DBG("[%s] Connected (conn %p)", addr, conn);
|
||||
|
||||
ctx = get_ctx_from_address(central);
|
||||
if (ctx == NULL) {
|
||||
LOG_DBG("no initialized context for %s, allocating..", addr);
|
||||
ctx = alloc_ctx();
|
||||
TEST_ASSERT(ctx, "Couldn't allocate ctx for conn %p", conn);
|
||||
|
||||
LOG_DBG("allocated context %p for %s", ctx, central);
|
||||
bt_addr_le_copy(&ctx->peer, central);
|
||||
}
|
||||
|
||||
ret = connect_l2cap_channel(conn, &ctx->le_chan);
|
||||
TEST_ASSERT(!ret, "Error connecting l2cap channel (err %d)", ret);
|
||||
}
|
||||
|
||||
static void acl_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("Disconnected from %s (reason 0x%02x)", addr, reason);
|
||||
}
|
||||
|
||||
static void increment(struct bt_conn *conn, void *user_data)
|
||||
{
|
||||
size_t *conn_count = user_data;
|
||||
|
||||
(*conn_count)++;
|
||||
}
|
||||
|
||||
static bool have_free_conn(void)
|
||||
{
|
||||
size_t conn_count = 0;
|
||||
|
||||
bt_conn_foreach(BT_CONN_TYPE_LE, increment, &conn_count);
|
||||
|
||||
return conn_count < CONFIG_BT_MAX_CONN;
|
||||
}
|
||||
|
||||
static const struct bt_data ad[] = {
|
||||
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
|
||||
BT_DATA(BT_DATA_NAME_COMPLETE, DUT_NAME, sizeof(DUT_NAME) - 1),
|
||||
};
|
||||
|
||||
static void start_advertising(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
LOG_DBG("starting advertiser");
|
||||
|
||||
err = bt_le_adv_start(BT_LE_ADV_CONN_ONE_TIME, ad, ARRAY_SIZE(ad), NULL, 0);
|
||||
TEST_ASSERT(!err, "Advertising failed to start (err %d)", err);
|
||||
}
|
||||
|
||||
static bool all_data_transferred(void)
|
||||
{
|
||||
size_t total_sdu_count = 0;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(contexts); i++) {
|
||||
total_sdu_count += contexts[i].sdu_count;
|
||||
}
|
||||
|
||||
TEST_ASSERT(total_sdu_count <= (SDU_NUM * CONFIG_BT_MAX_CONN),
|
||||
"Received more SDUs than expected");
|
||||
|
||||
return total_sdu_count == (SDU_NUM * CONFIG_BT_MAX_CONN);
|
||||
}
|
||||
|
||||
void entrypoint_dut(void)
|
||||
{
|
||||
/* Test purpose:
|
||||
*
|
||||
* For a peripheral device (DUT) that has multiple ACL connections to
|
||||
* central devices: Verify that the data streams on one connection are
|
||||
* not affected by one of the centrals going out of range or not
|
||||
* responding.
|
||||
*
|
||||
* Three devices:
|
||||
* - `dut`: sends L2CAP packets to p0 and p1
|
||||
*
|
||||
* DUT (in a loop):
|
||||
* - advertise as connectable
|
||||
* - [acl connected]
|
||||
* - establish L2CAP channel
|
||||
* - [l2 connected]
|
||||
* - send L2CAP data until ACL disconnected or SDU_NUM SDUs reached
|
||||
*
|
||||
* p0/1/2 (in a loop):
|
||||
* - scan & connect ACL
|
||||
* - [acl connected]
|
||||
* - [l2cap dynamic channel connected]
|
||||
* - receive data from DUT
|
||||
* - disconnect
|
||||
*
|
||||
* Verdict:
|
||||
* - DUT is able to transfer SDU_NUM SDUs to all peers. Data can be
|
||||
* dropped but resources should not leak, and the transfer should not
|
||||
* stall.
|
||||
*/
|
||||
int err;
|
||||
static struct bt_conn_cb peripheral_cb = {
|
||||
.connected = acl_connected,
|
||||
.disconnected = acl_disconnected,
|
||||
};
|
||||
|
||||
/* Mark test as in progress. */
|
||||
TEST_START("dut");
|
||||
|
||||
/* Initialize Bluetooth */
|
||||
err = bt_conn_cb_register(&peripheral_cb);
|
||||
TEST_ASSERT(err == 0, "Can't register callbacks (err %d)", err);
|
||||
|
||||
err = bt_enable(NULL);
|
||||
TEST_ASSERT(err == 0, "Can't enable Bluetooth (err %d)", err);
|
||||
|
||||
LOG_DBG("Bluetooth initialized");
|
||||
|
||||
while (!all_data_transferred()) {
|
||||
if (!have_free_conn() || IS_FLAG_SET(ADVERTISING)) {
|
||||
/* Sleep to not hammer the CPU checking the `if` */
|
||||
k_sleep(K_MSEC(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
start_advertising();
|
||||
SET_FLAG(ADVERTISING);
|
||||
|
||||
/* L2 channel is opened from conn->connected() */
|
||||
/* L2 data transfer is initiated from l2->connected() */
|
||||
/* L2 data transfer is initiated for next SDU from l2->sent() */
|
||||
|
||||
}
|
||||
|
||||
TEST_PASS_AND_EXIT("dut");
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#include "bs_tracing.h"
|
||||
#include "bstests.h"
|
||||
#include "babblekit/testcase.h"
|
||||
|
||||
extern void entrypoint_dut(void);
|
||||
extern void entrypoint_central(void);
|
||||
extern enum bst_result_t bst_result;
|
||||
|
||||
static void test_end_cb(void)
|
||||
{
|
||||
/* This callback will fire right before the executable returns */
|
||||
if (bst_result != Passed) {
|
||||
TEST_PRINT("Test has not passed.");
|
||||
}
|
||||
}
|
||||
|
||||
static const struct bst_test_instance entrypoints[] = {
|
||||
{
|
||||
.test_id = "dut",
|
||||
.test_delete_f = test_end_cb,
|
||||
.test_main_f = entrypoint_dut,
|
||||
},
|
||||
{
|
||||
.test_id = "central",
|
||||
.test_delete_f = test_end_cb,
|
||||
.test_main_f = entrypoint_central,
|
||||
},
|
||||
BSTEST_END_MARKER,
|
||||
};
|
||||
|
||||
static struct bst_test_list *install(struct bst_test_list *tests)
|
||||
{
|
||||
return bst_add_tests(tests, entrypoints);
|
||||
};
|
||||
|
||||
bst_test_install_t test_installers[] = {install, NULL};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
bst_main();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
#!/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
|
31
tests/bsim/bluetooth/host/l2cap/multilink_peripheral/test_scripts/run.sh
Executable file
31
tests/bsim/bluetooth/host/l2cap/multilink_peripheral/test_scripts/run.sh
Executable file
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2024 Nordic Semiconductor
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -eu
|
||||
|
||||
source ${ZEPHYR_BASE}/tests/bsim/sh_common.source
|
||||
|
||||
test_name="$(guess_test_long_name)"
|
||||
|
||||
simulation_id=${test_name}
|
||||
|
||||
SIM_LEN_US=$((40 * 1000 * 1000))
|
||||
|
||||
test_exe="${BSIM_OUT_PATH}/bin/bs_${BOARD_TS}_${test_name}_prj_conf"
|
||||
|
||||
cd ${BSIM_OUT_PATH}/bin
|
||||
|
||||
Execute ./bs_2G4_phy_v1 -dump_imm -s=${simulation_id} -D=3 -sim_length=${SIM_LEN_US} $@
|
||||
|
||||
Execute "${test_exe}" -s=${simulation_id} -d=0 -rs=420 -RealEncryption=1 -testid=dut
|
||||
|
||||
# Start centrals with an offset, so the CONN_IND packets don't clash on-air.
|
||||
# Since code executes in zero-time, this will always happen if we don't stagger
|
||||
# the connection creation somehow.
|
||||
Execute "${test_exe}" -s=${simulation_id} \
|
||||
-d=1 -rs=169 -RealEncryption=1 -testid=central -delay_init -start_offset=1e3
|
||||
Execute "${test_exe}" -s=${simulation_id} \
|
||||
-d=2 -rs=690 -RealEncryption=1 -testid=central -delay_init -start_offset=10e3
|
||||
|
||||
wait_for_background_jobs
|
Loading…
Add table
Add a link
Reference in a new issue