/* bap_iso.c - BAP ISO handling */ /* * Copyright (c) 2022 Codecoup * Copyright (c) 2023 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bap_iso.h" #include "audio_internal.h" #include "bap_endpoint.h" LOG_MODULE_REGISTER(bt_bap_iso, CONFIG_BT_BAP_ISO_LOG_LEVEL); /* TODO: Optimize the ISO_POOL_SIZE */ #define ISO_POOL_SIZE CONFIG_BT_ISO_MAX_CHAN static struct bt_bap_iso iso_pool[ISO_POOL_SIZE]; struct bt_bap_iso *bt_bap_iso_new(void) { struct bt_bap_iso *iso = NULL; for (size_t i = 0; i < ARRAY_SIZE(iso_pool); i++) { if (atomic_cas(&iso_pool[i].ref, 0, 1)) { iso = &iso_pool[i]; break; } } if (!iso) { return NULL; } (void)memset(iso, 0, offsetof(struct bt_bap_iso, ref)); return iso; } struct bt_bap_iso *bt_bap_iso_ref(struct bt_bap_iso *iso) { atomic_val_t old; __ASSERT_NO_MSG(iso != NULL); /* Reference counter must be checked to avoid incrementing ref from * zero, then we should return NULL instead. * Loop on clear-and-set in case someone has modified the reference * count since the read, and start over again when that happens. */ do { old = atomic_get(&iso->ref); if (!old) { return NULL; } } while (!atomic_cas(&iso->ref, old, old + 1)); return iso; } void bt_bap_iso_unref(struct bt_bap_iso *iso) { atomic_val_t old; __ASSERT_NO_MSG(iso != NULL); old = atomic_dec(&iso->ref); __ASSERT(old > 0, "iso reference counter is 0"); } void bt_bap_iso_foreach(bt_bap_iso_func_t func, void *user_data) { for (size_t i = 0; i < ARRAY_SIZE(iso_pool); i++) { struct bt_bap_iso *iso = bt_bap_iso_ref(&iso_pool[i]); bool iter; if (!iso) { continue; } iter = func(iso, user_data); bt_bap_iso_unref(iso); if (!iter) { return; } } } struct bt_bap_iso_find_param { struct bt_bap_iso *iso; bt_bap_iso_func_t func; void *user_data; }; static bool bt_bap_iso_find_cb(struct bt_bap_iso *iso, void *user_data) { struct bt_bap_iso_find_param *param = user_data; bool found; found = param->func(iso, param->user_data); if (found) { param->iso = bt_bap_iso_ref(iso); } return !found; } struct bt_bap_iso *bt_bap_iso_find(bt_bap_iso_func_t func, void *user_data) { struct bt_bap_iso_find_param param = { .iso = NULL, .func = func, .user_data = user_data, }; bt_bap_iso_foreach(bt_bap_iso_find_cb, ¶m); return param.iso; } void bt_bap_iso_init(struct bt_bap_iso *iso, struct bt_iso_chan_ops *ops) { iso->chan.ops = ops; iso->chan.qos = &iso->qos; /* Setup the QoS for both Tx and Rx * This is due to the limitation in the ISO API where pointers like * the `qos->tx` shall be initialized before the CIS is created */ iso->chan.qos->rx = &iso->rx.qos; iso->chan.qos->tx = &iso->tx.qos; } static struct bt_bap_iso_dir *bap_iso_get_iso_dir(bool unicast_client, struct bt_bap_iso *iso, enum bt_audio_dir dir) { /* TODO FIX FOR CLIENT */ if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && unicast_client) { /* For the unicast client, the direction and tx/rx is reversed */ if (dir == BT_AUDIO_DIR_SOURCE) { return &iso->rx; } else { return &iso->tx; } } if (dir == BT_AUDIO_DIR_SINK) { return &iso->rx; } else { return &iso->tx; } } void bt_bap_iso_configure_data_path(struct bt_bap_ep *ep, struct bt_audio_codec_cfg *codec_cfg) { struct bt_bap_iso *bap_iso = ep->iso; struct bt_iso_chan_qos *qos = bap_iso->chan.qos; const bool is_unicast_client = IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && bt_bap_ep_is_unicast_client(ep); struct bt_bap_iso_dir *iso_dir = bap_iso_get_iso_dir(is_unicast_client, bap_iso, ep->dir); struct bt_iso_chan_path *path = &iso_dir->path; /* Setup the data path objects */ if (iso_dir == &bap_iso->rx) { qos->rx->path = path; } else { qos->tx->path = path; } /* Configure the data path to either use the controller for transcoding, or set the path to * be transparent to indicate that the transcoding happens somewhere else */ path->pid = codec_cfg->path_id; if (codec_cfg->ctlr_transcode) { path->format = codec_cfg->id; path->cid = codec_cfg->cid; path->vid = codec_cfg->vid; path->delay = 0; path->cc_len = codec_cfg->data_len; path->cc = codec_cfg->data; } else { path->format = BT_HCI_CODING_FORMAT_TRANSPARENT; path->cid = 0; path->vid = 0; path->delay = 0; path->cc_len = 0; path->cc = NULL; } } static bool is_unicast_client_ep(struct bt_bap_ep *ep) { return IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && bt_bap_ep_is_unicast_client(ep); } void bt_bap_iso_bind_ep(struct bt_bap_iso *iso, struct bt_bap_ep *ep) { struct bt_bap_iso_dir *iso_dir; __ASSERT_NO_MSG(ep != NULL); __ASSERT_NO_MSG(iso != NULL); __ASSERT(ep->iso == NULL, "ep %p bound with iso %p already", ep, ep->iso); __ASSERT(ep->dir == BT_AUDIO_DIR_SINK || ep->dir == BT_AUDIO_DIR_SOURCE, "invalid dir: %u", ep->dir); LOG_DBG("iso %p ep %p dir %s", iso, ep, bt_audio_dir_str(ep->dir)); iso_dir = bap_iso_get_iso_dir(is_unicast_client_ep(ep), iso, ep->dir); __ASSERT(iso_dir->ep == NULL, "iso %p bound with ep %p", iso, iso_dir); iso_dir->ep = ep; ep->iso = bt_bap_iso_ref(iso); } void bt_bap_iso_unbind_ep(struct bt_bap_iso *iso, struct bt_bap_ep *ep) { struct bt_bap_iso_dir *iso_dir; __ASSERT_NO_MSG(ep != NULL); __ASSERT_NO_MSG(iso != NULL); __ASSERT(ep->iso == iso, "ep %p not bound with iso %p, was bound to %p", ep, iso, ep->iso); __ASSERT(ep->dir == BT_AUDIO_DIR_SINK || ep->dir == BT_AUDIO_DIR_SOURCE, "Invalid dir: %u", ep->dir); LOG_DBG("iso %p ep %p dir %s", iso, ep, bt_audio_dir_str(ep->dir)); iso_dir = bap_iso_get_iso_dir(is_unicast_client_ep(ep), iso, ep->dir); __ASSERT(iso_dir->ep == ep, "iso %p not bound with ep %p", iso, ep); iso_dir->ep = NULL; bt_bap_iso_unref(ep->iso); ep->iso = NULL; } struct bt_bap_ep *bt_bap_iso_get_ep(bool unicast_client, struct bt_bap_iso *iso, enum bt_audio_dir dir) { struct bt_bap_iso_dir *iso_dir; __ASSERT(dir == BT_AUDIO_DIR_SINK || dir == BT_AUDIO_DIR_SOURCE, "invalid dir: %u", dir); LOG_DBG("iso %p dir %s", iso, bt_audio_dir_str(dir)); iso_dir = bap_iso_get_iso_dir(unicast_client, iso, dir); return iso_dir->ep; } struct bt_bap_ep *bt_bap_iso_get_paired_ep(const struct bt_bap_ep *ep) { if (ep->iso->rx.ep == ep) { return ep->iso->tx.ep; } else { return ep->iso->rx.ep; } } #if defined(CONFIG_BT_BAP_UNICAST_CLIENT) void bt_bap_iso_bind_stream(struct bt_bap_iso *bap_iso, struct bt_bap_stream *stream, enum bt_audio_dir dir) { struct bt_bap_iso_dir *bap_iso_ep; __ASSERT_NO_MSG(stream != NULL); __ASSERT_NO_MSG(bap_iso != NULL); __ASSERT(stream->bap_iso == NULL, "stream %p bound with bap_iso %p already", stream, stream->bap_iso); LOG_DBG("bap_iso %p stream %p dir %s", bap_iso, stream, bt_audio_dir_str(dir)); /* For the unicast client, the direction and tx/rx is reversed */ if (dir == BT_AUDIO_DIR_SOURCE) { bap_iso_ep = &bap_iso->rx; } else { bap_iso_ep = &bap_iso->tx; } __ASSERT(bap_iso_ep->stream == NULL, "bap_iso %p bound with stream %p", bap_iso, bap_iso_ep->stream); bap_iso_ep->stream = stream; stream->bap_iso = bt_bap_iso_ref(bap_iso); } void bt_bap_iso_unbind_stream(struct bt_bap_iso *bap_iso, struct bt_bap_stream *stream, enum bt_audio_dir dir) { struct bt_bap_iso_dir *bap_iso_ep; __ASSERT_NO_MSG(stream != NULL); __ASSERT_NO_MSG(bap_iso != NULL); __ASSERT(stream->bap_iso != NULL, "stream %p not bound with an bap_iso", stream); LOG_DBG("bap_iso %p stream %p dir %s", bap_iso, stream, bt_audio_dir_str(dir)); /* For the unicast client, the direction and tx/rx is reversed */ if (dir == BT_AUDIO_DIR_SOURCE) { bap_iso_ep = &bap_iso->rx; } else { bap_iso_ep = &bap_iso->tx; } __ASSERT(bap_iso_ep->stream == stream, "bap_iso %p (%p) not bound with stream %p (%p)", bap_iso, bap_iso_ep->stream, stream, stream->bap_iso); bap_iso_ep->stream = NULL; bt_bap_iso_unref(bap_iso); stream->bap_iso = NULL; } struct bt_bap_stream *bt_bap_iso_get_stream(struct bt_bap_iso *iso, enum bt_audio_dir dir) { __ASSERT(dir == BT_AUDIO_DIR_SINK || dir == BT_AUDIO_DIR_SOURCE, "invalid dir: %u", dir); LOG_DBG("iso %p dir %s", iso, bt_audio_dir_str(dir)); /* For the unicast client, the direction and tx/rx is reversed */ if (dir == BT_AUDIO_DIR_SOURCE) { return iso->rx.stream; } else { return iso->tx.stream; } } #endif /* CONFIG_BT_BAP_UNICAST_CLIENT */