diff --git a/doc/releases/release-notes-4.3.rst b/doc/releases/release-notes-4.3.rst index 8a3692e6aea90..c185f0846052e 100644 --- a/doc/releases/release-notes-4.3.rst +++ b/doc/releases/release-notes-4.3.rst @@ -114,6 +114,7 @@ New APIs and options * :c:struct:`bt_bap_stream` now contains an ``iso`` field as a reference to the ISO channel * :c:func:`bt_bap_unicast_group_get_info` * :c:func:`bt_cap_unicast_group_get_info` + * :c:func:`bt_bap_ep_get_conn` * Host diff --git a/include/zephyr/bluetooth/audio/bap.h b/include/zephyr/bluetooth/audio/bap.h index d480837c97138..54beef46022ee 100644 --- a/include/zephyr/bluetooth/audio/bap.h +++ b/include/zephyr/bluetooth/audio/bap.h @@ -882,6 +882,21 @@ struct bt_bap_ep_info { */ int bt_bap_ep_get_info(const struct bt_bap_ep *ep, struct bt_bap_ep_info *info); +/** + * @brief Get the pointer to the ACL connection of an endpoint + * + * The caller gets a new reference to the connection object, if not NULL, which must be + * released with bt_conn_unref() once done using the object. + * + * @param ep The endpoint to get the ACL connection of + * + * @return The ACL connection pointer. + * Will always be NULL for broadcast endpoints. + * Will be NULL for Unicast Server endpoints if the endpoint is not configured by a client. + * Will be NULL for Unicast Client endpoints if @p does not match a discovered endpoint. + */ +struct bt_conn *bt_bap_ep_get_conn(const struct bt_bap_ep *ep); + /** * @brief Basic Audio Profile stream structure. * @@ -1967,10 +1982,7 @@ struct bt_bap_unicast_client_cb { /** * @brief Register unicast client callbacks. * - * Only one callback structure can be registered, and attempting to - * registering more than one will result in an error. - * - * @param cb Unicast client callback structure. + * @param cb Unicast client callback structure to register. * * @retval 0 Success * @retval -EINVAL @p cb is NULL. @@ -1978,6 +1990,16 @@ struct bt_bap_unicast_client_cb { */ int bt_bap_unicast_client_register_cb(struct bt_bap_unicast_client_cb *cb); +/** + * @brief Unregister unicast client callbacks. + * + * @param cb Unicast client callback structure to unregister. + * + * @retval 0 Success + * @retval -EINVAL @p cb is NULL or @p cb was not registered + */ +int bt_bap_unicast_client_unregister_cb(struct bt_bap_unicast_client_cb *cb); + /** * @brief Discover remote capabilities and endpoints * diff --git a/subsys/bluetooth/audio/ascs.c b/subsys/bluetooth/audio/ascs.c index 4636ca605a378..1f0045afe1048 100644 --- a/subsys/bluetooth/audio/ascs.c +++ b/subsys/bluetooth/audio/ascs.c @@ -3259,4 +3259,16 @@ int bt_ascs_unregister(void) return err; } +struct bt_conn *bt_ascs_ep_get_conn(const struct bt_bap_ep *ep) +{ + struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); + + __ASSERT_NO_MSG(bt_ascs_has_ep(ep)); + + if (ase->conn == NULL) { + return NULL; + } + + return bt_conn_ref(ase->conn); +} #endif /* BT_BAP_UNICAST_SERVER */ diff --git a/subsys/bluetooth/audio/ascs_internal.h b/subsys/bluetooth/audio/ascs_internal.h index 649b5c1706571..041727712dfc4 100644 --- a/subsys/bluetooth/audio/ascs_internal.h +++ b/subsys/bluetooth/audio/ascs_internal.h @@ -364,5 +364,6 @@ void bt_ascs_foreach_ep(struct bt_conn *conn, bt_bap_ep_func_t func, void *user_ int bt_ascs_register(uint8_t snk_cnt, uint8_t src_cnt); int bt_ascs_unregister(void); +struct bt_conn *bt_ascs_ep_get_conn(const struct bt_bap_ep *ep); #endif /* BT_ASCS_INTERNAL_H */ diff --git a/subsys/bluetooth/audio/bap_internal.h b/subsys/bluetooth/audio/bap_internal.h index 6d7ff219b3543..d6a20ef769af2 100644 --- a/subsys/bluetooth/audio/bap_internal.h +++ b/subsys/bluetooth/audio/bap_internal.h @@ -147,3 +147,5 @@ bool bt_bap_broadcast_sink_has_ep(const struct bt_bap_ep *ep); bool bt_bap_broadcast_source_has_ep(const struct bt_bap_ep *ep); bool bt_bap_unicast_client_has_ep(const struct bt_bap_ep *ep); bool bt_bap_unicast_server_has_ep(const struct bt_bap_ep *ep); +struct bt_conn *bt_bap_unicast_client_ep_get_conn(const struct bt_bap_ep *ep); +struct bt_conn *bt_bap_unicast_server_ep_get_conn(const struct bt_bap_ep *ep); diff --git a/subsys/bluetooth/audio/bap_stream.c b/subsys/bluetooth/audio/bap_stream.c index e33a7d79f6bde..b463d74113c2b 100644 --- a/subsys/bluetooth/audio/bap_stream.c +++ b/subsys/bluetooth/audio/bap_stream.c @@ -173,6 +173,31 @@ int bt_bap_ep_get_info(const struct bt_bap_ep *ep, struct bt_bap_ep_info *info) return 0; } +struct bt_conn *bt_bap_ep_get_conn(const struct bt_bap_ep *ep) +{ + struct bt_conn *conn; + + if (ep == NULL) { + LOG_DBG("ep is NULL"); + + return NULL; + } + + if ((IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SOURCE) && bt_bap_broadcast_source_has_ep(ep)) || + (IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SINK) && bt_bap_broadcast_sink_has_ep(ep))) { + conn = NULL; + } else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && bt_bap_unicast_client_has_ep(ep)) { + conn = bt_bap_unicast_client_ep_get_conn(ep); + } else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && bt_bap_unicast_server_has_ep(ep)) { + conn = bt_bap_unicast_server_ep_get_conn(ep); + } else { + LOG_DBG("Invalid endpoint %p", ep); + conn = NULL; + } + + return conn; +} + enum bt_bap_ascs_reason bt_audio_verify_qos(const struct bt_bap_qos_cfg *qos) { if (qos->interval < BT_ISO_SDU_INTERVAL_MIN || diff --git a/subsys/bluetooth/audio/bap_unicast_client.c b/subsys/bluetooth/audio/bap_unicast_client.c index cbbc56088740b..81fa2ae957d8b 100644 --- a/subsys/bluetooth/audio/bap_unicast_client.c +++ b/subsys/bluetooth/audio/bap_unicast_client.c @@ -460,6 +460,41 @@ bool bt_bap_unicast_client_has_ep(const struct bt_bap_ep *ep) return false; } +struct bt_conn *bt_bap_unicast_client_ep_get_conn(const struct bt_bap_ep *ep) +{ + for (size_t i = 0U; i < ARRAY_SIZE(uni_cli_insts); i++) { +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + if (PART_OF_ARRAY(uni_cli_insts[i].snks, ep)) { + ARRAY_FOR_EACH_PTR(uni_cli_insts[i].snks, client_ep) { + if (&client_ep->ep == ep) { + if (client_ep->handle == BAP_HANDLE_UNUSED) { + return NULL; + } + + return bt_conn_lookup_index((uint8_t)i); + } + } + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + if (PART_OF_ARRAY(uni_cli_insts[i].srcs, ep)) { + ARRAY_FOR_EACH_PTR(uni_cli_insts[i].srcs, client_ep) { + if (&client_ep->ep == ep) { + if (client_ep->handle == BAP_HANDLE_UNUSED) { + return NULL; + } + + return bt_conn_lookup_index((uint8_t)i); + } + } + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + } + + return NULL; +} + static void unicast_client_ep_init(struct bt_bap_ep *ep, uint16_t handle, uint8_t dir) { struct bt_bap_unicast_client_ep *client_ep; @@ -4684,3 +4719,18 @@ int bt_bap_unicast_client_register_cb(struct bt_bap_unicast_client_cb *cb) return 0; } + +int bt_bap_unicast_client_unregister_cb(struct bt_bap_unicast_client_cb *cb) +{ + if (cb == NULL) { + LOG_DBG("cb was NULL"); + return -EINVAL; + } + + if (!sys_slist_find_and_remove(&unicast_client_cbs, &cb->_node)) { + LOG_DBG("cb was not registered"); + return -EALREADY; + } + + return 0; +} diff --git a/subsys/bluetooth/audio/bap_unicast_server.c b/subsys/bluetooth/audio/bap_unicast_server.c index e46ed4ff4b282..f7edb918b0297 100644 --- a/subsys/bluetooth/audio/bap_unicast_server.c +++ b/subsys/bluetooth/audio/bap_unicast_server.c @@ -227,3 +227,8 @@ bool bt_bap_unicast_server_has_ep(const struct bt_bap_ep *ep) { return bt_ascs_has_ep(ep); } + +struct bt_conn *bt_bap_unicast_server_ep_get_conn(const struct bt_bap_ep *ep) +{ + return bt_ascs_ep_get_conn(ep); +} diff --git a/subsys/bluetooth/audio/cap_initiator.c b/subsys/bluetooth/audio/cap_initiator.c index 550da6b5f5b2f..2e799e1145954 100644 --- a/subsys/bluetooth/audio/cap_initiator.c +++ b/subsys/bluetooth/audio/cap_initiator.c @@ -1158,9 +1158,11 @@ bool bt_cap_initiator_valid_unicast_audio_start_param( const union bt_cap_set_member *member = &stream_param->member; const struct bt_cap_stream *cap_stream = stream_param->stream; const struct bt_audio_codec_cfg *codec_cfg = stream_param->codec_cfg; + const struct bt_bap_ep *ep = stream_param->ep; const struct bt_bap_stream *bap_stream; const struct bt_conn *member_conn = bt_cap_common_get_member_conn(param->type, member); + struct bt_conn *ep_conn; if (member == NULL) { LOG_DBG("param->members[%zu] is NULL", i); @@ -1182,11 +1184,26 @@ bool bt_cap_initiator_valid_unicast_audio_start_param( return false; } - CHECKIF(stream_param->ep == NULL) { + if (ep == NULL) { LOG_DBG("param->stream_params[%zu].ep is NULL", i); return false; } + ep_conn = bt_bap_ep_get_conn(ep); + if (ep_conn == NULL) { + LOG_DBG("param->stream_params[%zu].ep is invalid", i); + return false; + } + if (ep_conn != member_conn) { + LOG_DBG("param->stream_params[%zu].ep conn %p does not match " + "param->members[%zu] %p", + i, ep_conn, i, member_conn); + bt_conn_unref(ep_conn); + + return false; + } + bt_conn_unref(ep_conn); + CHECKIF(member == NULL) { LOG_DBG("param->stream_params[%zu].member is NULL", i); return false; diff --git a/tests/bluetooth/audio/cap_initiator/include/test_common.h b/tests/bluetooth/audio/cap_initiator/include/test_common.h index 53a2d1a403198..16328f0518f1b 100644 --- a/tests/bluetooth/audio/cap_initiator/include/test_common.h +++ b/tests/bluetooth/audio/cap_initiator/include/test_common.h @@ -1,10 +1,11 @@ /* test_common.h */ /* - * Copyright (c) 2024 Nordic Semiconductor ASA + * Copyright (c) 2024-2025 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include @@ -14,7 +15,7 @@ void test_mocks_init(void); void test_mocks_cleanup(void); -void test_conn_init(struct bt_conn *conn); +void test_conn_init(struct bt_conn *conn, uint8_t index); void test_unicast_set_state(struct bt_cap_stream *cap_stream, struct bt_conn *conn, struct bt_bap_ep *ep, struct bt_bap_lc3_preset *preset, diff --git a/tests/bluetooth/audio/cap_initiator/src/main.c b/tests/bluetooth/audio/cap_initiator/src/main.c index a15c1fc1c1f04..256f982d87e9a 100644 --- a/tests/bluetooth/audio/cap_initiator/src/main.c +++ b/tests/bluetooth/audio/cap_initiator/src/main.c @@ -43,7 +43,7 @@ struct cap_initiator_test_suite_fixture { static void cap_initiator_test_suite_fixture_init(struct cap_initiator_test_suite_fixture *fixture) { for (size_t i = 0; i < ARRAY_SIZE(fixture->conns); i++) { - test_conn_init(&fixture->conns[i]); + test_conn_init(&fixture->conns[i], i); } } diff --git a/tests/bluetooth/audio/cap_initiator/src/test_common.c b/tests/bluetooth/audio/cap_initiator/src/test_common.c index fe3650c7c1160..d7c161c68ad6b 100644 --- a/tests/bluetooth/audio/cap_initiator/src/test_common.c +++ b/tests/bluetooth/audio/cap_initiator/src/test_common.c @@ -1,11 +1,13 @@ /* test_common.c - common procedures for unit test of CAP initiator */ /* - * Copyright (c) 2024 Nordic Semiconductor ASA + * Copyright (c) 2024-2025 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ +#include + #include #include #include @@ -32,9 +34,9 @@ void test_mocks_cleanup(void) mock_cap_initiator_cleanup(); } -void test_conn_init(struct bt_conn *conn) +void test_conn_init(struct bt_conn *conn, uint8_t index) { - conn->index = 0; + conn->index = index; conn->info.type = BT_CONN_TYPE_LE; conn->info.role = BT_CONN_ROLE_CENTRAL; conn->info.state = BT_CONN_STATE_CONNECTED; diff --git a/tests/bluetooth/audio/cap_initiator/src/test_unicast_start.c b/tests/bluetooth/audio/cap_initiator/src/test_unicast_start.c index 09e85780ffdd3..fc6801ed34d34 100644 --- a/tests/bluetooth/audio/cap_initiator/src/test_unicast_start.c +++ b/tests/bluetooth/audio/cap_initiator/src/test_unicast_start.c @@ -36,9 +36,22 @@ #include "ztest_assert.h" #include "ztest_test.h" +BUILD_ASSERT(CONFIG_BT_MAX_CONN *(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT + + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT) >= + CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT); + +/* Either BT_AUDIO_DIR_SINK or BT_AUDIO_DIR_SOURCE */ +#define INDEX_TO_DIR(_idx) (((_idx) & 1U) + 1U) + +DECLARE_FAKE_VOID_FUNC(mock_bap_discover_endpoint, struct bt_conn *, enum bt_audio_dir, + struct bt_bap_ep *); +DEFINE_FAKE_VOID_FUNC(mock_bap_discover_endpoint, struct bt_conn *, enum bt_audio_dir, + struct bt_bap_ep *); + struct cap_initiator_test_unicast_start_fixture { struct bt_cap_stream cap_streams[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT]; - struct bt_bap_ep eps[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT]; + struct bt_bap_ep *snk_eps[CONFIG_BT_MAX_CONN][CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT]; + struct bt_bap_ep *src_eps[CONFIG_BT_MAX_CONN][CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT]; struct bt_cap_unicast_audio_start_stream_param audio_start_stream_params[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT]; struct bt_cap_unicast_audio_start_param audio_start_param; @@ -51,9 +64,9 @@ static void cap_initiator_test_unicast_start_fixture_init( struct cap_initiator_test_unicast_start_fixture *fixture) { struct bt_cap_unicast_group_stream_pair_param - pair_params[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT] = {0}; + group_pair_params[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT] = {0}; struct bt_cap_unicast_group_stream_param - stream_params[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT] = {0}; + group_stream_param[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT] = {0}; struct bt_cap_unicast_group_param group_param = {0}; size_t stream_cnt = 0U; size_t pair_cnt = 0U; @@ -63,24 +76,20 @@ static void cap_initiator_test_unicast_start_fixture_init( BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED); for (size_t i = 0U; i < ARRAY_SIZE(fixture->conns); i++) { - test_conn_init(&fixture->conns[i]); + test_conn_init(&fixture->conns[i], i); } - for (size_t i = 0U; i < ARRAY_SIZE(fixture->eps); i++) { - const uint8_t dir = (i & 1) + 1; /* Makes it either 1 or 2 (sink or source)*/ + while (stream_cnt < ARRAY_SIZE(group_stream_param)) { + const enum bt_audio_dir dir = INDEX_TO_DIR(stream_cnt); - fixture->eps[i].dir = dir; - } - - while (stream_cnt < ARRAY_SIZE(stream_params)) { - stream_params[stream_cnt].stream = &fixture->cap_streams[stream_cnt]; - stream_params[stream_cnt].qos_cfg = &fixture->preset.qos; + group_stream_param[stream_cnt].stream = &fixture->cap_streams[stream_cnt]; + group_stream_param[stream_cnt].qos_cfg = &fixture->preset.qos; /* Switch between sink and source depending on index*/ - if ((stream_cnt & 1) == 0) { - pair_params[pair_cnt].tx_param = &stream_params[stream_cnt]; + if (dir == BT_AUDIO_DIR_SINK) { + group_pair_params[pair_cnt].tx_param = &group_stream_param[stream_cnt]; } else { - pair_params[pair_cnt].rx_param = &stream_params[stream_cnt]; + group_pair_params[pair_cnt].rx_param = &group_stream_param[stream_cnt]; } pair_cnt = DIV_ROUND_UP(stream_cnt, 2U); @@ -89,25 +98,126 @@ static void cap_initiator_test_unicast_start_fixture_init( group_param.packing = BT_ISO_PACKING_SEQUENTIAL; group_param.params_count = pair_cnt; - group_param.params = pair_params; + group_param.params = group_pair_params; err = bt_cap_unicast_group_create(&group_param, &fixture->unicast_group); zassert_equal(err, 0, "Unexpected return value %d", err); +} + +static void *cap_initiator_test_unicast_start_setup(void) +{ + struct cap_initiator_test_unicast_start_fixture *fixture; + + fixture = malloc(sizeof(*fixture)); + zassert_not_null(fixture); + + return fixture; +} + +static void mock_discover(struct cap_initiator_test_unicast_start_fixture *fixture) +{ + struct bt_bap_unicast_client_cb unicast_client_cb = { + .endpoint = mock_bap_discover_endpoint, + }; + int err; + + err = bt_bap_unicast_client_register_cb(&unicast_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + for (size_t i = 0U; i < ARRAY_SIZE(fixture->conns); i++) { + RESET_FAKE(mock_bap_discover_endpoint); + err = bt_bap_unicast_client_discover(&fixture->conns[i], BT_AUDIO_DIR_SINK); + zassert_equal(0, err, "Unexpected return value %d", err); + + /* TODO: use callback to populate eps */ + + zexpect_call_count("unicast_client_cb.bap_discover_endpoint", + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT, + mock_bap_discover_endpoint_fake.call_count); + for (size_t j = 0U; j < mock_bap_discover_endpoint_fake.call_count; j++) { + /* Verify conn */ + zassert_equal(mock_bap_discover_endpoint_fake.arg0_history[j], + &fixture->conns[i], "%p", + mock_bap_discover_endpoint_fake.arg0_history[j]); + + /* Verify dir */ + zassert_equal(mock_bap_discover_endpoint_fake.arg1_history[j], + BT_AUDIO_DIR_SINK, "%d", + mock_bap_discover_endpoint_fake.arg1_history[j]); + + /* Verify and store ep */ + zassert_not_equal(mock_bap_discover_endpoint_fake.arg2_history[j], NULL, + "%p", mock_bap_discover_endpoint_fake.arg2_history[j]); + + fixture->snk_eps[fixture->conns[i].index][j] = + mock_bap_discover_endpoint_fake.arg2_history[j]; + } + RESET_FAKE(mock_bap_discover_endpoint); + err = bt_bap_unicast_client_discover(&fixture->conns[i], BT_AUDIO_DIR_SOURCE); + zassert_equal(0, err, "Unexpected return value %d", err); + + zexpect_call_count("unicast_client_cb.bap_discover_endpoint", + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT, + mock_bap_discover_endpoint_fake.call_count); + for (size_t j = 0U; j < mock_bap_discover_endpoint_fake.call_count; j++) { + /* Verify conn */ + zassert_equal(mock_bap_discover_endpoint_fake.arg0_history[j], + &fixture->conns[i], "%p", + mock_bap_discover_endpoint_fake.arg0_history[j]); + + /* Verify dir */ + zassert_equal(mock_bap_discover_endpoint_fake.arg1_history[j], + BT_AUDIO_DIR_SOURCE, "%d", + mock_bap_discover_endpoint_fake.arg1_history[j]); + + /* Verify and store ep */ + zassert_not_equal(mock_bap_discover_endpoint_fake.arg2_history[j], NULL, + "%p", mock_bap_discover_endpoint_fake.arg2_history[j]); + + fixture->src_eps[fixture->conns[i].index][j] = + mock_bap_discover_endpoint_fake.arg2_history[j]; + } + } + + /* We don't need the callbacks anymore */ + err = bt_bap_unicast_client_unregister_cb(&unicast_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); +} + +static void init_default_params(struct cap_initiator_test_unicast_start_fixture *fixture) +{ /* Setup default params */ ARRAY_FOR_EACH(fixture->audio_start_stream_params, i) { struct bt_cap_unicast_audio_start_stream_param *stream_param = &fixture->audio_start_stream_params[i]; + /* We pair 2 streams, so only increase conn_index every 2nd stream and otherwise * round robin on all conns */ - const size_t conn_index = (i / 2) % ARRAY_SIZE(fixture->conns); + const size_t conn_index = (i / 2U) % ARRAY_SIZE(fixture->conns); + const size_t ep_index = i / (ARRAY_SIZE(fixture->conns) * 2U); + const enum bt_audio_dir dir = INDEX_TO_DIR(i); stream_param->stream = &fixture->cap_streams[i]; stream_param->codec_cfg = &fixture->preset.codec_cfg; - /* Distribute the streams equally among the connections */ + + /* Distribute the streams like + * [0]: conn[0] src[0] + * [1]: conn[0] snk[0] + * [2]: conn[1] src[0] + * [3]: conn[0] snk[0] + * [4]: conn[0] src[1] + * [5]: conn[0] snk[1] + * [6]: conn[1] src[1] + * [7]: conn[0] snk[1] + */ stream_param->member.member = &fixture->conns[conn_index]; - stream_param->ep = &fixture->eps[i]; + if (dir == BT_AUDIO_DIR_SINK) { + stream_param->ep = fixture->snk_eps[conn_index][ep_index]; + } else { + stream_param->ep = fixture->src_eps[conn_index][ep_index]; + } } fixture->audio_start_param.type = BT_CAP_SET_TYPE_AD_HOC; @@ -115,16 +225,6 @@ static void cap_initiator_test_unicast_start_fixture_init( fixture->audio_start_param.stream_params = fixture->audio_start_stream_params; } -static void *cap_initiator_test_unicast_start_setup(void) -{ - struct cap_initiator_test_unicast_start_fixture *fixture; - - fixture = malloc(sizeof(*fixture)); - zassert_not_null(fixture); - - return fixture; -} - static void cap_initiator_test_unicast_start_before(void *f) { struct cap_initiator_test_unicast_start_fixture *fixture = f; @@ -135,6 +235,9 @@ static void cap_initiator_test_unicast_start_before(void *f) err = bt_cap_initiator_register_cb(&mock_cap_initiator_cb); zassert_equal(0, err, "Unexpected return value %d", err); + + mock_discover(fixture); + init_default_params(fixture); } static void cap_initiator_test_unicast_start_after(void *f) diff --git a/tests/bluetooth/audio/cap_initiator/src/test_unicast_stop.c b/tests/bluetooth/audio/cap_initiator/src/test_unicast_stop.c index dd456d16a6e09..6bd881fc9d7c7 100644 --- a/tests/bluetooth/audio/cap_initiator/src/test_unicast_stop.c +++ b/tests/bluetooth/audio/cap_initiator/src/test_unicast_stop.c @@ -47,7 +47,7 @@ static void cap_initiator_test_unicast_stop_fixture_init( BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED); for (size_t i = 0U; i < ARRAY_SIZE(fixture->conns); i++) { - test_conn_init(&fixture->conns[i]); + test_conn_init(&fixture->conns[i], i); } for (size_t i = 0U; i < ARRAY_SIZE(fixture->eps); i++) { diff --git a/tests/bluetooth/audio/cap_initiator/uut/bap_unicast_client.c b/tests/bluetooth/audio/cap_initiator/uut/bap_unicast_client.c index eb34b97b4a048..130d9152c3075 100644 --- a/tests/bluetooth/audio/cap_initiator/uut/bap_unicast_client.c +++ b/tests/bluetooth/audio/cap_initiator/uut/bap_unicast_client.c @@ -24,18 +24,64 @@ #include "bap_endpoint.h" #include "bap_iso.h" +#include "conn.h" -static struct bt_bap_unicast_client_cb *unicast_client_cb; +static sys_slist_t unicast_client_cbs = SYS_SLIST_STATIC_INIT(&unicast_client_cbs); static struct bt_bap_unicast_group bap_unicast_group; +static struct unicast_client { +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + struct bt_bap_ep snks[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT]; +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + struct bt_bap_ep srcs[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT]; +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + struct bt_conn *conn; +} uni_cli_insts[CONFIG_BT_MAX_CONN]; + bool bt_bap_unicast_client_has_ep(const struct bt_bap_ep *ep) { - return true; + ARRAY_FOR_EACH_PTR(uni_cli_insts, uni_cli) { +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + if (IS_ARRAY_ELEMENT(uni_cli->snks, ep)) { + return true; + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + if (IS_ARRAY_ELEMENT(uni_cli->srcs, ep)) { + return true; + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + } + + return false; +} + +struct bt_conn *bt_bap_unicast_client_ep_get_conn(const struct bt_bap_ep *ep) +{ + ARRAY_FOR_EACH_PTR(uni_cli_insts, uni_cli) { +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + if (IS_ARRAY_ELEMENT(uni_cli->snks, ep)) { + return uni_cli->conn; + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + if (IS_ARRAY_ELEMENT(uni_cli->srcs, ep)) { + return uni_cli->conn; + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + } + + return NULL; } int bt_bap_unicast_client_config(struct bt_bap_stream *stream, const struct bt_audio_codec_cfg *codec_cfg) { + struct bt_bap_unicast_client_cb *listener, *next; + if (stream == NULL || stream->ep == NULL || codec_cfg == NULL) { return -EINVAL; } @@ -48,9 +94,11 @@ int bt_bap_unicast_client_config(struct bt_bap_stream *stream, return -EINVAL; } - if (unicast_client_cb != NULL && unicast_client_cb->config != NULL) { - unicast_client_cb->config(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, - BT_BAP_ASCS_REASON_NONE); + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&unicast_client_cbs, listener, next, _node) { + if (listener->config != NULL) { + listener->config(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + } } stream->ep->state = BT_BAP_EP_STATE_CODEC_CONFIGURED; @@ -66,6 +114,7 @@ int bt_bap_unicast_client_config(struct bt_bap_stream *stream, int bt_bap_unicast_client_qos(struct bt_conn *conn, struct bt_bap_unicast_group *group) { + struct bt_bap_unicast_client_cb *listener, *next; struct bt_bap_stream *stream; if (conn == NULL || group == NULL) { @@ -86,9 +135,12 @@ int bt_bap_unicast_client_qos(struct bt_conn *conn, struct bt_bap_unicast_group SYS_SLIST_FOR_EACH_CONTAINER(&group->streams, stream, _node) { if (stream->conn == conn) { - if (unicast_client_cb != NULL && unicast_client_cb->qos != NULL) { - unicast_client_cb->qos(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, - BT_BAP_ASCS_REASON_NONE); + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&unicast_client_cbs, listener, next, + _node) { + if (listener->qos != NULL) { + listener->qos(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + } } stream->ep->state = BT_BAP_EP_STATE_QOS_CONFIGURED; @@ -105,6 +157,8 @@ int bt_bap_unicast_client_qos(struct bt_conn *conn, struct bt_bap_unicast_group int bt_bap_unicast_client_enable(struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len) { + struct bt_bap_unicast_client_cb *listener, *next; + if (stream == NULL) { return -EINVAL; } @@ -116,9 +170,11 @@ int bt_bap_unicast_client_enable(struct bt_bap_stream *stream, const uint8_t met return -EINVAL; } - if (unicast_client_cb != NULL && unicast_client_cb->enable != NULL) { - unicast_client_cb->enable(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, - BT_BAP_ASCS_REASON_NONE); + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&unicast_client_cbs, listener, next, _node) { + if (listener->enable != NULL) { + listener->enable(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + } } stream->ep->state = BT_BAP_EP_STATE_ENABLING; @@ -133,6 +189,8 @@ int bt_bap_unicast_client_enable(struct bt_bap_stream *stream, const uint8_t met int bt_bap_unicast_client_metadata(struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len) { + struct bt_bap_unicast_client_cb *listener, *next; + if (stream == NULL) { return -EINVAL; } @@ -145,9 +203,11 @@ int bt_bap_unicast_client_metadata(struct bt_bap_stream *stream, const uint8_t m return -EINVAL; } - if (unicast_client_cb != NULL && unicast_client_cb->metadata != NULL) { - unicast_client_cb->metadata(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, - BT_BAP_ASCS_REASON_NONE); + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&unicast_client_cbs, listener, next, _node) { + if (listener->metadata != NULL) { + listener->metadata(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + } } if (stream->ops != NULL && stream->ops->metadata_updated != NULL) { @@ -194,6 +254,8 @@ int bt_bap_unicast_client_connect(struct bt_bap_stream *stream) int bt_bap_unicast_client_start(struct bt_bap_stream *stream) { + struct bt_bap_unicast_client_cb *listener, *next; + /* As per the ASCS spec, only source streams can be started by the client */ if (stream == NULL || stream->ep == NULL || stream->ep->dir == BT_AUDIO_DIR_SINK) { return -EINVAL; @@ -206,9 +268,11 @@ int bt_bap_unicast_client_start(struct bt_bap_stream *stream) return -EINVAL; } - if (unicast_client_cb != NULL && unicast_client_cb->start != NULL) { - unicast_client_cb->start(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, - BT_BAP_ASCS_REASON_NONE); + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&unicast_client_cbs, listener, next, _node) { + if (listener->start != NULL) { + listener->start(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + } } stream->ep->state = BT_BAP_EP_STATE_STREAMING; @@ -222,6 +286,8 @@ int bt_bap_unicast_client_start(struct bt_bap_stream *stream) int bt_bap_unicast_client_disable(struct bt_bap_stream *stream) { + struct bt_bap_unicast_client_cb *listener, *next; + if (stream == NULL || stream->ep == NULL) { return -EINVAL; } @@ -241,9 +307,11 @@ int bt_bap_unicast_client_disable(struct bt_bap_stream *stream) * when leaving the streaming state in a non-release manner */ - if (unicast_client_cb != NULL && unicast_client_cb->disable != NULL) { - unicast_client_cb->disable(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, - BT_BAP_ASCS_REASON_NONE); + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&unicast_client_cbs, listener, next, _node) { + if (listener->disable != NULL) { + listener->disable(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + } } /* Disabled sink ASEs go directly to the QoS configured state */ @@ -274,6 +342,8 @@ int bt_bap_unicast_client_disable(struct bt_bap_stream *stream) int bt_bap_unicast_client_stop(struct bt_bap_stream *stream) { + struct bt_bap_unicast_client_cb *listener, *next; + printk("%s %p\n", __func__, stream); /* As per the ASCS spec, only source streams can be stopped by the client */ @@ -288,9 +358,11 @@ int bt_bap_unicast_client_stop(struct bt_bap_stream *stream) return -EINVAL; } - if (unicast_client_cb != NULL && unicast_client_cb->stop != NULL) { - unicast_client_cb->stop(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, - BT_BAP_ASCS_REASON_NONE); + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&unicast_client_cbs, listener, next, _node) { + if (listener->stop != NULL) { + listener->stop(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + } } stream->ep->state = BT_BAP_EP_STATE_QOS_CONFIGURED; @@ -332,6 +404,8 @@ int bt_bap_unicast_client_stop(struct bt_bap_stream *stream) int bt_bap_unicast_client_release(struct bt_bap_stream *stream) { + struct bt_bap_unicast_client_cb *listener, *next; + printk("%s %p\n", __func__, stream); if (stream == NULL || stream->ep == NULL) { @@ -349,9 +423,11 @@ int bt_bap_unicast_client_release(struct bt_bap_stream *stream) return -EINVAL; } - if (unicast_client_cb != NULL && unicast_client_cb->release != NULL) { - unicast_client_cb->release(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, - BT_BAP_ASCS_REASON_NONE); + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&unicast_client_cbs, listener, next, _node) { + if (listener->release != NULL) { + listener->release(stream, BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + } } stream->ep->state = BT_BAP_EP_STATE_IDLE; @@ -366,7 +442,28 @@ int bt_bap_unicast_client_release(struct bt_bap_stream *stream) int bt_bap_unicast_client_register_cb(struct bt_bap_unicast_client_cb *cb) { - unicast_client_cb = cb; + if (cb == NULL) { + return -EINVAL; + } + + if (sys_slist_find(&unicast_client_cbs, &cb->_node, NULL)) { + return -EEXIST; + } + + sys_slist_append(&unicast_client_cbs, &cb->_node); + + return 0; +} + +int bt_bap_unicast_client_unregister_cb(struct bt_bap_unicast_client_cb *cb) +{ + if (cb == NULL) { + return -EINVAL; + } + + if (!sys_slist_find_and_remove(&unicast_client_cbs, &cb->_node)) { + return -EALREADY; + } return 0; } @@ -656,3 +753,43 @@ int bt_bap_unicast_group_foreach_stream(struct bt_bap_unicast_group *unicast_gro return 0; } + +int bt_bap_unicast_client_discover(struct bt_conn *conn, enum bt_audio_dir dir) +{ + struct bt_bap_unicast_client_cb *listener, *next; + struct unicast_client *client; + + if (conn == NULL) { + return -ENOTCONN; + } + + client = &uni_cli_insts[conn->index]; + client->conn = conn; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&unicast_client_cbs, listener, next, _node) { + if (listener->endpoint != NULL) { +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + if (dir == BT_AUDIO_DIR_SINK) { + ARRAY_FOR_EACH_PTR(client->snks, snk) { + snk->dir = BT_AUDIO_DIR_SINK; + listener->endpoint(conn, dir, snk); + } + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + if (dir == BT_AUDIO_DIR_SOURCE) { + ARRAY_FOR_EACH_PTR(client->srcs, src) { + src->dir = BT_AUDIO_DIR_SOURCE; + listener->endpoint(conn, dir, src); + } + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + } + + if (listener->discover != NULL) { + listener->discover(conn, 0, dir); + } + } + + return 0; +} diff --git a/tests/bsim/bluetooth/audio/src/bap_broadcast_sink_test.c b/tests/bsim/bluetooth/audio/src/bap_broadcast_sink_test.c index 32522d79cba5c..1d677609a949c 100644 --- a/tests/bsim/bluetooth/audio/src/bap_broadcast_sink_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_broadcast_sink_test.c @@ -565,6 +565,7 @@ static void stream_started_cb(struct bt_bap_stream *stream) { struct audio_test_stream *test_stream = audio_test_stream_from_bap_stream(stream); struct bt_bap_ep_info info; + struct bt_conn *ep_conn; int err; memset(&test_stream->last_info, 0, sizeof(test_stream->last_info)); @@ -602,6 +603,12 @@ static void stream_started_cb(struct bt_bap_stream *stream) return; } + ep_conn = bt_bap_ep_get_conn(stream->ep); + if (ep_conn != NULL) { + FAIL("Invalid conn from endpoint: %p", ep_conn); + return; + } + printk("Stream %p started\n", stream); k_sem_give(&sem_stream_started); diff --git a/tests/bsim/bluetooth/audio/src/bap_broadcast_source_test.c b/tests/bsim/bluetooth/audio/src/bap_broadcast_source_test.c index 9f6417eee2901..7613d07cdb8d2 100644 --- a/tests/bsim/bluetooth/audio/src/bap_broadcast_source_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_broadcast_source_test.c @@ -178,6 +178,7 @@ static void stream_started_cb(struct bt_bap_stream *stream) { struct audio_test_stream *test_stream = audio_test_stream_from_bap_stream(stream); struct bt_bap_ep_info info; + struct bt_conn *ep_conn; int err; test_stream->seq_num = 0U; @@ -214,6 +215,12 @@ static void stream_started_cb(struct bt_bap_stream *stream) return; } + ep_conn = bt_bap_ep_get_conn(stream->ep); + if (ep_conn != NULL) { + FAIL("Invalid conn from endpoint: %p", ep_conn); + return; + } + err = bap_stream_tx_register(stream); if (err != 0) { FAIL("Failed to register stream %p for TX: %d\n", stream, err); diff --git a/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c b/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c index 69da47a4e2e5b..86de4b6424430 100644 --- a/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c @@ -69,12 +69,21 @@ CREATE_FLAG(flag_operation_success); static void stream_configured(struct bt_bap_stream *stream, const struct bt_bap_qos_cfg_pref *pref) { + struct bt_conn *ep_conn; + printk("Configured stream %p\n", stream); /* TODO: The preference should be used/taken into account when * setting the QoS */ + ep_conn = bt_bap_ep_get_conn(stream->ep); + if (ep_conn == NULL || stream->conn != ep_conn) { + FAIL("Invalid conn from endpoint: %p", ep_conn); + return; + } + bt_conn_unref(ep_conn); + SET_FLAG(flag_stream_codec_configured); } diff --git a/tests/bsim/bluetooth/audio/src/bap_unicast_server_test.c b/tests/bsim/bluetooth/audio/src/bap_unicast_server_test.c index 980915d21b8c3..e9d3d8a6af1d0 100644 --- a/tests/bsim/bluetooth/audio/src/bap_unicast_server_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_unicast_server_test.c @@ -114,8 +114,6 @@ static int lc3_config(struct bt_conn *conn, const struct bt_bap_ep *ep, enum bt_ bt_bap_unicast_server_foreach_ep(conn, print_ase_info, NULL); - SET_FLAG(flag_stream_configured); - *pref = qos_pref; return 0; @@ -222,6 +220,23 @@ static const struct bt_bap_unicast_server_cb unicast_server_cb = { .release = lc3_release, }; +static void stream_configured_cb(struct bt_bap_stream *stream, + const struct bt_bap_qos_cfg_pref *pref) +{ + struct bt_conn *ep_conn; + + printk("Configured stream %p\n", stream); + + ep_conn = bt_bap_ep_get_conn(stream->ep); + if (ep_conn == NULL || stream->conn != ep_conn) { + FAIL("Invalid conn from endpoint: %p", ep_conn); + return; + } + bt_conn_unref(ep_conn); + + SET_FLAG(flag_stream_configured); +} + static void stream_enabled_cb(struct bt_bap_stream *stream) { struct bt_bap_ep_info ep_info; @@ -279,6 +294,7 @@ static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) } static struct bt_bap_stream_ops stream_ops = { + .configured = stream_configured_cb, .enabled = stream_enabled_cb, .started = stream_started_cb, .stopped = stream_stopped_cb,