tests: Bluetooth: Add ACL re-assembly test
This tests shows that there is a problematic buffer configuration. The host locks up when `CONFIG_BT_BUF_ACL_RX_COUNT` is smaller or equal to the number of connected peers, and all those peers send an L2CAP PDU that needs re-assembly at the same time. Although this seems far-fetched, this bug has been observed in a real-life situation on actual hardware. Signed-off-by: Jonathan Rico <jonathan.rico@nordicsemi.no>
This commit is contained in:
parent
96d8682a5b
commit
ff5c577aa8
17 changed files with 1208 additions and 0 deletions
|
@ -16,4 +16,5 @@ target_include_directories(babblekit PUBLIC
|
|||
|
||||
target_sources(babblekit PRIVATE
|
||||
src/sync.c
|
||||
src/device.c
|
||||
)
|
||||
|
|
18
tests/bsim/babblekit/include/babblekit/device.h
Normal file
18
tests/bsim/babblekit/include/babblekit/device.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* This file provides utilities related to the device and cli arguments.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* @brief Get the device's simulation number
|
||||
*
|
||||
* This returns the device's number in the BabbleSim simulation.
|
||||
* Ie will return 1 if the device was started with the "-d 1" argument.
|
||||
*
|
||||
* @return Device number in simulation
|
||||
*/
|
||||
int bk_device_get_number(void);
|
13
tests/bsim/babblekit/src/device.c
Normal file
13
tests/bsim/babblekit/src/device.c
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "argparse.h"
|
||||
#include "bs_types.h"
|
||||
|
||||
unsigned int bk_device_get_number(void)
|
||||
{
|
||||
return get_device_nbr();
|
||||
}
|
|
@ -27,6 +27,8 @@ app=tests/bsim/bluetooth/host/misc/disconnect/tester compile
|
|||
app=tests/bsim/bluetooth/host/misc/conn_stress/central compile
|
||||
app=tests/bsim/bluetooth/host/misc/conn_stress/peripheral compile
|
||||
app=tests/bsim/bluetooth/host/misc/hfc compile
|
||||
app=tests/bsim/bluetooth/host/misc/hfc_multilink/dut compile
|
||||
app=tests/bsim/bluetooth/host/misc/hfc_multilink/tester compile
|
||||
app=tests/bsim/bluetooth/host/misc/unregister_conn_cb compile
|
||||
app=tests/bsim/bluetooth/host/misc/sample_test compile
|
||||
|
||||
|
|
20
tests/bsim/bluetooth/host/misc/hfc_multilink/data.h
Normal file
20
tests/bsim/bluetooth/host/misc/hfc_multilink/data.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_TESTS_BSIM_BLUETOOTH_HOST_MISC_HFC_MULTILINK_SRC_DATA_H_
|
||||
#define ZEPHYR_TESTS_BSIM_BLUETOOTH_HOST_MISC_HFC_MULTILINK_SRC_DATA_H_
|
||||
|
||||
#define TESTER_NAME "tester"
|
||||
#define SDU_NUM 3
|
||||
#define L2CAP_TEST_PSM 0x0080
|
||||
/* use the first dynamic channel ID */
|
||||
#define L2CAP_TEST_CID 0x0040
|
||||
#define PAYLOAD_LEN 50
|
||||
|
||||
#define EXPECTED_CONN_INTERVAL 50
|
||||
#define CONN_INTERVAL_TOL 20
|
||||
|
||||
#endif /* ZEPHYR_TESTS_BSIM_BLUETOOTH_HOST_MISC_HFC_MULTILINK_SRC_DATA_H_ */
|
|
@ -0,0 +1,28 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
|
||||
project(hfc_multilink)
|
||||
|
||||
# This contains a variety of helper functions that abstract away common tasks,
|
||||
# like scanning, setting up a connection, querying the peer for a given
|
||||
# characteristic, etc..
|
||||
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
|
||||
)
|
15
tests/bsim/bluetooth/host/misc/hfc_multilink/dut/Kconfig
Normal file
15
tests/bsim/bluetooth/host/misc/hfc_multilink/dut/Kconfig
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Kconfig options for the test
|
||||
#
|
||||
# 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"
|
36
tests/bsim/bluetooth/host/misc/hfc_multilink/dut/prj.conf
Normal file
36
tests/bsim/bluetooth/host/misc/hfc_multilink/dut/prj.conf
Normal file
|
@ -0,0 +1,36 @@
|
|||
CONFIG_LOG=y
|
||||
CONFIG_ASSERT=y
|
||||
CONFIG_THREAD_NAME=y
|
||||
CONFIG_LOG_THREAD_ID_PREFIX=y
|
||||
CONFIG_ARCH_POSIX_TRAP_ON_FATAL=y
|
||||
|
||||
CONFIG_APP_LOG_LEVEL_DBG=y
|
||||
# CONFIG_BT_CONN_LOG_LEVEL_DBG=y
|
||||
# CONFIG_BT_ATT_LOG_LEVEL_DBG=y
|
||||
# CONFIG_BT_GATT_LOG_LEVEL_DBG=y
|
||||
|
||||
CONFIG_BT=y
|
||||
CONFIG_BT_DEVICE_NAME="sample-test"
|
||||
CONFIG_BT_CENTRAL=y
|
||||
|
||||
# Dependency of testlib/adv and testlib/scan.
|
||||
CONFIG_BT_EXT_ADV=y
|
||||
|
||||
# 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
|
||||
|
||||
# As many host buffers as connection slots
|
||||
# The whole test hinges on bufs <= links, which is a valid
|
||||
# configuration at the time of writing this test.
|
||||
CONFIG_BT_MAX_CONN=3
|
||||
CONFIG_BT_BUF_ACL_RX_COUNT=3
|
||||
# It passes with 4
|
||||
# CONFIG_BT_BUF_ACL_RX_COUNT=4
|
||||
CONFIG_BT_HCI_ACL_FLOW_CONTROL=y
|
212
tests/bsim/bluetooth/host/misc/hfc_multilink/dut/src/dut.c
Normal file
212
tests/bsim/bluetooth/host/misc/hfc_multilink/dut/src/dut.c
Normal file
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* 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 "testlib/conn.h"
|
||||
#include "testlib/scan.h"
|
||||
|
||||
#include "babblekit/flags.h"
|
||||
#include "babblekit/testcase.h"
|
||||
|
||||
/* local includes */
|
||||
#include "data.h"
|
||||
|
||||
LOG_MODULE_REGISTER(dut, CONFIG_APP_LOG_LEVEL);
|
||||
|
||||
#define NUM_TESTERS CONFIG_BT_MAX_CONN
|
||||
|
||||
/* This test will fail when CONFIG_BT_MAX_CONN == CONFIG_BT_BUF_ACL_RX_COUNT */
|
||||
BUILD_ASSERT(CONFIG_BT_BUF_ACL_RX_COUNT == CONFIG_BT_MAX_CONN);
|
||||
|
||||
struct tester {
|
||||
size_t sdu_count;
|
||||
struct bt_conn *conn;
|
||||
struct bt_l2cap_le_chan le_chan;
|
||||
};
|
||||
|
||||
static struct tester testers[NUM_TESTERS];
|
||||
|
||||
static struct tester *get_tester(struct bt_conn *conn)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(testers); i++) {
|
||||
if (testers[i].conn == conn) {
|
||||
return &testers[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
char addr[BT_ADDR_LE_STR_LEN];
|
||||
struct tester *tester = get_tester(chan->conn);
|
||||
|
||||
tester->sdu_count += 1;
|
||||
|
||||
bt_addr_le_to_str(bt_conn_get_dst(chan->conn), addr, sizeof(addr));
|
||||
|
||||
LOG_INF("Received SDU %d / %d from (%s)", tester->sdu_count, SDU_NUM, addr);
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
struct tester *tester = get_tester(conn);
|
||||
struct bt_l2cap_le_chan *le_chan = &tester->le_chan;
|
||||
|
||||
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 = L2CAP_TEST_PSM;
|
||||
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 struct bt_conn *connect_tester(void)
|
||||
{
|
||||
int err;
|
||||
bt_addr_le_t tester = {};
|
||||
struct bt_conn *conn = NULL;
|
||||
char addr[BT_ADDR_LE_STR_LEN];
|
||||
|
||||
/* The device address will not change. Scan only once in order to reduce
|
||||
* test time.
|
||||
*/
|
||||
err = bt_testlib_scan_find_name(&tester, TESTER_NAME);
|
||||
TEST_ASSERT(!err, "Failed to start scan (err %d)", err);
|
||||
|
||||
/* Create a connection using that address */
|
||||
err = bt_testlib_connect(&tester, &conn);
|
||||
TEST_ASSERT(!err, "Failed to initiate connection (err %d)", err);
|
||||
|
||||
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
|
||||
LOG_DBG("Connected to %s", addr);
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
static bool all_data_transferred(void)
|
||||
{
|
||||
size_t total_sdu_count = 0;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(testers); i++) {
|
||||
total_sdu_count += testers[i].sdu_count;
|
||||
}
|
||||
|
||||
TEST_ASSERT(total_sdu_count <= (SDU_NUM * NUM_TESTERS), "Received more SDUs than expected");
|
||||
|
||||
return total_sdu_count == (SDU_NUM * NUM_TESTERS);
|
||||
}
|
||||
|
||||
void entrypoint_dut(void)
|
||||
{
|
||||
/* Multilink Host Flow Control (HFC) test
|
||||
*
|
||||
* Test purpose:
|
||||
*
|
||||
* Verifies that we are able to do L2CAP recombination on multiple links
|
||||
* when we only have as many buffers as links.
|
||||
*
|
||||
* Devices:
|
||||
* - `dut`: receives L2CAP PDUs from testers
|
||||
* - `tester`: send ACL packets (parts of large L2CAP PDU) very slowly
|
||||
*
|
||||
* Procedure:
|
||||
*
|
||||
* DUT:
|
||||
* - establish connection to tester
|
||||
* - [acl connected]
|
||||
* - establish L2CAP channel
|
||||
* - [l2 connected]
|
||||
* - receive L2CAP PDUs until SDU_NUM is reached
|
||||
* - mark test as passed and terminate simulation
|
||||
*
|
||||
* tester 0/1/2:
|
||||
* - scan & connect ACL
|
||||
* - [acl connected]
|
||||
* - [l2cap dynamic channel connected]
|
||||
* (and then in a loop)
|
||||
* - send part of L2CAP PDU
|
||||
* - wait a set amount of time
|
||||
* - exit loop when SDU_NUM sent
|
||||
*
|
||||
* [verdict]
|
||||
* - dut application is able to receive all expected L2CAP packets from
|
||||
* the testers
|
||||
*/
|
||||
int err;
|
||||
|
||||
/* Mark test as in progress. */
|
||||
TEST_START("dut");
|
||||
|
||||
/* Initialize Bluetooth */
|
||||
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);
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(testers); i++) {
|
||||
LOG_DBG("Connecting tester %d", i);
|
||||
testers[i].sdu_count = 0;
|
||||
testers[i].conn = connect_tester();
|
||||
}
|
||||
|
||||
LOG_DBG("Connected all testers");
|
||||
|
||||
while (!all_data_transferred()) {
|
||||
/* Wait until we have received all expected data. */
|
||||
k_sleep(K_MSEC(100));
|
||||
}
|
||||
|
||||
TEST_PASS_AND_EXIT("dut");
|
||||
}
|
45
tests/bsim/bluetooth/host/misc/hfc_multilink/dut/src/main.c
Normal file
45
tests/bsim/bluetooth/host/misc/hfc_multilink/dut/src/main.c
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 enum bst_result_t bst_result;
|
||||
|
||||
|
||||
static void test_end_cb(void)
|
||||
{
|
||||
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,
|
||||
},
|
||||
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;
|
||||
}
|
14
tests/bsim/bluetooth/host/misc/hfc_multilink/test_scripts/_compile.sh
Executable file
14
tests/bsim/bluetooth/host/misc/hfc_multilink/test_scripts/_compile.sh
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright 2023 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)/dut" compile
|
||||
app="$(guess_test_relpath)/tester" compile
|
||||
|
||||
wait_for_background_jobs
|
31
tests/bsim/bluetooth/host/misc/hfc_multilink/test_scripts/run.sh
Executable file
31
tests/bsim/bluetooth/host/misc/hfc_multilink/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}
|
||||
verbosity_level=2
|
||||
|
||||
# Simulated test runtime (as seen by Zephyr and the PHY)
|
||||
# The test will be terminated much earlier if it passes
|
||||
SIM_LEN_US=$((10 * 1000 * 1000))
|
||||
|
||||
tester_exe="${BSIM_OUT_PATH}/bin/bs_${BOARD_TS}_${test_name}_tester_prj_conf"
|
||||
dut_exe="${BSIM_OUT_PATH}/bin/bs_${BOARD_TS}_${test_name}_dut_prj_conf"
|
||||
|
||||
cd ${BSIM_OUT_PATH}/bin
|
||||
|
||||
Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} -D=4 -sim_length=${SIM_LEN_US} $@
|
||||
|
||||
Execute "${dut_exe}" -v=${verbosity_level} -s=${simulation_id} -d=0 -rs=420 -testid=dut
|
||||
|
||||
Execute "${tester_exe}" -s=${simulation_id} -d=1 -rs=100 -testid=tester
|
||||
Execute "${tester_exe}" -s=${simulation_id} -d=2 -rs=200 -testid=tester
|
||||
Execute "${tester_exe}" -s=${simulation_id} -d=3 -rs=300 -testid=tester
|
||||
|
||||
wait_for_background_jobs
|
|
@ -0,0 +1,19 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(hfc_multilink_tester)
|
||||
|
||||
target_sources(app PRIVATE src/main.c src/tester.c)
|
||||
|
||||
# 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(
|
||||
../
|
||||
${ZEPHYR_BASE}/subsys/bluetooth/common/
|
||||
${BSIM_COMPONENTS_PATH}/libUtilv1/src/
|
||||
${BSIM_COMPONENTS_PATH}/libPhyComv1/src/
|
||||
)
|
15
tests/bsim/bluetooth/host/misc/hfc_multilink/tester/Kconfig
Normal file
15
tests/bsim/bluetooth/host/misc/hfc_multilink/tester/Kconfig
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Kconfig options for the test
|
||||
#
|
||||
# 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"
|
19
tests/bsim/bluetooth/host/misc/hfc_multilink/tester/prj.conf
Normal file
19
tests/bsim/bluetooth/host/misc/hfc_multilink/tester/prj.conf
Normal file
|
@ -0,0 +1,19 @@
|
|||
CONFIG_LOG=y
|
||||
CONFIG_ASSERT=y
|
||||
|
||||
CONFIG_BT=y
|
||||
CONFIG_BT_HCI_RAW=y
|
||||
CONFIG_BT_MAX_CONN=1
|
||||
|
||||
CONFIG_BT_BUF_CMD_TX_COUNT=10
|
||||
|
||||
CONFIG_BT_BUF_ACL_RX_SIZE=255
|
||||
CONFIG_BT_BUF_CMD_TX_SIZE=255
|
||||
CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE=255
|
||||
|
||||
# Work around bug in throughput calculation
|
||||
# FIXME: check if this still applies
|
||||
CONFIG_BT_CTLR_FORCE_MD_AUTO=n
|
||||
CONFIG_BT_CTLR_ADVANCED_FEATURES=y
|
||||
|
||||
# CONFIG_APP_LOG_LEVEL_DBG=y
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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_tester(void);
|
||||
extern enum bst_result_t bst_result;
|
||||
|
||||
|
||||
static void test_end_cb(void)
|
||||
{
|
||||
if (bst_result != Passed) {
|
||||
TEST_PRINT("Test has not passed.");
|
||||
}
|
||||
}
|
||||
|
||||
static const struct bst_test_instance entrypoints[] = {
|
||||
{
|
||||
.test_id = "tester",
|
||||
.test_delete_f = test_end_cb,
|
||||
.test_main_f = entrypoint_tester,
|
||||
},
|
||||
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;
|
||||
}
|
675
tests/bsim/bluetooth/host/misc/hfc_multilink/tester/src/tester.c
Normal file
675
tests/bsim/bluetooth/host/misc/hfc_multilink/tester/src/tester.c
Normal file
|
@ -0,0 +1,675 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <zephyr/sys/__assert.h>
|
||||
|
||||
#include <zephyr/net/buf.h>
|
||||
#include <zephyr/bluetooth/buf.h>
|
||||
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/hci.h>
|
||||
#include <zephyr/bluetooth/hci_raw.h>
|
||||
#include <zephyr/bluetooth/gap.h>
|
||||
|
||||
#include "common/bt_str.h"
|
||||
|
||||
#include "host/conn_internal.h"
|
||||
#include "host/l2cap_internal.h"
|
||||
|
||||
#include "babblekit/flags.h"
|
||||
#include "babblekit/device.h"
|
||||
#include "babblekit/testcase.h"
|
||||
|
||||
/* local includes */
|
||||
#include "data.h"
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(tester, CONFIG_APP_LOG_LEVEL);
|
||||
|
||||
DEFINE_FLAG(is_connected);
|
||||
DEFINE_FLAG(flag_l2cap_connected);
|
||||
|
||||
static K_FIFO_DEFINE(rx_queue);
|
||||
|
||||
#define CMD_BUF_SIZE MAX(BT_BUF_EVT_RX_SIZE, BT_BUF_CMD_TX_SIZE)
|
||||
NET_BUF_POOL_FIXED_DEFINE(hci_cmd_pool, CONFIG_BT_BUF_CMD_TX_COUNT, CMD_BUF_SIZE, 8, NULL);
|
||||
|
||||
static K_SEM_DEFINE(cmd_sem, 1, 1);
|
||||
static struct k_sem acl_pkts;
|
||||
static struct k_sem tx_credits;
|
||||
static uint16_t peer_mps;
|
||||
static uint16_t conn_handle;
|
||||
|
||||
static uint16_t active_opcode = 0xFFFF;
|
||||
static struct net_buf *cmd_rsp;
|
||||
|
||||
struct net_buf *bt_hci_cmd_create(uint16_t opcode, uint8_t param_len)
|
||||
{
|
||||
struct bt_hci_cmd_hdr *hdr;
|
||||
struct net_buf *buf;
|
||||
|
||||
LOG_DBG("opcode 0x%04x param_len %u", opcode, param_len);
|
||||
|
||||
buf = net_buf_alloc(&hci_cmd_pool, K_FOREVER);
|
||||
__ASSERT_NO_MSG(buf);
|
||||
|
||||
LOG_DBG("buf %p", buf);
|
||||
|
||||
net_buf_reserve(buf, BT_BUF_RESERVE);
|
||||
|
||||
bt_buf_set_type(buf, BT_BUF_CMD);
|
||||
|
||||
hdr = net_buf_add(buf, sizeof(*hdr));
|
||||
hdr->opcode = sys_cpu_to_le16(opcode);
|
||||
hdr->param_len = param_len;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void handle_cmd_complete(struct net_buf *buf)
|
||||
{
|
||||
struct bt_hci_evt_hdr *hdr;
|
||||
uint8_t status, ncmd;
|
||||
uint16_t opcode;
|
||||
|
||||
struct net_buf_simple_state state;
|
||||
|
||||
net_buf_simple_save(&buf->b, &state);
|
||||
|
||||
hdr = net_buf_pull_mem(buf, sizeof(*hdr));
|
||||
|
||||
if (hdr->evt == BT_HCI_EVT_CMD_COMPLETE) {
|
||||
struct bt_hci_evt_cmd_complete *evt;
|
||||
|
||||
evt = net_buf_pull_mem(buf, sizeof(*evt));
|
||||
status = 0;
|
||||
ncmd = evt->ncmd;
|
||||
opcode = sys_le16_to_cpu(evt->opcode);
|
||||
|
||||
} else if (hdr->evt == BT_HCI_EVT_CMD_STATUS) {
|
||||
struct bt_hci_evt_cmd_status *evt;
|
||||
|
||||
evt = net_buf_pull_mem(buf, sizeof(*evt));
|
||||
status = buf->data[0];
|
||||
ncmd = evt->ncmd;
|
||||
opcode = sys_le16_to_cpu(evt->opcode);
|
||||
|
||||
} else {
|
||||
__ASSERT_NO_MSG(0);
|
||||
}
|
||||
|
||||
LOG_DBG("opcode 0x%04x status %x", opcode, status);
|
||||
|
||||
__ASSERT(status == 0x00, "cmd status: %x", status);
|
||||
|
||||
__ASSERT(active_opcode == opcode, "unexpected opcode %x != %x", active_opcode, opcode);
|
||||
|
||||
if (active_opcode) {
|
||||
active_opcode = 0xFFFF;
|
||||
cmd_rsp = net_buf_ref(buf);
|
||||
net_buf_simple_restore(&buf->b, &state);
|
||||
}
|
||||
|
||||
if (ncmd) {
|
||||
k_sem_give(&cmd_sem);
|
||||
}
|
||||
}
|
||||
|
||||
static void verify_interval(uint16_t interval)
|
||||
{
|
||||
uint16_t min = EXPECTED_CONN_INTERVAL - CONN_INTERVAL_TOL;
|
||||
uint16_t max = EXPECTED_CONN_INTERVAL + CONN_INTERVAL_TOL;
|
||||
|
||||
TEST_ASSERT(interval > min, "Conn interval %d < %d", interval, min);
|
||||
TEST_ASSERT(interval < max, "Conn interval %d > %d", interval, max);
|
||||
}
|
||||
|
||||
static void handle_meta_event(struct net_buf *buf)
|
||||
{
|
||||
uint8_t code = buf->data[2];
|
||||
|
||||
switch (code) {
|
||||
case BT_HCI_EVT_LE_ENH_CONN_COMPLETE:
|
||||
case BT_HCI_EVT_LE_ENH_CONN_COMPLETE_V2:
|
||||
struct bt_hci_evt_le_enh_conn_complete *evt = (void *)(&buf->data[3]);
|
||||
|
||||
conn_handle = evt->handle;
|
||||
LOG_DBG("connected: handle: %d interval %d", conn_handle, evt->interval);
|
||||
|
||||
verify_interval(evt->interval);
|
||||
SET_FLAG(is_connected);
|
||||
break;
|
||||
case BT_HCI_EVT_LE_CHAN_SEL_ALGO:
|
||||
/* do nothing */
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("unhandled meta event %x", code);
|
||||
LOG_HEXDUMP_ERR(buf->data, buf->len, "HCI META EVT");
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_ncp(struct net_buf *buf)
|
||||
{
|
||||
struct bt_hci_evt_hdr *hdr;
|
||||
|
||||
hdr = net_buf_pull_mem(buf, sizeof(*hdr));
|
||||
|
||||
struct bt_hci_evt_num_completed_packets *evt = (void *)buf->data;
|
||||
|
||||
uint16_t handle, count;
|
||||
|
||||
handle = sys_le16_to_cpu(evt->h[0].handle);
|
||||
count = sys_le16_to_cpu(evt->h[0].count);
|
||||
|
||||
LOG_DBG("sent %d packets", count);
|
||||
|
||||
while (count--) {
|
||||
k_sem_give(&acl_pkts);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_l2cap_credits(struct net_buf *buf)
|
||||
{
|
||||
struct bt_l2cap_le_credits *ev = (void *)buf->data;
|
||||
uint16_t credits = sys_le16_to_cpu(ev->credits);
|
||||
|
||||
LOG_DBG("got credits: %d", credits);
|
||||
while (credits--) {
|
||||
k_sem_give(&tx_credits);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_l2cap_connected(struct net_buf *buf)
|
||||
{
|
||||
struct bt_l2cap_le_conn_rsp *rsp = (void *)buf->data;
|
||||
|
||||
uint16_t credits = sys_le16_to_cpu(rsp->credits);
|
||||
uint16_t mtu = sys_le16_to_cpu(rsp->mtu);
|
||||
uint16_t mps = sys_le16_to_cpu(rsp->mps);
|
||||
|
||||
peer_mps = mps;
|
||||
|
||||
LOG_DBG("l2cap connected: mtu %d mps %d credits: %d", mtu, mps, credits);
|
||||
|
||||
k_sem_init(&tx_credits, credits, credits);
|
||||
SET_FLAG(flag_l2cap_connected);
|
||||
}
|
||||
|
||||
static void handle_sig(struct net_buf *buf)
|
||||
{
|
||||
struct bt_l2cap_sig_hdr *hdr;
|
||||
|
||||
hdr = net_buf_pull_mem(buf, sizeof(*hdr));
|
||||
|
||||
switch (hdr->code) {
|
||||
case BT_L2CAP_LE_CONN_RSP:
|
||||
handle_l2cap_connected(buf);
|
||||
return;
|
||||
case BT_L2CAP_LE_CREDITS:
|
||||
handle_l2cap_credits(buf);
|
||||
return;
|
||||
case BT_L2CAP_DISCONN_REQ:
|
||||
TEST_FAIL("channel disconnected\n");
|
||||
return;
|
||||
default:
|
||||
TEST_FAIL("unhandled opcode %x\n", hdr->code);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_l2cap(struct net_buf *buf)
|
||||
{
|
||||
struct bt_l2cap_hdr *hdr;
|
||||
uint16_t cid;
|
||||
|
||||
hdr = net_buf_pull_mem(buf, sizeof(*hdr));
|
||||
cid = sys_le16_to_cpu(hdr->cid);
|
||||
|
||||
__ASSERT_NO_MSG(buf->len == hdr->len);
|
||||
LOG_DBG("Packet for CID %u len %u", cid, buf->len);
|
||||
LOG_HEXDUMP_DBG(buf->data, buf->len, "l2cap");
|
||||
|
||||
/* signaling PDU */
|
||||
if (cid == 0x0005) {
|
||||
handle_sig(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
/* CoC PDU */
|
||||
if (cid == 0x0040) {
|
||||
TEST_FAIL("unexpected data rx");
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_acl(struct net_buf *buf)
|
||||
{
|
||||
struct bt_hci_acl_hdr *hdr;
|
||||
uint16_t len, handle;
|
||||
uint8_t flags;
|
||||
|
||||
hdr = net_buf_pull_mem(buf, sizeof(*hdr));
|
||||
len = sys_le16_to_cpu(hdr->len);
|
||||
handle = sys_le16_to_cpu(hdr->handle);
|
||||
|
||||
flags = bt_acl_flags(handle);
|
||||
handle = bt_acl_handle(handle);
|
||||
|
||||
/* fragmentation not supported */
|
||||
__ASSERT_NO_MSG(flags == BT_ACL_START);
|
||||
|
||||
LOG_DBG("ACL: conn %d len %d flags %d", handle, len, flags);
|
||||
LOG_HEXDUMP_DBG(buf->data, buf->len, "HCI ACL");
|
||||
|
||||
handle_l2cap(buf);
|
||||
}
|
||||
|
||||
static void recv(struct net_buf *buf)
|
||||
{
|
||||
LOG_HEXDUMP_DBG(buf->data, buf->len, "HCI RX");
|
||||
|
||||
uint8_t code = buf->data[0];
|
||||
|
||||
if (bt_buf_get_type(buf) == BT_BUF_EVT) {
|
||||
switch (code) {
|
||||
case BT_HCI_EVT_CMD_COMPLETE:
|
||||
case BT_HCI_EVT_CMD_STATUS:
|
||||
handle_cmd_complete(buf);
|
||||
break;
|
||||
case BT_HCI_EVT_LE_META_EVENT:
|
||||
handle_meta_event(buf);
|
||||
break;
|
||||
case BT_HCI_EVT_DISCONN_COMPLETE:
|
||||
UNSET_FLAG(is_connected);
|
||||
break;
|
||||
case BT_HCI_EVT_NUM_COMPLETED_PACKETS:
|
||||
handle_ncp(buf);
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("unhandled msg %x", code);
|
||||
LOG_HEXDUMP_ERR(buf->data, buf->len, "HCI EVT");
|
||||
}
|
||||
|
||||
/* handlers should take a ref if they want to access the buffer
|
||||
* later
|
||||
*/
|
||||
net_buf_unref(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bt_buf_get_type(buf) == BT_BUF_ACL_IN) {
|
||||
handle_acl(buf);
|
||||
net_buf_unref(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_ERR("HCI RX (not data or event)");
|
||||
net_buf_unref(buf);
|
||||
}
|
||||
|
||||
static void send_cmd(uint16_t opcode, struct net_buf *cmd, struct net_buf **rsp)
|
||||
{
|
||||
LOG_DBG("opcode %x", opcode);
|
||||
|
||||
if (!cmd) {
|
||||
cmd = bt_hci_cmd_create(opcode, 0);
|
||||
}
|
||||
|
||||
k_sem_take(&cmd_sem, K_FOREVER);
|
||||
__ASSERT_NO_MSG(active_opcode == 0xFFFF);
|
||||
|
||||
active_opcode = opcode;
|
||||
|
||||
LOG_HEXDUMP_DBG(cmd->data, cmd->len, "HCI TX");
|
||||
bt_send(cmd);
|
||||
|
||||
/* Wait until the command completes */
|
||||
k_sem_take(&cmd_sem, K_FOREVER);
|
||||
k_sem_give(&cmd_sem);
|
||||
|
||||
net_buf_unref(cmd);
|
||||
|
||||
/* return response. it's okay if cmd_rsp gets overwritten, since the app
|
||||
* gets the ref to the underlying buffer when this fn returns.
|
||||
*/
|
||||
if (rsp) {
|
||||
*rsp = cmd_rsp;
|
||||
} else {
|
||||
net_buf_unref(cmd_rsp);
|
||||
cmd_rsp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static K_THREAD_STACK_DEFINE(rx_thread_stack, 1024);
|
||||
static struct k_thread rx_thread_data;
|
||||
|
||||
static void rx_thread(void *p1, void *p2, void *p3)
|
||||
{
|
||||
LOG_DBG("start HCI rx");
|
||||
|
||||
while (1) {
|
||||
struct net_buf *buf;
|
||||
|
||||
/* Wait until a buffer is available */
|
||||
buf = net_buf_get(&rx_queue, K_FOREVER);
|
||||
recv(buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void le_read_buffer_size_complete(struct net_buf *rsp)
|
||||
{
|
||||
struct bt_hci_rp_le_read_buffer_size *rp = (void *)rsp->data;
|
||||
|
||||
LOG_DBG("status 0x%02x", rp->status);
|
||||
LOG_DBG("max len %d max num %d", rp->le_max_len, rp->le_max_num);
|
||||
|
||||
k_sem_init(&acl_pkts, rp->le_max_num, rp->le_max_num);
|
||||
net_buf_unref(rsp);
|
||||
}
|
||||
|
||||
static void read_max_data_len(uint16_t *tx_octets, uint16_t *tx_time)
|
||||
{
|
||||
struct bt_hci_rp_le_read_max_data_len *rp;
|
||||
struct net_buf *rsp;
|
||||
|
||||
send_cmd(BT_HCI_OP_LE_READ_MAX_DATA_LEN, NULL, &rsp);
|
||||
|
||||
rp = (void *)rsp->data;
|
||||
*tx_octets = sys_le16_to_cpu(rp->max_tx_octets);
|
||||
*tx_time = sys_le16_to_cpu(rp->max_tx_time);
|
||||
net_buf_unref(rsp);
|
||||
}
|
||||
|
||||
static void write_default_data_len(uint16_t tx_octets, uint16_t tx_time)
|
||||
{
|
||||
struct bt_hci_cp_le_write_default_data_len *cp;
|
||||
struct net_buf *buf = bt_hci_cmd_create(BT_HCI_OP_LE_WRITE_DEFAULT_DATA_LEN, sizeof(*cp));
|
||||
|
||||
__ASSERT_NO_MSG(buf);
|
||||
|
||||
cp = net_buf_add(buf, sizeof(*cp));
|
||||
cp->max_tx_octets = sys_cpu_to_le16(tx_octets);
|
||||
cp->max_tx_time = sys_cpu_to_le16(tx_time);
|
||||
|
||||
send_cmd(BT_HCI_OP_LE_WRITE_DEFAULT_DATA_LEN, buf, NULL);
|
||||
}
|
||||
|
||||
static void set_data_len(void)
|
||||
{
|
||||
uint16_t tx_octets, tx_time;
|
||||
|
||||
read_max_data_len(&tx_octets, &tx_time);
|
||||
write_default_data_len(tx_octets, tx_time);
|
||||
}
|
||||
|
||||
static void set_event_mask(uint16_t opcode)
|
||||
{
|
||||
struct bt_hci_cp_set_event_mask *cp_mask;
|
||||
struct net_buf *buf;
|
||||
uint64_t mask = 0U;
|
||||
|
||||
/* The two commands have the same length/params */
|
||||
buf = bt_hci_cmd_create(opcode, sizeof(*cp_mask));
|
||||
__ASSERT_NO_MSG(buf);
|
||||
|
||||
/* Forward all events */
|
||||
cp_mask = net_buf_add(buf, sizeof(*cp_mask));
|
||||
mask = UINT64_MAX;
|
||||
sys_put_le64(mask, cp_mask->events);
|
||||
|
||||
send_cmd(opcode, buf, NULL);
|
||||
}
|
||||
|
||||
static void set_random_address(void)
|
||||
{
|
||||
struct net_buf *buf;
|
||||
bt_addr_le_t addr = {BT_ADDR_LE_RANDOM, {{0x0A, 0x89, 0x67, 0x45, 0x23, 0xC1}}};
|
||||
|
||||
/* Allow multilink */
|
||||
addr.a.val[3] = bk_device_get_number();
|
||||
|
||||
LOG_DBG("%s", bt_addr_str(&addr.a));
|
||||
|
||||
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_RANDOM_ADDRESS, sizeof(addr.a));
|
||||
__ASSERT_NO_MSG(buf);
|
||||
|
||||
net_buf_add_mem(buf, &addr.a, sizeof(addr.a));
|
||||
send_cmd(BT_HCI_OP_LE_SET_RANDOM_ADDRESS, buf, NULL);
|
||||
}
|
||||
|
||||
static void start_adv(uint16_t interval, const char *name, size_t name_len)
|
||||
{
|
||||
struct bt_hci_cp_le_set_adv_data data;
|
||||
struct bt_hci_cp_le_set_adv_param set_param;
|
||||
struct net_buf *buf;
|
||||
|
||||
/* name_len should also not include the \0 */
|
||||
__ASSERT(name_len < (31 - 2), "name_len should be < 30");
|
||||
|
||||
(void)memset(&data, 0, sizeof(data));
|
||||
data.len = name_len + 2;
|
||||
data.data[0] = name_len + 1;
|
||||
data.data[1] = BT_DATA_NAME_COMPLETE;
|
||||
memcpy(&data.data[2], name, name_len);
|
||||
|
||||
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_DATA, sizeof(data));
|
||||
__ASSERT_NO_MSG(buf);
|
||||
net_buf_add_mem(buf, &data, sizeof(data));
|
||||
send_cmd(BT_HCI_OP_LE_SET_ADV_DATA, buf, NULL);
|
||||
|
||||
(void)memset(&set_param, 0, sizeof(set_param));
|
||||
set_param.min_interval = sys_cpu_to_le16(interval);
|
||||
set_param.max_interval = sys_cpu_to_le16(interval);
|
||||
set_param.channel_map = 0x07;
|
||||
set_param.filter_policy = BT_LE_ADV_FP_NO_FILTER;
|
||||
set_param.type = BT_HCI_ADV_IND;
|
||||
set_param.own_addr_type = 0x01; /* random */
|
||||
|
||||
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_PARAM, sizeof(set_param));
|
||||
__ASSERT_NO_MSG(buf);
|
||||
net_buf_add_mem(buf, &set_param, sizeof(set_param));
|
||||
|
||||
send_cmd(BT_HCI_OP_LE_SET_ADV_PARAM, buf, NULL);
|
||||
|
||||
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_ENABLE, 1);
|
||||
__ASSERT_NO_MSG(buf);
|
||||
|
||||
net_buf_add_u8(buf, BT_HCI_LE_ADV_ENABLE);
|
||||
send_cmd(BT_HCI_OP_LE_SET_ADV_ENABLE, buf, NULL);
|
||||
}
|
||||
|
||||
NET_BUF_POOL_DEFINE(acl_tx_pool, 100, BT_L2CAP_SDU_BUF_SIZE(200), 8, NULL);
|
||||
|
||||
static struct net_buf *alloc_l2cap_pdu(void)
|
||||
{
|
||||
struct net_buf *buf;
|
||||
uint16_t reserve;
|
||||
|
||||
buf = net_buf_alloc(&acl_tx_pool, K_FOREVER);
|
||||
__ASSERT_NO_MSG(buf);
|
||||
|
||||
reserve = sizeof(struct bt_l2cap_hdr);
|
||||
reserve += sizeof(struct bt_hci_acl_hdr) + BT_BUF_RESERVE;
|
||||
|
||||
net_buf_reserve(buf, reserve);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static struct net_buf *l2cap_create_le_sig_pdu(uint8_t code, uint8_t ident, uint16_t len)
|
||||
{
|
||||
struct bt_l2cap_sig_hdr *hdr;
|
||||
struct net_buf *buf;
|
||||
|
||||
buf = alloc_l2cap_pdu();
|
||||
|
||||
hdr = net_buf_add(buf, sizeof(*hdr));
|
||||
hdr->code = code;
|
||||
hdr->ident = ident;
|
||||
hdr->len = sys_cpu_to_le16(len);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int send_acl(struct net_buf *buf, uint8_t flags)
|
||||
{
|
||||
struct bt_hci_acl_hdr *hdr;
|
||||
|
||||
hdr = net_buf_push(buf, sizeof(*hdr));
|
||||
hdr->handle = sys_cpu_to_le16(bt_acl_handle_pack(conn_handle, flags));
|
||||
hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr));
|
||||
|
||||
bt_buf_set_type(buf, BT_BUF_ACL_OUT);
|
||||
|
||||
k_sem_take(&acl_pkts, K_FOREVER);
|
||||
|
||||
return bt_send(buf);
|
||||
}
|
||||
|
||||
static void push_l2cap_pdu_header(struct net_buf *dst, uint16_t len, uint16_t cid)
|
||||
{
|
||||
struct bt_l2cap_hdr *hdr;
|
||||
|
||||
hdr = net_buf_push(dst, sizeof(*hdr));
|
||||
hdr->len = sys_cpu_to_le16(len);
|
||||
hdr->cid = sys_cpu_to_le16(cid);
|
||||
}
|
||||
|
||||
static void send_l2cap_packet(struct net_buf *buf, uint16_t cid)
|
||||
{
|
||||
push_l2cap_pdu_header(buf, buf->len, cid);
|
||||
send_acl(buf, BT_ACL_START_NO_FLUSH);
|
||||
}
|
||||
|
||||
static void open_l2cap(void)
|
||||
{
|
||||
struct net_buf *buf;
|
||||
struct bt_l2cap_le_conn_req *req;
|
||||
|
||||
buf = l2cap_create_le_sig_pdu(BT_L2CAP_LE_CONN_REQ, 1, sizeof(*req));
|
||||
|
||||
req = net_buf_add(buf, sizeof(*req));
|
||||
req->psm = sys_cpu_to_le16(L2CAP_TEST_PSM);
|
||||
req->scid = sys_cpu_to_le16(L2CAP_TEST_CID);
|
||||
|
||||
/* we don't intend on receiving anything. use the smallest allowed
|
||||
* values and no initial credits.
|
||||
*/
|
||||
req->mtu = sys_cpu_to_le16(23);
|
||||
req->mps = sys_cpu_to_le16(23);
|
||||
req->credits = sys_cpu_to_le16(0);
|
||||
|
||||
send_l2cap_packet(buf, BT_L2CAP_CID_LE_SIG);
|
||||
|
||||
WAIT_FOR_FLAG(flag_l2cap_connected);
|
||||
}
|
||||
|
||||
static void send_l2cap_sdu(uint8_t *data, uint16_t data_len, uint16_t mps, uint16_t on_air_size)
|
||||
{
|
||||
uint16_t frag_len;
|
||||
uint8_t flags = BT_ACL_START_NO_FLUSH;
|
||||
|
||||
/* Only MPS-sized SDUs */
|
||||
__ASSERT_NO_MSG(data_len <= (mps - BT_L2CAP_SDU_HDR_SIZE));
|
||||
|
||||
/* Need to fit both headers on the first ACL fragment */
|
||||
__ASSERT_NO_MSG(on_air_size >= (BT_L2CAP_SDU_HDR_SIZE + BT_L2CAP_HDR_SIZE));
|
||||
|
||||
LOG_HEXDUMP_DBG(data, data_len, "send SDU:");
|
||||
|
||||
/* Since we send one PDU (but many HCI ACL fragments) we only need one
|
||||
* (PDU) credit.
|
||||
*/
|
||||
k_sem_take(&tx_credits, K_FOREVER);
|
||||
|
||||
for (int i = 0; data_len; i++) {
|
||||
struct net_buf *buf = net_buf_alloc(&acl_tx_pool, K_FOREVER);
|
||||
|
||||
__ASSERT_NO_MSG(buf);
|
||||
net_buf_reserve(buf, BT_L2CAP_SDU_CHAN_SEND_RESERVE);
|
||||
|
||||
frag_len = MIN(data_len, on_air_size);
|
||||
|
||||
if (i == 0) {
|
||||
/* The first packet the first part of both the SDU and
|
||||
* the PDU. It then needs to contain both headers.
|
||||
*/
|
||||
net_buf_push_le16(buf, data_len);
|
||||
frag_len -= BT_L2CAP_SDU_HDR_SIZE;
|
||||
|
||||
push_l2cap_pdu_header(buf, data_len + BT_L2CAP_SDU_HDR_SIZE, 0x0040);
|
||||
frag_len -= BT_L2CAP_HDR_SIZE;
|
||||
}
|
||||
|
||||
/* copy data into ACL frag */
|
||||
net_buf_add_mem(buf, data, frag_len);
|
||||
data = &data[frag_len];
|
||||
data_len -= frag_len;
|
||||
|
||||
LOG_DBG("send ACL frag %d (%d bytes, remaining %d)", i, buf->len, data_len);
|
||||
LOG_HEXDUMP_DBG(buf->data, buf->len, "ACL Fragment");
|
||||
|
||||
send_acl(buf, flags);
|
||||
flags = BT_ACL_CONT;
|
||||
}
|
||||
}
|
||||
|
||||
void entrypoint_tester(void)
|
||||
{
|
||||
bt_enable_raw(&rx_queue);
|
||||
|
||||
/* Start the RX thread */
|
||||
k_thread_create(&rx_thread_data, rx_thread_stack, K_THREAD_STACK_SIZEOF(rx_thread_stack),
|
||||
rx_thread, NULL, NULL, NULL, K_PRIO_PREEMPT(0), 0, K_NO_WAIT);
|
||||
k_thread_name_set(&rx_thread_data, "HCI RX");
|
||||
|
||||
k_thread_priority_set(k_current_get(), K_PRIO_PREEMPT(0));
|
||||
|
||||
/* Initialize controller */
|
||||
struct net_buf *rsp;
|
||||
|
||||
send_cmd(BT_HCI_OP_RESET, NULL, NULL);
|
||||
send_cmd(BT_HCI_OP_LE_READ_BUFFER_SIZE, NULL, &rsp);
|
||||
le_read_buffer_size_complete(rsp);
|
||||
|
||||
set_data_len();
|
||||
set_event_mask(BT_HCI_OP_SET_EVENT_MASK);
|
||||
set_event_mask(BT_HCI_OP_LE_SET_EVENT_MASK);
|
||||
set_random_address();
|
||||
|
||||
/* Start advertising & wait for a connection */
|
||||
start_adv(40, TESTER_NAME, sizeof(TESTER_NAME) - 1);
|
||||
WAIT_FOR_FLAG(is_connected);
|
||||
LOG_INF("connected");
|
||||
|
||||
/* Connect to the central's dynamic L2CAP server */
|
||||
open_l2cap();
|
||||
|
||||
/* Prepare the data for sending */
|
||||
uint8_t data[PAYLOAD_LEN];
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(data); i++) {
|
||||
data[i] = (uint8_t)i;
|
||||
}
|
||||
|
||||
/* Start sending data at a set time + offset[device_num].
|
||||
*
|
||||
* The connection is created with ~30-50ms interval, so that should be
|
||||
* enough to have the DUT re-assembling L2CAP PDUs from all the peers at
|
||||
* the same time.
|
||||
*/
|
||||
int delay = bk_device_get_number() * 2 * EXPECTED_CONN_INTERVAL;
|
||||
|
||||
k_msleep(delay);
|
||||
|
||||
for (int i = 0; i < SDU_NUM; i++) {
|
||||
LOG_INF("Sending SDU %d / %d", i + 1, SDU_NUM);
|
||||
send_l2cap_sdu(data, sizeof(data), peer_mps, 8);
|
||||
}
|
||||
|
||||
TEST_PASS("Sent all %d SDUs", SDU_NUM);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue