Bluetooth: audio: ascs: Fix possible race condition

This fixes possible ASE state race condition. The notification is sent
immediately once the ASE state changed that eliminates a situation where
the state was changed by user action (API function call) when the state
was not yet notified to the remote Unicast Client.

Fixes: BAP/USR/SCC/BV-158-C
Signed-off-by: Mariusz Skamra <mariusz.skamra@codecoup.pl>
This commit is contained in:
Mariusz Skamra 2022-11-14 10:54:02 +01:00 committed by Carles Cufí
commit 8cd6c55d96
2 changed files with 63 additions and 79 deletions

View file

@ -73,8 +73,11 @@ K_MEM_SLAB_DEFINE(ase_slab, sizeof(struct bt_ascs_ase),
__alignof__(struct bt_ascs_ase)); __alignof__(struct bt_ascs_ase));
static struct bt_ascs sessions[CONFIG_BT_MAX_CONN]; static struct bt_ascs sessions[CONFIG_BT_MAX_CONN];
NET_BUF_SIMPLE_DEFINE_STATIC(ase_buf, CONFIG_BT_L2CAP_TX_MTU);
static int control_point_notify(struct bt_conn *conn, const void *data, uint16_t len); static int control_point_notify(struct bt_conn *conn, const void *data, uint16_t len);
static int ascs_ep_get_status(struct bt_audio_ep *ep,
struct net_buf_simple *buf);
static void bt_ascs_ase_return_to_slab(struct bt_ascs_ase *ase) static void bt_ascs_ase_return_to_slab(struct bt_ascs_ase *ase)
{ {
@ -100,7 +103,16 @@ static struct bt_ascs_ase *bt_ascs_ase_get_from_slab(void)
static void ase_status_changed(struct bt_audio_ep *ep, uint8_t old_state, static void ase_status_changed(struct bt_audio_ep *ep, uint8_t old_state,
uint8_t state) uint8_t state)
{ {
k_work_submit(&ep->work); struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep);
struct bt_conn *conn = ase->ascs->conn;
LOG_DBG("ase %p, ep %p", ase, ep);
if (conn != NULL && conn->state == BT_CONN_CONNECTED) {
ascs_ep_get_status(ep, &ase_buf);
bt_gatt_notify(conn, ase->attr, ase_buf.data, ase_buf.len);
}
} }
void ascs_ep_set_state(struct bt_audio_ep *ep, uint8_t state) void ascs_ep_set_state(struct bt_audio_ep *ep, uint8_t state)
@ -222,6 +234,15 @@ void ascs_ep_set_state(struct bt_audio_ep *ep, uint8_t state)
ops->metadata_updated(stream); ops->metadata_updated(stream);
} }
/* SINK ASEs can autonomously go into the streaming state if
* the CIS is connected
*/
if (ep->dir == BT_AUDIO_DIR_SINK &&
ep->iso != NULL &&
ep->iso->chan.state == BT_ISO_STATE_CONNECTED) {
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_STREAMING);
}
break; break;
case BT_AUDIO_EP_STATE_STREAMING: case BT_AUDIO_EP_STATE_STREAMING:
switch (old_state) { switch (old_state) {
@ -293,7 +314,27 @@ void ascs_ep_set_state(struct bt_audio_ep *ep, uint8_t state)
return; return;
} }
break; /* no-op*/ if (ep->iso == NULL ||
ep->iso->chan.state == BT_ISO_STATE_DISCONNECTED) {
if (ep->iso != NULL) {
bt_audio_iso_unbind_ep(ep->iso, ep);
}
bt_audio_stream_detach(stream);
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_IDLE);
} else {
/* Either the client or the server may disconnect the
* CISes when entering the releasing state.
*/
const int err = bt_audio_stream_disconnect(stream);
if (err != 0) {
LOG_ERR("Failed to disconnect stream %p: %d",
stream, err);
}
}
break;
default: default:
LOG_ERR("Invalid state: %u", state); LOG_ERR("Invalid state: %u", state);
break; break;
@ -553,6 +594,11 @@ static void ascs_ep_iso_connected(struct bt_audio_ep *ep)
if (ep->dir == BT_AUDIO_DIR_SOURCE && !ep->receiver_ready) { if (ep->dir == BT_AUDIO_DIR_SOURCE && !ep->receiver_ready) {
return; return;
} else if (ep->dir == BT_AUDIO_DIR_SINK) {
/* SINK ASEs can autonomously go into the streaming state if
* the CIS is connected
*/
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_STREAMING);
} }
stream = ep->stream; stream = ep->stream;
@ -621,9 +667,8 @@ static void ascs_ep_iso_disconnected(struct bt_audio_ep *ep, uint8_t reason)
if (ep->status.state == BT_AUDIO_EP_STATE_RELEASING) { if (ep->status.state == BT_AUDIO_EP_STATE_RELEASING) {
bt_audio_iso_unbind_ep(ep->iso, ep); bt_audio_iso_unbind_ep(ep->iso, ep);
bt_audio_stream_detach(stream);
/* Trigger a call to ase_process to handle the cleanup */ ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_IDLE);
k_work_submit(&ep->work);
} else { } else {
/* The ASE state machine goes into different states from this operation /* The ASE state machine goes into different states from this operation
* based on whether it is a source or a sink ASE. * based on whether it is a source or a sink ASE.
@ -981,60 +1026,6 @@ static struct bt_ascs *ascs_get(struct bt_conn *conn)
return session; return session;
} }
NET_BUF_SIMPLE_DEFINE_STATIC(ase_buf, CONFIG_BT_L2CAP_TX_MTU);
static void ase_process(struct k_work *work)
{
struct bt_audio_ep *ep = CONTAINER_OF(work, struct bt_audio_ep, work);
struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep);
struct bt_audio_stream *stream = ep->stream;
const uint8_t ep_state = ep->status.state;
struct bt_conn *conn = ase->ascs->conn;
LOG_DBG("ase %p, ep %p, ep.stream %p", ase, ep, stream);
if (conn != NULL && conn->state == BT_CONN_CONNECTED) {
ascs_ep_get_status(ep, &ase_buf);
bt_gatt_notify(conn, ase->attr,
ase_buf.data, ase_buf.len);
}
/* Stream shall be NULL in the idle state, and non-NULL otherwise */
__ASSERT(ep_state == BT_AUDIO_EP_STATE_IDLE ?
stream == NULL : stream != NULL,
"stream is NULL");
if (ep_state == BT_AUDIO_EP_STATE_RELEASING) {
if (ep->iso == NULL ||
ep->iso->chan.state == BT_ISO_STATE_DISCONNECTED) {
if (ep->iso != NULL) {
bt_audio_iso_unbind_ep(ep->iso, ep);
}
bt_audio_stream_detach(stream);
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_IDLE);
} else {
/* Either the client or the server may disconnect the
* CISes when entering the releasing state.
*/
const int err = bt_audio_stream_disconnect(stream);
if (err != 0) {
LOG_ERR("Failed to disconnect stream %p: %d", stream, err);
}
}
} else if (ep_state == BT_AUDIO_EP_STATE_ENABLING) {
/* SINK ASEs can autonomously go into the streaming state if
* the CIS is connected
*/
if (ep->dir == BT_AUDIO_DIR_SINK &&
ep->iso != NULL &&
ep->iso->chan.state == BT_ISO_STATE_CONNECTED) {
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_STREAMING);
}
}
}
static uint8_t ase_attr_cb(const struct bt_gatt_attr *attr, uint16_t handle, static uint8_t ase_attr_cb(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data) void *user_data)
{ {
@ -1056,8 +1047,6 @@ void ascs_ep_init(struct bt_audio_ep *ep, uint8_t id)
(void)memset(ep, 0, sizeof(*ep)); (void)memset(ep, 0, sizeof(*ep));
ep->status.id = id; ep->status.id = id;
ep->dir = ASE_DIR(id); ep->dir = ASE_DIR(id);
k_work_init(&ep->work, ase_process);
} }
static void ase_init(struct bt_ascs_ase *ase, uint8_t id) static void ase_init(struct bt_ascs_ase *ase, uint8_t id)
@ -1527,6 +1516,19 @@ static int ase_stream_qos(struct bt_audio_stream *stream,
stream->qos = qos; stream->qos = qos;
/* We setup the data path here, as this is the earliest where
* we have the ISO <-> EP coupling completed (due to setting
* the CIS ID in the QoS procedure).
*/
if (ep->dir == BT_AUDIO_DIR_SINK) {
bt_audio_codec_to_iso_path(&ep->iso->rx.path, stream->codec);
} else {
bt_audio_codec_to_iso_path(&ep->iso->tx.path, stream->codec);
}
ep->cig_id = cig_id;
ep->cis_id = cis_id;
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_QOS_CONFIGURED); ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_QOS_CONFIGURED);
bt_audio_stream_iso_listen(stream); bt_audio_stream_iso_listen(stream);
@ -1589,23 +1591,8 @@ static void ase_qos(struct bt_ascs_ase *ase, const struct bt_ascs_qos *qos)
ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_QOS_OP, ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_QOS_OP,
err, reason); err, reason);
return; return;
} else {
/* We setup the data path here, as this is the earliest where
* we have the ISO <-> EP coupling completed (due to setting
* the CIS ID in the QoS procedure).
*/
if (ep->dir == BT_AUDIO_DIR_SINK) {
bt_audio_codec_to_iso_path(&ep->iso->rx.path,
stream->codec);
} else {
bt_audio_codec_to_iso_path(&ep->iso->tx.path,
stream->codec);
}
} }
ep->cig_id = cig_id;
ep->cis_id = cis_id;
ascs_cp_rsp_success(ASE_ID(ase), BT_ASCS_QOS_OP); ascs_cp_rsp_success(ASE_ID(ase), BT_ASCS_QOS_OP);
} }

View file

@ -50,9 +50,6 @@ struct bt_audio_ep {
struct bt_audio_unicast_group *unicast_group; struct bt_audio_unicast_group *unicast_group;
struct bt_audio_broadcast_source *broadcast_source; struct bt_audio_broadcast_source *broadcast_source;
struct bt_audio_broadcast_sink *broadcast_sink; struct bt_audio_broadcast_sink *broadcast_sink;
/* ASCS ASE Control Point Work */
struct k_work work;
}; };
struct bt_audio_unicast_group { struct bt_audio_unicast_group {