From 656221e0f300ed605e436f88927c4215eacd5219 Mon Sep 17 00:00:00 2001 From: Mariusz Skamra Date: Tue, 4 Jul 2023 09:15:27 +0200 Subject: [PATCH] Bluetooth: audio: ascs: Defer ASE state transition This adds handling of ASE control point operations in separate thread so that the notifications of ASE state changes are sent from non-BT thread. This ensures bt_gatt_notify_cb to be blocking waiting for available buffers to send the notifications. Signed-off-by: Mariusz Skamra --- subsys/bluetooth/audio/ascs.c | 456 +++++++++++--------- subsys/bluetooth/audio/ascs_internal.h | 2 +- subsys/bluetooth/audio/bap_unicast_server.c | 27 +- tests/bluetooth/audio/mocks/src/kernel.c | 17 + 4 files changed, 284 insertions(+), 218 deletions(-) diff --git a/subsys/bluetooth/audio/ascs.c b/subsys/bluetooth/audio/ascs.c index c6a72aa8e9bbe08..ebf9ba73b17e4d2 100644 --- a/subsys/bluetooth/audio/ascs.c +++ b/subsys/bluetooth/audio/ascs.c @@ -60,6 +60,8 @@ static struct bt_ascs_ase { struct bt_bap_ep ep; const struct bt_gatt_attr *attr; struct k_work_delayable disconnect_work; + struct k_work state_transition_work; + enum bt_bap_ep_state state_pending; } ase_pool[CONFIG_BT_ASCS_MAX_ACTIVE_ASES]; #define MAX_CODEC_CONFIG \ @@ -123,12 +125,14 @@ static void ase_free(struct bt_ascs_ase *ase) ase->conn = NULL; } -static void ase_status_changed(struct bt_bap_ep *ep, uint8_t old_state, uint8_t state) +static void ase_status_changed(struct bt_ascs_ase *ase, uint8_t state) { - struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); struct bt_conn *conn = ase->conn; - LOG_DBG("ase %p, ep %p", ase, ep); + LOG_DBG("ase %p id 0x%02x %s -> %s", ase, ase->ep.status.id, + bt_bap_ep_state_str(ascs_ep_get_state(&ase->ep)), bt_bap_ep_state_str(state)); + + ase->ep.status.state = state; if (conn != NULL) { struct bt_conn_info conn_info; @@ -154,7 +158,7 @@ static void ase_status_changed(struct bt_bap_ep *ep, uint8_t old_state, uint8_t return; } - ascs_ep_get_status(ep, &ase_buf); + ascs_ep_get_status(&ase->ep, &ase_buf); ntf_size = MIN(max_ntf_size, ase_buf.len); if (ntf_size < ase_buf.len) { @@ -162,7 +166,8 @@ static void ase_status_changed(struct bt_bap_ep *ep, uint8_t old_state, uint8_t ntf_size, ase_buf.len); } - bt_gatt_notify(conn, ase->attr, ase_buf.data, ntf_size); + err = bt_gatt_notify(conn, ase->attr, ase_buf.data, ntf_size); + __ASSERT_NO_MSG(err == 0); k_sem_give(&ase_buf_sem); } @@ -236,37 +241,145 @@ static int ascs_disconnect_stream(struct bt_bap_stream *stream) K_MSEC(CONFIG_BT_ASCS_ISO_DISCONNECT_DELAY)); } -void ascs_ep_set_state(struct bt_bap_ep *ep, uint8_t state) +static void ase_set_state_idle(struct bt_ascs_ase *ase) { - struct bt_bap_stream *stream; - bool state_changed; - uint8_t old_state; + struct bt_bap_stream *stream = ase->ep.stream; - if (!ep) { - return; + __ASSERT_NO_MSG(stream != NULL); + + ase->ep.receiver_ready = false; + + ase_status_changed(ase, BT_BAP_EP_STATE_IDLE); + + bt_bap_stream_reset(stream); + + if (stream->ops != NULL && stream->ops->released != NULL) { + stream->ops->released(stream); } - /* TODO: Verify state changes */ + ase_free(ase); +} + +static void ase_set_state_codec_configured(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; - old_state = ep->status.state; - ep->status.state = state; - state_changed = old_state != state; + __ASSERT_NO_MSG(stream != NULL); - LOG_DBG("ep %p id 0x%02x %s -> %s", ep, ep->status.id, bt_bap_ep_state_str(old_state), - bt_bap_ep_state_str(state)); + ase->ep.receiver_ready = false; - /* Notify clients*/ - ase_status_changed(ep, old_state, state); + ase_status_changed(ase, BT_BAP_EP_STATE_CODEC_CONFIGURED); - if (ep->stream == NULL) { - return; + if (stream->ops != NULL && stream->ops->configured != NULL) { + stream->ops->configured(stream, &ase->ep.qos_pref); } +} - stream = ep->stream; +static void ase_set_state_qos_configured(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; - if (state_changed && old_state == BT_BAP_EP_STATE_STREAMING) { - /* We left the streaming state, let the upper layers know that the stream is stopped - */ + __ASSERT_NO_MSG(stream != NULL); + + ase->ep.receiver_ready = false; + + ase_status_changed(ase, BT_BAP_EP_STATE_QOS_CONFIGURED); + + if (stream->ops != NULL && stream->ops->qos_set != NULL) { + stream->ops->qos_set(stream); + } +} + +static void ase_set_state_enabling(struct bt_ascs_ase *ase) +{ + const bool state_changed = ascs_ep_get_state(&ase->ep) != BT_BAP_EP_STATE_ENABLING; + struct bt_bap_stream *stream = ase->ep.stream; + + __ASSERT_NO_MSG(stream != NULL); + + ase_status_changed(ase, BT_BAP_EP_STATE_ENABLING); + + if (state_changed && stream->ops != NULL && stream->ops->enabled != NULL) { + stream->ops->enabled(stream); + } else if (!state_changed && stream->ops != NULL && stream->ops->metadata_updated != NULL) { + stream->ops->metadata_updated(stream); + } + + /* SINK ASEs can autonomously go into the streaming state if the CIS is connected */ + if (ase->ep.dir == BT_AUDIO_DIR_SINK && ase->ep.receiver_ready && ase->ep.iso != NULL && + ase->ep.iso->chan.state == BT_ISO_STATE_CONNECTED) { + ascs_ep_set_state(&ase->ep, BT_BAP_EP_STATE_STREAMING); + } +} + +static void ase_set_state_streaming(struct bt_ascs_ase *ase) +{ + const bool state_changed = ascs_ep_get_state(&ase->ep) != BT_BAP_EP_STATE_STREAMING; + struct bt_bap_stream *stream = ase->ep.stream; + + __ASSERT_NO_MSG(stream != NULL); + + ase_status_changed(ase, BT_BAP_EP_STATE_STREAMING); + + if (state_changed && stream->ops != NULL && stream->ops->started != NULL) { + stream->ops->started(stream); + } else if (!state_changed && stream->ops != NULL && stream->ops->metadata_updated != NULL) { + stream->ops->metadata_updated(stream); + } +} + +static void ase_set_state_disabling(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; + + __ASSERT_NO_MSG(stream != NULL); + + ase->ep.receiver_ready = false; + + ase_status_changed(ase, BT_BAP_EP_STATE_DISABLING); + + if (stream->ops != NULL && stream->ops->disabled != NULL) { + stream->ops->disabled(stream); + } +} + +static void ase_set_state_releasing(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; + + __ASSERT_NO_MSG(stream != NULL); + + ase->ep.receiver_ready = false; + + ase_status_changed(ase, BT_BAP_EP_STATE_RELEASING); + + /* Either the client or the server may disconnect the CISes when entering the releasing + * state. + */ + if (bt_bap_stream_can_disconnect(stream)) { + int err; + + err = ascs_disconnect_stream(stream); + if (err < 0) { + LOG_ERR("Failed to disconnect stream %p: %d", stream, err); + } + } else { + ascs_ep_set_state(&ase->ep, BT_BAP_EP_STATE_IDLE); + } +} + +static void state_transition_work_handler(struct k_work *work) +{ + struct bt_ascs_ase *ase = CONTAINER_OF(work, struct bt_ascs_ase, state_transition_work); + const enum bt_bap_ep_state old_state = ascs_ep_get_state(&ase->ep); + const enum bt_bap_ep_state new_state = ase->state_pending; + struct bt_bap_stream *stream = ase->ep.stream; + struct bt_bap_ep *ep = &ase->ep; + + __ASSERT_NO_MSG(stream != NULL); + + /* We left the streaming state, let the upper layers know that the stream is stopped */ + if (old_state != new_state && old_state == BT_BAP_EP_STATE_STREAMING) { struct bt_bap_stream_ops *ops = stream->ops; uint8_t reason = ep->reason; @@ -285,198 +398,143 @@ void ascs_ep_set_state(struct bt_bap_ep *ep, uint8_t state) } } - if (stream->ops != NULL) { - const struct bt_bap_stream_ops *ops = stream->ops; - - switch (state) { - case BT_BAP_EP_STATE_IDLE: - ep->receiver_ready = false; - bt_bap_stream_reset(stream); - - if (ops->released != NULL) { - ops->released(stream); - } - struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); + switch (new_state) { + case BT_BAP_EP_STATE_IDLE: + ase_set_state_idle(ase); + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + ase_set_state_codec_configured(ase); + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + ase_set_state_qos_configured(ase); + break; + case BT_BAP_EP_STATE_ENABLING: + ase_set_state_enabling(ase); + break; + case BT_BAP_EP_STATE_STREAMING: + ase_set_state_streaming(ase); + break; + case BT_BAP_EP_STATE_DISABLING: + ase_set_state_disabling(ase); + break; + case BT_BAP_EP_STATE_RELEASING: + ase_set_state_releasing(ase); + break; + default: + __ASSERT_PRINT("Invalid state %d", new_state); + } +} - ase_free(ase); +int ascs_ep_set_state(struct bt_bap_ep *ep, uint8_t state) +{ + struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); + const enum bt_bap_ep_state old_state = ascs_ep_get_state(&ase->ep); + int err; - break; + switch (state) { + case BT_BAP_EP_STATE_IDLE: + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + switch (old_state) { + case BT_BAP_EP_STATE_IDLE: case BT_BAP_EP_STATE_CODEC_CONFIGURED: - switch (old_state) { - case BT_BAP_EP_STATE_IDLE: - case BT_BAP_EP_STATE_CODEC_CONFIGURED: - case BT_BAP_EP_STATE_QOS_CONFIGURED: - case BT_BAP_EP_STATE_RELEASING: - break; - default: - BT_ASSERT_MSG(false, "Invalid state transition: %s -> %s", - bt_bap_ep_state_str(old_state), - bt_bap_ep_state_str(ep->status.state)); - return; - } - - ep->receiver_ready = false; - - if (ops->configured != NULL) { - ops->configured(stream, &ep->qos_pref); - } - - break; case BT_BAP_EP_STATE_QOS_CONFIGURED: - /* QoS configured have different allowed states - * depending on the endpoint type - */ - if (ep->dir == BT_AUDIO_DIR_SOURCE) { - switch (old_state) { - case BT_BAP_EP_STATE_CODEC_CONFIGURED: - case BT_BAP_EP_STATE_QOS_CONFIGURED: - case BT_BAP_EP_STATE_DISABLING: - break; - default: - BT_ASSERT_MSG(false, "Invalid state transition: %s -> %s", - bt_bap_ep_state_str(old_state), - bt_bap_ep_state_str(ep->status.state)); - return; - } - } else { - switch (old_state) { - case BT_BAP_EP_STATE_CODEC_CONFIGURED: - case BT_BAP_EP_STATE_QOS_CONFIGURED: - case BT_BAP_EP_STATE_ENABLING: - case BT_BAP_EP_STATE_STREAMING: - break; - default: - BT_ASSERT_MSG(false, "Invalid state transition: %s -> %s", - bt_bap_ep_state_str(old_state), - bt_bap_ep_state_str(ep->status.state)); - return; - } - } - - ep->receiver_ready = false; + case BT_BAP_EP_STATE_RELEASING: + break; + default: + goto invalid_transition; + } - if (ops->qos_set != NULL) { - ops->qos_set(stream); + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + switch (old_state) { + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + case BT_BAP_EP_STATE_QOS_CONFIGURED: + break; + case BT_BAP_EP_STATE_DISABLING: + if (ase->ep.dir != BT_AUDIO_DIR_SOURCE) { + goto invalid_transition; } - break; case BT_BAP_EP_STATE_ENABLING: - switch (old_state) { - case BT_BAP_EP_STATE_QOS_CONFIGURED: - case BT_BAP_EP_STATE_ENABLING: - break; - default: - BT_ASSERT_MSG(false, "Invalid state transition: %s -> %s", - bt_bap_ep_state_str(old_state), - bt_bap_ep_state_str(ep->status.state)); - return; - } - - if (state_changed && ops->enabled != NULL) { - ops->enabled(stream); - } else if (!state_changed && ops->metadata_updated) { - 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->receiver_ready && - ep->iso != NULL && - ep->iso->chan.state == BT_ISO_STATE_CONNECTED) { - ascs_ep_set_state(ep, BT_BAP_EP_STATE_STREAMING); + case BT_BAP_EP_STATE_STREAMING: + if (ase->ep.dir != BT_AUDIO_DIR_SINK) { + goto invalid_transition; } + break; + default: + goto invalid_transition; + } + break; + case BT_BAP_EP_STATE_ENABLING: + switch (old_state) { + case BT_BAP_EP_STATE_QOS_CONFIGURED: + case BT_BAP_EP_STATE_ENABLING: break; + default: + goto invalid_transition; + } + + break; + case BT_BAP_EP_STATE_STREAMING: + switch (old_state) { + case BT_BAP_EP_STATE_ENABLING: case BT_BAP_EP_STATE_STREAMING: - switch (old_state) { - case BT_BAP_EP_STATE_ENABLING: - case BT_BAP_EP_STATE_STREAMING: - break; - default: - BT_ASSERT_MSG(false, "Invalid state transition: %s -> %s", - bt_bap_ep_state_str(old_state), - bt_bap_ep_state_str(ep->status.state)); - return; - } + break; + default: + goto invalid_transition; + } - if (state_changed && ops->started != NULL) { - ops->started(stream); - } else if (!state_changed && ops->metadata_updated) { - ops->metadata_updated(stream); + break; + case BT_BAP_EP_STATE_DISABLING: + switch (old_state) { + case BT_BAP_EP_STATE_ENABLING: + case BT_BAP_EP_STATE_STREAMING: + if (ase->ep.dir != BT_AUDIO_DIR_SOURCE) { + goto invalid_transition; } + break; + default: + goto invalid_transition; + } + break; + case BT_BAP_EP_STATE_RELEASING: + switch (old_state) { + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + case BT_BAP_EP_STATE_QOS_CONFIGURED: + case BT_BAP_EP_STATE_ENABLING: + case BT_BAP_EP_STATE_STREAMING: break; case BT_BAP_EP_STATE_DISABLING: - if (ep->dir == BT_AUDIO_DIR_SOURCE) { - switch (old_state) { - case BT_BAP_EP_STATE_ENABLING: - case BT_BAP_EP_STATE_STREAMING: - break; - default: - BT_ASSERT_MSG(false, "Invalid state transition: %s -> %s", - bt_bap_ep_state_str(old_state), - bt_bap_ep_state_str(ep->status.state)); - return; - } - } else { - /* Sinks cannot go into the disabling state */ - BT_ASSERT_MSG(false, "Invalid state transition: %s -> %s", - bt_bap_ep_state_str(old_state), - bt_bap_ep_state_str(ep->status.state)); - return; + if (ase->ep.dir != BT_AUDIO_DIR_SOURCE) { + goto invalid_transition; } + break; + default: + goto invalid_transition; + } - ep->receiver_ready = false; - - if (ops->disabled != NULL) { - ops->disabled(stream); - } + break; + default: + goto invalid_transition; + } - break; - case BT_BAP_EP_STATE_RELEASING: - switch (old_state) { - case BT_BAP_EP_STATE_CODEC_CONFIGURED: - case BT_BAP_EP_STATE_QOS_CONFIGURED: - case BT_BAP_EP_STATE_ENABLING: - case BT_BAP_EP_STATE_STREAMING: - break; - case BT_BAP_EP_STATE_DISABLING: - if (ep->dir == BT_AUDIO_DIR_SOURCE) { - break; - } /* else fall through for sink */ - - /* fall through */ - default: - BT_ASSERT_MSG(false, "Invalid state transition: %s -> %s", - bt_bap_ep_state_str(old_state), - bt_bap_ep_state_str(ep->status.state)); - return; - } + ase->state_pending = state; - ep->receiver_ready = false; + err = k_work_submit(&ase->state_transition_work); + if (err < 0) { + return err; + } - if (bt_bap_stream_can_disconnect(stream)) { - /* Either the client or the server may disconnect the - * CISes when entering the releasing state. - */ - const int err = ascs_disconnect_stream(stream); + return 0; - if (err < 0) { - LOG_ERR("Failed to disconnect stream %p: %d", - stream, err); - } - } else { - ascs_ep_set_state(ep, BT_BAP_EP_STATE_IDLE); - } +invalid_transition: + BT_ASSERT_MSG(false, "Invalid state transition: %s -> %s", + bt_bap_ep_state_str(old_state), bt_bap_ep_state_str(state)); - break; - default: - LOG_ERR("Invalid state: %u", state); - break; - } - } + return -EBADMSG; } static void ascs_codec_data_add(struct net_buf_simple *buf, const char *prefix, uint8_t num, @@ -794,7 +852,6 @@ static void ascs_iso_connected(struct bt_iso_chan *chan) static void ascs_ep_iso_disconnected(struct bt_bap_ep *ep, uint8_t reason) { struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); - const struct bt_bap_stream_ops *ops; struct bt_bap_stream *stream; stream = ep->stream; @@ -803,8 +860,6 @@ static void ascs_ep_iso_disconnected(struct bt_bap_ep *ep, uint8_t reason) return; } - ops = stream->ops; - LOG_DBG("stream %p ep %p reason 0x%02x", stream, stream->ep, reason); if (ep->status.state == BT_BAP_EP_STATE_ENABLING && @@ -956,7 +1011,6 @@ static void ase_release(struct bt_ascs_ase *ase) ase->ep.reason = BT_HCI_ERR_REMOTE_USER_TERM_CONN; ascs_ep_set_state(&ase->ep, BT_BAP_EP_STATE_RELEASING); - /* At this point, `ase` object might have been free'd if automously went to Idle */ ascs_cp_rsp_success(ase_id); } @@ -1132,6 +1186,7 @@ static void ase_init(struct bt_ascs_ase *ase, struct bt_conn *conn, uint8_t id) k_work_init_delayable(&ase->disconnect_work, ascs_disconnect_stream_work_handler); + k_work_init(&ase->state_transition_work, state_transition_work_handler); } static struct bt_ascs_ase *ase_new(struct bt_conn *conn, uint8_t id) @@ -1510,7 +1565,12 @@ int bt_ascs_config_ase(struct bt_conn *conn, struct bt_bap_stream *stream, bt_bap_stream_attach(conn, stream, ep, &ep->codec_cfg); - ascs_ep_set_state(ep, BT_BAP_EP_STATE_CODEC_CONFIGURED); + err = ascs_ep_set_state(ep, BT_BAP_EP_STATE_CODEC_CONFIGURED); + if (err != 0) { + bt_bap_stream_detach(stream); + ase_free(ase); + return err; + } return 0; } diff --git a/subsys/bluetooth/audio/ascs_internal.h b/subsys/bluetooth/audio/ascs_internal.h index ec8f763ba1573e8..fa0513655d89b02 100644 --- a/subsys/bluetooth/audio/ascs_internal.h +++ b/subsys/bluetooth/audio/ascs_internal.h @@ -340,7 +340,7 @@ static inline const char *bt_ascs_reason_str(uint8_t reason) int bt_ascs_init(const struct bt_bap_unicast_server_cb *cb); void bt_ascs_cleanup(void); -void ascs_ep_set_state(struct bt_bap_ep *ep, uint8_t state); +int ascs_ep_set_state(struct bt_bap_ep *ep, uint8_t state); int bt_ascs_config_ase(struct bt_conn *conn, struct bt_bap_stream *stream, struct bt_audio_codec_cfg *codec_cfg, diff --git a/subsys/bluetooth/audio/bap_unicast_server.c b/subsys/bluetooth/audio/bap_unicast_server.c index 54c37649d449460..40f2e5fed5351d3 100644 --- a/subsys/bluetooth/audio/bap_unicast_server.c +++ b/subsys/bluetooth/audio/bap_unicast_server.c @@ -86,9 +86,7 @@ int bt_bap_unicast_server_reconfig(struct bt_bap_stream *stream, (void)memcpy(&ep->codec_cfg, codec_cfg, sizeof(*codec_cfg)); - ascs_ep_set_state(ep, BT_BAP_EP_STATE_CODEC_CONFIGURED); - - return 0; + return ascs_ep_set_state(ep, BT_BAP_EP_STATE_CODEC_CONFIGURED); } int bt_bap_unicast_server_start(struct bt_bap_stream *stream) @@ -106,11 +104,11 @@ int bt_bap_unicast_server_start(struct bt_bap_stream *stream) * else wait for ISO to be connected */ if (ep->iso->chan.state == BT_ISO_STATE_CONNECTED) { - ascs_ep_set_state(ep, BT_BAP_EP_STATE_STREAMING); - } else { - ep->receiver_ready = true; + return ascs_ep_set_state(ep, BT_BAP_EP_STATE_STREAMING); } + ep->receiver_ready = true; + return 0; } @@ -140,9 +138,7 @@ int bt_bap_unicast_server_metadata(struct bt_bap_stream *stream, struct bt_audio } /* Set the state to the same state to trigger the notifications */ - ascs_ep_set_state(ep, ep->status.state); - - return 0; + return ascs_ep_set_state(ep, ep->status.state); } int bt_bap_unicast_server_disable(struct bt_bap_stream *stream) @@ -172,12 +168,10 @@ int bt_bap_unicast_server_disable(struct bt_bap_stream *stream) * based on whether it is a source or a sink ASE. */ if (ep->dir == BT_AUDIO_DIR_SOURCE) { - ascs_ep_set_state(ep, BT_BAP_EP_STATE_DISABLING); - } else { - ascs_ep_set_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED); + return ascs_ep_set_state(ep, BT_BAP_EP_STATE_DISABLING); } - return 0; + return ascs_ep_set_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED); } int bt_bap_unicast_server_release(struct bt_bap_stream *stream) @@ -203,12 +197,7 @@ int bt_bap_unicast_server_release(struct bt_bap_stream *stream) /* Set reason in case this exits the streaming state */ ep->reason = BT_HCI_ERR_LOCALHOST_TERM_CONN; - /* ase_process will set the state to IDLE after sending the - * notification, finalizing the release - */ - ascs_ep_set_state(ep, BT_BAP_EP_STATE_RELEASING); - - return 0; + return ascs_ep_set_state(ep, BT_BAP_EP_STATE_RELEASING); } int bt_bap_unicast_server_config_ase(struct bt_conn *conn, struct bt_bap_stream *stream, diff --git a/tests/bluetooth/audio/mocks/src/kernel.c b/tests/bluetooth/audio/mocks/src/kernel.c index 554497e5916ac11..ce9dcab5d3a1747 100644 --- a/tests/bluetooth/audio/mocks/src/kernel.c +++ b/tests/bluetooth/audio/mocks/src/kernel.c @@ -56,6 +56,23 @@ int k_work_cancel_delayable(struct k_work_delayable *dwork) return 0; } +void k_work_init(struct k_work *work, k_work_handler_t handler) +{ + work->handler = handler; +} + +int k_work_submit(struct k_work *work) +{ + work->handler(work); + + return 0; +} + +int k_work_busy_get(const struct k_work *work) +{ + return 0; +} + int32_t k_sleep(k_timeout_t timeout) { struct k_work *work;