2022-01-07 17:23:45 +01:00
|
|
|
/* @file
|
|
|
|
* @brief Bluetooth ASCS
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2020 Intel Corporation
|
|
|
|
* Copyright (c) 2022 Nordic Semiconductor ASA
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
includes: prefer <zephyr/kernel.h> over <zephyr/zephyr.h>
As of today <zephyr/zephyr.h> is 100% equivalent to <zephyr/kernel.h>.
This patch proposes to then include <zephyr/kernel.h> instead of
<zephyr/zephyr.h> since it is more clear that you are including the
Kernel APIs and (probably) nothing else. <zephyr/zephyr.h> sounds like a
catch-all header that may be confusing. Most applications need to
include a bunch of other things to compile, e.g. driver headers or
subsystem headers like BT, logging, etc.
The idea of a catch-all header in Zephyr is probably not feasible
anyway. Reason is that Zephyr is not a library, like it could be for
example `libpython`. Zephyr provides many utilities nowadays: a kernel,
drivers, subsystems, etc and things will likely grow. A catch-all header
would be massive, difficult to keep up-to-date. It is also likely that
an application will only build a small subset. Note that subsystem-level
headers may use a catch-all approach to make things easier, though.
NOTE: This patch is **NOT** removing the header, just removing its usage
in-tree. I'd advocate for its deprecation (add a #warning on it), but I
understand many people will have concerns.
Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>
2022-08-25 09:58:46 +02:00
|
|
|
#include <zephyr/kernel.h>
|
2022-05-06 11:12:04 +02:00
|
|
|
#include <zephyr/sys/byteorder.h>
|
|
|
|
#include <zephyr/sys/check.h>
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-05-06 11:12:04 +02:00
|
|
|
#include <zephyr/device.h>
|
|
|
|
#include <zephyr/init.h>
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-05-06 11:12:04 +02:00
|
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
|
|
#include <zephyr/bluetooth/conn.h>
|
|
|
|
#include <zephyr/bluetooth/gatt.h>
|
2022-10-06 12:57:38 +02:00
|
|
|
#include "zephyr/bluetooth/iso.h"
|
2022-05-06 11:12:04 +02:00
|
|
|
#include <zephyr/bluetooth/audio/audio.h>
|
2022-09-30 13:33:21 +02:00
|
|
|
#include <zephyr/bluetooth/audio/capabilities.h>
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_ASCS)
|
|
|
|
#define LOG_MODULE_NAME bt_ascs
|
|
|
|
#include "common/log.h"
|
|
|
|
|
|
|
|
#include "../host/hci_core.h"
|
|
|
|
#include "../host/conn_internal.h"
|
|
|
|
|
2022-07-25 09:56:23 +02:00
|
|
|
#include "audio_internal.h"
|
2022-01-07 17:23:45 +01:00
|
|
|
#include "endpoint.h"
|
|
|
|
#include "unicast_server.h"
|
|
|
|
#include "pacs_internal.h"
|
2022-04-05 16:37:26 +02:00
|
|
|
#include "cap_internal.h"
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
#if defined(CONFIG_BT_AUDIO_UNICAST_SERVER)
|
|
|
|
|
|
|
|
#define ASE_ID(_ase) ase->ep.status.id
|
|
|
|
#define ASE_DIR(_id) \
|
2022-03-29 18:20:10 +02:00
|
|
|
(_id > CONFIG_BT_ASCS_ASE_SNK_COUNT ? BT_AUDIO_DIR_SOURCE : BT_AUDIO_DIR_SINK)
|
2022-01-07 17:23:45 +01:00
|
|
|
#define ASE_UUID(_id) \
|
|
|
|
(_id > CONFIG_BT_ASCS_ASE_SNK_COUNT ? BT_UUID_ASCS_ASE_SRC : BT_UUID_ASCS_ASE_SNK)
|
|
|
|
#define ASE_COUNT (CONFIG_BT_ASCS_ASE_SNK_COUNT + CONFIG_BT_ASCS_ASE_SRC_COUNT)
|
|
|
|
|
|
|
|
struct bt_ascs_ase {
|
|
|
|
struct bt_ascs *ascs;
|
|
|
|
struct bt_audio_ep ep;
|
|
|
|
struct k_work work;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct bt_ascs {
|
|
|
|
struct bt_conn *conn;
|
|
|
|
struct bt_ascs_ase ases[ASE_COUNT];
|
2022-02-14 14:18:03 +01:00
|
|
|
/* A single iso_channel may be used for 1 or 2 ases.
|
|
|
|
* Controlled by the client.
|
|
|
|
*/
|
|
|
|
struct bt_audio_iso isos[ASE_COUNT];
|
2022-01-07 17:23:45 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct bt_ascs sessions[CONFIG_BT_MAX_CONN];
|
|
|
|
|
2022-10-14 14:27:04 +02:00
|
|
|
static struct bt_ascs *ascs_get(struct bt_conn *conn);
|
2022-09-02 10:53:21 +02:00
|
|
|
static struct bt_ascs_ase *ase_find(struct bt_ascs *ascs, uint8_t id);
|
2022-10-06 13:51:51 +02:00
|
|
|
static int control_point_notify(struct bt_conn *conn, const void *data, uint16_t len);
|
2022-09-02 10:53:21 +02:00
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
static void ase_status_changed(struct bt_audio_ep *ep, uint8_t old_state,
|
|
|
|
uint8_t state)
|
|
|
|
{
|
|
|
|
struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep);
|
|
|
|
|
|
|
|
BT_DBG("ase %p conn %p", ase, ase->ascs->conn);
|
|
|
|
|
|
|
|
k_work_submit(&ase->work);
|
|
|
|
}
|
|
|
|
|
2022-02-22 09:13:09 +01:00
|
|
|
static void ascs_ep_unbind_audio_iso(struct bt_audio_ep *ep)
|
|
|
|
{
|
|
|
|
struct bt_audio_iso *audio_iso = ep->iso;
|
2022-05-24 13:59:45 +02:00
|
|
|
struct bt_audio_stream *stream = ep->stream;
|
2022-02-22 09:13:09 +01:00
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
if (audio_iso != NULL) {
|
|
|
|
struct bt_iso_chan_qos *qos;
|
2022-06-10 10:56:12 +02:00
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
qos = audio_iso->iso_chan.qos;
|
2022-02-22 09:13:09 +01:00
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
BT_ASSERT_MSG(stream != NULL, "Stream was NULL");
|
2022-02-22 09:13:09 +01:00
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
stream->iso = NULL;
|
|
|
|
if (audio_iso->sink_stream == stream) {
|
|
|
|
audio_iso->sink_stream = NULL;
|
|
|
|
qos->rx = NULL;
|
|
|
|
} else if (audio_iso->source_stream == stream) {
|
|
|
|
audio_iso->source_stream = NULL;
|
|
|
|
qos->tx = NULL;
|
|
|
|
} else {
|
|
|
|
BT_ERR("Stream %p not linked to audio_iso %p", stream, audio_iso);
|
|
|
|
}
|
2022-02-22 09:13:09 +01:00
|
|
|
}
|
2022-05-24 13:59:45 +02:00
|
|
|
|
|
|
|
ep->iso = NULL;
|
2022-02-22 09:13:09 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
void ascs_ep_set_state(struct bt_audio_ep *ep, uint8_t state)
|
|
|
|
{
|
2022-03-09 13:42:10 +01:00
|
|
|
struct bt_audio_stream *stream;
|
2022-08-23 14:41:54 +02:00
|
|
|
bool state_changed;
|
2022-01-07 17:23:45 +01:00
|
|
|
uint8_t old_state;
|
|
|
|
|
|
|
|
if (!ep) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO: Verify state changes */
|
|
|
|
|
|
|
|
old_state = ep->status.state;
|
|
|
|
ep->status.state = state;
|
2022-08-23 14:41:54 +02:00
|
|
|
state_changed = old_state != state;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
BT_DBG("ep %p id 0x%02x %s -> %s", ep, ep->status.id,
|
|
|
|
bt_audio_ep_state_str(old_state),
|
|
|
|
bt_audio_ep_state_str(state));
|
|
|
|
|
2022-03-09 13:42:10 +01:00
|
|
|
/* Notify clients*/
|
2022-01-07 17:23:45 +01:00
|
|
|
ase_status_changed(ep, old_state, state);
|
|
|
|
|
2022-08-23 14:41:54 +02:00
|
|
|
if (ep->stream == NULL) {
|
2022-01-07 17:23:45 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-09 13:42:10 +01:00
|
|
|
stream = ep->stream;
|
|
|
|
|
|
|
|
if (stream->ops != NULL) {
|
|
|
|
const struct bt_audio_stream_ops *ops = stream->ops;
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
case BT_AUDIO_EP_STATE_IDLE:
|
|
|
|
if (ops->released != NULL) {
|
|
|
|
ops->released(stream);
|
|
|
|
}
|
2022-08-23 15:35:40 +02:00
|
|
|
|
2022-03-09 13:42:10 +01:00
|
|
|
break;
|
|
|
|
case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
|
2022-08-23 15:35:40 +02:00
|
|
|
switch (old_state) {
|
|
|
|
case BT_AUDIO_EP_STATE_IDLE:
|
|
|
|
case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
|
|
|
|
case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
|
|
|
|
case BT_AUDIO_EP_STATE_RELEASING:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
BT_ASSERT_MSG(false,
|
|
|
|
"Invalid state transition: %s -> %s",
|
|
|
|
bt_audio_ep_state_str(old_state),
|
|
|
|
bt_audio_ep_state_str(ep->status.state));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-09 13:42:10 +01:00
|
|
|
if (ops->configured != NULL) {
|
|
|
|
ops->configured(stream, &ep->qos_pref);
|
|
|
|
}
|
2022-08-23 15:35:40 +02:00
|
|
|
|
2022-03-09 13:42:10 +01:00
|
|
|
break;
|
|
|
|
case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
|
2022-08-23 15:35:40 +02:00
|
|
|
/* QoS configured have different allowed states
|
|
|
|
* depending on the endpoint type
|
|
|
|
*/
|
|
|
|
if (ep->dir == BT_AUDIO_DIR_SOURCE) {
|
|
|
|
switch (old_state) {
|
|
|
|
case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
|
|
|
|
case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
|
|
|
|
case BT_AUDIO_EP_STATE_DISABLING:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
BT_ASSERT_MSG(false,
|
|
|
|
"Invalid state transition: %s -> %s",
|
|
|
|
bt_audio_ep_state_str(old_state),
|
|
|
|
bt_audio_ep_state_str(ep->status.state));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch (old_state) {
|
|
|
|
case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
|
|
|
|
case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
|
|
|
|
case BT_AUDIO_EP_STATE_ENABLING:
|
|
|
|
case BT_AUDIO_EP_STATE_STREAMING:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
BT_ASSERT_MSG(false,
|
|
|
|
"Invalid state transition: %s -> %s",
|
|
|
|
bt_audio_ep_state_str(old_state),
|
|
|
|
bt_audio_ep_state_str(ep->status.state));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-09 13:42:10 +01:00
|
|
|
if (ops->qos_set != NULL) {
|
|
|
|
ops->qos_set(stream);
|
|
|
|
}
|
2022-08-23 15:35:40 +02:00
|
|
|
|
2022-03-09 13:42:10 +01:00
|
|
|
break;
|
|
|
|
case BT_AUDIO_EP_STATE_ENABLING:
|
2022-08-23 15:35:40 +02:00
|
|
|
switch (old_state) {
|
|
|
|
case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
|
|
|
|
case BT_AUDIO_EP_STATE_ENABLING:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
BT_ASSERT_MSG(false,
|
|
|
|
"Invalid state transition: %s -> %s",
|
|
|
|
bt_audio_ep_state_str(old_state),
|
|
|
|
bt_audio_ep_state_str(ep->status.state));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-08-23 14:41:54 +02:00
|
|
|
if (state_changed && ops->enabled != NULL) {
|
2022-03-09 13:42:10 +01:00
|
|
|
ops->enabled(stream);
|
2022-08-23 14:41:54 +02:00
|
|
|
} else if (!state_changed && ops->metadata_updated) {
|
|
|
|
ops->metadata_updated(stream);
|
2022-03-09 13:42:10 +01:00
|
|
|
}
|
2022-08-23 15:35:40 +02:00
|
|
|
|
2022-03-09 13:42:10 +01:00
|
|
|
break;
|
|
|
|
case BT_AUDIO_EP_STATE_STREAMING:
|
2022-08-23 15:35:40 +02:00
|
|
|
switch (old_state) {
|
|
|
|
case BT_AUDIO_EP_STATE_ENABLING:
|
|
|
|
case BT_AUDIO_EP_STATE_STREAMING:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
BT_ASSERT_MSG(false,
|
|
|
|
"Invalid state transition: %s -> %s",
|
|
|
|
bt_audio_ep_state_str(old_state),
|
|
|
|
bt_audio_ep_state_str(ep->status.state));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-08-23 14:41:54 +02:00
|
|
|
if (state_changed && ops->started != NULL) {
|
2022-03-09 13:42:10 +01:00
|
|
|
ops->started(stream);
|
2022-08-23 14:41:54 +02:00
|
|
|
} else if (!state_changed && ops->metadata_updated) {
|
|
|
|
ops->metadata_updated(stream);
|
2022-03-09 13:42:10 +01:00
|
|
|
}
|
2022-08-23 15:35:40 +02:00
|
|
|
|
2022-03-09 13:42:10 +01:00
|
|
|
break;
|
|
|
|
case BT_AUDIO_EP_STATE_DISABLING:
|
2022-08-23 15:35:40 +02:00
|
|
|
if (ep->dir == BT_AUDIO_DIR_SOURCE) {
|
|
|
|
switch (old_state) {
|
|
|
|
case BT_AUDIO_EP_STATE_ENABLING:
|
|
|
|
case BT_AUDIO_EP_STATE_STREAMING:
|
2022-10-06 12:57:38 +02:00
|
|
|
ep->receiver_ready = false;
|
2022-08-23 15:35:40 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
BT_ASSERT_MSG(false,
|
|
|
|
"Invalid state transition: %s -> %s",
|
|
|
|
bt_audio_ep_state_str(old_state),
|
|
|
|
bt_audio_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_audio_ep_state_str(old_state),
|
|
|
|
bt_audio_ep_state_str(ep->status.state));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-09 13:42:10 +01:00
|
|
|
if (ops->disabled != NULL) {
|
|
|
|
ops->disabled(stream);
|
|
|
|
}
|
2022-08-23 15:35:40 +02:00
|
|
|
|
2022-03-09 13:42:10 +01:00
|
|
|
break;
|
2022-04-21 14:08:40 +02:00
|
|
|
case BT_AUDIO_EP_STATE_RELEASING:
|
2022-08-23 15:35:40 +02:00
|
|
|
switch (old_state) {
|
|
|
|
case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
|
|
|
|
case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
|
|
|
|
case BT_AUDIO_EP_STATE_ENABLING:
|
|
|
|
case BT_AUDIO_EP_STATE_STREAMING:
|
2022-10-06 12:57:38 +02:00
|
|
|
ep->receiver_ready = false;
|
2022-08-23 15:35:40 +02:00
|
|
|
break;
|
|
|
|
case BT_AUDIO_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_audio_ep_state_str(old_state),
|
|
|
|
bt_audio_ep_state_str(ep->status.state));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-21 14:08:40 +02:00
|
|
|
break; /* no-op*/
|
2022-03-09 13:42:10 +01:00
|
|
|
default:
|
|
|
|
BT_ERR("Invalid state: %u", state);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-23 14:41:54 +02:00
|
|
|
if (state_changed &&
|
|
|
|
state == BT_AUDIO_EP_STATE_CODEC_CONFIGURED &&
|
2022-06-10 10:56:12 +02:00
|
|
|
old_state != BT_AUDIO_EP_STATE_IDLE) {
|
2022-02-22 09:13:09 +01:00
|
|
|
ascs_ep_unbind_audio_iso(ep);
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ascs_codec_data_add(struct net_buf_simple *buf, const char *prefix,
|
|
|
|
uint8_t num, struct bt_codec_data *data)
|
|
|
|
{
|
|
|
|
struct bt_ascs_codec_config *cc;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < num; i++) {
|
|
|
|
struct bt_data *d = &data[i].data;
|
|
|
|
|
|
|
|
BT_DBG("#%u: %s type 0x%02x len %u", i, prefix, d->type,
|
|
|
|
d->data_len);
|
2022-10-24 11:23:12 +02:00
|
|
|
LOG_HEXDUMP_DBG(d->data, d->data_len, prefix);
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
cc = net_buf_simple_add(buf, sizeof(*cc));
|
|
|
|
cc->len = d->data_len + sizeof(cc->type);
|
|
|
|
cc->type = d->type;
|
|
|
|
net_buf_simple_add_mem(buf, d->data, d->data_len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ascs_ep_get_status_config(struct bt_audio_ep *ep,
|
|
|
|
struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
struct bt_ascs_ase_status_config *cfg;
|
|
|
|
struct bt_codec_qos_pref *pref = &ep->qos_pref;
|
|
|
|
|
|
|
|
cfg = net_buf_simple_add(buf, sizeof(*cfg));
|
|
|
|
cfg->framing = pref->unframed_supported ? BT_ASCS_QOS_FRAMING_UNFRAMED
|
|
|
|
: BT_ASCS_QOS_FRAMING_FRAMED;
|
|
|
|
cfg->phy = pref->phy;
|
|
|
|
cfg->rtn = pref->rtn;
|
|
|
|
cfg->latency = sys_cpu_to_le16(pref->latency);
|
|
|
|
sys_put_le24(pref->pd_min, cfg->pd_min);
|
|
|
|
sys_put_le24(pref->pd_max, cfg->pd_max);
|
|
|
|
sys_put_le24(pref->pref_pd_min, cfg->prefer_pd_min);
|
|
|
|
sys_put_le24(pref->pref_pd_max, cfg->prefer_pd_max);
|
|
|
|
cfg->codec.id = ep->codec.id;
|
|
|
|
cfg->codec.cid = sys_cpu_to_le16(ep->codec.cid);
|
|
|
|
cfg->codec.vid = sys_cpu_to_le16(ep->codec.vid);
|
|
|
|
|
|
|
|
BT_DBG("dir 0x%02x unframed_supported 0x%02x phy 0x%02x rtn %u "
|
|
|
|
"latency %u pd_min %u pd_max %u codec 0x%02x",
|
2022-02-03 12:57:55 +01:00
|
|
|
ep->dir, pref->unframed_supported, pref->phy,
|
2022-01-07 17:23:45 +01:00
|
|
|
pref->rtn, pref->latency, pref->pd_min, pref->pd_max,
|
|
|
|
ep->stream->codec->id);
|
|
|
|
|
|
|
|
cfg->cc_len = buf->len;
|
|
|
|
ascs_codec_data_add(buf, "data", ep->codec.data_count, ep->codec.data);
|
|
|
|
cfg->cc_len = buf->len - cfg->cc_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ascs_ep_get_status_qos(struct bt_audio_ep *ep,
|
|
|
|
struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
struct bt_ascs_ase_status_qos *qos;
|
|
|
|
|
|
|
|
qos = net_buf_simple_add(buf, sizeof(*qos));
|
|
|
|
qos->cig_id = ep->cig_id;
|
|
|
|
qos->cis_id = ep->cis_id;
|
|
|
|
sys_put_le24(ep->stream->qos->interval, qos->interval);
|
|
|
|
qos->framing = ep->stream->qos->framing;
|
|
|
|
qos->phy = ep->stream->qos->phy;
|
|
|
|
qos->sdu = sys_cpu_to_le16(ep->stream->qos->sdu);
|
|
|
|
qos->rtn = ep->stream->qos->rtn;
|
|
|
|
qos->latency = sys_cpu_to_le16(ep->stream->qos->latency);
|
|
|
|
sys_put_le24(ep->stream->qos->pd, qos->pd);
|
|
|
|
|
|
|
|
BT_DBG("dir 0x%02x codec 0x%02x interval %u framing 0x%02x phy 0x%02x "
|
|
|
|
"rtn %u latency %u pd %u",
|
2022-02-03 12:57:55 +01:00
|
|
|
ep->dir, ep->stream->codec->id,
|
2022-01-07 17:23:45 +01:00
|
|
|
ep->stream->qos->interval, ep->stream->qos->framing,
|
|
|
|
ep->stream->qos->phy, ep->stream->qos->rtn,
|
|
|
|
ep->stream->qos->latency, ep->stream->qos->pd);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ascs_ep_get_status_enable(struct bt_audio_ep *ep,
|
|
|
|
struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
struct bt_ascs_ase_status_enable *enable;
|
|
|
|
|
|
|
|
enable = net_buf_simple_add(buf, sizeof(*enable));
|
|
|
|
enable->cig_id = ep->cig_id;
|
|
|
|
enable->cis_id = ep->cis_id;
|
|
|
|
|
|
|
|
enable->metadata_len = buf->len;
|
|
|
|
ascs_codec_data_add(buf, "meta", ep->codec.meta_count, ep->codec.meta);
|
|
|
|
enable->metadata_len = buf->len - enable->metadata_len;
|
|
|
|
|
|
|
|
BT_DBG("dir 0x%02x cig 0x%02x cis 0x%02x",
|
2022-02-03 12:57:55 +01:00
|
|
|
ep->dir, ep->cig_id, ep->cis_id);
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ascs_ep_get_status(struct bt_audio_ep *ep,
|
|
|
|
struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
struct bt_ascs_ase_status *status;
|
|
|
|
|
|
|
|
if (!ep || !buf) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
BT_DBG("ep %p id 0x%02x state %s", ep, ep->status.id,
|
|
|
|
bt_audio_ep_state_str(ep->status.state));
|
|
|
|
|
|
|
|
/* Reset if buffer before using */
|
|
|
|
net_buf_simple_reset(buf);
|
|
|
|
|
|
|
|
status = net_buf_simple_add_mem(buf, &ep->status,
|
|
|
|
sizeof(ep->status));
|
|
|
|
|
|
|
|
switch (ep->status.state) {
|
|
|
|
case BT_AUDIO_EP_STATE_IDLE:
|
|
|
|
/* Fallthrough */
|
|
|
|
case BT_AUDIO_EP_STATE_RELEASING:
|
|
|
|
break;
|
|
|
|
case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
|
|
|
|
ascs_ep_get_status_config(ep, buf);
|
|
|
|
break;
|
|
|
|
case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
|
|
|
|
ascs_ep_get_status_qos(ep, buf);
|
|
|
|
break;
|
|
|
|
case BT_AUDIO_EP_STATE_ENABLING:
|
|
|
|
/* Fallthrough */
|
|
|
|
case BT_AUDIO_EP_STATE_STREAMING:
|
|
|
|
/* Fallthrough */
|
|
|
|
case BT_AUDIO_EP_STATE_DISABLING:
|
|
|
|
ascs_ep_get_status_enable(ep, buf);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
BT_ERR("Invalid Endpoint state");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ascs_iso_recv(struct bt_iso_chan *chan,
|
|
|
|
const struct bt_iso_recv_info *info,
|
|
|
|
struct net_buf *buf)
|
|
|
|
{
|
2022-02-14 14:18:03 +01:00
|
|
|
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
|
|
|
|
iso_chan);
|
2022-05-24 13:59:45 +02:00
|
|
|
struct bt_audio_stream *stream = audio_iso->sink_stream;
|
2022-02-14 14:18:03 +01:00
|
|
|
const struct bt_audio_stream_ops *ops;
|
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
if (stream == NULL) {
|
|
|
|
BT_ERR("Could not lookup stream by iso %p", chan);
|
|
|
|
return;
|
|
|
|
} else if (stream->ep == NULL) {
|
|
|
|
BT_ERR("Stream not associated with an ep");
|
2022-02-14 14:18:03 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-10-05 15:04:29 +02:00
|
|
|
/* Since 2 streams can share the same CIS, the CIS may be connected and
|
|
|
|
* capable of transferring data, without the bt_audio_stream being in
|
|
|
|
* the streaming state. In that case we simply ignore the data.
|
|
|
|
*/
|
|
|
|
if (stream->ep->status.state != BT_AUDIO_EP_STATE_STREAMING) {
|
|
|
|
if (IS_ENABLED(CONFIG_BT_AUDIO_DEBUG_STREAM_DATA)) {
|
|
|
|
BT_DBG("Stream %p is not in the streaming state: %u",
|
|
|
|
stream, stream->ep->status.state);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
ops = stream->ops;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-07-07 16:40:36 +02:00
|
|
|
if (IS_ENABLED(CONFIG_BT_AUDIO_DEBUG_STREAM_DATA)) {
|
|
|
|
BT_DBG("stream %p ep %p len %zu",
|
2022-05-24 13:59:45 +02:00
|
|
|
stream, stream->ep, net_buf_frags_len(buf));
|
2022-07-07 16:40:36 +02:00
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
if (ops != NULL && ops->recv != NULL) {
|
2022-05-24 13:59:45 +02:00
|
|
|
ops->recv(stream, info, buf);
|
2022-01-20 15:03:00 +01:00
|
|
|
} else {
|
|
|
|
BT_WARN("No callback for recv set");
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-07 14:18:26 +01:00
|
|
|
static void ascs_iso_sent(struct bt_iso_chan *chan)
|
|
|
|
{
|
2022-06-08 14:53:36 +02:00
|
|
|
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
|
|
|
|
iso_chan);
|
2022-05-24 13:59:45 +02:00
|
|
|
struct bt_audio_stream *stream = audio_iso->source_stream;
|
|
|
|
struct bt_audio_stream_ops *ops = stream->ops;
|
2022-03-07 14:18:26 +01:00
|
|
|
|
2022-09-26 17:15:24 +02:00
|
|
|
if (IS_ENABLED(CONFIG_BT_AUDIO_DEBUG_STREAM_DATA)) {
|
|
|
|
BT_DBG("stream %p ep %p", stream, stream->ep);
|
|
|
|
}
|
2022-03-07 14:18:26 +01:00
|
|
|
|
|
|
|
if (ops != NULL && ops->sent != NULL) {
|
2022-05-24 13:59:45 +02:00
|
|
|
ops->sent(stream);
|
2022-03-07 14:18:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-06 12:57:38 +02:00
|
|
|
static int ase_stream_start(struct bt_audio_stream *stream)
|
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
if (unicast_server_cb != NULL && unicast_server_cb->start != NULL) {
|
|
|
|
err = unicast_server_cb->start(stream);
|
|
|
|
} else {
|
|
|
|
err = -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
ascs_ep_set_state(stream->ep, BT_AUDIO_EP_STATE_STREAMING);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
static void ascs_iso_connected(struct bt_iso_chan *chan)
|
|
|
|
{
|
2022-02-14 14:18:03 +01:00
|
|
|
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
|
|
|
|
iso_chan);
|
2022-05-24 13:59:45 +02:00
|
|
|
struct bt_audio_stream *source_stream = audio_iso->source_stream;
|
|
|
|
struct bt_audio_stream *sink_stream = audio_iso->sink_stream;
|
|
|
|
struct bt_audio_stream *stream;
|
2022-02-21 13:42:57 +01:00
|
|
|
struct bt_audio_ep *ep;
|
2022-10-06 12:57:38 +02:00
|
|
|
int err;
|
2022-02-21 13:42:57 +01:00
|
|
|
|
2022-08-15 13:23:48 +02:00
|
|
|
if (sink_stream != NULL && sink_stream->iso == chan) {
|
2022-05-24 13:59:45 +02:00
|
|
|
stream = sink_stream;
|
2022-08-15 13:23:48 +02:00
|
|
|
} else if (source_stream != NULL && source_stream->iso == chan) {
|
2022-05-24 13:59:45 +02:00
|
|
|
stream = source_stream;
|
2022-08-15 13:23:48 +02:00
|
|
|
} else {
|
|
|
|
stream = NULL;
|
2022-02-21 13:42:57 +01:00
|
|
|
}
|
2022-02-14 14:18:03 +01:00
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
if (stream == NULL) {
|
|
|
|
BT_ERR("Could not lookup stream by iso %p", chan);
|
|
|
|
return;
|
|
|
|
} else if (stream->ep == NULL) {
|
|
|
|
BT_ERR("Stream not associated with an ep");
|
2022-02-14 14:18:03 +01:00
|
|
|
return;
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
ep = stream->ep;
|
|
|
|
|
|
|
|
BT_DBG("stream %p ep %p dir %u", stream, ep, ep->dir);
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
if (ep->status.state != BT_AUDIO_EP_STATE_ENABLING) {
|
|
|
|
BT_DBG("endpoint not in enabling state: %s",
|
|
|
|
bt_audio_ep_state_str(ep->status.state));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-10-06 12:57:38 +02:00
|
|
|
if (ep->dir == BT_AUDIO_DIR_SOURCE && !ep->receiver_ready) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ase_stream_start(stream);
|
|
|
|
if (err) {
|
|
|
|
BT_ERR("Could not start stream %d", err);
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void ascs_iso_disconnected(struct bt_iso_chan *chan, uint8_t reason)
|
|
|
|
{
|
2022-02-14 14:18:03 +01:00
|
|
|
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
|
|
|
|
iso_chan);
|
2022-05-24 13:59:45 +02:00
|
|
|
struct bt_audio_stream *source_stream = audio_iso->source_stream;
|
|
|
|
struct bt_audio_stream *sink_stream = audio_iso->sink_stream;
|
2022-02-14 14:18:03 +01:00
|
|
|
const struct bt_audio_stream_ops *ops;
|
|
|
|
struct bt_audio_stream *stream;
|
2022-02-21 13:42:57 +01:00
|
|
|
struct bt_audio_ep *ep;
|
|
|
|
|
2022-08-15 13:23:48 +02:00
|
|
|
if (sink_stream != NULL && sink_stream->iso == chan) {
|
2022-05-24 13:59:45 +02:00
|
|
|
stream = sink_stream;
|
2022-08-15 13:23:48 +02:00
|
|
|
} else if (source_stream != NULL && source_stream->iso == chan) {
|
2022-05-24 13:59:45 +02:00
|
|
|
stream = source_stream;
|
2022-08-15 13:23:48 +02:00
|
|
|
} else {
|
|
|
|
stream = NULL;
|
2022-02-21 13:42:57 +01:00
|
|
|
}
|
2022-02-14 14:18:03 +01:00
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
if (stream == NULL) {
|
|
|
|
BT_ERR("Could not lookup stream by iso %p", chan);
|
|
|
|
return;
|
|
|
|
} else if (stream->ep == NULL) {
|
|
|
|
BT_ERR("Stream not associated with an ep");
|
2022-02-14 14:18:03 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
ops = stream->ops;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
BT_DBG("stream %p ep %p reason 0x%02x", stream, stream->ep, reason);
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-02-02 17:12:30 +01:00
|
|
|
if (ops != NULL && ops->stopped != NULL) {
|
|
|
|
ops->stopped(stream);
|
2022-01-20 15:03:00 +01:00
|
|
|
} else {
|
2022-02-02 17:12:30 +01:00
|
|
|
BT_WARN("No callback for stopped set");
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
ep = stream->ep;
|
2022-03-21 09:17:45 +01:00
|
|
|
if (ep->status.state == BT_AUDIO_EP_STATE_RELEASING) {
|
2022-09-02 10:53:21 +02:00
|
|
|
struct bt_ascs_ase *ase;
|
2022-10-14 14:27:04 +02:00
|
|
|
struct bt_ascs *ascs = ascs_get(stream->conn);
|
2022-09-02 10:53:21 +02:00
|
|
|
|
|
|
|
ase = ase_find(ascs, ep->status.id);
|
|
|
|
if (ase == NULL) {
|
|
|
|
BT_WARN("Could find ASE based on ASCS %p with ep %p",
|
|
|
|
ascs, ep);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Trigger a call to ase_process to handle the cleanup */
|
|
|
|
k_work_submit(&ase->work);
|
2022-03-21 09:17:45 +01:00
|
|
|
} else {
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* The ASE state machine goes into different states from this operation
|
|
|
|
* based on whether it is a source or a sink ASE.
|
|
|
|
*/
|
2022-03-29 18:20:10 +02:00
|
|
|
if (ep->dir == BT_AUDIO_DIR_SOURCE) {
|
2022-03-21 09:17:45 +01:00
|
|
|
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_DISABLING);
|
|
|
|
} else {
|
|
|
|
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_QOS_CONFIGURED);
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-03-21 09:17:45 +01:00
|
|
|
err = bt_audio_stream_iso_listen(stream);
|
|
|
|
if (err != 0) {
|
|
|
|
BT_ERR("Could not make stream listen: %d", err);
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct bt_iso_chan_ops ascs_iso_ops = {
|
|
|
|
.recv = ascs_iso_recv,
|
2022-03-07 14:18:26 +01:00
|
|
|
.sent = ascs_iso_sent,
|
2022-01-07 17:23:45 +01:00
|
|
|
.connected = ascs_iso_connected,
|
|
|
|
.disconnected = ascs_iso_disconnected,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void ascs_ase_cfg_changed(const struct bt_gatt_attr *attr,
|
|
|
|
uint16_t value)
|
|
|
|
{
|
|
|
|
BT_DBG("attr %p value 0x%04x", attr, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
NET_BUF_SIMPLE_DEFINE_STATIC(rsp_buf, CONFIG_BT_L2CAP_TX_MTU);
|
|
|
|
|
|
|
|
static void ascs_cp_rsp_alloc(uint8_t op)
|
|
|
|
{
|
|
|
|
struct bt_ascs_cp_rsp *rsp;
|
|
|
|
|
|
|
|
rsp = net_buf_simple_add(&rsp_buf, sizeof(*rsp));
|
|
|
|
rsp->op = op;
|
|
|
|
rsp->num_ase = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add response to an opcode/ASE ID */
|
|
|
|
static void ascs_cp_rsp_add(uint8_t id, uint8_t op, uint8_t code,
|
|
|
|
uint8_t reason)
|
|
|
|
{
|
|
|
|
struct bt_ascs_cp_rsp *rsp = (void *)rsp_buf.__buf;
|
|
|
|
struct bt_ascs_cp_ase_rsp *ase_rsp;
|
|
|
|
|
|
|
|
BT_DBG("id 0x%02x op %s (0x%02x) code %s (0x%02x) reason %s (0x%02x)",
|
|
|
|
id, bt_ascs_op_str(op), op, bt_ascs_rsp_str(code), code,
|
|
|
|
bt_ascs_reason_str(reason), reason);
|
|
|
|
|
|
|
|
/* Allocate response if buffer is empty */
|
|
|
|
if (!rsp_buf.len) {
|
|
|
|
ascs_cp_rsp_alloc(op);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rsp->num_ase == 0xff) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (code) {
|
|
|
|
/* If the Response_Code value is 0x01 or 0x02, Number_of_ASEs shall be
|
|
|
|
* set to 0xFF.
|
|
|
|
*/
|
|
|
|
case BT_ASCS_RSP_NOT_SUPPORTED:
|
|
|
|
case BT_ASCS_RSP_TRUNCATED:
|
|
|
|
rsp->num_ase = 0xff;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rsp->num_ase++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ase_rsp = net_buf_simple_add(&rsp_buf, sizeof(*ase_rsp));
|
|
|
|
ase_rsp->id = id;
|
|
|
|
ase_rsp->code = code;
|
|
|
|
ase_rsp->reason = reason;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ascs_cp_rsp_add_errno(uint8_t id, uint8_t op, int err,
|
|
|
|
uint8_t reason)
|
|
|
|
{
|
2022-09-02 10:53:21 +02:00
|
|
|
BT_DBG("id %u op %u err %d reason %u", id, op, err, reason);
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
switch (err) {
|
|
|
|
case -ENOBUFS:
|
|
|
|
case -ENOMEM:
|
|
|
|
return ascs_cp_rsp_add(id, op, BT_ASCS_RSP_NO_MEM,
|
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
case -EINVAL:
|
|
|
|
switch (op) {
|
|
|
|
case BT_ASCS_CONFIG_OP:
|
|
|
|
/* Fallthrough */
|
|
|
|
case BT_ASCS_QOS_OP:
|
|
|
|
return ascs_cp_rsp_add(id, op,
|
|
|
|
BT_ASCS_RSP_CONF_INVALID,
|
|
|
|
reason);
|
|
|
|
case BT_ASCS_ENABLE_OP:
|
|
|
|
/* Fallthrough */
|
|
|
|
case BT_ASCS_METADATA_OP:
|
|
|
|
return ascs_cp_rsp_add(id, op,
|
|
|
|
BT_ASCS_RSP_METADATA_INVALID,
|
|
|
|
reason);
|
|
|
|
default:
|
|
|
|
return ascs_cp_rsp_add(id, op, BT_ASCS_RSP_UNSPECIFIED,
|
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
}
|
|
|
|
case -ENOTSUP:
|
|
|
|
switch (op) {
|
|
|
|
case BT_ASCS_CONFIG_OP:
|
|
|
|
/* Fallthrough */
|
|
|
|
case BT_ASCS_QOS_OP:
|
|
|
|
return ascs_cp_rsp_add(id, op,
|
|
|
|
BT_ASCS_RSP_CONF_UNSUPPORTED,
|
|
|
|
reason);
|
|
|
|
case BT_ASCS_ENABLE_OP:
|
|
|
|
/* Fallthrough */
|
|
|
|
case BT_ASCS_METADATA_OP:
|
|
|
|
return ascs_cp_rsp_add(id, op,
|
|
|
|
BT_ASCS_RSP_METADATA_UNSUPPORTED,
|
|
|
|
reason);
|
|
|
|
default:
|
|
|
|
return ascs_cp_rsp_add(id, op,
|
|
|
|
BT_ASCS_RSP_NOT_SUPPORTED,
|
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
}
|
|
|
|
case -EBADMSG:
|
|
|
|
return ascs_cp_rsp_add(id, op, BT_ASCS_RSP_INVALID_ASE_STATE,
|
|
|
|
BT_ASCS_REASON_NONE);
|
2022-04-05 11:50:38 +02:00
|
|
|
case -EACCES:
|
|
|
|
switch (op) {
|
|
|
|
case BT_ASCS_METADATA_OP:
|
|
|
|
return ascs_cp_rsp_add(id, op,
|
|
|
|
BT_ASCS_RSP_METADATA_REJECTED,
|
|
|
|
reason);
|
|
|
|
default:
|
|
|
|
return ascs_cp_rsp_add(id, op, BT_ASCS_RSP_UNSPECIFIED,
|
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
default:
|
|
|
|
return ascs_cp_rsp_add(id, op, BT_ASCS_RSP_UNSPECIFIED,
|
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ascs_cp_rsp_success(uint8_t id, uint8_t op)
|
|
|
|
{
|
|
|
|
ascs_cp_rsp_add(id, op, BT_ASCS_RSP_SUCCESS, BT_ASCS_REASON_NONE);
|
|
|
|
}
|
|
|
|
|
Bluetooth: Audio: Remove caching ASE on release
Remove the support for caching ASEs on release.
This is mainly due to the 2-stage design in Zephyr: We
have the bt_audio_stream and the bt_audio_ep, where the
the latter represents an ASE in most cases.
However, once the endpoint goes into the codec configured
state, then a stream must be bound to it. This would be fine
(although a stream is technically not established at this point),
except that a stream is also, for unicast, specified for a
specific ACL. Once the ACL disconnects, the stream technically
becomes invalid, and if the stream is invalid, so is the endpoint.
Based on that, there's no reason (or even option) to keep the
endpoint in a codec configured state, as it would not
have a valid stream associated with it.
If we want to support an ASE going into the codec
configured state on ASE release (or ACL disconnect),
the relationsship between ASEs and streams needs to
be redefined.
Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
2022-09-01 16:14:15 +02:00
|
|
|
static void ase_release(struct bt_ascs_ase *ase)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
2022-09-02 10:53:21 +02:00
|
|
|
BT_DBG("ase %p state %s",
|
|
|
|
ase, bt_audio_ep_state_str(ase->ep.status.state));
|
2022-01-07 17:23:45 +01:00
|
|
|
|
Bluetooth: Audio: Remove caching ASE on release
Remove the support for caching ASEs on release.
This is mainly due to the 2-stage design in Zephyr: We
have the bt_audio_stream and the bt_audio_ep, where the
the latter represents an ASE in most cases.
However, once the endpoint goes into the codec configured
state, then a stream must be bound to it. This would be fine
(although a stream is technically not established at this point),
except that a stream is also, for unicast, specified for a
specific ACL. Once the ACL disconnects, the stream technically
becomes invalid, and if the stream is invalid, so is the endpoint.
Based on that, there's no reason (or even option) to keep the
endpoint in a codec configured state, as it would not
have a valid stream associated with it.
If we want to support an ASE going into the codec
configured state on ASE release (or ACL disconnect),
the relationsship between ASEs and streams needs to
be redefined.
Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
2022-09-01 16:14:15 +02:00
|
|
|
if (ase->ep.status.state == BT_AUDIO_EP_STATE_RELEASING) {
|
|
|
|
/* already releasing */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
if (unicast_server_cb != NULL && unicast_server_cb->release != NULL) {
|
|
|
|
err = unicast_server_cb->release(ase->ep.stream);
|
|
|
|
} else {
|
|
|
|
err = -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_RELEASE_OP, err,
|
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
Bluetooth: Audio: Remove caching ASE on release
Remove the support for caching ASEs on release.
This is mainly due to the 2-stage design in Zephyr: We
have the bt_audio_stream and the bt_audio_ep, where the
the latter represents an ASE in most cases.
However, once the endpoint goes into the codec configured
state, then a stream must be bound to it. This would be fine
(although a stream is technically not established at this point),
except that a stream is also, for unicast, specified for a
specific ACL. Once the ACL disconnects, the stream technically
becomes invalid, and if the stream is invalid, so is the endpoint.
Based on that, there's no reason (or even option) to keep the
endpoint in a codec configured state, as it would not
have a valid stream associated with it.
If we want to support an ASE going into the codec
configured state on ASE release (or ACL disconnect),
the relationsship between ASEs and streams needs to
be redefined.
Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
2022-09-01 16:14:15 +02:00
|
|
|
ascs_ep_set_state(&ase->ep, BT_AUDIO_EP_STATE_RELEASING);
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
ascs_cp_rsp_success(ASE_ID(ase), BT_ASCS_RELEASE_OP);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ase_disable(struct bt_ascs_ase *ase)
|
|
|
|
{
|
|
|
|
struct bt_audio_stream *stream;
|
|
|
|
struct bt_audio_ep *ep;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
BT_DBG("ase %p", ase);
|
|
|
|
|
|
|
|
ep = &ase->ep;
|
|
|
|
|
|
|
|
switch (ep->status.state) {
|
|
|
|
/* Valid only if ASE_State field = 0x03 (Enabling) */
|
|
|
|
case BT_AUDIO_EP_STATE_ENABLING:
|
|
|
|
/* or 0x04 (Streaming) */
|
|
|
|
case BT_AUDIO_EP_STATE_STREAMING:
|
|
|
|
break;
|
|
|
|
default:
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Invalid operation in state: %s", bt_audio_ep_state_str(ep->status.state));
|
2022-01-07 17:23:45 +01:00
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_DISABLE_OP,
|
|
|
|
-EBADMSG, BT_ASCS_REASON_NONE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
stream = ep->stream;
|
|
|
|
|
|
|
|
if (unicast_server_cb != NULL && unicast_server_cb->disable != NULL) {
|
|
|
|
err = unicast_server_cb->disable(stream);
|
|
|
|
} else {
|
|
|
|
err = -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
BT_ERR("Disable failed: %d", err);
|
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_DISABLE_OP,
|
|
|
|
err, BT_ASCS_REASON_NONE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The ASE state machine goes into different states from this operation
|
|
|
|
* based on whether it is a source or a sink ASE.
|
|
|
|
*/
|
2022-03-29 18:20:10 +02:00
|
|
|
if (ep->dir == BT_AUDIO_DIR_SOURCE) {
|
2022-01-07 17:23:45 +01:00
|
|
|
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_DISABLING);
|
|
|
|
} else {
|
|
|
|
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_QOS_CONFIGURED);
|
|
|
|
}
|
|
|
|
|
|
|
|
ascs_cp_rsp_success(ASE_ID(ase), BT_ASCS_DISABLE_OP);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void disconnected(struct bt_conn *conn, uint8_t reason)
|
|
|
|
{
|
2022-10-14 14:27:04 +02:00
|
|
|
struct bt_ascs *session = &sessions[bt_conn_index(conn)];
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-10-14 14:27:04 +02:00
|
|
|
if (session->conn == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-10-14 14:27:04 +02:00
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(session->ases); i++) {
|
|
|
|
struct bt_ascs_ase *ase = &session->ases[i];
|
|
|
|
struct bt_audio_stream *stream = ase->ep.stream;
|
2022-06-15 18:02:14 +02:00
|
|
|
|
2022-10-14 14:27:04 +02:00
|
|
|
if (ase->ep.status.state != BT_AUDIO_EP_STATE_IDLE) {
|
|
|
|
/* ase_process will handle the final state transition into idle state */
|
|
|
|
ase_release(ase);
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
2022-10-14 14:27:04 +02:00
|
|
|
if (stream != NULL && stream->conn != NULL) {
|
|
|
|
bt_conn_unref(stream->conn);
|
|
|
|
stream->conn = NULL;
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
}
|
2022-10-14 14:27:04 +02:00
|
|
|
|
|
|
|
bt_conn_unref(session->conn);
|
|
|
|
session->conn = NULL;
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
2022-10-14 14:27:04 +02:00
|
|
|
BT_CONN_CB_DEFINE(conn_cb) = {
|
2022-01-07 17:23:45 +01:00
|
|
|
.disconnected = disconnected,
|
|
|
|
};
|
|
|
|
|
2022-08-05 11:48:48 +02:00
|
|
|
static struct bt_audio_iso *audio_iso_get_or_new(struct bt_ascs *ascs, uint8_t cig_id,
|
|
|
|
uint8_t cis_id)
|
2022-02-22 09:13:09 +01:00
|
|
|
{
|
2022-05-24 13:59:45 +02:00
|
|
|
struct bt_audio_iso *free_audio_iso = NULL;
|
2022-02-22 09:13:09 +01:00
|
|
|
|
2022-08-05 11:48:48 +02:00
|
|
|
BT_DBG("ascs %p cig_id 0x%02x cis_id 0x%02x", ascs, cis_id, cis_id);
|
2022-06-10 10:56:12 +02:00
|
|
|
|
2022-02-22 09:13:09 +01:00
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(ascs->isos); i++) {
|
|
|
|
struct bt_audio_iso *audio_iso = &ascs->isos[i];
|
2022-08-05 11:48:48 +02:00
|
|
|
const struct bt_audio_ep *ep;
|
2022-05-24 13:59:45 +02:00
|
|
|
|
2022-08-05 11:48:48 +02:00
|
|
|
if (audio_iso->sink_stream == NULL && audio_iso->source_stream == NULL) {
|
|
|
|
free_audio_iso = audio_iso;
|
|
|
|
continue;
|
|
|
|
} else if (audio_iso->sink_stream && audio_iso->sink_stream->ep) {
|
|
|
|
ep = audio_iso->sink_stream->ep;
|
|
|
|
} else if (audio_iso->source_stream && audio_iso->source_stream->ep) {
|
|
|
|
ep = audio_iso->source_stream->ep;
|
|
|
|
} else {
|
|
|
|
/* XXX: Stream not associated with endpoint. Should we assert??? */
|
|
|
|
continue;
|
|
|
|
}
|
2022-05-24 13:59:45 +02:00
|
|
|
|
2022-08-05 11:48:48 +02:00
|
|
|
if (ep->cig_id == cig_id && ep->cis_id == cis_id) {
|
2022-05-24 13:59:45 +02:00
|
|
|
return audio_iso;
|
|
|
|
}
|
2022-02-22 09:13:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return free_audio_iso;
|
|
|
|
}
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
static void ase_stream_add(struct bt_ascs *ascs, struct bt_ascs_ase *ase,
|
|
|
|
struct bt_audio_stream *stream)
|
|
|
|
{
|
|
|
|
BT_DBG("ase %p stream %p", ase, stream);
|
|
|
|
ase->ep.stream = stream;
|
|
|
|
stream->conn = ascs->conn;
|
|
|
|
stream->ep = &ase->ep;
|
2022-02-14 14:18:03 +01:00
|
|
|
stream->iso = &ase->ep.iso->iso_chan;
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct bt_ascs *ascs_get(struct bt_conn *conn)
|
|
|
|
{
|
2022-10-14 14:27:04 +02:00
|
|
|
struct bt_ascs *session = &sessions[bt_conn_index(conn)];
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-10-14 14:27:04 +02:00
|
|
|
if (session->conn == NULL) {
|
|
|
|
session->conn = bt_conn_ref(conn);
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
2022-10-14 14:27:04 +02:00
|
|
|
return session;
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
NET_BUF_SIMPLE_DEFINE_STATIC(ase_buf, CONFIG_BT_L2CAP_TX_MTU);
|
|
|
|
|
|
|
|
static void ase_process(struct k_work *work)
|
|
|
|
{
|
|
|
|
struct bt_ascs_ase *ase = CONTAINER_OF(work, struct bt_ascs_ase, work);
|
2022-09-29 15:29:17 +02:00
|
|
|
struct bt_audio_ep *ep = &ase->ep;
|
|
|
|
const struct bt_audio_iso *audio_iso = ep->iso;
|
|
|
|
struct bt_audio_stream *stream = ep->stream;
|
|
|
|
const uint8_t ep_state = ep->status.state;
|
|
|
|
struct bt_conn *conn = ase->ascs->conn;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-09-29 15:29:17 +02:00
|
|
|
BT_DBG("ase %p, ep %p, ep.stream %p", ase, ep, stream);
|
2022-04-22 10:51:07 +02:00
|
|
|
|
2022-09-29 15:29:17 +02:00
|
|
|
if (conn != NULL && conn->state == BT_CONN_CONNECTED) {
|
|
|
|
ascs_ep_get_status(ep, &ase_buf);
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-09-29 15:29:17 +02:00
|
|
|
bt_gatt_notify(conn, ep->server.attr,
|
Bluetooth: Audio: Remove caching ASE on release
Remove the support for caching ASEs on release.
This is mainly due to the 2-stage design in Zephyr: We
have the bt_audio_stream and the bt_audio_ep, where the
the latter represents an ASE in most cases.
However, once the endpoint goes into the codec configured
state, then a stream must be bound to it. This would be fine
(although a stream is technically not established at this point),
except that a stream is also, for unicast, specified for a
specific ACL. Once the ACL disconnects, the stream technically
becomes invalid, and if the stream is invalid, so is the endpoint.
Based on that, there's no reason (or even option) to keep the
endpoint in a codec configured state, as it would not
have a valid stream associated with it.
If we want to support an ASE going into the codec
configured state on ASE release (or ACL disconnect),
the relationsship between ASEs and streams needs to
be redefined.
Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
2022-09-01 16:14:15 +02:00
|
|
|
ase_buf.data, ase_buf.len);
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-09-29 15:29:17 +02:00
|
|
|
/* 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");
|
2022-09-02 10:53:21 +02:00
|
|
|
|
2022-09-29 15:29:17 +02:00
|
|
|
if (ep_state == BT_AUDIO_EP_STATE_RELEASING) {
|
2022-09-02 10:53:21 +02:00
|
|
|
|
2022-09-29 15:29:17 +02:00
|
|
|
if (audio_iso == NULL ||
|
|
|
|
audio_iso->iso_chan.state == BT_ISO_STATE_DISCONNECTED) {
|
|
|
|
ascs_ep_unbind_audio_iso(ep);
|
2022-09-02 10:53:21 +02:00
|
|
|
bt_audio_stream_detach(stream);
|
2022-09-29 15:29:17 +02:00
|
|
|
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_IDLE);
|
2022-09-02 10:53:21 +02:00
|
|
|
} else {
|
|
|
|
/* Either the client or the server may disconnect the
|
|
|
|
* CISes when entering the releasing state.
|
|
|
|
*/
|
|
|
|
const int err = bt_audio_stream_disconnect(stream);
|
2022-07-27 12:44:15 +02:00
|
|
|
|
2022-09-02 10:53:21 +02:00
|
|
|
if (err != 0) {
|
|
|
|
BT_ERR("Failed to disconnect stream %p: %d",
|
|
|
|
stream, err);
|
|
|
|
}
|
|
|
|
}
|
2022-09-29 15:29:17 +02:00
|
|
|
} 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 &&
|
|
|
|
audio_iso != NULL &&
|
|
|
|
audio_iso->iso_chan.state == BT_ISO_STATE_CONNECTED) {
|
|
|
|
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_STREAMING);
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint8_t ase_attr_cb(const struct bt_gatt_attr *attr, uint16_t handle,
|
|
|
|
void *user_data)
|
|
|
|
{
|
|
|
|
struct bt_ascs_ase *ase = user_data;
|
|
|
|
|
2022-07-25 09:56:23 +02:00
|
|
|
if (ase->ep.status.id == POINTER_TO_UINT(BT_AUDIO_CHRC_USER_DATA(attr))) {
|
2022-07-26 17:27:49 +02:00
|
|
|
ase->ep.server.attr = attr;
|
|
|
|
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
return BT_GATT_ITER_CONTINUE;
|
|
|
|
}
|
|
|
|
|
2022-08-05 11:48:48 +02:00
|
|
|
static int ascs_ep_stream_bind_audio_iso(struct bt_audio_stream *stream,
|
|
|
|
struct bt_audio_iso *audio_iso)
|
2022-02-22 09:13:09 +01:00
|
|
|
{
|
2022-05-24 13:59:45 +02:00
|
|
|
const enum bt_audio_dir dir = stream->ep->dir;
|
2022-06-10 10:56:12 +02:00
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
BT_DBG("stream %p, dir %u audio_iso %p", stream, dir, audio_iso);
|
2022-02-22 09:13:09 +01:00
|
|
|
|
|
|
|
if (dir == BT_AUDIO_DIR_SOURCE) {
|
2022-08-05 11:48:48 +02:00
|
|
|
if (audio_iso->source_stream == NULL) {
|
|
|
|
audio_iso->source_stream = stream;
|
|
|
|
} else if (audio_iso->source_stream != stream) {
|
|
|
|
BT_WARN("Bound with source_stream %p already", audio_iso->source_stream);
|
|
|
|
return -EADDRINUSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
audio_iso->iso_chan.ops = &ascs_iso_ops;
|
|
|
|
audio_iso->iso_chan.qos = &audio_iso->iso_qos;
|
|
|
|
audio_iso->iso_chan.qos->tx = &audio_iso->source_io_qos;
|
2022-09-12 16:50:43 +02:00
|
|
|
audio_iso->iso_chan.qos->tx->path = &audio_iso->source_path;
|
|
|
|
audio_iso->iso_chan.qos->tx->path->cc = audio_iso->source_path_cc;
|
2022-02-22 09:13:09 +01:00
|
|
|
} else if (dir == BT_AUDIO_DIR_SINK) {
|
2022-08-05 11:48:48 +02:00
|
|
|
if (audio_iso->sink_stream == NULL) {
|
|
|
|
audio_iso->sink_stream = stream;
|
|
|
|
} else if (audio_iso->sink_stream != stream) {
|
|
|
|
BT_WARN("Bound with sink_stream %p already", audio_iso->sink_stream);
|
|
|
|
return -EADDRINUSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
audio_iso->iso_chan.ops = &ascs_iso_ops;
|
|
|
|
audio_iso->iso_chan.qos = &audio_iso->iso_qos;
|
|
|
|
audio_iso->iso_chan.qos->rx = &audio_iso->sink_io_qos;
|
2022-06-03 15:12:05 +02:00
|
|
|
audio_iso->iso_chan.qos->rx->path = &audio_iso->sink_path;
|
|
|
|
audio_iso->iso_chan.qos->rx->path->cc = audio_iso->sink_path_cc;
|
2022-02-22 09:13:09 +01:00
|
|
|
} else {
|
|
|
|
__ASSERT(false, "Invalid dir: %u", dir);
|
|
|
|
}
|
2022-06-10 10:56:12 +02:00
|
|
|
|
2022-08-05 11:48:48 +02:00
|
|
|
stream->iso = &audio_iso->iso_chan;
|
2022-05-24 13:59:45 +02:00
|
|
|
stream->ep->iso = audio_iso;
|
2022-08-05 11:48:48 +02:00
|
|
|
|
|
|
|
return 0;
|
2022-02-22 09:13:09 +01:00
|
|
|
}
|
|
|
|
|
2022-06-10 10:56:12 +02:00
|
|
|
void ascs_ep_init(struct bt_audio_ep *ep, uint8_t id)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
|
|
|
BT_DBG("ep %p id 0x%02x", ep, id);
|
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
(void)memset(ep, 0, sizeof(*ep));
|
2022-01-07 17:23:45 +01:00
|
|
|
ep->status.id = id;
|
2022-02-02 19:52:01 +01:00
|
|
|
ep->dir = ASE_DIR(id);
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
2022-06-10 10:56:12 +02:00
|
|
|
static void ase_init(struct bt_ascs_ase *ase, uint8_t id)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
|
|
|
memset(ase, 0, sizeof(*ase));
|
2022-06-10 10:56:12 +02:00
|
|
|
ascs_ep_init(&ase->ep, id);
|
2022-07-26 17:27:49 +02:00
|
|
|
|
|
|
|
/* Lookup ASE characteristic */
|
|
|
|
bt_gatt_foreach_attr_type(0x0001, 0xffff, ASE_UUID(id), NULL, 0, ase_attr_cb, ase);
|
|
|
|
|
|
|
|
__ASSERT(ase->ep.server.attr, "ASE characteristic not found\n");
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
k_work_init(&ase->work, ase_process);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct bt_ascs_ase *ase_new(struct bt_ascs *ascs, uint8_t id)
|
|
|
|
{
|
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (id) {
|
|
|
|
if (id > ASE_COUNT) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
i = id;
|
|
|
|
ase = &ascs->ases[i - 1];
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ASE_COUNT; i++) {
|
|
|
|
ase = &ascs->ases[i];
|
|
|
|
|
|
|
|
if (!ase->ep.status.id) {
|
|
|
|
i++;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
done:
|
2022-06-10 10:56:12 +02:00
|
|
|
ase_init(ase, i);
|
2022-01-07 17:23:45 +01:00
|
|
|
ase->ascs = ascs;
|
|
|
|
|
|
|
|
return ase;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct bt_ascs_ase *ase_find(struct bt_ascs *ascs, uint8_t id)
|
|
|
|
{
|
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
|
|
|
|
if (!id || id > ASE_COUNT) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ase = &ascs->ases[id - 1];
|
|
|
|
if (ase->ep.status.id == id) {
|
|
|
|
return ase;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct bt_ascs_ase *ase_get(struct bt_ascs *ascs, uint8_t id)
|
|
|
|
{
|
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
|
|
|
|
ase = ase_find(ascs, id);
|
|
|
|
if (ase) {
|
|
|
|
return ase;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ase_new(ascs, id);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t ascs_ase_read(struct bt_conn *conn,
|
|
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
|
|
uint16_t len, uint16_t offset)
|
|
|
|
{
|
2022-10-14 14:27:04 +02:00
|
|
|
struct bt_ascs *ascs = ascs_get(conn);
|
2022-01-07 17:23:45 +01:00
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
|
|
|
|
BT_DBG("conn %p attr %p buf %p len %u offset %u", conn, attr, buf, len,
|
|
|
|
offset);
|
|
|
|
|
2022-07-25 09:56:23 +02:00
|
|
|
ase = ase_get(ascs, POINTER_TO_UINT(BT_AUDIO_CHRC_USER_DATA(attr)));
|
2022-01-07 17:23:45 +01:00
|
|
|
if (!ase) {
|
|
|
|
BT_ERR("Unable to get ASE");
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
|
|
|
|
}
|
|
|
|
|
|
|
|
ascs_ep_get_status(&ase->ep, &ase_buf);
|
|
|
|
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, ase_buf.data,
|
|
|
|
ase_buf.len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ascs_cp_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
|
|
|
|
{
|
|
|
|
BT_DBG("attr %p value 0x%04x", attr, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool ascs_codec_config_store(struct bt_data *data, void *user_data)
|
|
|
|
{
|
|
|
|
struct bt_codec *codec = user_data;
|
|
|
|
struct bt_codec_data *cdata;
|
|
|
|
|
|
|
|
if (codec->data_count >= ARRAY_SIZE(codec->data)) {
|
|
|
|
BT_ERR("No slot available for Codec Config");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
cdata = &codec->data[codec->data_count];
|
|
|
|
|
|
|
|
if (data->data_len > sizeof(cdata->value)) {
|
|
|
|
BT_ERR("Not enough space for Codec Config: %u > %zu",
|
|
|
|
data->data_len, sizeof(cdata->value));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
BT_DBG("#%u type 0x%02x len %u", codec->data_count, data->type,
|
|
|
|
data->data_len);
|
|
|
|
|
|
|
|
cdata->data.type = data->type;
|
|
|
|
cdata->data.data_len = data->data_len;
|
|
|
|
|
|
|
|
/* Deep copy data contents */
|
|
|
|
cdata->data.data = cdata->value;
|
|
|
|
(void)memcpy(cdata->value, data->data, data->data_len);
|
|
|
|
|
2022-10-24 11:23:12 +02:00
|
|
|
LOG_HEXDUMP_DBG(cdata->value, data->data_len, "data");
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
codec->data_count++;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-10-07 10:20:57 +02:00
|
|
|
struct codec_lookup_id_data {
|
|
|
|
uint8_t id;
|
|
|
|
struct bt_codec *codec;
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool codec_lookup_id(const struct bt_audio_capability *capability, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_lookup_id_data *data = user_data;
|
|
|
|
|
|
|
|
if (capability->codec->id == data->id) {
|
|
|
|
data->codec = capability->codec;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
static int ascs_ep_set_codec(struct bt_audio_ep *ep, uint8_t id, uint16_t cid,
|
|
|
|
uint16_t vid, struct net_buf_simple *buf,
|
|
|
|
uint8_t len, struct bt_codec *codec)
|
|
|
|
{
|
|
|
|
struct net_buf_simple ad;
|
2022-10-07 10:20:57 +02:00
|
|
|
struct codec_lookup_id_data lookup_data = {
|
|
|
|
.id = id,
|
|
|
|
};
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
if (ep == NULL && codec == NULL) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2022-09-30 13:33:21 +02:00
|
|
|
BT_DBG("ep %p dir %u codec id 0x%02x cid 0x%04x vid 0x%04x len %u",
|
|
|
|
ep, ep->dir, id, cid, vid, len);
|
|
|
|
|
2022-10-07 10:20:57 +02:00
|
|
|
bt_audio_foreach_capability(ep->dir, codec_lookup_id, &lookup_data);
|
|
|
|
|
|
|
|
if (lookup_data.codec == NULL) {
|
2022-09-30 13:33:21 +02:00
|
|
|
return -ENOENT;
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
if (codec == NULL) {
|
|
|
|
codec = &ep->codec;
|
|
|
|
}
|
|
|
|
|
|
|
|
codec->id = id;
|
|
|
|
codec->cid = cid;
|
|
|
|
codec->vid = vid;
|
|
|
|
codec->data_count = 0;
|
2022-10-07 10:20:57 +02:00
|
|
|
codec->path_id = lookup_data.codec->path_id;
|
2022-09-30 13:33:21 +02:00
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
if (len == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
net_buf_simple_init_with_data(&ad, net_buf_simple_pull_mem(buf, len),
|
|
|
|
len);
|
|
|
|
|
|
|
|
/* Parse LTV entries */
|
|
|
|
bt_data_parse(&ad, ascs_codec_config_store, codec);
|
|
|
|
|
|
|
|
/* Check if all entries could be parsed */
|
|
|
|
if (ad.len) {
|
|
|
|
BT_ERR("Unable to parse Codec Config: len %u", ad.len);
|
|
|
|
(void)memset(codec, 0, sizeof(*codec));
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ase_config(struct bt_ascs *ascs, struct bt_ascs_ase *ase,
|
|
|
|
const struct bt_ascs_config *cfg,
|
|
|
|
struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
struct bt_audio_stream *stream;
|
|
|
|
struct bt_codec codec;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
BT_DBG("ase %p latency 0x%02x phy 0x%02x codec 0x%02x "
|
|
|
|
"cid 0x%04x vid 0x%04x codec config len 0x%02x", ase,
|
|
|
|
cfg->latency, cfg->phy, cfg->codec.id, cfg->codec.cid,
|
|
|
|
cfg->codec.vid, cfg->cc_len);
|
|
|
|
|
|
|
|
if (cfg->latency < BT_ASCS_CONFIG_LATENCY_LOW ||
|
|
|
|
cfg->latency > BT_ASCS_CONFIG_LATENCY_HIGH) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Invalid latency: 0x%02x", cfg->latency);
|
2022-01-07 17:23:45 +01:00
|
|
|
ascs_cp_rsp_add(ASE_ID(ase), BT_ASCS_CONFIG_OP,
|
|
|
|
BT_ASCS_RSP_CONF_INVALID,
|
|
|
|
BT_ASCS_REASON_LATENCY);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cfg->phy < BT_ASCS_CONFIG_PHY_LE_1M ||
|
|
|
|
cfg->phy > BT_ASCS_CONFIG_PHY_LE_CODED) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Invalid PHY: 0x%02x", cfg->phy);
|
2022-01-07 17:23:45 +01:00
|
|
|
ascs_cp_rsp_add(ASE_ID(ase), BT_ASCS_CONFIG_OP,
|
|
|
|
BT_ASCS_RSP_CONF_INVALID, BT_ASCS_REASON_PHY);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (ase->ep.status.state) {
|
|
|
|
/* Valid only if ASE_State field = 0x00 (Idle) */
|
|
|
|
case BT_AUDIO_EP_STATE_IDLE:
|
|
|
|
/* or 0x01 (Codec Configured) */
|
|
|
|
case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
|
|
|
|
/* or 0x02 (QoS Configured) */
|
|
|
|
case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
|
|
|
|
break;
|
|
|
|
default:
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Invalid operation in state: %s",
|
|
|
|
bt_audio_ep_state_str(ase->ep.status.state));
|
2022-01-07 17:23:45 +01:00
|
|
|
ascs_cp_rsp_add(ASE_ID(ase), BT_ASCS_CONFIG_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_ASE_STATE, 0x00);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Store current codec configuration to be able to restore it
|
|
|
|
* in case of error.
|
|
|
|
*/
|
|
|
|
(void)memcpy(&codec, &ase->ep.codec, sizeof(codec));
|
|
|
|
|
|
|
|
if (ascs_ep_set_codec(&ase->ep, cfg->codec.id,
|
|
|
|
sys_le16_to_cpu(cfg->codec.cid),
|
|
|
|
sys_le16_to_cpu(cfg->codec.vid),
|
|
|
|
buf, cfg->cc_len, &ase->ep.codec)) {
|
|
|
|
(void)memcpy(&ase->ep.codec, &codec, sizeof(codec));
|
|
|
|
ascs_cp_rsp_add(ASE_ID(ase), BT_ASCS_CONFIG_OP,
|
|
|
|
BT_ASCS_RSP_CONF_INVALID,
|
|
|
|
BT_ASCS_REASON_CODEC_DATA);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ase->ep.stream != NULL) {
|
|
|
|
if (unicast_server_cb != NULL &&
|
|
|
|
unicast_server_cb->reconfig != NULL) {
|
|
|
|
err = unicast_server_cb->reconfig(ase->ep.stream,
|
2022-02-03 12:57:55 +01:00
|
|
|
ase->ep.dir,
|
2022-01-07 17:23:45 +01:00
|
|
|
&ase->ep.codec,
|
|
|
|
&ase->ep.qos_pref);
|
|
|
|
} else {
|
|
|
|
err = -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err != 0) {
|
|
|
|
uint8_t reason = BT_ASCS_REASON_CODEC_DATA;
|
|
|
|
|
|
|
|
BT_ERR("Reconfig failed: %d", err);
|
|
|
|
|
|
|
|
(void)memcpy(&ase->ep.codec, &codec, sizeof(codec));
|
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase),
|
|
|
|
BT_ASCS_CONFIG_OP,
|
|
|
|
err, reason);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
stream = ase->ep.stream;
|
|
|
|
} else {
|
|
|
|
stream = NULL;
|
|
|
|
if (unicast_server_cb != NULL &&
|
|
|
|
unicast_server_cb->config != NULL) {
|
|
|
|
err = unicast_server_cb->config(ascs->conn, &ase->ep,
|
2022-02-03 12:57:55 +01:00
|
|
|
ase->ep.dir,
|
2022-01-07 17:23:45 +01:00
|
|
|
&ase->ep.codec, &stream,
|
|
|
|
&ase->ep.qos_pref);
|
|
|
|
} else {
|
|
|
|
err = -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err != 0 || stream == NULL) {
|
|
|
|
BT_ERR("Config failed, err: %d, stream %p",
|
|
|
|
err, stream);
|
|
|
|
|
|
|
|
(void)memcpy(&ase->ep.codec, &codec, sizeof(codec));
|
|
|
|
ascs_cp_rsp_add(ASE_ID(ase), BT_ASCS_CONFIG_OP,
|
|
|
|
BT_ASCS_RSP_CONF_REJECTED,
|
|
|
|
BT_ASCS_REASON_CODEC_DATA);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
ase_stream_add(ascs, ase, stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
ascs_cp_rsp_success(ASE_ID(ase), BT_ASCS_CONFIG_OP);
|
|
|
|
|
|
|
|
/* TODO: bt_audio_stream_attach duplicates some of the
|
|
|
|
* ase_stream_add. Should be cleaned up.
|
|
|
|
*/
|
|
|
|
bt_audio_stream_attach(ascs->conn, stream, &ase->ep,
|
|
|
|
&ase->ep.codec);
|
|
|
|
|
|
|
|
ascs_ep_set_state(&ase->ep, BT_AUDIO_EP_STATE_CODEC_CONFIGURED);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t ascs_config(struct bt_ascs *ascs, struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
const struct bt_ascs_config_op *req;
|
|
|
|
const struct bt_ascs_config *cfg;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*req)) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Config");
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
req = net_buf_simple_pull_mem(buf, sizeof(*req));
|
|
|
|
|
|
|
|
BT_DBG("num_ases %u", req->num_ases);
|
|
|
|
|
2022-08-04 09:46:26 +02:00
|
|
|
if (req->num_ases < 1) {
|
|
|
|
BT_WARN("Number_of_ASEs parameter value is less than 1");
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
} else if (buf->len < req->num_ases * sizeof(*cfg)) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Config: len %u < %zu", buf->len,
|
|
|
|
req->num_ases * sizeof(*cfg));
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < req->num_ases; i++) {
|
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*cfg)) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Config: len %u < %zu", buf->len, sizeof(*cfg));
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg = net_buf_simple_pull_mem(buf, sizeof(*cfg));
|
|
|
|
|
|
|
|
if (buf->len < cfg->cc_len) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Codec Config len %u != %u", buf->len, cfg->cc_len);
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
BT_DBG("ase 0x%02x cc_len %u", cfg->ase, cfg->cc_len);
|
|
|
|
|
|
|
|
if (cfg->ase) {
|
|
|
|
ase = ase_get(ascs, cfg->ase);
|
|
|
|
} else {
|
|
|
|
ase = ase_new(ascs, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ase) {
|
|
|
|
ascs_cp_rsp_add(cfg->ase, BT_ASCS_CONFIG_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_ASE, 0x00);
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Unknown ase 0x%02x", cfg->ase);
|
2022-01-07 17:23:45 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ase_config(ascs, ase, cfg, buf);
|
|
|
|
if (err != 0) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Config");
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf->size;
|
|
|
|
}
|
|
|
|
|
2022-02-22 09:13:09 +01:00
|
|
|
static int ase_stream_qos(struct bt_audio_stream *stream,
|
|
|
|
struct bt_codec_qos *qos,
|
|
|
|
struct bt_ascs *ascs,
|
|
|
|
uint8_t cig_id,
|
|
|
|
uint8_t cis_id)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
2022-02-22 09:13:09 +01:00
|
|
|
struct bt_audio_iso *audio_iso;
|
|
|
|
struct bt_audio_ep *ep;
|
2022-08-05 11:48:48 +02:00
|
|
|
int err;
|
2022-02-22 09:13:09 +01:00
|
|
|
|
2022-05-24 13:59:45 +02:00
|
|
|
BT_DBG("stream %p ep %p qos %p", stream, stream->ep, qos);
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
if (stream == NULL || stream->ep == NULL || qos == NULL) {
|
2022-05-24 13:59:45 +02:00
|
|
|
BT_DBG("Invalid input stream, ep or qos pointers");
|
2022-01-07 17:23:45 +01:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2022-02-22 09:13:09 +01:00
|
|
|
ep = stream->ep;
|
|
|
|
|
|
|
|
switch (ep->status.state) {
|
2022-01-07 17:23:45 +01:00
|
|
|
/* Valid only if ASE_State field = 0x01 (Codec Configured) */
|
|
|
|
case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
|
|
|
|
/* or 0x02 (QoS Configured) */
|
|
|
|
case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
|
|
|
|
break;
|
|
|
|
default:
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Invalid operation in state: %s", bt_audio_ep_state_str(ep->status.state));
|
2022-01-07 17:23:45 +01:00
|
|
|
return -EBADMSG;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!bt_audio_valid_qos(qos)) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!bt_audio_valid_stream_qos(stream, qos)) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unicast_server_cb != NULL && unicast_server_cb->qos != NULL) {
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = unicast_server_cb->qos(stream, qos);
|
|
|
|
if (err != 0) {
|
2022-05-24 13:59:45 +02:00
|
|
|
BT_DBG("Application returned error: %d", err);
|
2022-01-07 17:23:45 +01:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 11:48:48 +02:00
|
|
|
audio_iso = audio_iso_get_or_new(ascs, cig_id, cis_id);
|
2022-02-22 09:13:09 +01:00
|
|
|
if (audio_iso == NULL) {
|
2022-08-05 11:48:48 +02:00
|
|
|
BT_ERR("Could not allocate audio_iso");
|
2022-02-22 09:13:09 +01:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2022-08-05 11:48:48 +02:00
|
|
|
err = ascs_ep_stream_bind_audio_iso(stream, audio_iso);
|
|
|
|
if (err < 0) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
stream->qos = qos;
|
|
|
|
|
2022-02-22 09:13:09 +01:00
|
|
|
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_QOS_CONFIGURED);
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
bt_audio_stream_iso_listen(stream);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ase_qos(struct bt_ascs_ase *ase, const struct bt_ascs_qos *qos)
|
|
|
|
{
|
2022-06-03 15:12:05 +02:00
|
|
|
struct bt_audio_ep *ep = &ase->ep;
|
|
|
|
struct bt_audio_stream *stream = ep->stream;
|
|
|
|
struct bt_codec_qos *cqos = &ep->qos;
|
2022-02-22 09:13:09 +01:00
|
|
|
const uint8_t cig_id = qos->cig;
|
|
|
|
const uint8_t cis_id = qos->cis;
|
2022-01-07 17:23:45 +01:00
|
|
|
int err;
|
|
|
|
|
|
|
|
cqos->interval = sys_get_le24(qos->interval);
|
|
|
|
cqos->framing = qos->framing;
|
|
|
|
cqos->phy = qos->phy;
|
|
|
|
cqos->sdu = sys_le16_to_cpu(qos->sdu);
|
|
|
|
cqos->rtn = qos->rtn;
|
|
|
|
cqos->latency = sys_le16_to_cpu(qos->latency);
|
|
|
|
cqos->pd = sys_get_le24(qos->pd);
|
|
|
|
|
|
|
|
BT_DBG("ase %p cig 0x%02x cis 0x%02x interval %u framing 0x%02x "
|
|
|
|
"phy 0x%02x sdu %u rtn %u latency %u pd %u", ase, qos->cig,
|
|
|
|
qos->cis, cqos->interval, cqos->framing, cqos->phy, cqos->sdu,
|
|
|
|
cqos->rtn, cqos->latency, cqos->pd);
|
|
|
|
|
2022-02-22 09:13:09 +01:00
|
|
|
err = ase_stream_qos(stream, cqos, ase->ascs, cig_id, cis_id);
|
2022-01-07 17:23:45 +01:00
|
|
|
if (err) {
|
|
|
|
uint8_t reason = BT_ASCS_REASON_NONE;
|
|
|
|
|
|
|
|
BT_ERR("QoS failed: err %d", err);
|
|
|
|
|
|
|
|
if (err == -ENOTSUP) {
|
|
|
|
if (cqos->interval == 0) {
|
|
|
|
reason = BT_ASCS_REASON_INTERVAL;
|
|
|
|
} else if (cqos->framing == 0xff) {
|
|
|
|
reason = BT_ASCS_REASON_FRAMING;
|
|
|
|
} else if (cqos->phy == 0) {
|
|
|
|
reason = BT_ASCS_REASON_PHY;
|
|
|
|
} else if (cqos->sdu == 0xffff) {
|
|
|
|
reason = BT_ASCS_REASON_SDU;
|
|
|
|
} else if (cqos->latency == 0) {
|
|
|
|
reason = BT_ASCS_REASON_LATENCY;
|
|
|
|
} else if (cqos->pd == 0) {
|
|
|
|
reason = BT_ASCS_REASON_PD;
|
|
|
|
}
|
2022-08-05 11:48:48 +02:00
|
|
|
} else if (err == -EADDRINUSE) {
|
|
|
|
reason = BT_ASCS_REASON_CIS;
|
|
|
|
/* FIXME: Ugly workaround to send Response_Code
|
|
|
|
* 0x09 = Invalid Configuration Parameter Value
|
|
|
|
*/
|
|
|
|
err = -EINVAL;
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
memset(cqos, 0, sizeof(*cqos));
|
|
|
|
|
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_QOS_OP,
|
|
|
|
err, reason);
|
|
|
|
return;
|
2022-06-03 15:12:05 +02:00
|
|
|
} 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->sink_path,
|
|
|
|
stream->codec);
|
|
|
|
} else {
|
|
|
|
bt_audio_codec_to_iso_path(&ep->iso->source_path,
|
|
|
|
stream->codec);
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
2022-06-03 15:12:05 +02:00
|
|
|
ep->cig_id = cig_id;
|
|
|
|
ep->cis_id = cis_id;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
ascs_cp_rsp_success(ASE_ID(ase), BT_ASCS_QOS_OP);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t ascs_qos(struct bt_ascs *ascs, struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
const struct bt_ascs_qos_op *req;
|
|
|
|
const struct bt_ascs_qos *qos;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*req)) {
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
req = net_buf_simple_pull_mem(buf, sizeof(*req));
|
|
|
|
|
|
|
|
BT_DBG("num_ases %u", req->num_ases);
|
|
|
|
|
2022-08-04 09:46:26 +02:00
|
|
|
if (req->num_ases < 1) {
|
|
|
|
BT_WARN("Number_of_ASEs parameter value is less than 1");
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
} else if (buf->len < req->num_ases * sizeof(*qos)) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE QoS: len %u < %zu", buf->len, req->num_ases * sizeof(*qos));
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < req->num_ases; i++) {
|
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
|
|
|
|
qos = net_buf_simple_pull_mem(buf, sizeof(*qos));
|
|
|
|
|
|
|
|
BT_DBG("ase 0x%02x", qos->ase);
|
|
|
|
|
|
|
|
ase = ase_find(ascs, qos->ase);
|
|
|
|
if (!ase) {
|
|
|
|
ascs_cp_rsp_add(qos->ase, BT_ASCS_QOS_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_ASE, 0x00);
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Unknown ase 0x%02x", qos->ase);
|
2022-01-07 17:23:45 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ase_qos(ase, qos);
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf->size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool ascs_codec_store_metadata(struct bt_data *data, void *user_data)
|
|
|
|
{
|
|
|
|
struct bt_codec *codec = user_data;
|
|
|
|
struct bt_codec_data *meta;
|
|
|
|
|
|
|
|
meta = &codec->meta[codec->meta_count];
|
|
|
|
meta->data.type = data->type;
|
|
|
|
meta->data.data_len = data->data_len;
|
|
|
|
|
|
|
|
/* Deep copy data contents */
|
|
|
|
meta->data.data = meta->value;
|
|
|
|
(void)memcpy(meta->value, data->data, data->data_len);
|
|
|
|
|
2022-04-04 23:21:16 +02:00
|
|
|
BT_DBG("#%zu: data: %s",
|
|
|
|
codec->meta_count,
|
|
|
|
bt_hex(meta->value, data->data_len));
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
codec->meta_count++;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-04-04 23:21:16 +02:00
|
|
|
struct ascs_parse_result {
|
|
|
|
int err;
|
|
|
|
size_t count;
|
2022-04-05 16:37:26 +02:00
|
|
|
const struct bt_audio_ep *ep;
|
2022-04-04 23:21:16 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
static bool ascs_parse_metadata(struct bt_data *data, void *user_data)
|
|
|
|
{
|
|
|
|
struct ascs_parse_result *result = user_data;
|
2022-04-05 16:37:26 +02:00
|
|
|
const struct bt_audio_ep *ep = result->ep;
|
|
|
|
const uint8_t data_len = data->data_len;
|
|
|
|
const uint8_t data_type = data->type;
|
|
|
|
const uint8_t *data_value = data->data;
|
2022-04-04 23:21:16 +02:00
|
|
|
|
|
|
|
result->count++;
|
|
|
|
|
2022-04-05 16:37:26 +02:00
|
|
|
BT_DBG("#%u type 0x%02x len %u", result->count, data_type, data_len);
|
2022-04-04 23:21:16 +02:00
|
|
|
|
|
|
|
if (result->count > CONFIG_BT_CODEC_MAX_METADATA_COUNT) {
|
|
|
|
BT_ERR("Not enough buffers for Codec Config Metadata: %zu > %zu",
|
|
|
|
result->count, CONFIG_BT_CODEC_MAX_METADATA_LEN);
|
|
|
|
result->err = -ENOMEM;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-04-05 16:37:26 +02:00
|
|
|
if (data_len > CONFIG_BT_CODEC_MAX_METADATA_LEN) {
|
2022-04-04 23:21:16 +02:00
|
|
|
BT_ERR("Not enough space for Codec Config Metadata: %u > %zu",
|
|
|
|
data->data_len, CONFIG_BT_CODEC_MAX_METADATA_LEN);
|
|
|
|
result->err = -ENOMEM;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-04-05 11:50:38 +02:00
|
|
|
/* The CAP acceptor shall not accept metadata with
|
|
|
|
* unsupported stream context.
|
|
|
|
*/
|
2022-04-05 16:37:26 +02:00
|
|
|
if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR)) {
|
|
|
|
if (data_type == BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT) {
|
|
|
|
const uint16_t context = sys_get_le16(data_value);
|
2022-04-05 11:50:38 +02:00
|
|
|
|
2022-04-05 16:37:26 +02:00
|
|
|
if (!bt_pacs_context_available(ep->dir, context)) {
|
2022-08-03 16:02:33 +02:00
|
|
|
BT_WARN("Context 0x%04x is unavailable", context);
|
|
|
|
|
2022-04-05 16:37:26 +02:00
|
|
|
result->err = -EACCES;
|
2022-04-05 11:50:38 +02:00
|
|
|
|
2022-04-05 16:37:26 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if (data_type == BT_AUDIO_METADATA_TYPE_CCID_LIST) {
|
|
|
|
/* Verify that the CCID is a known CCID on the
|
|
|
|
* writing device
|
|
|
|
*/
|
|
|
|
for (uint8_t i = 0; i < data_len; i++) {
|
|
|
|
const uint8_t ccid = data_value[i];
|
|
|
|
|
|
|
|
if (!bt_cap_acceptor_ccid_exist(ep->stream->conn,
|
|
|
|
ccid)) {
|
|
|
|
BT_WARN("CCID %u is unknown", ccid);
|
|
|
|
|
|
|
|
/* TBD:
|
|
|
|
* Should we reject the Metadata?
|
|
|
|
*
|
|
|
|
* Should unknown CCIDs trigger a
|
|
|
|
* discovery procedure for TBS or MCS?
|
|
|
|
*
|
|
|
|
* Or should we just accept as is, and
|
|
|
|
* then let the application decide?
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
}
|
2022-04-05 11:50:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-04 23:21:16 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-04-05 11:50:38 +02:00
|
|
|
static int ascs_verify_metadata(const struct net_buf_simple *buf,
|
2022-04-05 16:37:26 +02:00
|
|
|
struct bt_audio_ep *ep)
|
2022-04-04 23:21:16 +02:00
|
|
|
{
|
2022-04-05 11:50:38 +02:00
|
|
|
struct ascs_parse_result result = {
|
|
|
|
.count = 0U,
|
|
|
|
.err = 0,
|
2022-04-05 16:37:26 +02:00
|
|
|
.ep = ep
|
2022-04-05 11:50:38 +02:00
|
|
|
};
|
2022-04-04 23:21:16 +02:00
|
|
|
struct net_buf_simple meta_ltv;
|
|
|
|
|
|
|
|
/* Clone the buf to avoid pulling data from the original buffer */
|
|
|
|
net_buf_simple_clone(buf, &meta_ltv);
|
|
|
|
|
|
|
|
/* Parse LTV entries */
|
|
|
|
bt_data_parse(&meta_ltv, ascs_parse_metadata, &result);
|
|
|
|
|
|
|
|
/* Check if all entries could be parsed */
|
|
|
|
if (meta_ltv.len != 0) {
|
|
|
|
BT_ERR("Unable to parse Metadata: len %u", meta_ltv.len);
|
|
|
|
|
|
|
|
if (meta_ltv.len > 2) {
|
|
|
|
/* Value of the Metadata Type field in error */
|
|
|
|
return meta_ltv.data[2];
|
|
|
|
}
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2022-08-22 12:55:02 +02:00
|
|
|
return result.err;
|
2022-04-04 23:21:16 +02:00
|
|
|
}
|
|
|
|
|
2022-10-17 13:25:42 +02:00
|
|
|
static int ascs_ep_set_metadata(struct bt_audio_ep *ep, struct net_buf_simple *buf, uint8_t len,
|
|
|
|
struct bt_codec *codec)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
2022-04-04 23:21:16 +02:00
|
|
|
struct net_buf_simple meta_ltv;
|
2022-01-07 17:23:45 +01:00
|
|
|
int err;
|
|
|
|
|
|
|
|
if (ep == NULL && codec == NULL) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
BT_DBG("ep %p len %u codec %p", ep, len, codec);
|
|
|
|
|
|
|
|
if (len == 0) {
|
2022-04-04 23:21:16 +02:00
|
|
|
(void)memset(codec->meta, 0, sizeof(codec->meta));
|
2022-01-07 17:23:45 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (codec == NULL) {
|
|
|
|
codec = &ep->codec;
|
|
|
|
}
|
|
|
|
|
2022-04-04 23:21:16 +02:00
|
|
|
/* Extract metadata LTV for this specific endpoint */
|
|
|
|
net_buf_simple_init_with_data(&meta_ltv,
|
|
|
|
net_buf_simple_pull_mem(buf, len),
|
2022-01-07 17:23:45 +01:00
|
|
|
len);
|
|
|
|
|
2022-04-05 16:37:26 +02:00
|
|
|
err = ascs_verify_metadata(&meta_ltv, ep);
|
2022-04-04 23:21:16 +02:00
|
|
|
if (err != 0) {
|
2022-01-07 17:23:45 +01:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2022-08-22 13:00:45 +02:00
|
|
|
/* reset cached metadata */
|
|
|
|
ep->codec.meta_count = 0;
|
|
|
|
|
2022-04-04 23:21:16 +02:00
|
|
|
/* store data contents */
|
|
|
|
bt_data_parse(&meta_ltv, ascs_codec_store_metadata, codec);
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ase_metadata(struct bt_ascs_ase *ase, uint8_t op,
|
|
|
|
struct bt_ascs_metadata *meta,
|
|
|
|
struct net_buf_simple *buf)
|
|
|
|
{
|
2022-06-17 08:31:39 +02:00
|
|
|
struct bt_codec_data metadata_backup[CONFIG_BT_CODEC_MAX_DATA_COUNT];
|
2022-01-07 17:23:45 +01:00
|
|
|
struct bt_audio_stream *stream;
|
|
|
|
struct bt_audio_ep *ep;
|
|
|
|
uint8_t state;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
BT_DBG("ase %p meta->len %u", ase, meta->len);
|
|
|
|
|
|
|
|
ep = &ase->ep;
|
|
|
|
state = ep->status.state;
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
/* Valid for an ASE only if ASE_State field = 0x03 (Enabling) */
|
|
|
|
case BT_AUDIO_EP_STATE_ENABLING:
|
|
|
|
/* or 0x04 (Streaming) */
|
|
|
|
case BT_AUDIO_EP_STATE_STREAMING:
|
|
|
|
break;
|
|
|
|
default:
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Invalid operation in state: %s", bt_audio_ep_state_str(state));
|
2022-01-07 17:23:45 +01:00
|
|
|
err = -EBADMSG;
|
2022-08-04 13:38:40 +02:00
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), op, err,
|
2022-01-07 17:23:45 +01:00
|
|
|
buf->len ? *buf->data : 0x00);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!meta->len) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
2022-06-17 08:31:39 +02:00
|
|
|
/* Backup existing metadata */
|
|
|
|
(void)memcpy(metadata_backup, ep->codec.meta, sizeof(metadata_backup));
|
2022-01-07 17:23:45 +01:00
|
|
|
err = ascs_ep_set_metadata(ep, buf, meta->len, &ep->codec);
|
|
|
|
if (err) {
|
|
|
|
if (err < 0) {
|
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), op, err, 0x00);
|
|
|
|
} else {
|
|
|
|
ascs_cp_rsp_add(ASE_ID(ase), op,
|
|
|
|
BT_ASCS_RSP_METADATA_INVALID, err);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
stream = ep->stream;
|
|
|
|
if (unicast_server_cb != NULL && unicast_server_cb->metadata != NULL) {
|
2022-02-24 11:22:29 +01:00
|
|
|
err = unicast_server_cb->metadata(stream, ep->codec.meta,
|
|
|
|
ep->codec.meta_count);
|
2022-01-07 17:23:45 +01:00
|
|
|
} else {
|
|
|
|
err = -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
2022-06-17 08:31:39 +02:00
|
|
|
/* Restore backup */
|
|
|
|
(void)memcpy(ep->codec.meta, metadata_backup,
|
|
|
|
sizeof(metadata_backup));
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
BT_ERR("Metadata failed: %d", err);
|
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), op, err,
|
|
|
|
buf->len ? *buf->data : 0x00);
|
2022-06-17 08:31:39 +02:00
|
|
|
return err;
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the state to the same state to trigger the notifications */
|
|
|
|
ascs_ep_set_state(ep, ep->status.state);
|
|
|
|
done:
|
|
|
|
ascs_cp_rsp_success(ASE_ID(ase), op);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ase_enable(struct bt_ascs_ase *ase, struct bt_ascs_metadata *meta,
|
|
|
|
struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
struct bt_audio_stream *stream;
|
|
|
|
struct bt_audio_ep *ep;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
BT_DBG("ase %p buf->len %u", ase, buf->len);
|
|
|
|
|
|
|
|
ep = &ase->ep;
|
|
|
|
|
|
|
|
/* Valid for an ASE only if ASE_State field = 0x02 (QoS Configured) */
|
|
|
|
if (ep->status.state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
|
|
|
|
err = -EBADMSG;
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Invalid operation in state: %s", bt_audio_ep_state_str(ep->status.state));
|
2022-01-07 17:23:45 +01:00
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_ENABLE_OP, err,
|
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ascs_ep_set_metadata(ep, buf, meta->len, &ep->codec);
|
|
|
|
if (err) {
|
|
|
|
if (err < 0) {
|
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_ENABLE_OP,
|
|
|
|
err, 0x00);
|
|
|
|
} else {
|
|
|
|
ascs_cp_rsp_add(ASE_ID(ase), BT_ASCS_ENABLE_OP,
|
|
|
|
BT_ASCS_RSP_METADATA_INVALID, err);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
stream = ep->stream;
|
|
|
|
if (unicast_server_cb != NULL && unicast_server_cb->enable != NULL) {
|
2022-02-24 11:22:29 +01:00
|
|
|
err = unicast_server_cb->enable(stream, ep->codec.meta,
|
|
|
|
ep->codec.meta_count);
|
2022-01-07 17:23:45 +01:00
|
|
|
} else {
|
|
|
|
err = -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
BT_ERR("Enable rejected: %d", err);
|
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_ENABLE_OP, err,
|
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
return -EFAULT;
|
|
|
|
}
|
|
|
|
|
|
|
|
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_ENABLING);
|
|
|
|
|
|
|
|
ascs_cp_rsp_success(ASE_ID(ase), BT_ASCS_ENABLE_OP);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t ascs_enable(struct bt_ascs *ascs, struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
const struct bt_ascs_enable_op *req;
|
|
|
|
struct bt_ascs_metadata *meta;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*req)) {
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
req = net_buf_simple_pull_mem(buf, sizeof(*req));
|
|
|
|
|
|
|
|
BT_DBG("num_ases %u", req->num_ases);
|
|
|
|
|
2022-08-04 09:46:26 +02:00
|
|
|
if (req->num_ases < 1) {
|
|
|
|
BT_WARN("Number_of_ASEs parameter value is less than 1");
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
} else if (buf->len < req->num_ases * sizeof(*meta)) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Metadata: len %u < %zu", buf->len,
|
|
|
|
req->num_ases * sizeof(*meta));
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < req->num_ases; i++) {
|
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
|
|
|
|
meta = net_buf_simple_pull_mem(buf, sizeof(*meta));
|
|
|
|
|
|
|
|
BT_DBG("ase 0x%02x meta->len %u", meta->ase, meta->len);
|
|
|
|
|
|
|
|
if (buf->len < meta->len) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Enable Metadata len %u != %u", buf->len, meta->len);
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
ase = ase_find(ascs, meta->ase);
|
|
|
|
if (!ase) {
|
|
|
|
ascs_cp_rsp_add(meta->ase, BT_ASCS_ENABLE_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_ASE, 0x00);
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Unknown ase 0x%02x", meta->ase);
|
2022-01-07 17:23:45 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ase_enable(ase, meta, buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf->size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ase_start(struct bt_ascs_ase *ase)
|
|
|
|
{
|
|
|
|
struct bt_audio_stream *stream;
|
|
|
|
struct bt_audio_ep *ep;
|
|
|
|
|
|
|
|
BT_DBG("ase %p", ase);
|
|
|
|
|
|
|
|
ep = &ase->ep;
|
|
|
|
|
|
|
|
/* Valid for an ASE only if ASE_State field = 0x02 (QoS Configured) */
|
|
|
|
if (ep->status.state != BT_AUDIO_EP_STATE_ENABLING) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Invalid operation in state: %s", bt_audio_ep_state_str(ep->status.state));
|
2022-10-06 12:57:38 +02:00
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_START_OP, -EBADMSG,
|
2022-01-07 17:23:45 +01:00
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the ASE_ID written by the client represents a Sink ASE, the
|
|
|
|
* server shall not accept the Receiver Start Ready operation for that
|
|
|
|
* ASE. The server shall send a notification of the ASE Control Point
|
|
|
|
* characteristic to the client, and the server shall set the
|
|
|
|
* Response_Code value for that ASE to 0x05 (Invalid ASE direction).
|
|
|
|
*/
|
2022-03-29 18:20:10 +02:00
|
|
|
if (ep->dir == BT_AUDIO_DIR_SINK) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Start failed: invalid operation for Sink");
|
2022-01-07 17:23:45 +01:00
|
|
|
ascs_cp_rsp_add(ASE_ID(ase), BT_ASCS_START_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_DIR, BT_ASCS_REASON_NONE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-10-06 12:57:38 +02:00
|
|
|
ep->receiver_ready = true;
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
stream = ep->stream;
|
2022-10-06 12:57:38 +02:00
|
|
|
if (stream->iso->state == BT_ISO_STATE_CONNECTED) {
|
|
|
|
int err;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-10-06 12:57:38 +02:00
|
|
|
err = ase_stream_start(stream);
|
|
|
|
if (err) {
|
|
|
|
BT_ERR("Start failed: %d", err);
|
|
|
|
ascs_cp_rsp_add(ASE_ID(ase), BT_ASCS_START_OP, err,
|
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
return;
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ascs_cp_rsp_success(ASE_ID(ase), BT_ASCS_START_OP);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t ascs_start(struct bt_ascs *ascs, struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
const struct bt_ascs_start_op *req;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*req)) {
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
req = net_buf_simple_pull_mem(buf, sizeof(*req));
|
|
|
|
|
|
|
|
BT_DBG("num_ases %u", req->num_ases);
|
|
|
|
|
2022-08-04 09:46:26 +02:00
|
|
|
if (req->num_ases < 1) {
|
|
|
|
BT_WARN("Number_of_ASEs parameter value is less than 1");
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
} else if (buf->len < req->num_ases) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Start: len %u < %u", buf->len, req->num_ases);
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < req->num_ases; i++) {
|
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
uint8_t id;
|
|
|
|
|
|
|
|
id = net_buf_simple_pull_u8(buf);
|
|
|
|
|
|
|
|
BT_DBG("ase 0x%02x", id);
|
|
|
|
|
|
|
|
ase = ase_find(ascs, id);
|
|
|
|
if (!ase) {
|
|
|
|
ascs_cp_rsp_add(id, BT_ASCS_START_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_ASE, 0x00);
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Unknown ase 0x%02x", id);
|
2022-01-07 17:23:45 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ase_start(ase);
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf->size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t ascs_disable(struct bt_ascs *ascs, struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
const struct bt_ascs_disable_op *req;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*req)) {
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
req = net_buf_simple_pull_mem(buf, sizeof(*req));
|
|
|
|
|
|
|
|
BT_DBG("num_ases %u", req->num_ases);
|
|
|
|
|
2022-08-04 09:46:26 +02:00
|
|
|
if (req->num_ases < 1) {
|
|
|
|
BT_WARN("Number_of_ASEs parameter value is less than 1");
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
} else if (buf->len < req->num_ases) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Disable: len %u < %u", buf->len, req->num_ases);
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < req->num_ases; i++) {
|
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
uint8_t id;
|
|
|
|
|
|
|
|
id = net_buf_simple_pull_u8(buf);
|
|
|
|
|
|
|
|
BT_DBG("ase 0x%02x", id);
|
|
|
|
|
|
|
|
ase = ase_find(ascs, id);
|
|
|
|
if (!ase) {
|
|
|
|
ascs_cp_rsp_add(id, BT_ASCS_DISABLE_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_ASE_STATE, 0);
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Unknown ase 0x%02x", id);
|
2022-01-07 17:23:45 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ase_disable(ase);
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf->size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ase_stop(struct bt_ascs_ase *ase)
|
|
|
|
{
|
|
|
|
struct bt_audio_stream *stream;
|
|
|
|
struct bt_audio_ep *ep;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
BT_DBG("ase %p", ase);
|
|
|
|
|
|
|
|
ep = &ase->ep;
|
|
|
|
|
|
|
|
/* If the ASE_ID written by the client represents a Sink ASE, the
|
|
|
|
* server shall not accept the Receiver Stop Ready operation for that
|
|
|
|
* ASE. The server shall send a notification of the ASE Control Point
|
|
|
|
* characteristic to the client, and the server shall set the
|
|
|
|
* Response_Code value for that ASE to 0x05 (Invalid ASE direction).
|
|
|
|
*/
|
2022-03-29 18:20:10 +02:00
|
|
|
if (ase->ep.dir == BT_AUDIO_DIR_SINK) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Stop failed: invalid operation for Sink");
|
2022-01-07 17:23:45 +01:00
|
|
|
ascs_cp_rsp_add(ASE_ID(ase), BT_ASCS_STOP_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_DIR, BT_ASCS_REASON_NONE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ep->status.state != BT_AUDIO_EP_STATE_DISABLING) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Invalid operation in state: %s", bt_audio_ep_state_str(ep->status.state));
|
2022-08-04 13:38:40 +02:00
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_STOP_OP, -EBADMSG,
|
2022-01-07 17:23:45 +01:00
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
stream = ep->stream;
|
|
|
|
if (unicast_server_cb != NULL && unicast_server_cb->stop != NULL) {
|
|
|
|
err = unicast_server_cb->stop(stream);
|
|
|
|
} else {
|
|
|
|
err = -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
BT_ERR("Stop failed: %d", err);
|
|
|
|
ascs_cp_rsp_add_errno(ASE_ID(ase), BT_ASCS_STOP_OP, err,
|
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the Receiver Stop Ready operation has completed successfully the
|
|
|
|
* Unicast Client or the Unicast Server may terminate a CIS established
|
|
|
|
* for that ASE by following the Connected Isochronous Stream Terminate
|
|
|
|
* procedure defined in Volume 3, Part C, Section 9.3.15.
|
|
|
|
*/
|
|
|
|
err = bt_audio_stream_disconnect(stream);
|
2022-08-04 15:39:21 +02:00
|
|
|
if (err != -ENOTCONN && err != 0) {
|
2022-01-07 17:23:45 +01:00
|
|
|
BT_ERR("Could not disconnect the CIS: %d", err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_QOS_CONFIGURED);
|
|
|
|
err = bt_audio_stream_iso_listen(stream);
|
|
|
|
if (err != 0) {
|
|
|
|
BT_ERR("Could not make stream listen: %d", err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ascs_cp_rsp_success(ASE_ID(ase), BT_ASCS_STOP_OP);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t ascs_stop(struct bt_ascs *ascs, struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
const struct bt_ascs_start_op *req;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*req)) {
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
req = net_buf_simple_pull_mem(buf, sizeof(*req));
|
|
|
|
|
|
|
|
BT_DBG("num_ases %u", req->num_ases);
|
|
|
|
|
2022-08-04 09:46:26 +02:00
|
|
|
if (req->num_ases < 1) {
|
|
|
|
BT_WARN("Number_of_ASEs parameter value is less than 1");
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
} else if (buf->len < req->num_ases) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Start: len %u < %u", buf->len, req->num_ases);
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < req->num_ases; i++) {
|
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
uint8_t id;
|
|
|
|
|
|
|
|
id = net_buf_simple_pull_u8(buf);
|
|
|
|
|
|
|
|
BT_DBG("ase 0x%02x", id);
|
|
|
|
|
|
|
|
ase = ase_find(ascs, id);
|
|
|
|
if (!ase) {
|
|
|
|
ascs_cp_rsp_add(id, BT_ASCS_STOP_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_ASE, 0x00);
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Unknown ase 0x%02x", id);
|
2022-01-07 17:23:45 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ase_stop(ase);
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf->size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t ascs_metadata(struct bt_ascs *ascs, struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
const struct bt_ascs_metadata_op *req;
|
|
|
|
struct bt_ascs_metadata *meta;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*req)) {
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
req = net_buf_simple_pull_mem(buf, sizeof(*req));
|
|
|
|
|
|
|
|
BT_DBG("num_ases %u", req->num_ases);
|
|
|
|
|
2022-08-04 09:46:26 +02:00
|
|
|
if (req->num_ases < 1) {
|
|
|
|
BT_WARN("Number_of_ASEs parameter value is less than 1");
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
} else if (buf->len < req->num_ases * sizeof(*meta)) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Metadata: len %u < %zu", buf->len,
|
|
|
|
req->num_ases * sizeof(*meta));
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < req->num_ases; i++) {
|
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
|
|
|
|
meta = net_buf_simple_pull_mem(buf, sizeof(*meta));
|
|
|
|
|
|
|
|
if (buf->len < meta->len) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Metadata: len %u < %u", buf->len, meta->len);
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
BT_DBG("ase 0x%02x meta->len %u", meta->ase, meta->len);
|
|
|
|
|
|
|
|
ase = ase_find(ascs, meta->ase);
|
|
|
|
if (!ase) {
|
|
|
|
ascs_cp_rsp_add(meta->ase, BT_ASCS_METADATA_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_ASE, 0x00);
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Unknown ase 0x%02x", meta->ase);
|
2022-01-07 17:23:45 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ase_metadata(ase, BT_ASCS_METADATA_OP, meta, buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf->size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t ascs_release(struct bt_ascs *ascs, struct net_buf_simple *buf)
|
|
|
|
{
|
|
|
|
const struct bt_ascs_release_op *req;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*req)) {
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
req = net_buf_simple_pull_mem(buf, sizeof(*req));
|
|
|
|
|
|
|
|
BT_DBG("num_ases %u", req->num_ases);
|
|
|
|
|
2022-08-04 09:46:26 +02:00
|
|
|
if (req->num_ases < 1) {
|
|
|
|
BT_WARN("Number_of_ASEs parameter value is less than 1");
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
} else if (buf->len < req->num_ases) {
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Malformed ASE Release: len %u < %u", buf->len, req->num_ases);
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < req->num_ases; i++) {
|
|
|
|
uint8_t id;
|
|
|
|
struct bt_ascs_ase *ase;
|
|
|
|
|
|
|
|
id = net_buf_simple_pull_u8(buf);
|
|
|
|
|
|
|
|
BT_DBG("ase 0x%02x", id);
|
|
|
|
|
|
|
|
ase = ase_find(ascs, id);
|
|
|
|
if (!ase) {
|
|
|
|
ascs_cp_rsp_add(id, BT_ASCS_RELEASE_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_ASE, 0);
|
2022-08-04 13:23:06 +02:00
|
|
|
BT_WARN("Unknown ase 0x%02x", id);
|
2022-01-07 17:23:45 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-09-02 10:53:21 +02:00
|
|
|
if (ase->ep.status.state == BT_AUDIO_EP_STATE_IDLE ||
|
|
|
|
ase->ep.status.state == BT_AUDIO_EP_STATE_RELEASING) {
|
2022-08-04 12:10:57 +02:00
|
|
|
BT_WARN("Invalid operation in state: %s",
|
|
|
|
bt_audio_ep_state_str(ase->ep.status.state));
|
|
|
|
ascs_cp_rsp_add(id, BT_ASCS_RELEASE_OP,
|
|
|
|
BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
Bluetooth: Audio: Remove caching ASE on release
Remove the support for caching ASEs on release.
This is mainly due to the 2-stage design in Zephyr: We
have the bt_audio_stream and the bt_audio_ep, where the
the latter represents an ASE in most cases.
However, once the endpoint goes into the codec configured
state, then a stream must be bound to it. This would be fine
(although a stream is technically not established at this point),
except that a stream is also, for unicast, specified for a
specific ACL. Once the ACL disconnects, the stream technically
becomes invalid, and if the stream is invalid, so is the endpoint.
Based on that, there's no reason (or even option) to keep the
endpoint in a codec configured state, as it would not
have a valid stream associated with it.
If we want to support an ASE going into the codec
configured state on ASE release (or ACL disconnect),
the relationsship between ASEs and streams needs to
be redefined.
Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
2022-09-01 16:14:15 +02:00
|
|
|
ase_release(ase);
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return buf->size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t ascs_cp_write(struct bt_conn *conn,
|
|
|
|
const struct bt_gatt_attr *attr, const void *data,
|
|
|
|
uint16_t len, uint16_t offset, uint8_t flags)
|
|
|
|
{
|
2022-10-14 14:27:04 +02:00
|
|
|
struct bt_ascs *ascs = ascs_get(conn);
|
2022-01-07 17:23:45 +01:00
|
|
|
const struct bt_ascs_ase_cp *req;
|
|
|
|
struct net_buf_simple buf;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (offset) {
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (len < sizeof(*req)) {
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
net_buf_simple_init_with_data(&buf, (void *) data, len);
|
|
|
|
|
|
|
|
req = net_buf_simple_pull_mem(&buf, sizeof(*req));
|
|
|
|
|
|
|
|
BT_DBG("conn %p attr %p buf %p len %u op %s (0x%02x)", conn,
|
|
|
|
attr, data, len, bt_ascs_op_str(req->op), req->op);
|
|
|
|
|
|
|
|
/* Reset/empty response buffer before using it again */
|
|
|
|
net_buf_simple_reset(&rsp_buf);
|
|
|
|
|
|
|
|
switch (req->op) {
|
|
|
|
case BT_ASCS_CONFIG_OP:
|
|
|
|
ret = ascs_config(ascs, &buf);
|
|
|
|
break;
|
|
|
|
case BT_ASCS_QOS_OP:
|
|
|
|
ret = ascs_qos(ascs, &buf);
|
|
|
|
break;
|
|
|
|
case BT_ASCS_ENABLE_OP:
|
|
|
|
ret = ascs_enable(ascs, &buf);
|
|
|
|
break;
|
|
|
|
case BT_ASCS_START_OP:
|
|
|
|
ret = ascs_start(ascs, &buf);
|
|
|
|
break;
|
|
|
|
case BT_ASCS_DISABLE_OP:
|
|
|
|
ret = ascs_disable(ascs, &buf);
|
|
|
|
break;
|
|
|
|
case BT_ASCS_STOP_OP:
|
|
|
|
ret = ascs_stop(ascs, &buf);
|
|
|
|
break;
|
|
|
|
case BT_ASCS_METADATA_OP:
|
|
|
|
ret = ascs_metadata(ascs, &buf);
|
|
|
|
break;
|
|
|
|
case BT_ASCS_RELEASE_OP:
|
|
|
|
ret = ascs_release(ascs, &buf);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ascs_cp_rsp_add(0x00, req->op, BT_ASCS_RSP_NOT_SUPPORTED, 0);
|
|
|
|
BT_DBG("Unknown opcode");
|
2022-05-12 12:25:42 +02:00
|
|
|
goto respond;
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ret == BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN)) {
|
|
|
|
ascs_cp_rsp_add(0, req->op, BT_ASCS_RSP_TRUNCATED,
|
|
|
|
BT_ASCS_REASON_NONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
respond:
|
2022-10-06 13:51:51 +02:00
|
|
|
control_point_notify(ascs->conn, rsp_buf.data, rsp_buf.len);
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2022-09-29 11:30:25 +02:00
|
|
|
#define BT_ASCS_ASE_DEFINE(_uuid, _id) \
|
|
|
|
BT_AUDIO_CHRC(_uuid, \
|
|
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
|
|
|
|
BT_GATT_PERM_READ_ENCRYPT, \
|
|
|
|
ascs_ase_read, NULL, UINT_TO_POINTER(_id)), \
|
|
|
|
BT_AUDIO_CCC(ascs_ase_cfg_changed)
|
|
|
|
#define BT_ASCS_ASE_SNK_DEFINE(_n, ...) BT_ASCS_ASE_DEFINE(BT_UUID_ASCS_ASE_SNK, (_n) + 1)
|
|
|
|
#define BT_ASCS_ASE_SRC_DEFINE(_n, ...) BT_ASCS_ASE_DEFINE(BT_UUID_ASCS_ASE_SRC, (_n) + 1 + \
|
|
|
|
CONFIG_BT_ASCS_ASE_SNK_COUNT)
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
BT_GATT_SERVICE_DEFINE(ascs_svc,
|
|
|
|
BT_GATT_PRIMARY_SERVICE(BT_UUID_ASCS),
|
2022-07-25 09:56:23 +02:00
|
|
|
BT_AUDIO_CHRC(BT_UUID_ASCS_ASE_CP,
|
|
|
|
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY,
|
|
|
|
BT_GATT_PERM_WRITE_ENCRYPT,
|
|
|
|
NULL, ascs_cp_write, NULL),
|
|
|
|
BT_AUDIO_CCC(ascs_cp_cfg_changed),
|
2022-10-06 13:51:51 +02:00
|
|
|
LISTIFY(CONFIG_BT_ASCS_ASE_SNK_COUNT, BT_ASCS_ASE_SNK_DEFINE, (,)),
|
|
|
|
LISTIFY(CONFIG_BT_ASCS_ASE_SRC_COUNT, BT_ASCS_ASE_SRC_DEFINE, (,)),
|
2022-01-07 17:23:45 +01:00
|
|
|
);
|
|
|
|
|
2022-10-06 13:51:51 +02:00
|
|
|
static int control_point_notify(struct bt_conn *conn, const void *data, uint16_t len)
|
|
|
|
{
|
|
|
|
return bt_gatt_notify_uuid(conn, BT_UUID_ASCS_ASE_CP, ascs_svc.attrs, data, len);
|
|
|
|
}
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
#endif /* BT_AUDIO_UNICAST_SERVER */
|