blob: 3478f91a24c4ac2092ab43b2d6e2f4cc79c6adac [file] [log] [blame]
/* bap_iso.c - BAP ISO handling */
/*
* Copyright (c) 2022 Codecoup
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "bap_iso.h"
#include "audio_internal.h"
#include "bap_endpoint.h"
#include <zephyr/logging/log.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, &param);
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 points 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 connected if
* ever want to use it for TX, and ditto for RX. They cannot be
* initialized after the CIS has been connected
*/
iso->chan.qos->rx = &iso->rx.qos;
iso->chan.qos->rx->path = &iso->rx.path;
iso->chan.qos->rx->path->cc = iso->rx.cc;
iso->chan.qos->tx = &iso->tx.qos;
iso->chan.qos->tx->path = &iso->tx.path;
iso->chan.qos->tx->path->cc = iso->tx.cc;
}
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;
}
}
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)
{
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(stream->dir));
/* For the unicast client, the direction and tx/rx is reversed */
if (stream->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)
{
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(stream->dir));
/* For the unicast client, the direction and tx/rx is reversed */
if (stream->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 */