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 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/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 e3b6ee9284fe0..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,8 @@ #include #include #include +#include +#include #include #include #include @@ -25,76 +27,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 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 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); 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) { @@ -118,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, @@ -126,56 +75,33 @@ static void iso_connected(struct bt_iso_chan *chan) printk("ISO Channel %p connected\n", chan); - seq_num = 0U; - enqueue_cnt = ENQUEUE_COUNT; - - if (chan == default_chan) { - /* Start send timer */ - k_work_schedule(&iso_send_work, K_MSEC(0)); - - 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); + + /* 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(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) { - k_work_cancel_delayable(&iso_send_work); + err = iso_tx_unregister(chan); + TEST_ASSERT(err == 0, "Failed to unregister chan for TX: %d", err); - UNSET_FLAG(flag_iso_connected); - } + test_chan->disconnect_reason = reason; + + 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); - - 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, @@ -203,18 +129,25 @@ 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 */ } + + iso_tx_init(); } 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; @@ -223,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); @@ -312,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) { @@ -344,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) { @@ -373,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); - WAIT_FOR_FLAG_UNSET(flag_iso_connected); + return; + } + + 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); @@ -455,6 +404,32 @@ static void reset_bluetooth(void) } } +static void wait_tx_complete(void) +{ + + 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) { init(); @@ -462,21 +437,10 @@ 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) { - disconnect_cis(); - 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)); + wait_tx_complete(); + disconnect_cis(); + disconnect_acl(); + terminate_cig(); TEST_PASS("Test passed"); } @@ -486,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(); @@ -494,14 +458,10 @@ 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(); - - while (seq_num < 100U) { - k_sleep(K_USEC(interval_us)); - } - + wait_tx_complete(); disconnect_cis(); disconnect_acl(); terminate_cig(); 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..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("Incoming data channel %p len %u\n", 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 new file mode 100644 index 0000000000000..acdf1a3466792 --- /dev/null +++ b/tests/bsim/bluetooth/host/iso/common/iso_tx.c @@ -0,0 +1,270 @@ +/* + * 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) { + 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; + } + + 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 */