Skip to content

Commit

Permalink
Bluetooth: Host: Test L2CAP -EINPROGRESS feature
Browse files Browse the repository at this point in the history
The test implementation is based on a copy of the HFC multilink test.
The test verifies that the stack respects the reference counting of SDU
buffers when the L2CAP -EINPROGRESS feature is used.

Signed-off-by: Aleksander Wasaznik <[email protected]>
  • Loading branch information
alwa-nordic committed Aug 9, 2024
1 parent 9f2dc36 commit 2da64d7
Show file tree
Hide file tree
Showing 16 changed files with 1,238 additions and 1 deletion.
11 changes: 11 additions & 0 deletions subsys/bluetooth/host/hci_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <zephyr/net/buf.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/slist.h>
#include <zephyr/sys/byteorder.h>
Expand Down Expand Up @@ -262,13 +263,23 @@ void bt_send_one_host_num_completed_packets(uint16_t handle)
BT_ASSERT_MSG(err == 0, "Unable to send Host NCP (err %d)", err);
}

#if defined(CONFIG_BT_TESTING)
__weak void bt_testing_trace_event_acl_pool_destroy(struct net_buf *buf)
{
}
#endif

#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL)
void bt_hci_host_num_completed_packets(struct net_buf *buf)
{
uint16_t handle = acl(buf)->handle;
struct bt_conn *conn;
uint8_t index = acl(buf)->index;

if (IS_ENABLED(CONFIG_BT_TESTING)) {
bt_testing_trace_event_acl_pool_destroy(buf);
}

net_buf_destroy(buf);

if (acl(buf)->host_ncp_sent) {
Expand Down
20 changes: 20 additions & 0 deletions tests/bsim/bluetooth/host/l2cap/einprogress/data.h
Original file line number Diff line number Diff line change
@@ -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 1
#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_ */
28 changes: 28 additions & 0 deletions tests/bsim/bluetooth/host/l2cap/einprogress/dut/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions tests/bsim/bluetooth/host/l2cap/einprogress/dut/Kconfig
Original file line number Diff line number Diff line change
@@ -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"
33 changes: 33 additions & 0 deletions tests/bsim/bluetooth/host/l2cap/einprogress/dut/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
CONFIG_LOG=y
CONFIG_ASSERT=y
CONFIG_THREAD_NAME=y
CONFIG_LOG_THREAD_ID_PREFIX=y
CONFIG_ARCH_POSIX_TRAP_ON_FATAL=y
CONFIG_BT_TESTING=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

CONFIG_BT_MAX_CONN=3
CONFIG_BT_BUF_ACL_RX_COUNT=4

CONFIG_BT_HCI_ACL_FLOW_CONTROL=y
223 changes: 223 additions & 0 deletions tests/bsim/bluetooth/host/l2cap/einprogress/dut/src/dut.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*
* 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 <zephyr/net/buf.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/util_macro.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);

struct tester {
size_t sdu_count;
struct bt_conn *conn;
struct bt_l2cap_le_chan le_chan;
struct k_fifo ack_todo;
};

static atomic_t acl_pool_refs_held[CONFIG_BT_BUF_ACL_RX_COUNT];
static struct tester tester;

BUILD_ASSERT(IS_ENABLED(CONFIG_BT_TESTING));
BUILD_ASSERT(IS_ENABLED(CONFIG_BT_HCI_ACL_FLOW_CONTROL));
void bt_testing_trace_event_acl_pool_destroy(struct net_buf *destroyed_buf)
{
int buf_id = net_buf_id(destroyed_buf);
__ASSERT_NO_MSG(0 <= buf_id && buf_id < ARRAY_SIZE(acl_pool_refs_held));

Check warning on line 43 in tests/bsim/bluetooth/host/l2cap/einprogress/dut/src/dut.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LINE_SPACING

tests/bsim/bluetooth/host/l2cap/einprogress/dut/src/dut.c:43 Missing a blank line after declarations
TEST_ASSERT(acl_pool_refs_held[buf_id] == 0,
"ACL buf was destroyed while tester still held a reference");
}

static void acl_pool_refs_held_add(struct net_buf *buf)
{
int buf_id = net_buf_id(buf);
__ASSERT_NO_MSG(0 <= buf_id && buf_id < CONFIG_BT_BUF_ACL_RX_COUNT);

Check warning on line 51 in tests/bsim/bluetooth/host/l2cap/einprogress/dut/src/dut.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LINE_SPACING

tests/bsim/bluetooth/host/l2cap/einprogress/dut/src/dut.c:51 Missing a blank line after declarations
atomic_inc(&acl_pool_refs_held[buf_id]);
}

static void acl_pool_refs_held_remove(struct net_buf *buf)
{
int buf_id = net_buf_id(buf);
__ASSERT_NO_MSG(0 <= buf_id && buf_id < ARRAY_SIZE(acl_pool_refs_held));

Check warning on line 58 in tests/bsim/bluetooth/host/l2cap/einprogress/dut/src/dut.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LINE_SPACING

tests/bsim/bluetooth/host/l2cap/einprogress/dut/src/dut.c:58 Missing a blank line after declarations
atomic_val_t old = atomic_dec(&acl_pool_refs_held[buf_id]);

__ASSERT(old != 0, "Tester error: releasing a reference that was not held");
}

static struct tester *get_tester(struct bt_conn *conn)
{
if (tester.conn == conn) {
return &tester;
}

return NULL;
}

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);

/* Move buf. Ownership is ours if we return -EINPROGRESS. */
acl_pool_refs_held_add(buf);
net_buf_put(&tester->ack_todo, buf);

return -EINPROGRESS;
}

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 = {
.recv = recv_cb,
};

struct tester *tester = get_tester(conn);
struct bt_l2cap_le_chan *le_chan = &tester->le_chan;

k_fifo_init(&tester->ack_todo);
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;

total_sdu_count += tester.sdu_count;

TEST_ASSERT(total_sdu_count <= SDU_NUM, "Received more SDUs than expected");

return total_sdu_count == SDU_NUM;
}

void entrypoint_dut(void)
{
/* Test reference counting in Host when using L2CAP -EINPROGRESS feature.
*
* Devices:
* - `dut`: receives L2CAP PDUs from tester
* - `tester`: send L2CAP packets
*
* Procedure:
*
* DUT:
* - establish connection to tester
* - [acl connected]
* - establish L2CAP channel
* - [l2 connected]
* - receive one L2CAP SDU, returning EINPROGRESS
* - monitor that our SDU reference is respected
* - return the buf to the stack using bt_l2cap_chan_recv_complete
* - mark test as passed and terminate simulation
*
* tester 0:
* - 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]
* - retained SDU buffer reference from recv() is respected by stack
*/
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);

LOG_DBG("Connecting tester");
tester.sdu_count = 0;
tester.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));

struct net_buf *ack_buf = net_buf_get(&tester.ack_todo, K_NO_WAIT);
if (ack_buf) {

Check warning on line 215 in tests/bsim/bluetooth/host/l2cap/einprogress/dut/src/dut.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LINE_SPACING

tests/bsim/bluetooth/host/l2cap/einprogress/dut/src/dut.c:215 Missing a blank line after declarations
acl_pool_refs_held_remove(ack_buf);
err = bt_l2cap_chan_recv_complete(&tester.le_chan.chan, ack_buf);
TEST_ASSERT(!err);
}
}

TEST_PASS_AND_EXIT("dut");
}
Loading

0 comments on commit 2da64d7

Please sign in to comment.