blob: 7db0f8def301e78cfc603ea21bd832c6e03787bb [file] [log] [blame]
/*
* Copyright (c) 2023 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_DBG);
CREATE_FLAG(is_connected);
CREATE_FLAG(flag_l2cap_connected);
#define L2CAP_MPS CONFIG_BT_L2CAP_TX_MTU
#define SDU_NUM 3
#define SDU_LEN (2 * L2CAP_MPS)
/* We intentionally send smaller SDUs than the channel can fit */
#define L2CAP_MTU (2 * SDU_LEN)
/* Only one SDU transmitted or received at a time */
NET_BUF_POOL_DEFINE(sdu_pool, 1, BT_L2CAP_SDU_BUF_SIZE(L2CAP_MTU),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
static uint8_t tx_data[SDU_LEN];
static uint16_t rx_cnt;
K_SEM_DEFINE(sdu_received, 0, 1);
struct test_ctx {
struct bt_l2cap_le_chan le_chan;
size_t tx_left;
} test_ctx;
int l2cap_chan_send(struct bt_l2cap_chan *chan, uint8_t *data, size_t len)
{
struct net_buf *buf;
int ret;
LOG_DBG("chan %p conn %u data %p len %d", chan, bt_conn_index(chan->conn), data, len);
buf = net_buf_alloc(&sdu_pool, K_NO_WAIT);
if (buf == NULL) {
FAIL("No more memory\n");
return -ENOMEM;
}
net_buf_reserve(buf, BT_L2CAP_SDU_CHAN_SEND_RESERVE);
net_buf_add_mem(buf, data, len);
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_pool, K_NO_WAIT);
}
void continue_sending(struct test_ctx *ctx)
{
struct bt_l2cap_chan *chan = &ctx->le_chan.chan;
LOG_DBG("%p, left %d", chan, ctx->tx_left);
if (ctx->tx_left) {
l2cap_chan_send(chan, tx_data, sizeof(tx_data));
} else {
LOG_DBG("Done sending %u", bt_conn_index(chan->conn));
}
}
void sent_cb(struct bt_l2cap_chan *chan)
{
LOG_DBG("%p", chan);
if (test_ctx.tx_left) {
test_ctx.tx_left--;
}
continue_sending(&test_ctx);
}
void recv_cb(struct bt_l2cap_chan *l2cap_chan, size_t sdu_len, off_t seg_offset,
struct net_buf_simple *seg)
{
LOG_DBG("sdu len %u frag offset %u frag len %u", sdu_len, seg_offset, seg->len);
ASSERT(sdu_len == sizeof(tx_data), "Recv SDU length does not match send length.");
/* Verify SDU data matches TX'd data. */
ASSERT(memcmp(seg->data, &tx_data[seg_offset], seg->len) == 0, "RX data doesn't match TX");
if (seg_offset + seg->len == sdu_len) {
/* Don't give credits right away. The taker of this
* semaphore will give the credits after sleeping a bit.
*/
rx_cnt++;
k_sem_give(&sdu_received);
} else {
/* To test that we really gave two initial credits, we
* "forget" to send one credit after the first PDU of
* the first SDU.
*/
if (!(rx_cnt == 0 && seg_offset == 0)) {
/* Give more credits to complete SDU. */
LOG_DBG("Giving credits for continuing SDU.");
bt_l2cap_chan_give_credits(&test_ctx.le_chan.chan, 1);
}
}
}
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);
/* TODO: check that actual MPS < expected MPS */
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,
.seg_recv = recv_cb,
.sent = sent_cb,
};
int server_accept_cb(struct bt_conn *conn, struct bt_l2cap_server *server,
struct bt_l2cap_chan **chan)
{
struct bt_l2cap_le_chan *le_chan = &test_ctx.le_chan;
*chan = &le_chan->chan;
/* Always default initialize the chan. */
memset(le_chan, 0, sizeof(*le_chan));
le_chan->chan.ops = &ops;
le_chan->rx.mtu = L2CAP_MTU;
le_chan->rx.mps = BT_L2CAP_RX_MTU;
/* Credits can be given before returning from this
* accept-handler and after the 'connected' event. Credits given
* before completing the accept are sent in the 'initial
* credits' field of the connection response PDU.
*/
bt_l2cap_chan_give_credits(*chan, 2);
return 0;
}
static struct bt_l2cap_server test_l2cap_server = {.accept = server_accept_cb};
static int l2cap_server_register(bt_security_t sec_level)
{
int err;
test_l2cap_server.psm = 0;
test_l2cap_server.sec_level = sec_level;
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));
if (conn_err) {
FAIL("Failed to connect to %s (%u)", addr, conn_err);
return;
}
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);
}
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)
{
int err;
int psm;
LOG_DBG("*L2CAP CREDITS Peripheral started*");
/* Prepare tx_data */
for (size_t i = 0; i < sizeof(tx_data); i++) {
tx_data[i] = (uint8_t)i;
}
err = bt_enable(NULL);
if (err) {
FAIL("Can't enable Bluetooth (err %d)", err);
return;
}
LOG_DBG("Peripheral Bluetooth initialized.");
LOG_DBG("Connectable advertising...");
err = bt_le_adv_start(BT_LE_ADV_CONN_OT, NULL, 0, NULL, 0);
if (err) {
FAIL("Advertising failed to start (err %d)", err);
return;
}
LOG_DBG("Advertising started.");
LOG_DBG("Peripheral waiting for connection...");
WAIT_FOR_FLAG_SET(is_connected);
LOG_DBG("Peripheral Connected.");
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_sem_take(&sdu_received, K_FOREVER);
/* Sleep enough so the peer has time to attempt sending another
* SDU. If it still has credits, it's in its right to do so. If
* it does so before we release the ref below, then allocation
* will fail and the channel will be disconnected.
*/
k_sleep(K_SECONDS(5));
LOG_DBG("release SDU ref");
LOG_DBG("Giving credits for new SDU.");
bt_l2cap_chan_give_credits(&test_ctx.le_chan.chan, 1);
}
bt_conn_foreach(BT_CONN_TYPE_LE, disconnect_device, NULL);
LOG_INF("Total received: %d", rx_cnt);
ASSERT(rx_cnt == SDU_NUM, "Did not receive expected no of SDUs\n");
PASS("L2CAP CREDITS Peripheral passed\n");
}
static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
struct net_buf_simple *ad)
{
char str[BT_ADDR_LE_STR_LEN];
int err;
struct bt_conn *conn;
struct bt_le_conn_param *param;
err = bt_le_scan_stop();
if (err) {
FAIL("Stop LE scan failed (err %d)", err);
return;
}
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);
if (err) {
FAIL("Create conn failed (err %d)", err);
return;
}
}
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,
};
int err;
UNSET_FLAG(is_connected);
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 bt_l2cap_le_chan *le_chan = &test_ctx.le_chan;
*le_chan = (struct bt_l2cap_le_chan){
.chan.ops = &ops,
.rx.mtu = L2CAP_MTU,
.rx.mps = BT_L2CAP_RX_MTU,
};
UNSET_FLAG(flag_l2cap_connected);
/* Credits can be given before requesting the connection and
* after the 'connected' event. Credits given before connecting
* are sent in the 'initial credits' field of the connection
* request PDU.
*/
bt_l2cap_chan_give_credits(&le_chan->chan, 1);
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);
}
static void connect_l2cap_ecred_channel(struct bt_conn *conn, void *data)
{
int err;
struct bt_l2cap_le_chan *le_chan = &test_ctx.le_chan;
struct bt_l2cap_chan *chan_list[2] = {&le_chan->chan, 0};
*le_chan = (struct bt_l2cap_le_chan){
.chan.ops = &ops,
.rx.mtu = L2CAP_MTU,
.rx.mps = BT_L2CAP_RX_MTU,
};
UNSET_FLAG(flag_l2cap_connected);
/* Credits can be given before requesting the connection and
* after the 'connected' event. Credits given before connecting
* are sent in the 'initial credits' field of the connection
* request PDU.
*/
bt_l2cap_chan_give_credits(&le_chan->chan, 1);
err = bt_l2cap_ecred_chan_connect(conn, chan_list, 0x0080);
ASSERT(!err, "Error connecting l2cap channel (err %d)\n", err);
WAIT_FOR_FLAG_SET(flag_l2cap_connected);
}
static void test_central_main(void)
{
int err;
LOG_DBG("*L2CAP CREDITS Central started*");
/* 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_peripheral();
/* Connect L2CAP channels */
LOG_DBG("Connect L2CAP channels");
if (IS_ENABLED(CONFIG_BT_L2CAP_ECRED)) {
bt_conn_foreach(BT_CONN_TYPE_LE, connect_l2cap_ecred_channel, NULL);
} else {
bt_conn_foreach(BT_CONN_TYPE_LE, connect_l2cap_channel, NULL);
}
/* Send SDU_NUM SDUs to each peripheral */
test_ctx.tx_left = SDU_NUM;
l2cap_chan_send(&test_ctx.le_chan.chan, tx_data, sizeof(tx_data));
LOG_DBG("Wait until all transfers are completed.");
while (test_ctx.tx_left) {
k_msleep(100);
}
WAIT_FOR_FLAG_UNSET(is_connected);
LOG_DBG("Peripheral disconnected.");
PASS("L2CAP CREDITS Central passed\n");
}
static const struct bst_test_instance test_def[] = {
{.test_id = "peripheral",
.test_descr = "Peripheral L2CAP CREDITS",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_peripheral_main},
{.test_id = "central",
.test_descr = "Central L2CAP CREDITS",
.test_post_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_credits_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_def);
}
extern struct bst_test_list *test_main_l2cap_credits_install(struct bst_test_list *tests);
bst_test_install_t test_installers[] = {test_main_l2cap_credits_install, NULL};
int main(void)
{
bst_main();
return 0;
}