diff --git a/subsys/bluetooth/audio/bap_stream.c b/subsys/bluetooth/audio/bap_stream.c index b37d38bd5e893ec..ac6ed0820411f91 100644 --- a/subsys/bluetooth/audio/bap_stream.c +++ b/subsys/bluetooth/audio/bap_stream.c @@ -699,7 +699,6 @@ int bt_bap_stream_enable(struct bt_bap_stream *stream, const uint8_t meta[], siz int bt_bap_stream_stop(struct bt_bap_stream *stream) { - enum bt_iso_state iso_state; struct bt_bap_ep *ep; uint8_t role; int err; @@ -726,27 +725,6 @@ int bt_bap_stream_stop(struct bt_bap_stream *stream) return -EBADMSG; } - /* ASCS_v1.0 3.2 ASE state machine transitions - * - * If the server detects link loss of a CIS for an ASE in the Streaming state or the - * Disabling state, the server shall immediately transition that ASE to the QoS Configured - * state. - * - * This effectively means that if an ASE no longer has a connected CIS, the server shall - * bring it to the QoS Configured state. That means that we, as a unicast client, should not - * attempt to stop it - */ - if (ep->iso == NULL) { - LOG_DBG("Stream endpoint does not have a CIS, server will stop the ASE"); - return -EALREADY; - } - - iso_state = ep->iso->chan.state; - if (iso_state != BT_ISO_STATE_CONNECTED && iso_state != BT_ISO_STATE_CONNECTING) { - LOG_DBG("Stream endpoint CIS is not connected, server will stop the ASE"); - return -EALREADY; - } - err = bt_bap_unicast_client_stop(stream); if (err != 0) { LOG_DBG("Stopping stream failed: %d", err); diff --git a/subsys/bluetooth/audio/bap_unicast_client.c b/subsys/bluetooth/audio/bap_unicast_client.c index e3312a24be312fb..bd9b59fd82defba 100644 --- a/subsys/bluetooth/audio/bap_unicast_client.c +++ b/subsys/bluetooth/audio/bap_unicast_client.c @@ -3481,6 +3481,7 @@ 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_ep *ep = stream->ep; + enum bt_iso_state iso_state; struct net_buf_simple *buf; struct bt_ascs_start_op *req; int err; @@ -3493,6 +3494,27 @@ int bt_bap_unicast_client_stop(struct bt_bap_stream *stream) return -ENOTCONN; } + /* ASCS_v1.0 3.2 ASE state machine transitions + * + * If the server detects link loss of a CIS for an ASE in the Streaming state or the + * Disabling state, the server shall immediately transition that ASE to the QoS Configured + * state. + * + * This effectively means that if an ASE no longer has a connected CIS, the server shall + * bring it to the QoS Configured state. That means that we, as a unicast client, should not + * attempt to stop it + */ + if (ep->iso == NULL) { + LOG_DBG("Stream endpoint does not have a CIS, server will stop the ASE"); + return -EALREADY; + } + + iso_state = ep->iso->chan.state; + if (iso_state != BT_ISO_STATE_CONNECTED && iso_state != BT_ISO_STATE_CONNECTING) { + LOG_DBG("Stream endpoint CIS is not connected, server will stop the ASE"); + return -EALREADY; + } + buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, BT_ASCS_STOP_OP); if (buf == NULL) { LOG_DBG("Could not create PDU"); diff --git a/subsys/bluetooth/audio/cap_initiator.c b/subsys/bluetooth/audio/cap_initiator.c index 23295848b143598..cb8bb3e5161381e 100644 --- a/subsys/bluetooth/audio/cap_initiator.c +++ b/subsys/bluetooth/audio/cap_initiator.c @@ -352,6 +352,10 @@ static enum bt_bap_ep_state stream_get_state(const struct bt_bap_stream *bap_str struct bt_bap_ep_info ep_info; int err; + if (bap_stream->ep == NULL) { + return BT_BAP_EP_STATE_IDLE; + } + err = bt_bap_ep_get_info(bap_stream->ep, &ep_info); if (err != 0) { LOG_DBG("Failed to get endpoint info %p: %d", bap_stream, err); @@ -390,24 +394,48 @@ static bool stream_is_dir(const struct bt_bap_stream *bap_stream, enum bt_audio_ return ep_info.dir == dir; } -static bool iso_is_in_state(const struct bt_cap_stream *cap_stream, enum bt_iso_state state) +static enum bt_iso_state bap_stream_get_iso_state(const struct bt_bap_stream *bap_stream) { - const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream; struct bt_bap_ep_info ep_info; int err; + if (bap_stream->ep == NULL) { + return BT_ISO_STATE_DISCONNECTED; + } + err = bt_bap_ep_get_info(bap_stream->ep, &ep_info); if (err != 0) { LOG_DBG("Failed to get endpoint info %p: %d", bap_stream, err); - return false; + return BT_ISO_STATE_DISCONNECTED; } if (ep_info.iso_chan == NULL) { - return state == BT_ISO_STATE_DISCONNECTED; + return BT_ISO_STATE_DISCONNECTED; } - return state == ep_info.iso_chan->state; + return ep_info.iso_chan->state; +} + +static bool iso_is_in_state(const struct bt_cap_stream *cap_stream, enum bt_iso_state state) +{ + const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream; + + return bap_stream_get_iso_state(bap_stream) == state; +} + +static void set_cap_stream_in_progress(struct bt_cap_stream *cap_stream, bool value) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + for (size_t i = 0U; i < active_proc->proc_cnt; i++) { + if (cap_stream == active_proc->proc_param.initiator[i].stream) { + active_proc->proc_param.initiator[i].in_progress = value; + return; + } + } + + __ASSERT(false, "CAP stream %p not in active_proc", cap_stream); } /** @@ -440,7 +468,7 @@ static void update_proc_done_cnt(struct bt_cap_common_proc *active_proc) * the states to determine how far we are. */ for (size_t i = 0U; i < active_proc->proc_cnt; i++) { - const struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_initiator_proc_param *proc_param; struct bt_cap_stream *cap_stream; struct bt_bap_stream *bap_stream; enum bt_bap_ep_state state; @@ -501,7 +529,7 @@ static void update_proc_done_cnt(struct bt_cap_common_proc *active_proc) * the states to determine how far we are. */ for (size_t i = 0U; i < active_proc->proc_cnt; i++) { - const struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_initiator_proc_param *proc_param; struct bt_cap_stream *cap_stream; struct bt_bap_stream *bap_stream; enum bt_bap_ep_state state; @@ -539,7 +567,7 @@ static void update_proc_done_cnt(struct bt_cap_common_proc *active_proc) /* For metadata we cannot check the states for all streams, as it does not trigger a * state change */ - const struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_initiator_proc_param *proc_param; struct bt_cap_stream *cap_stream; struct bt_bap_stream *bap_stream; enum bt_bap_ep_state state; @@ -588,6 +616,11 @@ get_next_proc_param(struct bt_cap_common_proc *active_proc) enum bt_bap_ep_state state; proc_param = &active_proc->proc_param.initiator[i]; + + if (proc_param->in_progress) { + continue; + } + cap_stream = proc_param->stream; bap_stream = &cap_stream->bap_stream; state = stream_get_state(bap_stream); @@ -813,6 +846,43 @@ static void cap_initiator_unicast_audio_proc_complete(void) } } +void bt_cap_initiator_cp_cb(struct bt_cap_stream *cap_stream, enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason) +{ + if (!bt_cap_common_stream_in_active_proc(cap_stream)) { + /* State change happened outside of a procedure; ignore */ + return; + } + + LOG_DBG("cap_stream %p", cap_stream); + + set_cap_stream_in_progress(cap_stream, false); + + if (rsp_code != BT_BAP_ASCS_RSP_CODE_SUCCESS) { + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + /* In the case that the control point write is rejected, we will not get a ASE state + * change notification. This is considered an error that shall abort the current + * procedure. + */ + active_proc->proc_done_cnt++; + + LOG_DBG("Control point operation on stream %p failed with %d and reason %d", + cap_stream, rsp_code, reason); + + /* Unexpected callback - Abort */ + bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); + + if (bt_cap_common_proc_is_aborted()) { + if (bt_cap_common_proc_all_handled()) { + cap_initiator_unicast_audio_proc_complete(); + } + + return; + } + } +} + static int cap_initiator_unicast_audio_configure( const struct bt_cap_unicast_audio_start_param *param) { @@ -868,6 +938,7 @@ static int cap_initiator_unicast_audio_configure( conn = proc_param->start.conn; ep = proc_param->start.ep; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; /* Since BAP operations may require a write long or a read long on the notification, * we cannot assume that we can do multiple streams at once, thus do it one at a time. @@ -970,6 +1041,7 @@ void bt_cap_initiator_codec_configured(struct bt_cap_stream *cap_stream) codec_cfg = proc_param->start.codec_cfg; next_bap_stream = &next_cap_stream->bap_stream; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_config(conn, next_bap_stream, ep, codec_cfg); if (err != 0) { @@ -1037,7 +1109,16 @@ void bt_cap_initiator_codec_configured(struct bt_cap_stream *cap_stream) break; } - active_proc->proc_initiated_cnt++; + for (size_t j = 0U; j < active_proc->proc_cnt; j++) { + proc_param = &active_proc->proc_param.initiator[j]; + if (proc_param->stream->bap_stream.conn == conns[i]) { + active_proc->proc_initiated_cnt++; + proc_param->in_progress = false; + break; + } + } + + proc_param->in_progress = true; err = bt_bap_stream_qos(conns[i], unicast_group); if (err != 0) { @@ -1067,7 +1148,7 @@ void bt_cap_initiator_qos_configured(struct bt_cap_stream *cap_stream) return; } - LOG_DBG("cap_stream %p", cap_stream); + LOG_DBG("bt_cap_initiator_qos_configured cap_stream %p", cap_stream); if (!(bt_cap_common_proc_is_type(BT_CAP_COMMON_PROC_TYPE_START) && bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG)) && @@ -1100,6 +1181,8 @@ void bt_cap_initiator_qos_configured(struct bt_cap_stream *cap_stream) return; } + /* WHY IS STREAM 0 NOT IDLE HERE? */ + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_ENABLE); proc_param = get_next_proc_param(active_proc); if (proc_param == NULL) { @@ -1114,6 +1197,7 @@ void bt_cap_initiator_qos_configured(struct bt_cap_stream *cap_stream) next_cap_stream = proc_param->stream; bap_stream = &next_cap_stream->bap_stream; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_enable(bap_stream, bap_stream->codec_cfg->meta, bap_stream->codec_cfg->meta_len); @@ -1135,6 +1219,7 @@ void bt_cap_initiator_qos_configured(struct bt_cap_stream *cap_stream) next_cap_stream = proc_param->stream; next_bap_stream = &next_cap_stream->bap_stream; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_release(next_bap_stream); if (err != 0) { @@ -1188,6 +1273,7 @@ void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream) next_bap_stream = &next_cap_stream->bap_stream; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_enable(next_bap_stream, next_bap_stream->codec_cfg->meta, next_bap_stream->codec_cfg->meta_len); @@ -1212,6 +1298,7 @@ void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream) } bap_stream = &proc_param->stream->bap_stream; + proc_param->in_progress = true; err = bt_bap_stream_connect(bap_stream); if (err == -EALREADY) { @@ -1219,6 +1306,7 @@ void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream) * NOTE: It's important that we do not do any additional functionality after * calling this */ + proc_param->in_progress = false; bt_cap_initiator_connected(proc_param->stream); } else if (err != 0) { LOG_DBG("Failed to connect stream %p: %d", proc_param->stream, err); @@ -1246,6 +1334,8 @@ void bt_cap_initiator_connected(struct bt_cap_stream *cap_stream) LOG_DBG("cap_stream %p", cap_stream); + set_cap_stream_in_progress(cap_stream, false); + if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_CONNECT)) { /* Unexpected callback - Abort */ bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); @@ -1276,21 +1366,26 @@ void bt_cap_initiator_connected(struct bt_cap_stream *cap_stream) struct bt_bap_stream *next_bap_stream; proc_param = get_next_proc_param(active_proc); - __ASSERT(proc_param != NULL, "proc is not done, but could not get next proc_param"); - next_cap_stream = proc_param->stream; - next_bap_stream = &next_cap_stream->bap_stream; + if (proc_param != NULL) { + next_cap_stream = proc_param->stream; + next_bap_stream = &next_cap_stream->bap_stream; - active_proc->proc_initiated_cnt++; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; - err = bt_bap_stream_connect(next_bap_stream); - if (err == 0 || err == -EALREADY) { - /* Pending connected - wait for connected callback */ - } else if (err != 0) { - LOG_DBG("Failed to connect stream %p: %d", next_cap_stream, err); + err = bt_bap_stream_connect(next_bap_stream); + if (err == 0 || err == -EALREADY) { + if (err == -EALREADY) { + proc_param->in_progress = false; + } + /* Pending connected - wait for connected callback */ + } else if (err != 0) { + LOG_DBG("Failed to connect stream %p: %d", next_cap_stream, err); - bt_cap_common_abort_proc(next_bap_stream->conn, err); - cap_initiator_unicast_audio_proc_complete(); - } + bt_cap_common_abort_proc(next_bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(); + } + } /* else pending connection - wait for connected callback */ return; } @@ -1311,6 +1406,8 @@ void bt_cap_initiator_connected(struct bt_cap_stream *cap_stream) bap_stream = &proc_param->stream->bap_stream; if (stream_is_dir(bap_stream, BT_AUDIO_DIR_SOURCE)) { + proc_param->in_progress = true; + err = bt_bap_stream_start(bap_stream); if (err != 0) { LOG_DBG("Failed to start stream %p: %d", proc_param->stream, err); @@ -1335,8 +1432,12 @@ void bt_cap_initiator_started(struct bt_cap_stream *cap_stream) } /* Streams may go into the streaming state while we are connecting or starting them */ - if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_START) && - !bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_CONNECT)) { + if (bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_CONNECT)) { + /* If we are still connecting the streams, we terminate early as to not perform any + * start operations until all streams are connected + */ + return; + } else if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_START)) { /* Unexpected callback - Abort */ bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); } else { @@ -1352,25 +1453,29 @@ void bt_cap_initiator_started(struct bt_cap_stream *cap_stream) struct bt_bap_stream *next_bap_stream; proc_param = get_next_proc_param(active_proc); - __ASSERT(proc_param != NULL, "proc is not done, but could not get next proc_param"); - next_cap_stream = proc_param->stream; - next_bap_stream = &next_cap_stream->bap_stream; + if (proc_param != NULL) { + next_cap_stream = proc_param->stream; + next_bap_stream = &next_cap_stream->bap_stream; - if (stream_is_dir(next_bap_stream, BT_AUDIO_DIR_SOURCE)) { - int err; + if (stream_is_dir(next_bap_stream, BT_AUDIO_DIR_SOURCE)) { + int err; - err = bt_bap_stream_start(next_bap_stream); - if (err != 0) { - LOG_DBG("Failed to start stream %p: %d", next_cap_stream, err); + proc_param->in_progress = true; - /* End and mark procedure as aborted. - * If we have sent any requests over air, we will abort - * once all sent requests has completed - */ - bt_cap_common_abort_proc(next_bap_stream->conn, err); - cap_initiator_unicast_audio_proc_complete(); + err = bt_bap_stream_start(next_bap_stream); + if (err != 0) { + LOG_DBG("Failed to start stream %p: %d", next_cap_stream, + err); + + /* End and mark procedure as aborted. + * If we have sent any requests over air, we will abort + * once all sent requests has completed + */ + bt_cap_common_abort_proc(next_bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(); - return; + return; + } } } /* else await notifications from server */ @@ -1520,6 +1625,7 @@ int bt_cap_initiator_unicast_audio_update(const struct bt_cap_unicast_audio_upda meta_len = proc_param->meta_update.meta_len; meta = proc_param->meta_update.meta; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_metadata(bap_stream, meta, meta_len); if (err != 0) { @@ -1587,6 +1693,7 @@ void bt_cap_initiator_metadata_updated(struct bt_cap_stream *cap_stream) next_cap_stream = proc_param->stream; bap_stream = &next_cap_stream->bap_stream; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_metadata(bap_stream, meta, meta_len); if (err != 0) { @@ -1635,10 +1742,21 @@ static bool can_disable_stream(const struct bt_bap_stream *bap_stream) static bool can_stop_stream(const struct bt_bap_stream *bap_stream) { + enum bt_iso_state iso_state; + if (bap_stream->conn == NULL) { return false; } + if (stream_is_dir(bap_stream, BT_AUDIO_DIR_SINK)) { + return false; + } + + iso_state = bap_stream_get_iso_state(bap_stream); + if (iso_state != BT_ISO_STATE_CONNECTED && iso_state != BT_ISO_STATE_CONNECTING) { + return false; + } + return stream_is_in_state(bap_stream, BT_BAP_EP_STATE_DISABLING); } @@ -1769,6 +1887,8 @@ int bt_cap_initiator_unicast_audio_stop(const struct bt_cap_unicast_audio_stop_p } } + LOG_ERR("%d %d %d", can_disable, can_stop, can_release); + if (!can_disable && !can_stop && !can_release) { LOG_DBG("Cannot %s any streams", !can_disable ? "disable" : !can_stop ? "stop" @@ -1793,6 +1913,7 @@ int bt_cap_initiator_unicast_audio_stop(const struct bt_cap_unicast_audio_stop_p "proc is not started, but could not get next proc_param"); bap_stream = &proc_param->stream->bap_stream; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_disable(bap_stream); if (err != 0) { @@ -1811,6 +1932,7 @@ int bt_cap_initiator_unicast_audio_stop(const struct bt_cap_unicast_audio_stop_p "proc is not started, but could not get next proc_param"); bap_stream = &proc_param->stream->bap_stream; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_stop(bap_stream); if (err != 0) { @@ -1829,6 +1951,7 @@ int bt_cap_initiator_unicast_audio_stop(const struct bt_cap_unicast_audio_stop_p "proc is not started, but could not get next proc_param"); bap_stream = &proc_param->stream->bap_stream; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_release(bap_stream); if (err != 0) { @@ -1879,6 +2002,7 @@ void bt_cap_initiator_disabled(struct bt_cap_stream *cap_stream) next_cap_stream = proc_param->stream; next_bap_stream = &next_cap_stream->bap_stream; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_disable(next_bap_stream); if (err != 0) { @@ -1908,14 +2032,17 @@ void bt_cap_initiator_disabled(struct bt_cap_stream *cap_stream) next_cap_stream = proc_param->stream; next_bap_stream = &next_cap_stream->bap_stream; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_stop(next_bap_stream); - if (err != 0) { + if (err != 0 && err != -EALREADY) { LOG_DBG("Failed to stop stream %p: %d", next_cap_stream, err); bt_cap_common_abort_proc(next_bap_stream->conn, err); cap_initiator_unicast_audio_proc_complete(); - } + } else if (err == -EALREADY) { + proc_param->in_progress = false; + } /* else wait for server notification*/ } } @@ -1960,19 +2087,23 @@ void bt_cap_initiator_stopped(struct bt_cap_stream *cap_stream) int err; proc_param = get_next_proc_param(active_proc); - __ASSERT(proc_param != NULL, "proc is not done, but could not get next proc_param"); - next_cap_stream = proc_param->stream; - next_bap_stream = &next_cap_stream->bap_stream; + if (proc_param != NULL) { + next_cap_stream = proc_param->stream; + next_bap_stream = &next_cap_stream->bap_stream; - active_proc->proc_initiated_cnt++; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; - err = bt_bap_stream_stop(next_bap_stream); - if (err != 0) { - LOG_DBG("Failed to stop stream %p: %d", next_cap_stream, err); + err = bt_bap_stream_stop(next_bap_stream); + if (err != 0 && err != -EALREADY) { + LOG_DBG("Failed to stop stream %p: %d", next_cap_stream, err); - bt_cap_common_abort_proc(next_bap_stream->conn, err); - cap_initiator_unicast_audio_proc_complete(); - } + bt_cap_common_abort_proc(next_bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(); + } else if (err == -EALREADY) { + proc_param->in_progress = false; + } + } /* else await notification from server */ } else { /* We are done stopping streams now - We mark the next subproc. If * get_next_proc_param returns a NULL value it means that we are complete done. If @@ -2039,6 +2170,7 @@ void bt_cap_initiator_released(struct bt_cap_stream *cap_stream) next_cap_stream = proc_param->stream; next_bap_stream = &next_cap_stream->bap_stream; active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; err = bt_bap_stream_release(next_bap_stream); if (err != 0) { diff --git a/subsys/bluetooth/audio/cap_internal.h b/subsys/bluetooth/audio/cap_internal.h index 3543b35c3876e7a..43cf22a58afa615 100644 --- a/subsys/bluetooth/audio/cap_internal.h +++ b/subsys/bluetooth/audio/cap_internal.h @@ -34,6 +34,8 @@ void bt_cap_initiator_disabled(struct bt_cap_stream *cap_stream); void bt_cap_initiator_stopped(struct bt_cap_stream *cap_stream); void bt_cap_initiator_released(struct bt_cap_stream *cap_stream); void bt_cap_stream_ops_register_bap(struct bt_cap_stream *cap_stream); +void bt_cap_initiator_cp_cb(struct bt_cap_stream *cap_stream, enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason); enum bt_cap_common_proc_state { BT_CAP_COMMON_PROC_STATE_ACTIVE, @@ -87,6 +89,7 @@ struct bt_cap_initiator_proc_param { bool release; } stop; }; + bool in_progress; }; #if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT) diff --git a/subsys/bluetooth/audio/cap_stream.c b/subsys/bluetooth/audio/cap_stream.c index bbc645daa3e9d10..53c29fa53a64927 100644 --- a/subsys/bluetooth/audio/cap_stream.c +++ b/subsys/bluetooth/audio/cap_stream.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -280,9 +281,41 @@ static struct bt_bap_stream_ops bap_stream_ops = { .disconnected = cap_stream_disconnected_cb, }; +static void unicast_client_cp_cb(struct bt_bap_stream *bap_stream, + enum bt_bap_ascs_rsp_code rsp_code, enum bt_bap_ascs_reason reason) +{ + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + stream_is_central(bap_stream)) { + struct bt_cap_stream *cap_stream = + CONTAINER_OF(bap_stream, struct bt_cap_stream, bap_stream); + + bt_cap_initiator_cp_cb(cap_stream, rsp_code, reason); + } +} + void bt_cap_stream_ops_register_bap(struct bt_cap_stream *cap_stream) { bt_bap_stream_cb_register(&cap_stream->bap_stream, &bap_stream_ops); + + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT)) { + /* The CAP initiator can use the same callback for all of these as the result is the + * same: Abort current procedure + */ + static struct bt_bap_unicast_client_cb unicast_client_cb = { + .config = unicast_client_cp_cb, + .qos = unicast_client_cp_cb, + .enable = unicast_client_cp_cb, + .start = unicast_client_cp_cb, + .stop = unicast_client_cp_cb, + .disable = unicast_client_cp_cb, + .metadata = unicast_client_cp_cb, + .release = unicast_client_cp_cb, + }; + int err; + + err = bt_bap_unicast_client_register_cb(&unicast_client_cb); + __ASSERT_NO_MSG(err == 0 || err == -EEXIST); + } } void bt_cap_stream_ops_register(struct bt_cap_stream *stream, diff --git a/tests/bluetooth/audio/cap_initiator/prj.conf b/tests/bluetooth/audio/cap_initiator/prj.conf index 33283b79045a138..52b330d86aea83d 100644 --- a/tests/bluetooth/audio/cap_initiator/prj.conf +++ b/tests/bluetooth/audio/cap_initiator/prj.conf @@ -12,7 +12,6 @@ CONFIG_BT_BAP_UNICAST_CLIENT=y CONFIG_BT_CSIP_SET_COORDINATOR=y CONFIG_BT_CAP_INITIATOR=y -CONFIG_BT_CAP_INITIATOR_LOG_LEVEL_DBG=y # Support setting up a sink and source stream on 2 acceptors CONFIG_BT_MAX_CONN=2 @@ -26,3 +25,5 @@ CONFIG_ASSERT_LEVEL=2 CONFIG_ASSERT_VERBOSE=y CONFIG_BT_BAP_STREAM_LOG_LEVEL_DBG=y +CONFIG_BT_CAP_COMMON_LOG_LEVEL_DBG=y +CONFIG_BT_CAP_INITIATOR_LOG_LEVEL_DBG=y 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 387aeaf41ba3278..b87d82861cc0a79 100644 --- a/tests/bluetooth/audio/cap_initiator/uut/bap_unicast_client.c +++ b/tests/bluetooth/audio/cap_initiator/uut/bap_unicast_client.c @@ -12,14 +12,18 @@ #include #include +#include #include #include #include #include #include "bap_endpoint.h" +#include "bap_iso.h" #include "ztest_assert.h" +static struct bt_bap_unicast_client_cb *unicast_client_cb; + bool bt_bap_ep_is_unicast_client(const struct bt_bap_ep *ep) { return false; @@ -40,6 +44,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); + } + stream->ep->status.state = BT_BAP_EP_STATE_CODEC_CONFIGURED; if (stream->ops != NULL && stream->ops->configured != NULL) { @@ -73,6 +82,11 @@ 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); + } + stream->ep->status.state = BT_BAP_EP_STATE_QOS_CONFIGURED; if (stream->ops != NULL && stream->ops->qos_set != NULL) { @@ -87,6 +101,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) { + printk("%s %p %d\n", __func__, stream, stream->ep->dir); + if (stream == NULL) { return -EINVAL; } @@ -98,6 +114,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); + } + stream->ep->status.state = BT_BAP_EP_STATE_ENABLING; if (stream->ops != NULL && stream->ops->enabled != NULL) { @@ -122,6 +143,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); + } + if (stream->ops != NULL && stream->ops->metadata_updated != NULL) { stream->ops->metadata_updated(stream); } @@ -173,6 +199,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); + } + stream->ep->status.state = BT_BAP_EP_STATE_STREAMING; if (stream->ops != NULL && stream->ops->started != NULL) { @@ -203,6 +234,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); + } + /* Disabled sink ASEs go directly to the QoS configured state */ if (stream->ep->dir == BT_AUDIO_DIR_SINK) { stream->ep->status.state = BT_BAP_EP_STATE_QOS_CONFIGURED; @@ -245,6 +281,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); + } + stream->ep->status.state = BT_BAP_EP_STATE_QOS_CONFIGURED; if (stream->ops != NULL && stream->ops->stopped != NULL) { @@ -255,6 +296,30 @@ int bt_bap_unicast_client_stop(struct bt_bap_stream *stream) stream->ops->qos_set(stream); } + /* If the stream can be disconnected, BAP will disconnect the stream once it reaches the + * QoS Configured state. We simulator that behavior here, and if the stream is disconnected, + * then the Unicast Server will set any paired stream to the QoS Configured state + * autonomously as well. + */ + if (bt_bap_stream_can_disconnect(stream)) { + struct bt_bap_ep *pair_ep = bt_bap_iso_get_paired_ep(stream->ep); + + if (pair_ep != NULL && pair_ep->stream != NULL) { + struct bt_bap_stream *pair_stream = pair_ep->stream; + + pair_stream->ep->status.state = BT_BAP_EP_STATE_QOS_CONFIGURED; + + if (pair_stream->ops != NULL && pair_stream->ops->stopped != NULL) { + pair_stream->ops->stopped(pair_stream, + BT_HCI_ERR_LOCALHOST_TERM_CONN); + } + + if (pair_stream->ops != NULL && pair_stream->ops->qos_set != NULL) { + pair_stream->ops->qos_set(pair_stream); + } + } + } + return 0; } @@ -277,6 +342,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); + } + stream->ep->status.state = BT_BAP_EP_STATE_IDLE; bt_bap_stream_reset(stream); @@ -286,3 +356,10 @@ int bt_bap_unicast_client_release(struct bt_bap_stream *stream) return 0; } + +int bt_bap_unicast_client_register_cb(struct bt_bap_unicast_client_cb *cb) +{ + unicast_client_cb = cb; + + return 0; +} diff --git a/tests/bsim/bluetooth/audio/src/cap_acceptor_test.c b/tests/bsim/bluetooth/audio/src/cap_acceptor_test.c index c4012a583973c91..23addc8056a5922 100644 --- a/tests/bsim/bluetooth/audio/src/cap_acceptor_test.c +++ b/tests/bsim/bluetooth/audio/src/cap_acceptor_test.c @@ -503,12 +503,25 @@ static int unicast_server_qos(struct bt_bap_stream *stream, const struct bt_audi return 0; } +static bool ascs_data_func_cb(struct bt_data *data, void *user_data) +{ + struct bt_bap_ascs_rsp *rsp = (struct bt_bap_ascs_rsp *)user_data; + + if (!BT_AUDIO_METADATA_TYPE_IS_KNOWN(data->type)) { + printk("Invalid metadata type %u or length %u\n", data->type, data->data_len); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED, data->type); + return false; + } + + return true; +} + static int unicast_server_enable(struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len, struct bt_bap_ascs_rsp *rsp) { printk("Enable: stream %p meta_len %zu\n", stream, meta_len); - return 0; + return bt_audio_data_parse(meta, meta_len, ascs_data_func_cb, rsp); } static int unicast_server_start(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) @@ -518,19 +531,6 @@ static int unicast_server_start(struct bt_bap_stream *stream, struct bt_bap_ascs return 0; } -static bool ascs_data_func_cb(struct bt_data *data, void *user_data) -{ - struct bt_bap_ascs_rsp *rsp = (struct bt_bap_ascs_rsp *)user_data; - - if (!BT_AUDIO_METADATA_TYPE_IS_KNOWN(data->type)) { - printk("Invalid metadata type %u or length %u\n", data->type, data->data_len); - *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED, data->type); - return false; - } - - return true; -} - static int unicast_server_metadata(struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len, struct bt_bap_ascs_rsp *rsp) { diff --git a/tests/bsim/bluetooth/audio/src/cap_initiator_unicast_test.c b/tests/bsim/bluetooth/audio/src/cap_initiator_unicast_test.c index fac55233cf69cd9..0bb7e17343ad16c 100644 --- a/tests/bsim/bluetooth/audio/src/cap_initiator_unicast_test.c +++ b/tests/bsim/bluetooth/audio/src/cap_initiator_unicast_test.c @@ -85,6 +85,7 @@ CREATE_FLAG(flag_discovered); CREATE_FLAG(flag_codec_found); CREATE_FLAG(flag_endpoint_found); CREATE_FLAG(flag_started); +CREATE_FLAG(flag_start_failed); CREATE_FLAG(flag_start_timeout); CREATE_FLAG(flag_updated); CREATE_FLAG(flag_stopped); @@ -237,7 +238,8 @@ static void unicast_start_complete_cb(int err, struct bt_conn *conn) if (err == -ECANCELED) { SET_FLAG(flag_start_timeout); } else if (err != 0) { - FAIL("Failed to start (failing conn %p): %d", conn, err); + printk("Failed to start (failing conn %p): %d\n", conn, err); + SET_FLAG(flag_start_failed); } else { SET_FLAG(flag_started); } @@ -694,6 +696,7 @@ static void unicast_audio_stop(struct bt_bap_unicast_group *unicast_group) /* Stop without release first to verify that we enter the QoS Configured state */ UNSET_FLAG(flag_stopped); + printk("Stopping without relasing\n"); err = bt_cap_initiator_unicast_audio_stop(¶m); if (err != 0) { @@ -714,6 +717,7 @@ static void unicast_audio_stop(struct bt_bap_unicast_group *unicast_group) /* Stop with release first to verify that we enter the idle state */ UNSET_FLAG(flag_stopped); param.release = true; + printk("Relasing\n"); err = bt_cap_initiator_unicast_audio_stop(¶m); if (err != 0) { @@ -791,9 +795,12 @@ static void test_main_cap_initiator_unicast(void) discover_source(default_conn); for (size_t i = 0U; i < iterations; i++) { + printk("\nRunning iteration i=%zu\n\n", i); unicast_group_create(&unicast_group); for (size_t j = 0U; j < iterations; j++) { + printk("\nRunning iteration j=%zu\n\n", i); + unicast_audio_start(unicast_group, true); unicast_audio_update(); @@ -843,7 +850,7 @@ static void test_main_cap_initiator_unicast_inval(void) static void test_cap_initiator_unicast_timeout(void) { struct bt_bap_unicast_group *unicast_group; - const k_timeout_t timeout = K_SECONDS(1); + const k_timeout_t timeout = K_SECONDS(10); const size_t iterations = 2; init(); @@ -860,6 +867,7 @@ static void test_cap_initiator_unicast_timeout(void) unicast_group_create(&unicast_group); for (size_t j = 0U; j < iterations; j++) { + printk("\nRunning iteration #%zu\n\n", j); unicast_audio_start(unicast_group, false); k_sleep(timeout); @@ -880,6 +888,67 @@ static void test_cap_initiator_unicast_timeout(void) PASS("CAP initiator unicast timeout passed\n"); } +static void set_invalid_metadata_type(uint8_t type) +{ + const uint8_t val = 0xFF; + int err; + + err = bt_audio_codec_cfg_meta_set_val(&unicast_preset_16_2_1.codec_cfg, type, &val, + sizeof(val)); + if (err < 0) { + FAIL("Failed to set invalid metadata type: %d\n", err); + return; + } +} + +static void unset_invalid_metadata_type(uint8_t type) +{ + int err; + + err = bt_audio_codec_cfg_meta_unset_val(&unicast_preset_16_2_1.codec_cfg, type); + if (err < 0) { + FAIL("Failed to unset invalid metadata type: %d\n", err); + return; + } +} + +static void test_cap_initiator_unicast_ase_error(void) +{ + struct bt_bap_unicast_group *unicast_group; + const uint8_t inval_type = 0xFD; + + init(); + + scan_and_connect(); + + WAIT_FOR_FLAG(flag_mtu_exchanged); + + discover_cas(default_conn); + discover_sink(default_conn); + discover_source(default_conn); + + unicast_group_create(&unicast_group); + + set_invalid_metadata_type(inval_type); + + /* With invalid metadata type, start should fail */ + unicast_audio_start(unicast_group, false); + WAIT_FOR_FLAG(flag_start_failed); + + /* Remove invalid type and retry */ + unset_invalid_metadata_type(inval_type); + + /* Without invalid metadata type, start should pass */ + unicast_audio_start(unicast_group, true); + + unicast_audio_stop(unicast_group); + + unicast_group_delete(unicast_group); + unicast_group = NULL; + + PASS("CAP initiator unicast ASE error passed\n"); +} + static const struct named_lc3_preset *cap_get_named_preset(const char *preset_arg) { for (size_t i = 0U; i < ARRAY_SIZE(lc3_unicast_presets); i++) { @@ -1563,6 +1632,12 @@ static const struct bst_test_instance test_cap_initiator_unicast[] = { .test_tick_f = test_tick, .test_main_f = test_cap_initiator_unicast_timeout, }, + { + .test_id = "cap_initiator_unicast_ase_error", + .test_pre_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_cap_initiator_unicast_ase_error, + }, { .test_id = "cap_initiator_unicast_inval", .test_pre_init_f = test_init, diff --git a/tests/bsim/bluetooth/audio/test_scripts/cap_unicast_ase_error.sh b/tests/bsim/bluetooth/audio/test_scripts/cap_unicast_ase_error.sh new file mode 100755 index 000000000000000..b6faaddfd6e03ad --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/cap_unicast_ase_error.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="cap_unicast_ase_error" +VERBOSITY_LEVEL=2 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +printf "\n\n======== Running CAP unicast ASE error test =========\n\n" + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=cap_initiator_unicast_ase_error \ + -RealEncryption=1 -rs=46 -D=2 + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=cap_acceptor_unicast \ + -RealEncryption=1 -rs=23 -D=2 + +# Simulation time should be larger than the WAIT_TIME in common.h +Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=2 -sim_length=60e6 $@ + +wait_for_background_jobs