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 */