From c3b329c5ddc6825895461f1fb918a291c3ed91f5 Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Fri, 10 Oct 2025 15:05:57 +0200 Subject: [PATCH 1/3] tests: Bluetooth: BSIM: ISO: Refactor TX Refactor the TX for the ISO host BSIM tests to use a separate thread, instead of a timer. This is the recommended way to handle TX, and is what the LE Audio BSIM tests and samples use. The TX will send 1->sdu number of octets, increasing, to verify that any size can be sent (and to keep the check for this in the bis_receiver). Signed-off-by: Emil Gydesen --- .../bluetooth/host/iso/bis/CMakeLists.txt | 4 + .../host/iso/bis/src/bis_broadcaster.c | 97 +------ .../bluetooth/host/iso/bis/src/bis_receiver.c | 9 +- .../bsim/bluetooth/host/iso/bis/src/common.h | 8 +- .../bluetooth/host/iso/cis/CMakeLists.txt | 10 +- .../bluetooth/host/iso/cis/src/cis_central.c | 154 ++++------ .../host/iso/cis/src/cis_peripheral.c | 2 +- tests/bsim/bluetooth/host/iso/common/iso_tx.c | 272 ++++++++++++++++++ tests/bsim/bluetooth/host/iso/common/iso_tx.h | 87 ++++++ 9 files changed, 439 insertions(+), 204 deletions(-) create mode 100644 tests/bsim/bluetooth/host/iso/common/iso_tx.c create mode 100644 tests/bsim/bluetooth/host/iso/common/iso_tx.h diff --git a/tests/bsim/bluetooth/host/iso/bis/CMakeLists.txt b/tests/bsim/bluetooth/host/iso/bis/CMakeLists.txt index c9ecd0997a3f7..93e5639f50a62 100644 --- a/tests/bsim/bluetooth/host/iso/bis/CMakeLists.txt +++ b/tests/bsim/bluetooth/host/iso/bis/CMakeLists.txt @@ -13,9 +13,13 @@ target_sources(app PRIVATE src/bis_broadcaster.c src/bis_receiver.c src/main.c + + ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/iso/common/iso_tx.c ) zephyr_include_directories( ${BSIM_COMPONENTS_PATH}/libUtilv1/src/ ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/ + + ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/iso/common ) diff --git a/tests/bsim/bluetooth/host/iso/bis/src/bis_broadcaster.c b/tests/bsim/bluetooth/host/iso/bis/src/bis_broadcaster.c index 4b23eabf4d9c6..d49ef9e167534 100644 --- a/tests/bsim/bluetooth/host/iso/bis/src/bis_broadcaster.c +++ b/tests/bsim/bluetooth/host/iso/bis/src/bis_broadcaster.c @@ -26,6 +26,7 @@ #include "babblekit/testcase.h" #include "bstests.h" #include "common.h" +#include "iso_tx.h" LOG_MODULE_REGISTER(bis_broadcaster, LOG_LEVEL_INF); @@ -34,10 +35,6 @@ LOG_MODULE_REGISTER(bis_broadcaster, LOG_LEVEL_INF); extern enum bst_result_t bst_result; static struct bt_iso_chan iso_chans[CONFIG_BT_ISO_MAX_CHAN]; static struct bt_iso_chan *default_chan = &iso_chans[0]; -static uint16_t seq_num; -NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_ISO_TX_BUF_COUNT, - BT_ISO_SDU_BUF_SIZE(ARRAY_SIZE(mock_iso_data)), - CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); static struct bt_iso_chan_io_qos iso_tx = { .sdu = 0U, @@ -52,54 +49,6 @@ static struct bt_iso_chan_qos iso_qos = { DEFINE_FLAG_STATIC(flag_iso_connected); -static void send_data_cb(struct k_work *work); -K_WORK_DELAYABLE_DEFINE(iso_send_work, send_data_cb); - -static void send_data(struct bt_iso_chan *chan) -{ - static size_t len_to_send = 1U; - struct net_buf *buf; - int ret; - - if (!IS_FLAG_SET(flag_iso_connected)) { - /* TX has been aborted */ - return; - } - - buf = net_buf_alloc(&tx_pool, K_NO_WAIT); - TEST_ASSERT(buf != NULL, "Failed to allocate buffer"); - - net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); - - net_buf_add_mem(buf, mock_iso_data, len_to_send); - - ret = bt_iso_chan_send(default_chan, buf, seq_num++); - if (ret < 0) { - LOG_DBG("Failed to send ISO data: %d", ret); - net_buf_unref(buf); - - /* Reschedule for next interval */ - k_work_reschedule(&iso_send_work, K_USEC(SDU_INTERVAL_US)); - - return; - } - - len_to_send++; - if (len_to_send > chan->qos->tx->sdu) { - len_to_send = 1; - } -} - -static void send_data_cb(struct k_work *work) -{ - const uint16_t tx_pool_cnt = tx_pool.uninit_count; - - /* Send/enqueue as many as we can */ - for (uint16_t i = 0U; i < tx_pool_cnt; i++) { - send_data(default_chan); - } -} - static void iso_connected_cb(struct bt_iso_chan *chan) { const struct bt_iso_chan_path hci_path = { @@ -151,14 +100,16 @@ static void iso_connected_cb(struct bt_iso_chan *chan) IN_RANGE(info.broadcaster.bis_number, BT_ISO_BIS_INDEX_MIN, BT_ISO_BIS_INDEX_MAX), "Invalid BIS number 0x%02x", info.broadcaster.bis_number); + err = bt_iso_setup_data_path(chan, BT_HCI_DATAPATH_DIR_HOST_TO_CTLR, &hci_path); + TEST_ASSERT(err == 0, "Failed to set ISO data path: %d", err); + if (chan == default_chan) { - seq_num = 0U; + /* Register for TX to start sending */ + err = iso_tx_register(chan); + TEST_ASSERT(err == 0, "Failed to register chan for TX: %d", err); SET_FLAG(flag_iso_connected); } - - err = bt_iso_setup_data_path(chan, BT_HCI_DATAPATH_DIR_HOST_TO_CTLR, &hci_path); - TEST_ASSERT(err == 0, "Failed to set ISO data path: %d", err); } static void iso_disconnected_cb(struct bt_iso_chan *chan, uint8_t reason) @@ -166,20 +117,13 @@ static void iso_disconnected_cb(struct bt_iso_chan *chan, uint8_t reason) LOG_INF("ISO Channel %p disconnected (reason 0x%02x)", chan, reason); if (chan == default_chan) { - k_work_cancel_delayable(&iso_send_work); + int err; - UNSET_FLAG(flag_iso_connected); - } -} + err = iso_tx_unregister(chan); + TEST_ASSERT(err == 0, "Failed to unregister chan for TX: %d", err); -static void sdu_sent_cb(struct bt_iso_chan *chan) -{ - if (!IS_FLAG_SET(flag_iso_connected)) { - /* TX has been aborted */ - return; + UNSET_FLAG(flag_iso_connected); } - - send_data(chan); } static void init(void) @@ -187,7 +131,7 @@ static void init(void) static struct bt_iso_chan_ops iso_ops = { .disconnected = iso_disconnected_cb, .connected = iso_connected_cb, - .sent = sdu_sent_cb, + .sent = iso_tx_sent_cb, }; struct bt_le_local_features local_features; int err; @@ -212,6 +156,8 @@ static void init(void) } bk_sync_init(); + + iso_tx_init(); } static void create_ext_adv(struct bt_le_ext_adv **adv) @@ -277,18 +223,6 @@ static void create_big(struct bt_le_ext_adv *adv, size_t cnt, struct bt_iso_big WAIT_FOR_FLAG(flag_iso_connected); } -static void start_tx(void) -{ - const uint16_t tx_pool_cnt = tx_pool.uninit_count; - - LOG_INF("Starting TX"); - - /* Send/enqueue as many as we can */ - for (uint16_t i = 0U; i < tx_pool_cnt; i++) { - send_data(default_chan); - } -} - static void terminate_big(struct bt_iso_big *big) { int err; @@ -325,7 +259,6 @@ static void test_main(void) create_ext_adv(&adv); create_big(adv, 1U, &big); start_ext_adv(adv); - start_tx(); /* Wait for receiver to tell us to terminate */ bk_sync_wait(); @@ -358,7 +291,6 @@ static void test_main_disable(void) create_ext_adv(&adv); create_big(adv, ARRAY_SIZE(iso_chans), &big); start_ext_adv(adv); - start_tx(); /* Wait for receiver to tell us to terminate */ bk_sync_wait(); @@ -400,7 +332,6 @@ static void test_main_fragment(void) create_ext_adv(&adv); create_big(adv, 1U, &big); start_ext_adv(adv); - start_tx(); /* Wait for receiver to tell us to terminate */ bk_sync_wait(); diff --git a/tests/bsim/bluetooth/host/iso/bis/src/bis_receiver.c b/tests/bsim/bluetooth/host/iso/bis/src/bis_receiver.c index 877b58fc1c507..c9448b82f963e 100644 --- a/tests/bsim/bluetooth/host/iso/bis/src/bis_receiver.c +++ b/tests/bsim/bluetooth/host/iso/bis/src/bis_receiver.c @@ -20,6 +20,7 @@ #include "babblekit/testcase.h" #include "common.h" +#include "iso_tx.h" LOG_MODULE_REGISTER(bis_receiver, LOG_LEVEL_INF); @@ -89,20 +90,22 @@ static void iso_recv(struct bt_iso_chan *chan, const struct bt_iso_recv_info *in if (info->flags & BT_ISO_FLAGS_VALID) { static uint16_t last_buf_len; static uint32_t last_ts; + static size_t pass_cnt; static size_t rx_cnt; - LOG_DBG("Incoming data channel %p len %u", chan, buf->len); + rx_cnt++; + LOG_DBG("[%zu]: Incoming data channel %p len %u", rx_cnt, chan, buf->len); iso_log_data(buf->data, buf->len); if (memcmp(buf->data, mock_iso_data, buf->len) != 0) { TEST_FAIL("Unexpected data received"); } else if (last_buf_len != 0U && buf->len != 1U && buf->len != last_buf_len + 1) { TEST_FAIL("Unexpected data length (%u) received (expected 1 or %u)", - buf->len, last_buf_len); + buf->len, last_buf_len + 1); } else if (last_ts != 0U && info->ts > last_ts + 2 * SDU_INTERVAL_US) { TEST_FAIL("Unexpected timestamp (%u) received (expected %u)", info->ts, last_ts + SDU_INTERVAL_US); - } else if (rx_cnt++ > RX_CNT_TO_PASS) { + } else if (pass_cnt++ > RX_CNT_TO_PASS) { LOG_INF("Data received"); SET_FLAG(flag_data_received); } diff --git a/tests/bsim/bluetooth/host/iso/bis/src/common.h b/tests/bsim/bluetooth/host/iso/bis/src/common.h index 68b8b91ff701c..7e0e5a7633300 100644 --- a/tests/bsim/bluetooth/host/iso/bis/src/common.h +++ b/tests/bsim/bluetooth/host/iso/bis/src/common.h @@ -8,7 +8,7 @@ #include -#include +#include #include "bs_types.h" @@ -18,9 +18,3 @@ void test_init(void); void test_tick(bs_time_t HW_device_time); #define SDU_INTERVAL_US 10U * USEC_PER_MSEC /* 10 ms */ - -/* Generate 1 KiB of mock data going 0x00, 0x01, ..., 0xff, 0x00, 0x01, ..., 0xff, etc */ -#define ISO_DATA_GEN(_i, _) (uint8_t)_i -static const uint8_t mock_iso_data[] = { - LISTIFY(1024, ISO_DATA_GEN, (,)), -}; diff --git a/tests/bsim/bluetooth/host/iso/cis/CMakeLists.txt b/tests/bsim/bluetooth/host/iso/cis/CMakeLists.txt index 346345967c55f..47debaf768735 100644 --- a/tests/bsim/bluetooth/host/iso/cis/CMakeLists.txt +++ b/tests/bsim/bluetooth/host/iso/cis/CMakeLists.txt @@ -5,22 +5,24 @@ cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(bsim_test_iso_cis) -add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit) -target_link_libraries(app PRIVATE babblekit) - target_sources(app PRIVATE src/common.c src/cis_central.c src/cis_peripheral.c src/main.c + + ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/iso/common/iso_tx.c ) zephyr_include_directories( ${BSIM_COMPONENTS_PATH}/libUtilv1/src/ ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/ - ) + + ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/iso/common +) add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/common/testlib testlib) +add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit) target_link_libraries(app PRIVATE testlib diff --git a/tests/bsim/bluetooth/host/iso/cis/src/cis_central.c b/tests/bsim/bluetooth/host/iso/cis/src/cis_central.c index e3b6ee9284fe0..71fd868dad8cc 100644 --- a/tests/bsim/bluetooth/host/iso/cis/src/cis_central.c +++ b/tests/bsim/bluetooth/host/iso/cis/src/cis_central.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -25,76 +26,22 @@ #include "babblekit/flags.h" #include "bstests.h" #include "common.h" +#include "iso_tx.h" -#define ENQUEUE_COUNT 2 +#define EXPECTED_TX_CNT 100U extern enum bst_result_t bst_result; static struct bt_iso_chan iso_chans[CONFIG_BT_ISO_MAX_CHAN]; static struct bt_iso_chan *default_chan = &iso_chans[0]; static struct bt_iso_cig *cig; -static uint16_t seq_num; -static volatile size_t enqueue_cnt; static uint32_t latency_ms = 10U; /* 10ms */ static uint32_t interval_us = 10U * USEC_PER_MSEC; /* 10 ms */ -NET_BUF_POOL_FIXED_DEFINE(tx_pool, ENQUEUE_COUNT, BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU), - CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); +static uint8_t disconnect_reason; BUILD_ASSERT(CONFIG_BT_ISO_MAX_CHAN > 1, "CONFIG_BT_ISO_MAX_CHAN shall be at least 2"); DEFINE_FLAG_STATIC(flag_iso_connected); -static void send_data_cb(struct k_work *work) -{ - static uint8_t buf_data[CONFIG_BT_ISO_TX_MTU]; - static size_t len_to_send = 1; - static bool data_initialized; - struct net_buf *buf; - int ret; - - if (!IS_FLAG_SET(flag_iso_connected)) { - /* TX has been aborted */ - return; - } - - if (!data_initialized) { - for (int i = 0; i < ARRAY_SIZE(buf_data); i++) { - buf_data[i] = (uint8_t)i; - } - - data_initialized = true; - } - - buf = net_buf_alloc(&tx_pool, K_FOREVER); - net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); - - net_buf_add_mem(buf, buf_data, len_to_send); - - ret = bt_iso_chan_send(default_chan, buf, seq_num++); - if (ret < 0) { - printk("Failed to send ISO data (%d)\n", ret); - net_buf_unref(buf); - - /* Reschedule for next interval */ - k_work_reschedule(k_work_delayable_from_work(work), K_USEC(interval_us)); - - return; - } - - len_to_send++; - if (len_to_send > ARRAY_SIZE(buf_data)) { - len_to_send = 1; - } - - enqueue_cnt--; - if (enqueue_cnt > 0U) { - /* If we have more buffers available, we reschedule the workqueue item immediately - * to trigger another encode + TX, but without blocking this call for too long - */ - k_work_reschedule(k_work_delayable_from_work(work), K_NO_WAIT); - } -} -K_WORK_DELAYABLE_DEFINE(iso_send_work, send_data_cb); - static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, struct net_buf_simple *ad) { @@ -126,18 +73,16 @@ static void iso_connected(struct bt_iso_chan *chan) printk("ISO Channel %p connected\n", chan); - seq_num = 0U; - enqueue_cnt = ENQUEUE_COUNT; + err = bt_iso_setup_data_path(chan, BT_HCI_DATAPATH_DIR_HOST_TO_CTLR, &hci_path); + TEST_ASSERT(err == 0, "Failed to set ISO data path: %d", err); if (chan == default_chan) { - /* Start send timer */ - k_work_schedule(&iso_send_work, K_MSEC(0)); + /* Register for TX to start sending */ + err = iso_tx_register(chan); + TEST_ASSERT(err == 0, "Failed to register chan for TX: %d", err); SET_FLAG(flag_iso_connected); } - - err = bt_iso_setup_data_path(chan, BT_HCI_DATAPATH_DIR_HOST_TO_CTLR, &hci_path); - TEST_ASSERT(err == 0, "Failed to set ISO data path: %d", err); } static void iso_disconnected(struct bt_iso_chan *chan, uint8_t reason) @@ -147,35 +92,16 @@ static void iso_disconnected(struct bt_iso_chan *chan, uint8_t reason) printk("ISO Channel %p disconnected (reason 0x%02x)\n", chan, reason); if (chan == default_chan) { - k_work_cancel_delayable(&iso_send_work); + err = iso_tx_unregister(chan); + TEST_ASSERT(err == 0, "Failed to unregister chan for TX: %d", err); + + disconnect_reason = reason; UNSET_FLAG(flag_iso_connected); } err = bt_iso_remove_data_path(chan, BT_HCI_DATAPATH_DIR_HOST_TO_CTLR); TEST_ASSERT(err == 0, "Failed to remove ISO data path: %d", err); - - if (seq_num < 100) { - printk("Channel disconnected early, bumping seq_num to 1000 to end test\n"); - seq_num = 1000; - } -} - -static void sdu_sent_cb(struct bt_iso_chan *chan) -{ - int err; - - enqueue_cnt++; - - if (!IS_FLAG_SET(flag_iso_connected)) { - /* TX has been aborted */ - return; - } - - err = k_work_schedule(&iso_send_work, K_NO_WAIT); - if (err < 0) { - TEST_FAIL("Failed to schedule TX for chan %p: %d", chan, err); - } } static void init(void) @@ -183,7 +109,7 @@ static void init(void) static struct bt_iso_chan_ops iso_ops = { .connected = iso_connected, .disconnected = iso_disconnected, - .sent = sdu_sent_cb, + .sent = iso_tx_sent_cb, }; static struct bt_iso_chan_io_qos iso_tx = { .sdu = CONFIG_BT_ISO_TX_MTU, @@ -210,6 +136,8 @@ static void init(void) iso_chans[i].required_sec_level = BT_SECURITY_L2; #endif /* CONFIG_BT_SMP */ } + + iso_tx_init(); } static void set_cig_defaults(struct bt_iso_cig_param *param) @@ -455,6 +383,27 @@ static void reset_bluetooth(void) } } +static void wait_tx_complete(void) +{ + size_t tx_cnt; + + do { + tx_cnt = iso_tx_get_sent_cnt(default_chan); + k_sleep(K_USEC(interval_us)); + + if (!IS_FLAG_SET(flag_iso_connected)) { + /* We don't expect all TX to be complete in the test where the peripheral + * actively disconnects + */ + if (disconnect_reason != BT_HCI_ERR_REMOTE_USER_TERM_CONN) { + TEST_FAIL("Did not sent expected amount before disconnection"); + } + + return; + } + } while (tx_cnt < EXPECTED_TX_CNT); +} + static void test_main(void) { init(); @@ -462,21 +411,14 @@ static void test_main(void) reconfigure_cig(); connect_acl(); connect_cis(); - - while (seq_num < 100U) { - k_sleep(K_USEC(interval_us)); - } - - if (seq_num == 100) { + wait_tx_complete(); + if (IS_FLAG_SET(flag_iso_connected)) { disconnect_cis(); + } + if (IS_FLAG_SET(flag_connected)) { disconnect_acl(); - terminate_cig(); } - - /* check that all buffers returned to pool */ - TEST_ASSERT(atomic_get(&tx_pool.avail_count) == ENQUEUE_COUNT, - "tx_pool has non returned buffers, should be %u but is %u", - ENQUEUE_COUNT, atomic_get(&tx_pool.avail_count)); + terminate_cig(); TEST_PASS("Test passed"); } @@ -497,13 +439,13 @@ static void test_main_disable(void) create_cig(ARRAY_SIZE(iso_chans)); connect_acl(); connect_cis(); - - while (seq_num < 100U) { - k_sleep(K_USEC(interval_us)); + wait_tx_complete(); + if (IS_FLAG_SET(flag_iso_connected)) { + disconnect_cis(); + } + if (IS_FLAG_SET(flag_connected)) { + disconnect_acl(); } - - disconnect_cis(); - disconnect_acl(); terminate_cig(); TEST_PASS("Disable test passed"); diff --git a/tests/bsim/bluetooth/host/iso/cis/src/cis_peripheral.c b/tests/bsim/bluetooth/host/iso/cis/src/cis_peripheral.c index e8b3ff37a9d5f..05e46e9e83a8b 100644 --- a/tests/bsim/bluetooth/host/iso/cis/src/cis_peripheral.c +++ b/tests/bsim/bluetooth/host/iso/cis/src/cis_peripheral.c @@ -87,7 +87,7 @@ static void iso_recv(struct bt_iso_chan *chan, const struct bt_iso_recv_info *in { iso_recv_cnt++; if (info->flags & BT_ISO_FLAGS_VALID) { - printk("Incoming data channel %p len %u\n", chan, buf->len); + printk("[%zu]: Incoming data channel %p len %u\n", iso_recv_cnt, chan, buf->len); iso_print_data(buf->data, buf->len); SET_FLAG(flag_data_received); } diff --git a/tests/bsim/bluetooth/host/iso/common/iso_tx.c b/tests/bsim/bluetooth/host/iso/common/iso_tx.c new file mode 100644 index 0000000000000..9192ebd87234b --- /dev/null +++ b/tests/bsim/bluetooth/host/iso/common/iso_tx.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "babblekit/testcase.h" + +#include "iso_tx.h" + +/** Enqueue at least 2 per channel, but otherwise equal distribution based on the buf count */ +#define ENQUEUE_CNT MAX(2, (CONFIG_BT_ISO_TX_BUF_COUNT / CONFIG_BT_ISO_MAX_CHAN)) + +/** Mutex to prevent race conditions as the values are accessed by multiple threads */ +#define TX_MUTEX_TIMEOUT K_MSEC(1000) + +LOG_MODULE_REGISTER(iso_tx, LOG_LEVEL_INF); + +struct tx_stream { + struct bt_iso_chan *iso_chan; + struct k_mutex mutex; + uint16_t seq_num; + size_t tx_cnt; + atomic_t enqueued; +}; + +static struct tx_stream tx_streams[CONFIG_BT_ISO_MAX_CHAN]; + +static void tx_thread_func(void *arg1, void *arg2, void *arg3) +{ + /* We set the SDU size to 3 x CONFIG_BT_ISO_TX_MTU to support the fragmentation tests */ + NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_ISO_TX_BUF_COUNT, + BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU * 3), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + + /* This loop will attempt to send on all streams in the streaming state in a round robin + * fashion. + * The TX is controlled by the number of buffers configured, and increasing + * CONFIG_BT_ISO_TX_BUF_COUNT will allow for more streams in parallel, or to submit more + * buffers per stream. + * Once a buffer has been freed by the stack, it triggers the next TX. + */ + while (true) { + bool delay_and_retry = true; + + ARRAY_FOR_EACH_PTR(tx_streams, tx_stream) { + struct bt_iso_chan *iso_chan; + int err; + + err = k_mutex_lock(&tx_stream->mutex, K_NO_WAIT); + if (err != 0) { + continue; + } + + iso_chan = tx_stream->iso_chan; + if (iso_chan != NULL && iso_chan->state == BT_ISO_STATE_CONNECTED && + atomic_get(&tx_stream->enqueued) < ENQUEUE_CNT) { + /* Send between 1 and sdu number of octets */ + const size_t sdu = iso_chan->qos->tx->sdu; + const size_t len_to_send = 1 + (tx_stream->tx_cnt % sdu); + struct net_buf *buf; + + buf = net_buf_alloc(&tx_pool, K_FOREVER); + net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); + + net_buf_add_mem(buf, mock_iso_data, len_to_send); + + err = bt_iso_chan_send(iso_chan, buf, tx_stream->seq_num); + if (err == 0) { + tx_stream->seq_num++; + atomic_inc(&tx_stream->enqueued); + delay_and_retry = false; + } else { + if (iso_chan->state != BT_ISO_STATE_CONNECTED) { + /* Can happen if we disconnected while waiting for a + * buffer - Ignore + */ + } else { + TEST_FAIL("Unable to send: %d", err); + } + + net_buf_unref(buf); + } + } /* No-op if stream is not streaming */ + + err = k_mutex_unlock(&tx_stream->mutex); + TEST_ASSERT(err == 0, "Failed to unlock mutex: %d", err); + } + + if (delay_and_retry) { + /* In case of any errors or nothing sent, retry with a delay */ + k_sleep(K_MSEC(5)); + } + } +} + +int iso_tx_register(struct bt_iso_chan *iso_chan) +{ + int err; + + if (iso_chan == NULL) { + return -EINVAL; + } + + if (!iso_tx_can_send(iso_chan)) { + return -EINVAL; + } + + err = -ENOMEM; + ARRAY_FOR_EACH_PTR(tx_streams, tx_stream) { + int mutex_err; + + mutex_err = k_mutex_lock(&tx_stream->mutex, TX_MUTEX_TIMEOUT); + if (mutex_err != 0) { + continue; + } + + if (tx_stream->iso_chan == NULL) { + tx_stream->iso_chan = iso_chan; + tx_stream->seq_num = 0U; + tx_stream->tx_cnt = 0U; + + LOG_INF("Registered %p for TX", iso_chan); + + err = 0; + } + + mutex_err = k_mutex_unlock(&tx_stream->mutex); + TEST_ASSERT(mutex_err == 0, "Failed to unlock mutex: %d", err); + + if (err == 0) { + break; + } + } + + return err; +} + +int iso_tx_unregister(struct bt_iso_chan *iso_chan) +{ + int err; + + if (iso_chan == NULL) { + return -EINVAL; + } + + err = -ENODATA; + ARRAY_FOR_EACH_PTR(tx_streams, tx_stream) { + int mutex_err; + + mutex_err = k_mutex_lock(&tx_stream->mutex, TX_MUTEX_TIMEOUT); + if (mutex_err != 0) { + continue; + } + + if (tx_stream->iso_chan == iso_chan) { + + tx_stream->iso_chan = NULL; + + while (atomic_get(&tx_stream->enqueued) != 0) { + k_sleep(K_MSEC(100)); + } + + LOG_INF("Unregistered %p for TX", iso_chan); + + err = 0; + } + + mutex_err = k_mutex_unlock(&tx_stream->mutex); + TEST_ASSERT(mutex_err == 0, "Failed to unlock mutex: %d", err); + + if (err == 0) { + break; + } + } + + return err; +} + +void iso_tx_init(void) +{ + static bool thread_started; + + if (!thread_started) { + static K_KERNEL_STACK_DEFINE(tx_thread_stack, 1024U); + const int tx_thread_prio = K_PRIO_PREEMPT(5); + static struct k_thread tx_thread; + + ARRAY_FOR_EACH_PTR(tx_streams, tx_stream) { + const int err = k_mutex_init(&tx_stream->mutex); + + TEST_ASSERT(err == 0); + } + + k_thread_create(&tx_thread, tx_thread_stack, K_KERNEL_STACK_SIZEOF(tx_thread_stack), + tx_thread_func, NULL, NULL, NULL, tx_thread_prio, 0, K_NO_WAIT); + k_thread_name_set(&tx_thread, "TX thread"); + thread_started = true; + } +} + +bool iso_tx_can_send(const struct bt_iso_chan *iso_chan) +{ + struct bt_iso_info info; + int err; + + if (iso_chan == NULL || iso_chan->iso == NULL) { + return false; + } + + err = bt_iso_chan_get_info(iso_chan, &info); + if (err != 0) { + return false; + } + + return info.can_send; +} + +void iso_tx_sent_cb(struct bt_iso_chan *iso_chan) +{ + ARRAY_FOR_EACH_PTR(tx_streams, tx_stream) { + if (tx_stream->iso_chan == iso_chan) { + const atomic_val_t old = atomic_dec(&tx_stream->enqueued); + + if (old == 0) { + TEST_ASSERT("Old enqueue count was 0"); + } + + tx_stream->tx_cnt++; + if ((tx_stream->tx_cnt % 100U) == 0U) { + LOG_INF("Channel %p sent %zu SDUs", iso_chan, tx_stream->tx_cnt); + } + + break; + } + } +} + +size_t iso_tx_get_sent_cnt(const struct bt_iso_chan *iso_chan) +{ + if (iso_chan == NULL) { + return 0U; + } + + ARRAY_FOR_EACH_PTR(tx_streams, tx_stream) { + if (tx_stream->iso_chan == iso_chan) { + return tx_stream->seq_num; + } + } + + return 0U; +} diff --git a/tests/bsim/bluetooth/host/iso/common/iso_tx.h b/tests/bsim/bluetooth/host/iso/common/iso_tx.h new file mode 100644 index 0000000000000..ddd0c68cc0f70 --- /dev/null +++ b/tests/bsim/bluetooth/host/iso/common/iso_tx.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ISO_TX_H +#define ISO_TX_H + +#include +#include +#include + +#include +#include +#include +#include + +/** + * @brief Initialize TX + * + * This will initialize TX if not already initialized. This creates and starts a thread that + * will attempt to send data on all streams registered with iso_tx_register(). + */ +void iso_tx_init(void); + +/** + * @brief Register a stream for TX + * + * This will add it to the list of streams the TX thread will attempt to send on. + * + * @param bap_stream The stream to register + * + * @retval 0 on success + * @retval -EINVAL @p iso_chan is NULL + * @retval -EINVAL @p iso_chan is not configured for TX + * @retval -ENOMEM if not more streams can be registered + */ +int iso_tx_register(struct bt_iso_chan *iso_chan); + +/** + * @brief Unregister a stream for TX + * + * This will remove it to the list of streams the TX thread will attempt to send on. + * + * @param bap_stream The stream to unregister + * + * @retval 0 on success + * @retval -EINVAL @p bap_stream is NULL + * @retval -EINVAL @p bap_stream is not configured for TX + * @retval -EALREADY @p bap_stream is currently not registered + */ +int iso_tx_unregister(struct bt_iso_chan *iso_chan); + +/** + * @brief Test if the provided stream has been configured for TX + * + * @param bap_stream The stream to test for TX support + * + * @retval true if it has been configured for TX, and false if not + */ +bool iso_tx_can_send(const struct bt_iso_chan *iso_chan); + +/** + * @brief Callback to indicate a TX complete + * + * @param stream The stream that completed TX + */ +void iso_tx_sent_cb(struct bt_iso_chan *iso_chan); + +/** + * @brief Get the number of sent SDUs for an ISO channel + * + * Counter will be unavailable after iso_tx_unregister() + * + * @param iso_chan The ISO channel + * + * @return The number of sent SDUs + */ +size_t iso_tx_get_sent_cnt(const struct bt_iso_chan *iso_chan); + +/* Generate 1 KiB of mock data going 0x00, 0x01, ..., 0xff, 0x00, 0x01, ..., 0xff, etc */ +#define ISO_DATA_GEN(_i, _) (uint8_t)_i +static const uint8_t mock_iso_data[] = { + LISTIFY(1024, ISO_DATA_GEN, (,)), +}; +#endif /* ISO_TX_H */ From 68b3c32ffb47fcb009771097fdec0dc2bac4bc0f Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Mon, 13 Oct 2025 09:58:00 +0200 Subject: [PATCH 2/3] Bluetooth: Host: bt_iso_reset before bt_conn_cleanup_all Move the call to bt_iso_reset to before bt_conn_cleanup_all. The reason for this is that bt_conn_cleanup_all will disconnect all the ACL connections, which will put the CIS in a disconnecting state, without finalizing them. This means that if we get a num_completed_packet event between the call to bt_conn_cleanup_all and before the BT_HCI_OP_RESET request has been completed, we will trigger an assert in the hci_num_completed_packets function as the CIS still exists, but where we would already have cleaned up the pending TX without telling the controller. Moving the call to bt_iso_reset means that the CIS will have been fully terminated at this point, which will prevent the assert from happening. Since CIS depends on ACLs, this is also the logical order for resetting them. Signed-off-by: Emil Gydesen --- subsys/bluetooth/host/hci_core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/subsys/bluetooth/host/hci_core.c b/subsys/bluetooth/host/hci_core.c index 33306596b2ce7..e6a668ddd6534 100644 --- a/subsys/bluetooth/host/hci_core.c +++ b/subsys/bluetooth/host/hci_core.c @@ -4662,6 +4662,10 @@ int bt_disable(void) bt_periodic_sync_disable(); #endif /* CONFIG_BT_PER_ADV_SYNC */ + if (IS_ENABLED(CONFIG_BT_ISO)) { + bt_iso_reset(); + } + #if defined(CONFIG_BT_CONN) if (IS_ENABLED(CONFIG_BT_SMP)) { bt_pub_key_hci_disrupted(); @@ -4716,10 +4720,6 @@ int bt_disable(void) /* If random address was set up - clear it */ bt_addr_le_copy(&bt_dev.random_addr, BT_ADDR_LE_ANY); - if (IS_ENABLED(CONFIG_BT_ISO)) { - bt_iso_reset(); - } - bt_monitor_send(BT_MONITOR_CLOSE_INDEX, NULL, 0); /* Clear BT_DEV_ENABLE here to prevent early bt_enable() calls, before disable is From 3ac135f6b014102f4a6a5d7b8133124363c17d71 Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Mon, 13 Oct 2025 00:16:05 +0200 Subject: [PATCH 3/3] tests: Bluetooth: ISO: Expand CIS test to multiple CIS Expand the CIS tests to connect and use multiple CIS instead of only using the default_chan. Signed-off-by: Emil Gydesen --- tests/bsim/bluetooth/host/iso/cis/prj.conf | 12 +- .../bluetooth/host/iso/cis/src/cis_central.c | 166 ++++++++++-------- .../host/iso/cis/src/cis_peripheral.c | 105 ++++++++--- tests/bsim/bluetooth/host/iso/common/iso_tx.c | 4 +- 4 files changed, 179 insertions(+), 108 deletions(-) diff --git a/tests/bsim/bluetooth/host/iso/cis/prj.conf b/tests/bsim/bluetooth/host/iso/cis/prj.conf index 06f0282329678..975c23c64c73d 100644 --- a/tests/bsim/bluetooth/host/iso/cis/prj.conf +++ b/tests/bsim/bluetooth/host/iso/cis/prj.conf @@ -10,8 +10,8 @@ CONFIG_BT_DEVICE_NAME="CIS test" CONFIG_BT_ISO_CENTRAL=y CONFIG_BT_ISO_PERIPHERAL=y -CONFIG_BT_ISO_TX_BUF_COUNT=4 -CONFIG_BT_ISO_MAX_CHAN=4 +CONFIG_BT_ISO_TX_BUF_COUNT=6 +CONFIG_BT_ISO_MAX_CHAN=3 CONFIG_BT_ISO_TX_MTU=200 CONFIG_BT_ISO_RX_MTU=200 @@ -23,7 +23,7 @@ CONFIG_BT_CTLR_CENTRAL_ISO=y CONFIG_BT_CTLR_PERIPHERAL_ISO=y CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE=208 CONFIG_BT_CTLR_ISO_TX_SDU_LEN_MAX=200 -CONFIG_BT_CTLR_ISO_TX_BUFFERS=4 -CONFIG_BT_CTLR_ISOAL_SOURCES=2 -CONFIG_BT_CTLR_ISOAL_SINKS=2 -CONFIG_BT_CTLR_CONN_ISO_STREAMS_PER_GROUP=4 +CONFIG_BT_CTLR_ISO_TX_BUFFERS=6 +CONFIG_BT_CTLR_ISOAL_SOURCES=3 +CONFIG_BT_CTLR_ISOAL_SINKS=3 +CONFIG_BT_CTLR_CONN_ISO_STREAMS_PER_GROUP=3 diff --git a/tests/bsim/bluetooth/host/iso/cis/src/cis_central.c b/tests/bsim/bluetooth/host/iso/cis/src/cis_central.c index 71fd868dad8cc..ddeb1ccfbb8b2 100644 --- a/tests/bsim/bluetooth/host/iso/cis/src/cis_central.c +++ b/tests/bsim/bluetooth/host/iso/cis/src/cis_central.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -31,17 +32,17 @@ #define EXPECTED_TX_CNT 100U extern enum bst_result_t bst_result; -static struct bt_iso_chan iso_chans[CONFIG_BT_ISO_MAX_CHAN]; -static struct bt_iso_chan *default_chan = &iso_chans[0]; +static struct iso_test_chan { + struct bt_iso_chan iso_chan; + atomic_t flag_iso_connected; + uint8_t disconnect_reason; +} test_chans[CONFIG_BT_ISO_MAX_CHAN]; static struct bt_iso_cig *cig; static uint32_t latency_ms = 10U; /* 10ms */ static uint32_t interval_us = 10U * USEC_PER_MSEC; /* 10 ms */ -static uint8_t disconnect_reason; BUILD_ASSERT(CONFIG_BT_ISO_MAX_CHAN > 1, "CONFIG_BT_ISO_MAX_CHAN shall be at least 2"); -DEFINE_FLAG_STATIC(flag_iso_connected); - static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, struct net_buf_simple *ad) { @@ -65,6 +66,7 @@ static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, static void iso_connected(struct bt_iso_chan *chan) { + struct iso_test_chan *test_chan = CONTAINER_OF(chan, struct iso_test_chan, iso_chan); const struct bt_iso_chan_path hci_path = { .pid = BT_ISO_DATA_PATH_HCI, .format = BT_HCI_CODING_FORMAT_TRANSPARENT, @@ -76,30 +78,28 @@ static void iso_connected(struct bt_iso_chan *chan) err = bt_iso_setup_data_path(chan, BT_HCI_DATAPATH_DIR_HOST_TO_CTLR, &hci_path); TEST_ASSERT(err == 0, "Failed to set ISO data path: %d", err); - if (chan == default_chan) { - /* Register for TX to start sending */ - err = iso_tx_register(chan); - TEST_ASSERT(err == 0, "Failed to register chan for TX: %d", err); + /* Register for TX to start sending */ + err = iso_tx_register(chan); + TEST_ASSERT(err == 0, "Failed to register chan for TX: %d", err); - SET_FLAG(flag_iso_connected); - } + SET_FLAG(test_chan->flag_iso_connected); } static void iso_disconnected(struct bt_iso_chan *chan, uint8_t reason) { + struct iso_test_chan *test_chan = CONTAINER_OF(chan, struct iso_test_chan, iso_chan); int err; printk("ISO Channel %p disconnected (reason 0x%02x)\n", chan, reason); - if (chan == default_chan) { - err = iso_tx_unregister(chan); - TEST_ASSERT(err == 0, "Failed to unregister chan for TX: %d", err); + err = iso_tx_unregister(chan); + TEST_ASSERT(err == 0, "Failed to unregister chan for TX: %d", err); - disconnect_reason = reason; + test_chan->disconnect_reason = reason; - UNSET_FLAG(flag_iso_connected); - } + UNSET_FLAG(test_chan->flag_iso_connected); + printk("Removing data path\n"); err = bt_iso_remove_data_path(chan, BT_HCI_DATAPATH_DIR_HOST_TO_CTLR); TEST_ASSERT(err == 0, "Failed to remove ISO data path: %d", err); } @@ -129,11 +129,11 @@ static void init(void) return; } - for (size_t i = 0U; i < ARRAY_SIZE(iso_chans); i++) { - iso_chans[i].ops = &iso_ops; - iso_chans[i].qos = &iso_qos; + ARRAY_FOR_EACH_PTR(test_chans, test_chan) { + test_chan->iso_chan.ops = &iso_ops; + test_chan->iso_chan.qos = &iso_qos; #if defined(CONFIG_BT_SMP) - iso_chans[i].required_sec_level = BT_SECURITY_L2; + test_chan->iso_chan.required_sec_level = BT_SECURITY_L2; #endif /* CONFIG_BT_SMP */ } @@ -142,7 +142,12 @@ static void init(void) static void set_cig_defaults(struct bt_iso_cig_param *param) { - param->cis_channels = &default_chan; + /* By default we only configure a single CIS so that we can reconfigure the CIG with + * additional CIS + */ + static struct bt_iso_chan *chan = &test_chans[0].iso_chan; + + param->cis_channels = &chan; param->num_cis = 1U; param->sca = BT_GAP_SCA_UNKNOWN; param->packing = BT_ISO_PACKING_SEQUENTIAL; @@ -151,17 +156,16 @@ static void set_cig_defaults(struct bt_iso_cig_param *param) param->p_to_c_latency = latency_ms; /* ms */ param->c_to_p_interval = interval_us; /* us */ param->p_to_c_interval = interval_us; /* us */ - } static void create_cig(size_t iso_channels) { - struct bt_iso_chan *channels[ARRAY_SIZE(iso_chans)]; + struct bt_iso_chan *channels[ARRAY_SIZE(test_chans)]; struct bt_iso_cig_param param; int err; for (size_t i = 0U; i < iso_channels; i++) { - channels[i] = &iso_chans[i]; + channels[i] = &test_chans[i].iso_chan; } set_cig_defaults(¶m); @@ -240,18 +244,18 @@ static int reconfigure_cig_latency(struct bt_iso_cig_param *param) static void reconfigure_cig(void) { - struct bt_iso_chan *channels[2]; + struct bt_iso_chan *channels[ARRAY_SIZE(test_chans)]; struct bt_iso_cig_param param; int err; for (size_t i = 0U; i < ARRAY_SIZE(channels); i++) { - channels[i] = &iso_chans[i]; + channels[i] = &test_chans[i].iso_chan; } set_cig_defaults(¶m); - /* Test modifying existing CIS */ - default_chan->qos->tx->rtn++; + /* Test modifying existing CIS - All CIS share the same QoS*/ + test_chans[0].iso_chan.qos->tx->rtn++; err = bt_iso_cig_reconfigure(cig, ¶m); if (err != 0) { @@ -272,9 +276,10 @@ static void reconfigure_cig(void) return; } - /* Add CIS to the CIG and restore all other parameters */ + /* Add the last CIS to the CIG and restore all other parameters */ set_cig_defaults(¶m); param.cis_channels = &channels[1]; + param.num_cis = ARRAY_SIZE(channels) - 1; err = bt_iso_cig_reconfigure(cig, ¶m); if (err != 0) { @@ -301,40 +306,56 @@ static void connect_acl(void) static void connect_cis(void) { - const struct bt_iso_connect_param connect_param = { - .acl = default_conn, - .iso_chan = default_chan, - }; + struct bt_iso_connect_param connect_params[ARRAY_SIZE(test_chans)]; int err; - err = bt_iso_chan_connect(&connect_param, 1); - if (err) { + ARRAY_FOR_EACH(connect_params, i) { + connect_params[i].acl = default_conn; + connect_params[i].iso_chan = &test_chans[i].iso_chan; + } + + err = bt_iso_chan_connect(connect_params, ARRAY_SIZE(connect_params)); + if (err != 0) { TEST_FAIL("Failed to connect ISO (%d)", err); return; } - WAIT_FOR_FLAG(flag_iso_connected); + ARRAY_FOR_EACH_PTR(test_chans, test_chan) { + WAIT_FOR_FLAG(test_chan->flag_iso_connected); + } } static void disconnect_cis(void) { - int err; + printk("Disconnecting CIS)\n"); - err = bt_iso_chan_disconnect(default_chan); - if (err) { - TEST_FAIL("Failed to disconnect ISO (err %d)", err); + ARRAY_FOR_EACH_PTR(test_chans, test_chan) { + int err; - return; - } + if (!IS_FLAG_SET(test_chan->flag_iso_connected)) { + continue; + } + + err = bt_iso_chan_disconnect(&test_chan->iso_chan); + if (err != 0) { + TEST_FAIL("Failed to disconnect ISO (err %d)", err); + + return; + } - WAIT_FOR_FLAG_UNSET(flag_iso_connected); + WAIT_FOR_FLAG_UNSET(test_chan->flag_iso_connected); + } } static void disconnect_acl(void) { int err; + if (!IS_FLAG_SET(flag_connected)) { + return; + } + err = bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); if (err) { TEST_FAIL("Failed to disconnect ACL (err %d)", err); @@ -385,23 +406,28 @@ static void reset_bluetooth(void) static void wait_tx_complete(void) { - size_t tx_cnt; - - do { - tx_cnt = iso_tx_get_sent_cnt(default_chan); - k_sleep(K_USEC(interval_us)); - - if (!IS_FLAG_SET(flag_iso_connected)) { - /* We don't expect all TX to be complete in the test where the peripheral - * actively disconnects - */ - if (disconnect_reason != BT_HCI_ERR_REMOTE_USER_TERM_CONN) { - TEST_FAIL("Did not sent expected amount before disconnection"); - } - return; - } - } while (tx_cnt < EXPECTED_TX_CNT); + ARRAY_FOR_EACH_PTR(test_chans, test_chan) { + size_t tx_cnt; + + do { + tx_cnt = iso_tx_get_sent_cnt(&test_chan->iso_chan); + k_sleep(K_USEC(interval_us)); + + if (!IS_FLAG_SET(test_chan->flag_iso_connected)) { + /* We don't expect all TX to be complete in the test where the + * peripheral actively disconnects + */ + if (test_chan->disconnect_reason != + BT_HCI_ERR_REMOTE_USER_TERM_CONN) { + TEST_FAIL("Did not sent expected amount before " + "disconnection"); + } + + break; + } + } while (tx_cnt < EXPECTED_TX_CNT); + } } static void test_main(void) @@ -412,12 +438,8 @@ static void test_main(void) connect_acl(); connect_cis(); wait_tx_complete(); - if (IS_FLAG_SET(flag_iso_connected)) { - disconnect_cis(); - } - if (IS_FLAG_SET(flag_connected)) { - disconnect_acl(); - } + disconnect_cis(); + disconnect_acl(); terminate_cig(); TEST_PASS("Test passed"); @@ -428,7 +450,7 @@ static void test_main_disable(void) init(); /* Setup and connect before disabling */ - create_cig(ARRAY_SIZE(iso_chans)); + create_cig(ARRAY_SIZE(test_chans)); connect_acl(); connect_cis(); @@ -436,16 +458,12 @@ static void test_main_disable(void) reset_bluetooth(); /* Set everything up again to see if everything still works as expected */ - create_cig(ARRAY_SIZE(iso_chans)); + create_cig(ARRAY_SIZE(test_chans)); connect_acl(); connect_cis(); wait_tx_complete(); - if (IS_FLAG_SET(flag_iso_connected)) { - disconnect_cis(); - } - if (IS_FLAG_SET(flag_connected)) { - disconnect_acl(); - } + disconnect_cis(); + disconnect_acl(); terminate_cig(); TEST_PASS("Disable test passed"); diff --git a/tests/bsim/bluetooth/host/iso/cis/src/cis_peripheral.c b/tests/bsim/bluetooth/host/iso/cis/src/cis_peripheral.c index 05e46e9e83a8b..0645c69737926 100644 --- a/tests/bsim/bluetooth/host/iso/cis/src/cis_peripheral.c +++ b/tests/bsim/bluetooth/host/iso/cis/src/cis_peripheral.c @@ -10,12 +10,15 @@ #include #include +#include #include #include #include #include #include +#include #include +#include #include #include @@ -25,18 +28,21 @@ #include "babblekit/flags.h" #include "bstests.h" #include "common.h" +#include "syscalls/kernel.h" extern enum bst_result_t bst_result; -DEFINE_FLAG_STATIC(flag_data_received); - static const struct bt_data ad[] = { BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), }; -static struct bt_iso_chan iso_chan; + +static struct iso_test_chan { + struct bt_iso_chan iso_chan; + size_t iso_recv_cnt; + atomic_t flag_data_received; +} test_chans[CONFIG_BT_ISO_MAX_CHAN]; static size_t disconnect_after_recv_cnt; -static size_t iso_recv_cnt; /** Print data as d_0 d_1 d_2 ... d_(n-2) d_(n-1) d_(n) to show the 3 first and 3 last octets * @@ -85,15 +91,34 @@ static void disconnect_device(struct bt_conn *conn, void *data) static void iso_recv(struct bt_iso_chan *chan, const struct bt_iso_recv_info *info, struct net_buf *buf) { - iso_recv_cnt++; + struct iso_test_chan *test_chan = CONTAINER_OF(chan, struct iso_test_chan, iso_chan); + + test_chan->iso_recv_cnt++; if (info->flags & BT_ISO_FLAGS_VALID) { - printk("[%zu]: Incoming data channel %p len %u\n", iso_recv_cnt, chan, buf->len); + printk("[%zu]: Incoming data channel %p len %u\n", test_chan->iso_recv_cnt, chan, + buf->len); iso_print_data(buf->data, buf->len); - SET_FLAG(flag_data_received); + SET_FLAG(test_chan->flag_data_received); } - if (disconnect_after_recv_cnt && (iso_recv_cnt >= disconnect_after_recv_cnt)) { - printk("Disconnecting\n"); - bt_conn_foreach(BT_CONN_TYPE_LE, disconnect_device, NULL); + + if (disconnect_after_recv_cnt != 0) { + bool all_received = true; + + ARRAY_FOR_EACH_PTR(test_chans, tmp_test_chan) { + if (tmp_test_chan->iso_chan.iso != NULL && + tmp_test_chan->iso_recv_cnt < disconnect_after_recv_cnt) { + all_received = false; + break; + } + } + + /* If all connected streams have received `disconnect_after_recv_cnt` then we + * disconnect the ACL + */ + if (all_received) { + printk("Disconnecting\n"); + bt_conn_foreach(BT_CONN_TYPE_LE, disconnect_device, NULL); + } } } @@ -113,22 +138,34 @@ static void iso_connected(struct bt_iso_chan *chan) static void iso_disconnected(struct bt_iso_chan *chan, uint8_t reason) { + struct iso_test_chan *test_chan = CONTAINER_OF(chan, struct iso_test_chan, iso_chan); + printk("ISO Channel %p disconnected (reason 0x%02x)\n", chan, reason); + + if (!IS_FLAG_SET(test_chan->flag_data_received)) { + TEST_FAIL("Test failed: Chan %p did not receive expected data", chan); + } } static int iso_accept(const struct bt_iso_accept_info *info, struct bt_iso_chan **chan) { - printk("Incoming request from %p\n", (void *)info->acl); + int ret; - if (iso_chan.iso) { - TEST_FAIL("No channels available"); + printk("Incoming request from %p\n", (void *)info->acl); - return -ENOMEM; + ret = -ENOMEM; + ARRAY_FOR_EACH_PTR(test_chans, test_chan) { + if (test_chan->iso_chan.iso == NULL) { + *chan = &test_chan->iso_chan; + ret = 0; + } } - *chan = &iso_chan; + if (ret != 0) { + TEST_FAIL("No channels available"); + } - return 0; + return ret; } static void init(void) @@ -160,11 +197,13 @@ static void init(void) return; } - iso_chan.ops = &iso_ops; - iso_chan.qos = &iso_qos; + ARRAY_FOR_EACH_PTR(test_chans, test_chan) { + test_chan->iso_chan.ops = &iso_ops; + test_chan->iso_chan.qos = &iso_qos; #if defined(CONFIG_BT_SMP) - iso_chan.required_sec_level = BT_SECURITY_L2, + test_chan->iso_chan.required_sec_level = BT_SECURITY_L2; #endif /* CONFIG_BT_SMP */ + } err = bt_iso_server_register(&iso_server); if (err) { @@ -190,6 +229,24 @@ static void adv_connect(void) WAIT_FOR_FLAG(flag_connected); } +static void wait_all_cis_disconnected(void) +{ + bool all_cis_disconnected = false; + + while (!all_cis_disconnected) { + all_cis_disconnected = true; + + ARRAY_FOR_EACH_PTR(test_chans, test_chan) { + if (test_chan->iso_chan.iso != NULL) { + all_cis_disconnected = false; + break; + } + } + + k_sleep(K_MSEC(100)); + } +} + static void test_main(void) { init(); @@ -197,10 +254,9 @@ static void test_main(void) while (true) { adv_connect(); bt_testlib_conn_wait_free(); + wait_all_cis_disconnected(); - if (IS_FLAG_SET(flag_data_received)) { - TEST_PASS("Test passed"); - } + TEST_PASS("Test passed"); } } @@ -213,10 +269,9 @@ static void test_main_early_disconnect(void) while (true) { adv_connect(); bt_testlib_conn_wait_free(); + wait_all_cis_disconnected(); - if (IS_FLAG_SET(flag_data_received)) { - TEST_PASS("Test passed"); - } + TEST_PASS("Test passed"); } } diff --git a/tests/bsim/bluetooth/host/iso/common/iso_tx.c b/tests/bsim/bluetooth/host/iso/common/iso_tx.c index 9192ebd87234b..acdf1a3466792 100644 --- a/tests/bsim/bluetooth/host/iso/common/iso_tx.c +++ b/tests/bsim/bluetooth/host/iso/common/iso_tx.c @@ -174,15 +174,13 @@ int iso_tx_unregister(struct bt_iso_chan *iso_chan) } if (tx_stream->iso_chan == iso_chan) { - - tx_stream->iso_chan = NULL; - while (atomic_get(&tx_stream->enqueued) != 0) { k_sleep(K_MSEC(100)); } LOG_INF("Unregistered %p for TX", iso_chan); + tx_stream->iso_chan = NULL; err = 0; }