blob: 8da626fe3aa0620d8abc7e7ef2633c032165daaf [file]
// SPDX-License-Identifier: LGPL-2.1-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2022 Intel Corporation. All rights reserved.
* Copyright 2023-2025 NXP
*
*/
#define _GNU_SOURCE
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/uuid.h"
#include "src/shared/io.h"
#include "src/shared/queue.h"
#include "src/shared/util.h"
#include "src/shared/timeout.h"
#include "src/shared/att.h"
#include "src/shared/gatt-db.h"
#include "src/shared/gatt-server.h"
#include "src/shared/gatt-client.h"
#include "src/shared/bap.h"
#include "src/shared/ascs.h"
#include "src/shared/bap-debug.h"
/* Maximum number of ASE(s) */
#define NUM_SINKS 2
#define NUM_SOURCE 2
#define NUM_ASES (NUM_SINKS + NUM_SOURCE)
#define ASE_UUID(_id) (_id < NUM_SINKS ? ASE_SINK_UUID : ASE_SOURCE_UUID)
#define DBG(_bap, fmt, arg...) \
bap_debug(_bap, "%s:%s() " fmt, __FILE__, __func__, ## arg)
#define LTV(_type, _bytes...) \
{ \
.len = 1 + sizeof((uint8_t []) { _bytes }), \
.type = _type, \
.data = { _bytes }, \
}
#define BAP_PROCESS_TIMEOUT 10
#define BAP_FREQ_LTV_TYPE 1
#define BAP_DURATION_LTV_TYPE 2
#define BAP_CHANNEL_ALLOCATION_LTV_TYPE 3
#define BAP_FRAME_LEN_LTV_TYPE 4
#define CODEC_SPECIFIC_CONFIGURATION_MASK (\
(1<<BAP_FREQ_LTV_TYPE)|\
(1<<BAP_DURATION_LTV_TYPE)|\
(1<<BAP_FRAME_LEN_LTV_TYPE))
struct bt_bap_pac_changed {
unsigned int id;
bt_bap_pac_func_t added;
bt_bap_pac_func_t removed;
bt_bap_destroy_func_t destroy;
void *data;
};
struct bt_bap_ready {
unsigned int id;
bt_bap_ready_func_t func;
bt_bap_destroy_func_t destroy;
void *data;
};
struct bt_bap_state {
unsigned int id;
bt_bap_state_func_t func;
bt_bap_connecting_func_t connecting;
bt_bap_destroy_func_t destroy;
void *data;
};
struct bt_bap_bis_cb {
unsigned int id;
bt_bap_bis_func_t probe;
bt_bap_func_t remove;
bt_bap_destroy_func_t destroy;
void *data;
};
struct bt_bap_bcode_cb {
unsigned int id;
bt_bap_bcode_func_t func;
bt_bap_destroy_func_t destroy;
void *data;
};
struct bt_bap_cb {
unsigned int id;
bt_bap_func_t attached;
bt_bap_func_t detached;
void *user_data;
};
struct bt_pacs {
struct bt_bap_db *bdb;
struct gatt_db_attribute *service;
struct gatt_db_attribute *sink;
struct gatt_db_attribute *sink_ccc;
struct gatt_db_attribute *sink_loc;
struct gatt_db_attribute *sink_loc_ccc;
struct gatt_db_attribute *source;
struct gatt_db_attribute *source_ccc;
struct gatt_db_attribute *source_loc;
struct gatt_db_attribute *source_loc_ccc;
struct gatt_db_attribute *context;
struct gatt_db_attribute *context_ccc;
struct gatt_db_attribute *supported_context;
struct gatt_db_attribute *supported_context_ccc;
uint32_t source_loc_value;
uint32_t sink_loc_value;
uint16_t source_context_value;
uint16_t sink_context_value;
uint16_t supported_source_context_value;
uint16_t supported_sink_context_value;
};
struct bt_ase {
struct bt_ascs *ascs;
uint8_t id;
struct gatt_db_attribute *attr;
struct gatt_db_attribute *ccc;
};
struct bt_ascs {
struct bt_bap_db *bdb;
struct gatt_db_attribute *service;
struct bt_ase *ase[NUM_ASES];
struct gatt_db_attribute *ase_cp;
struct gatt_db_attribute *ase_cp_ccc;
};
struct bt_bap_db {
struct gatt_db *db;
struct bt_pacs *pacs;
struct bt_ascs *ascs;
struct queue *sinks;
struct queue *sources;
struct queue *broadcast_sources;
struct queue *broadcast_sinks;
};
struct bt_bap_req {
unsigned int id;
struct bt_bap_stream *stream;
uint8_t op;
struct queue *group;
struct iovec *iov;
size_t len;
bt_bap_stream_func_t func;
void *user_data;
};
typedef void (*bap_notify_t)(struct bt_bap *bap, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data);
struct bt_bap_notify {
unsigned int id;
struct bt_bap *bap;
bap_notify_t func;
void *user_data;
};
struct bt_bap {
int ref_count;
struct bt_bap_db *ldb;
struct bt_bap_db *rdb;
struct bt_gatt_client *client;
struct bt_att *att;
struct bt_bap_req *req;
unsigned int cp_id;
unsigned int process_id;
unsigned int disconn_id;
unsigned int idle_id;
bool in_cp_write;
struct queue *reqs;
struct queue *notify;
struct queue *streams;
struct queue *local_eps;
struct queue *remote_eps;
struct queue *pac_cbs;
struct queue *ready_cbs;
struct queue *state_cbs;
struct queue *bis_cbs;
struct queue *bcode_cbs;
bt_bap_debug_func_t debug_func;
bt_bap_destroy_func_t debug_destroy;
void *debug_data;
void *user_data;
};
struct bt_bap_pac {
struct bt_bap_db *bdb;
char *name;
uint8_t type;
struct bt_bap_codec codec;
struct bt_bap_pac_qos qos;
struct iovec *data;
struct iovec *metadata;
struct queue *channels;
struct bt_bap_pac_ops *ops;
void *user_data;
};
struct bt_bap_endpoint {
struct bt_bap *bap;
struct bt_bap_db *bdb;
struct bt_bap_stream *stream;
struct gatt_db_attribute *attr;
uint8_t id;
uint8_t dir;
uint8_t old_state;
uint8_t state;
unsigned int state_id;
};
struct bt_bap_stream_io {
struct bt_bap *bap;
int ref_count;
struct io *io;
bool connecting;
};
struct bt_bap_stream_ops {
uint8_t type;
void (*set_state)(struct bt_bap_stream *stream, uint8_t state);
unsigned int (*get_state)(struct bt_bap_stream *stream);
unsigned int (*config)(struct bt_bap_stream *stream,
struct bt_bap_qos *qos, struct iovec *data,
bt_bap_stream_func_t func, void *user_data);
unsigned int (*qos)(struct bt_bap_stream *stream,
struct bt_bap_qos *qos,
bt_bap_stream_func_t func, void *user_data);
unsigned int (*enable)(struct bt_bap_stream *stream, bool enable_links,
struct iovec *metadata,
bt_bap_stream_func_t func, void *user_data);
unsigned int (*start)(struct bt_bap_stream *stream,
bt_bap_stream_func_t func, void *user_data);
unsigned int (*disable)(struct bt_bap_stream *stream,
bool disable_links, bt_bap_stream_func_t func,
void *user_data);
unsigned int (*stop)(struct bt_bap_stream *stream,
bt_bap_stream_func_t func, void *user_data);
unsigned int (*metadata)(struct bt_bap_stream *stream,
struct iovec *data, bt_bap_stream_func_t func,
void *user_data);
unsigned int (*get_dir)(struct bt_bap_stream *stream);
unsigned int (*get_loc)(struct bt_bap_stream *stream);
unsigned int (*release)(struct bt_bap_stream *stream,
bt_bap_stream_func_t func, void *user_data);
void (*detach)(struct bt_bap_stream *stream);
bool (*set_io)(struct bt_bap_stream *stream, int fd);
struct bt_bap_stream_io* (*get_io)(struct bt_bap_stream *stream);
uint8_t (*io_dir)(struct bt_bap_stream *stream);
int (*io_link)(struct bt_bap_stream *stream,
struct bt_bap_stream *link);
int (*io_unlink)(struct bt_bap_stream *stream,
struct bt_bap_stream *link);
};
struct bt_bap_stream {
int ref_count;
struct bt_bap *bap;
struct bt_bap_endpoint *ep;
struct bt_bap_pac *lpac;
struct bt_bap_pac *rpac;
struct iovec *cc;
struct iovec *meta;
struct bt_bap_qos qos;
struct queue *links;
struct bt_bap_stream_io *io;
const struct bt_bap_stream_ops *ops;
uint8_t old_state;
uint8_t state;
unsigned int state_id;
struct queue *pending_states;
bool no_cache_config;
bool client;
bool locked;
bool need_reconfig;
void *user_data;
};
/* TODO: Figure out the capabilities types */
#define BT_CODEC_CAP_PARAMS 0x01
#define BT_CODEC_CAP_DRM 0x0a
#define BT_CODEC_CAP_DRM_VALUE 0x0b
struct bt_pac_metadata {
uint8_t len;
uint8_t data[0];
} __packed;
struct bt_pac {
struct bt_bap_codec codec; /* Codec ID */
uint8_t cc_len; /* Codec Capabilities Length */
struct bt_ltv cc[0]; /* Codec Specific Capabilities */
struct bt_pac_metadata meta[0]; /* Metadata */
} __packed;
struct bt_pacs_read_rsp {
uint8_t num_pac;
struct bt_pac pac[0];
} __packed;
struct bt_pacs_context {
uint16_t snk;
uint16_t src;
} __packed;
struct bt_base {
uint8_t big_id;
uint32_t pres_delay;
uint8_t next_bis_index;
struct queue *subgroups;
};
struct bt_subgroup {
uint8_t index;
struct bt_bap_codec codec;
struct iovec *caps;
struct iovec *meta;
struct queue *bises;
};
struct bt_bis {
uint8_t index;
struct iovec *caps;
};
/* Contains local bt_bap_db */
static struct queue *bap_db;
static struct queue *bap_cbs;
static struct queue *sessions;
/* Structure holding the parameters for Periodic Advertisement create sync.
* The full QOS is populated at the time the user selects and endpoint and
* configures it using SetConfiguration.
*/
struct bt_iso_qos bap_sink_pa_qos = {
.bcast = {
.options = 0x00,
.skip = 0x0000,
.sync_timeout = BT_ISO_SYNC_TIMEOUT,
.sync_cte_type = 0x00,
/* TODO: The following parameters are not needed for PA Sync.
* They will be removed when the kernel checks will be removed.
*/
.big = BT_ISO_QOS_BIG_UNSET,
.bis = BT_ISO_QOS_BIS_UNSET,
.encryption = 0x00,
.bcode = {0x00},
.mse = 0x00,
.timeout = BT_ISO_SYNC_TIMEOUT,
.sync_factor = 0x07,
.packing = 0x00,
.framing = 0x00,
.in = {
.interval = 10000,
.latency = 10,
.sdu = 40,
.phys = BIT(1),
.rtn = 2,
},
.out = {
.interval = 10000,
.latency = 10,
.sdu = 40,
.phys = BIT(1),
.rtn = 2,
}
}
};
static void bap_stream_set_io(void *data, void *user_data);
static void stream_find_io(void *data, void *user_data);
static void bap_stream_get_dir(void *data, void *user_data);
static struct bt_bap_stream_io *stream_io_ref(struct bt_bap_stream_io *io);
static int bap_bcast_io_unlink(struct bt_bap_stream *stream,
struct bt_bap_stream *link);
static bool bap_db_match(const void *data, const void *match_data)
{
const struct bt_bap_db *bdb = data;
const struct gatt_db *db = match_data;
return (bdb->db == db);
}
unsigned int bt_bap_pac_register(struct bt_bap *bap, bt_bap_pac_func_t added,
bt_bap_pac_func_t removed, void *user_data,
bt_bap_destroy_func_t destroy)
{
struct bt_bap_pac_changed *changed;
static unsigned int id;
if (!bap)
return 0;
changed = new0(struct bt_bap_pac_changed, 1);
changed->id = ++id ? id : ++id;
changed->added = added;
changed->removed = removed;
changed->destroy = destroy;
changed->data = user_data;
queue_push_tail(bap->pac_cbs, changed);
return changed->id;
}
static void pac_changed_free(void *data)
{
struct bt_bap_pac_changed *changed = data;
if (changed->destroy)
changed->destroy(changed->data);
free(changed);
}
static bool match_pac_changed_id(const void *data, const void *match_data)
{
const struct bt_bap_pac_changed *changed = data;
unsigned int id = PTR_TO_UINT(match_data);
return (changed->id == id);
}
bool bt_bap_pac_unregister(struct bt_bap *bap, unsigned int id)
{
struct bt_bap_pac_changed *changed;
if (!bap)
return false;
changed = queue_remove_if(bap->pac_cbs, match_pac_changed_id,
UINT_TO_PTR(id));
if (!changed)
return false;
pac_changed_free(changed);
return true;
}
static void pac_foreach(void *data, void *user_data)
{
struct bt_bap_pac *pac = data;
struct iovec *iov = user_data;
struct bt_pacs_read_rsp *rsp;
struct bt_pac *p;
struct bt_pac_metadata *meta;
if (!iov->iov_len) {
rsp = util_iov_push(iov, sizeof(*rsp));
rsp->num_pac = 0;
} else
rsp = iov->iov_base;
rsp->num_pac++;
p = util_iov_push(iov, sizeof(*p));
p->codec.id = pac->codec.id;
p->codec.cid = cpu_to_le16(pac->codec.cid);
p->codec.vid = cpu_to_le16(pac->codec.vid);
if (pac->data) {
p->cc_len = pac->data->iov_len;
util_iov_push_mem(iov, p->cc_len, pac->data->iov_base);
} else
p->cc_len = 0;
meta = util_iov_push(iov, sizeof(*meta));
if (pac->metadata) {
meta->len = pac->metadata->iov_len;
util_iov_push_mem(iov, meta->len, pac->metadata->iov_base);
} else
meta->len = 0;
}
static void pacs_sink_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_pacs *pacs = user_data;
struct bt_bap_db *bdb = pacs->bdb;
struct iovec iov;
uint8_t value[512];
memset(value, 0, sizeof(value));
iov.iov_base = value;
iov.iov_len = 0;
queue_foreach(bdb->sinks, pac_foreach, &iov);
queue_foreach(bdb->broadcast_sinks, pac_foreach, &iov);
if (offset > iov.iov_len) {
gatt_db_attribute_read_result(attrib, id,
BT_ATT_ERROR_INVALID_OFFSET,
NULL, 0);
return;
}
gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base + offset,
iov.iov_len - offset);
}
static void pacs_sink_loc_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_pacs *pacs = user_data;
uint32_t value = cpu_to_le32(pacs->sink_loc_value);
gatt_db_attribute_read_result(attrib, id, 0, (void *) &value,
sizeof(value));
}
static void pacs_source_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_pacs *pacs = user_data;
struct bt_bap_db *bdb = pacs->bdb;
struct iovec iov;
uint8_t value[512];
memset(value, 0, sizeof(value));
iov.iov_base = value;
iov.iov_len = 0;
queue_foreach(bdb->sources, pac_foreach, &iov);
if (offset > iov.iov_len) {
gatt_db_attribute_read_result(attrib, id,
BT_ATT_ERROR_INVALID_OFFSET,
NULL, 0);
return;
}
gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base + offset,
iov.iov_len - offset);
}
static void pacs_source_loc_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_pacs *pacs = user_data;
uint32_t value = cpu_to_le32(pacs->source_loc_value);
gatt_db_attribute_read_result(attrib, id, 0, (void *) &value,
sizeof(value));
}
static void pacs_context_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_pacs *pacs = user_data;
struct bt_pacs_context ctx = {
.snk = cpu_to_le16(pacs->sink_context_value),
.src = cpu_to_le16(pacs->source_context_value)
};
gatt_db_attribute_read_result(attrib, id, 0, (void *) &ctx,
sizeof(ctx));
}
static void pacs_supported_context_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_pacs *pacs = user_data;
struct bt_pacs_context ctx = {
.snk = cpu_to_le16(pacs->supported_sink_context_value),
.src = cpu_to_le16(pacs->supported_source_context_value)
};
gatt_db_attribute_read_result(attrib, id, 0, (void *) &ctx,
sizeof(ctx));
}
static struct bt_pacs *pacs_new(struct gatt_db *db)
{
struct bt_pacs *pacs;
bt_uuid_t uuid;
if (!db)
return NULL;
pacs = new0(struct bt_pacs, 1);
/* Populate DB with PACS attributes */
bt_uuid16_create(&uuid, PACS_UUID);
pacs->service = gatt_db_add_service(db, &uuid, true, 19);
bt_uuid16_create(&uuid, PAC_SINK_CHRC_UUID);
pacs->sink = gatt_db_service_add_characteristic(pacs->service, &uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
pacs_sink_read, NULL,
pacs);
pacs->sink_ccc = gatt_db_service_add_ccc(pacs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
bt_uuid16_create(&uuid, PAC_SINK_LOC_CHRC_UUID);
pacs->sink_loc = gatt_db_service_add_characteristic(pacs->service,
&uuid, BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
pacs_sink_loc_read, NULL,
pacs);
gatt_db_attribute_set_fixed_length(pacs->sink_loc, sizeof(uint32_t));
pacs->sink_loc_ccc = gatt_db_service_add_ccc(pacs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
bt_uuid16_create(&uuid, PAC_SOURCE_CHRC_UUID);
pacs->source = gatt_db_service_add_characteristic(pacs->service, &uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
pacs_source_read, NULL,
pacs);
pacs->source_ccc = gatt_db_service_add_ccc(pacs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
bt_uuid16_create(&uuid, PAC_SOURCE_LOC_CHRC_UUID);
pacs->source_loc = gatt_db_service_add_characteristic(pacs->service,
&uuid, BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
pacs_source_loc_read, NULL,
pacs);
gatt_db_attribute_set_fixed_length(pacs->source_loc, sizeof(uint32_t));
pacs->source_loc_ccc = gatt_db_service_add_ccc(pacs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
bt_uuid16_create(&uuid, PAC_CONTEXT);
pacs->context = gatt_db_service_add_characteristic(pacs->service,
&uuid, BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
pacs_context_read, NULL, pacs);
gatt_db_attribute_set_fixed_length(pacs->context,
sizeof(struct bt_pacs_context));
pacs->context_ccc = gatt_db_service_add_ccc(pacs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
bt_uuid16_create(&uuid, PAC_SUPPORTED_CONTEXT);
pacs->supported_context =
gatt_db_service_add_characteristic(pacs->service, &uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
pacs_supported_context_read, NULL,
pacs);
gatt_db_attribute_set_fixed_length(pacs->supported_context,
sizeof(struct bt_pacs_context));
pacs->supported_context_ccc = gatt_db_service_add_ccc(pacs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
gatt_db_service_set_active(pacs->service, true);
return pacs;
}
static void bap_debug(struct bt_bap *bap, const char *format, ...)
{
va_list ap;
if (!bap || !format || !bap->debug_func)
return;
va_start(ap, format);
util_debug_va(bap->debug_func, bap->debug_data, format, ap);
va_end(ap);
}
static void bap_disconnected(int err, void *user_data)
{
struct bt_bap *bap = user_data;
bap->disconn_id = 0;
DBG(bap, "bap %p disconnected err %d", bap, err);
bt_bap_detach(bap);
}
struct bt_bap *bt_bap_get_session(struct bt_att *att, struct gatt_db *db)
{
const struct queue_entry *entry;
struct bt_bap *bap;
for (entry = queue_get_entries(sessions); entry; entry = entry->next) {
struct bt_bap *bap = entry->data;
if (att == bt_bap_get_att(bap))
return bap;
}
bap = bt_bap_new(db, NULL);
if (!bap)
return NULL;
bap->att = att;
bt_bap_attach(bap, NULL);
return bap;
}
static bool bap_endpoint_match(const void *data, const void *match_data)
{
const struct bt_bap_endpoint *ep = data;
const struct gatt_db_attribute *attr = match_data;
return (ep->attr == attr);
}
static struct bt_bap_endpoint *bap_endpoint_new(struct bt_bap_db *bdb,
struct gatt_db_attribute *attr)
{
struct bt_bap_endpoint *ep;
bt_uuid_t uuid, source, sink;
if (!gatt_db_attribute_get_char_data(attr, NULL, NULL, NULL, NULL,
&uuid))
return NULL;
ep = new0(struct bt_bap_endpoint, 1);
ep->bdb = bdb;
ep->attr = attr;
bt_uuid16_create(&source, ASE_SOURCE_UUID);
bt_uuid16_create(&sink, ASE_SINK_UUID);
if (!bt_uuid_cmp(&source, &uuid))
ep->dir = BT_BAP_SOURCE;
else if (!bt_uuid_cmp(&sink, &uuid))
ep->dir = BT_BAP_SINK;
return ep;
}
static struct bt_bap_endpoint *bap_endpoint_new_broadcast(struct bt_bap_db *bdb,
uint8_t type)
{
struct bt_bap_endpoint *ep;
ep = new0(struct bt_bap_endpoint, 1);
ep->bdb = bdb;
ep->attr = NULL;
if (type == BT_BAP_BCAST_SINK)
ep->dir = BT_BAP_BCAST_SOURCE;
else
ep->dir = BT_BAP_BCAST_SINK;
return ep;
}
static struct bt_bap_endpoint *bap_get_endpoint(struct queue *endpoints,
struct bt_bap_db *db,
struct gatt_db_attribute *attr)
{
struct bt_bap_endpoint *ep;
if (!db || !attr)
return NULL;
ep = queue_find(endpoints, bap_endpoint_match, attr);
if (ep)
return ep;
ep = bap_endpoint_new(db, attr);
if (!ep)
return NULL;
queue_push_tail(endpoints, ep);
return ep;
}
static bool match_ep_type(const void *data, const void *match_data)
{
const struct bt_bap_endpoint *ep = data;
const uint8_t type = PTR_TO_INT(match_data);
return (ep->dir == type);
}
static struct bt_bap_endpoint *bap_get_endpoint_bcast(struct queue *endpoints,
struct bt_bap_db *db, uint8_t type)
{
struct bt_bap_endpoint *ep;
if (!db)
return NULL;
ep = queue_find(endpoints, match_ep_type, INT_TO_PTR(type));
if (ep)
return ep;
ep = bap_endpoint_new_broadcast(db, type);
if (!ep)
return NULL;
queue_push_tail(endpoints, ep);
return ep;
}
static bool bap_endpoint_match_id(const void *data, const void *match_data)
{
const struct bt_bap_endpoint *ep = data;
uint8_t id = PTR_TO_UINT(match_data);
return (ep->id == id);
}
static struct bt_bap_endpoint *bap_get_local_endpoint_id(struct bt_bap *bap,
uint8_t id)
{
struct bt_bap_endpoint *ep;
struct gatt_db_attribute *attr = NULL;
size_t i;
if (!bap)
return NULL;
ep = queue_find(bap->local_eps, bap_endpoint_match_id, UINT_TO_PTR(id));
if (ep)
return ep;
for (i = 0; i < ARRAY_SIZE(bap->ldb->ascs->ase); i++) {
struct bt_ase *ase = bap->ldb->ascs->ase[i];
if (id) {
if (ase->id != id)
continue;
attr = ase->attr;
break;
}
ep = queue_find(bap->local_eps, bap_endpoint_match, ase->attr);
if (!ep) {
attr = ase->attr;
break;
}
}
if (!attr)
return NULL;
ep = bap_endpoint_new(bap->ldb, attr);
if (!ep)
return NULL;
ep->id = id;
queue_push_tail(bap->local_eps, ep);
return ep;
}
static void ascs_ase_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_ase *ase = user_data;
struct bt_bap *bap = NULL;
struct bt_bap_endpoint *ep = NULL;
struct bt_ascs_ase_status rsp;
if (ase)
bap = bt_bap_get_session(att, ase->ascs->bdb->db);
if (bap)
ep = bap_get_endpoint(bap->local_eps, bap->ldb, attrib);
if (!ep) {
gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY,
NULL, 0);
return;
}
memset(&rsp, 0, sizeof(rsp));
/* Initialize Endpoint ID with ASE ID */
if (ase->id != ep->id)
ep->id = ase->id;
rsp.id = ep->id;
rsp.state = ep->state;
gatt_db_attribute_read_result(attrib, id, 0, (void *) &rsp,
sizeof(rsp));
}
static void ase_new(struct bt_ascs *ascs, int i)
{
struct bt_ase *ase;
bt_uuid_t uuid;
if (!ascs)
return;
ase = new0(struct bt_ase, 1);
ase->ascs = ascs;
ase->id = i + 1;
bt_uuid16_create(&uuid, ASE_UUID(i));
ase->attr = gatt_db_service_add_characteristic(ascs->service, &uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
ascs_ase_read, NULL,
ase);
ase->ccc = gatt_db_service_add_ccc(ascs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
ascs->ase[i] = ase;
}
static bool bap_codec_equal(const struct bt_bap_codec *c1,
const struct bt_bap_codec *c2)
{
/* Compare CID and VID if id is 0xff */
if (c1->id == 0xff)
return !memcmp(c1, c2, sizeof(*c1));
return c1->id == c2->id;
}
static void ascs_ase_rsp_add(struct iovec *iov, uint8_t id,
uint8_t code, uint8_t reason)
{
struct bt_ascs_cp_rsp *cp;
struct bt_ascs_ase_rsp *rsp;
if (!iov)
return;
cp = iov->iov_base;
if (cp->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:
cp->num_ase = 0xff;
break;
default:
cp->num_ase++;
break;
}
iov->iov_len += sizeof(*rsp);
iov->iov_base = realloc(iov->iov_base, iov->iov_len);
rsp = iov->iov_base + (iov->iov_len - sizeof(*rsp));
rsp->ase = id;
rsp->code = code;
rsp->reason = reason;
}
static void ascs_ase_rsp_success(struct iovec *iov, uint8_t id)
{
return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_SUCCESS,
BT_ASCS_REASON_NONE);
}
static void stream_notify_config(struct bt_bap_stream *stream)
{
struct bt_bap_endpoint *ep = stream->ep;
struct bt_bap_pac *lpac = stream->lpac;
struct bt_ascs_ase_status *status;
struct bt_ascs_ase_status_config *config;
size_t len;
DBG(stream->bap, "stream %p", stream);
if (!lpac)
return;
len = sizeof(*status) + sizeof(*config) + stream->cc->iov_len;
status = malloc(len);
memset(status, 0, len);
status->id = ep->id;
status->state = ep->state;
/* Initialize preferred settings if not set */
if (!lpac->qos.phys)
lpac->qos.phys = BIT(1);
if (!lpac->qos.rtn)
lpac->qos.rtn = 0x05;
if (!lpac->qos.latency)
lpac->qos.latency = 10;
if (!lpac->qos.pd_min)
lpac->qos.pd_min = 20000;
if (!lpac->qos.pd_max)
lpac->qos.pd_max = 40000;
if (!lpac->qos.ppd_min)
lpac->qos.ppd_min = lpac->qos.pd_min;
if (!lpac->qos.ppd_max)
lpac->qos.ppd_max = lpac->qos.pd_max;
/* TODO:Add support for setting preferred settings on bt_bap_pac */
config = (void *)status->params;
config->framing = lpac->qos.framing;
config->phys = lpac->qos.phys;
config->rtn = lpac->qos.rtn;
config->latency = cpu_to_le16(lpac->qos.latency);
put_le24(lpac->qos.pd_min, config->pd_min);
put_le24(lpac->qos.pd_max, config->pd_max);
put_le24(lpac->qos.ppd_min, config->ppd_min);
put_le24(lpac->qos.ppd_max, config->ppd_max);
config->codec = lpac->codec;
if (config->codec.id == 0x0ff) {
config->codec.vid = cpu_to_le16(config->codec.vid);
config->codec.cid = cpu_to_le16(config->codec.cid);
}
config->cc_len = stream->cc->iov_len;
memcpy(config->cc, stream->cc->iov_base, stream->cc->iov_len);
gatt_db_attribute_notify(ep->attr, (void *) status, len,
bt_bap_get_att(stream->bap));
free(status);
}
static void stream_notify_qos(struct bt_bap_stream *stream)
{
struct bt_bap_endpoint *ep = stream->ep;
struct bt_ascs_ase_status *status;
struct bt_ascs_ase_status_qos *qos;
size_t len;
DBG(stream->bap, "stream %p", stream);
len = sizeof(*status) + sizeof(*qos);
status = malloc(len);
memset(status, 0, len);
status->id = ep->id;
status->state = ep->state;
qos = (void *)status->params;
qos->cis_id = stream->qos.ucast.cis_id;
qos->cig_id = stream->qos.ucast.cig_id;
put_le24(stream->qos.ucast.io_qos.interval, qos->interval);
qos->framing = stream->qos.ucast.framing;
qos->phys = stream->qos.ucast.io_qos.phys;
qos->sdu = cpu_to_le16(stream->qos.ucast.io_qos.sdu);
qos->rtn = stream->qos.ucast.io_qos.rtn;
qos->latency = cpu_to_le16(stream->qos.ucast.io_qos.latency);
put_le24(stream->qos.ucast.delay, qos->pd);
gatt_db_attribute_notify(ep->attr, (void *) status, len,
bt_bap_get_att(stream->bap));
free(status);
}
static void stream_notify_metadata(struct bt_bap_stream *stream, uint8_t state)
{
struct bt_bap_endpoint *ep = stream->ep;
struct bt_ascs_ase_status *status;
struct bt_ascs_ase_status_metadata *meta;
size_t len;
size_t meta_len = 0;
DBG(stream->bap, "stream %p", stream);
if (stream->meta)
meta_len = stream->meta->iov_len;
len = sizeof(*status) + sizeof(*meta) + meta_len;
status = malloc(len);
memset(status, 0, len);
status->id = ep->id;
status->state = state;
meta = (void *)status->params;
meta->cis_id = stream->qos.ucast.cis_id;
meta->cig_id = stream->qos.ucast.cig_id;
if (stream->meta) {
meta->len = stream->meta->iov_len;
memcpy(meta->data, stream->meta->iov_base, meta->len);
}
gatt_db_attribute_notify(ep->attr, (void *) status, len,
bt_bap_get_att(stream->bap));
free(status);
}
static void stream_notify_release(struct bt_bap_stream *stream)
{
struct bt_bap_endpoint *ep = stream->ep;
struct bt_ascs_ase_status status;
DBG(stream->bap, "stream %p", stream);
memset(&status, 0, sizeof(status));
status.id = ep->id;
status.state = BT_ASCS_ASE_STATE_RELEASING;
gatt_db_attribute_notify(ep->attr, (void *)&status, sizeof(status),
bt_bap_get_att(stream->bap));
}
static void stream_notify_idle(struct bt_bap_stream *stream)
{
struct bt_bap_endpoint *ep = stream->ep;
struct bt_ascs_ase_status status;
DBG(stream->bap, "stream %p", stream);
memset(&status, 0, sizeof(status));
status.id = ep->id;
status.state = BT_ASCS_ASE_STATE_IDLE;
gatt_db_attribute_notify(ep->attr, (void *)&status, sizeof(status),
bt_bap_get_att(stream->bap));
}
static struct bt_bap *bt_bap_ref_safe(struct bt_bap *bap)
{
if (!bap || !bap->ref_count || !queue_find(sessions, NULL, bap))
return NULL;
return bt_bap_ref(bap);
}
static void bap_stream_clear_cfm(struct bt_bap_stream *stream)
{
if (!stream->lpac || !stream->lpac->ops || !stream->lpac->ops->clear)
return;
stream->lpac->ops->clear(stream, stream->lpac->user_data);
}
static int stream_io_get_fd(struct bt_bap_stream_io *io)
{
if (!io)
return -1;
return io_get_fd(io->io);
}
static void stream_io_free(void *data)
{
struct bt_bap_stream_io *io = data;
int fd;
fd = stream_io_get_fd(io);
DBG(io->bap, "fd %d", fd);
io_destroy(io->io);
free(io);
/* Shutdown using SHUT_WR as SHUT_RDWR cause the socket to HUP
* immediately instead of waiting for Disconnect Complete event.
*/
shutdown(fd, SHUT_WR);
}
static void stream_io_unref(struct bt_bap_stream_io *io)
{
if (!io)
return;
if (__sync_sub_and_fetch(&io->ref_count, 1))
return;
stream_io_free(io);
}
static void bap_stream_unlink(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
struct bt_bap_stream *link = user_data;
queue_remove(stream->links, link);
}
static void bap_stream_free(void *data)
{
struct bt_bap_stream *stream = data;
timeout_remove(stream->state_id);
queue_destroy(stream->pending_states, NULL);
if (stream->ep)
stream->ep->stream = NULL;
queue_foreach(stream->links, bap_stream_unlink, stream);
queue_destroy(stream->links, NULL);
stream_io_unref(stream->io);
util_iov_free(stream->cc, 1);
util_iov_free(stream->meta, 1);
free(stream);
}
static void bap_req_free(void *data)
{
struct bt_bap_req *req = data;
size_t i;
queue_destroy(req->group, bap_req_free);
for (i = 0; i < req->len; i++)
free(req->iov[i].iov_base);
free(req->iov);
free(req);
}
static void bap_req_complete(struct bt_bap_req *req,
const struct bt_ascs_ase_rsp *rsp)
{
struct queue *group;
if (!req->func)
goto done;
if (rsp)
req->func(req->stream, rsp->code, rsp->reason, req->user_data);
else
req->func(req->stream, BT_ASCS_RSP_UNSPECIFIED, 0x00,
req->user_data);
done:
/* Detach from request so it can be freed separately */
group = req->group;
req->group = NULL;
queue_foreach(group, (queue_foreach_func_t)bap_req_complete,
(void *)rsp);
queue_destroy(group, NULL);
bap_req_free(req);
}
static bool match_req_stream(const void *data, const void *match_data)
{
const struct bt_bap_req *req = data;
return req->stream == match_data;
}
static void bap_req_abort(void *data)
{
struct bt_bap_req *req = data;
struct bt_bap *bap = req->stream->bap;
DBG(bap, "req %p", req);
bap_req_complete(req, NULL);
}
static void bap_abort_stream_req(struct bt_bap *bap,
struct bt_bap_stream *stream)
{
queue_remove_all(bap->reqs, match_req_stream, stream, bap_req_abort);
if (bap->req && bap->req->stream == stream) {
struct bt_bap_req *req = bap->req;
bap->req = NULL;
bap_req_complete(req, NULL);
}
}
static void bt_bap_stream_unref(void *data)
{
struct bt_bap_stream *stream = data;
if (!stream)
return;
if (__sync_sub_and_fetch(&stream->ref_count, 1))
return;
bap_stream_free(stream);
}
static void bap_ucast_detach(struct bt_bap_stream *stream)
{
struct bt_bap_endpoint *ep = stream->ep;
if (!ep)
return;
DBG(stream->bap, "stream %p ep %p", stream, ep);
bap_abort_stream_req(stream->bap, stream);
queue_remove(stream->bap->streams, stream);
bap_stream_clear_cfm(stream);
ep->stream = NULL;
if (!stream->client) {
ep->state = BT_ASCS_ASE_STATE_IDLE;
ep->old_state = BT_ASCS_ASE_STATE_IDLE;
}
bt_bap_stream_unref(stream);
}
static void bap_bcast_src_detach(struct bt_bap_stream *stream)
{
struct bt_bap_endpoint *ep = stream->ep;
if (!ep)
return;
DBG(stream->bap, "stream %p ep %p", stream, ep);
queue_remove(stream->bap->streams, stream);
bap_stream_clear_cfm(stream);
stream->ep = NULL;
ep->stream = NULL;
bt_bap_stream_unref(stream);
}
static void bap_bcast_sink_detach(struct bt_bap_stream *stream)
{
DBG(stream->bap, "stream %p", stream);
queue_remove(stream->bap->streams, stream);
bap_stream_clear_cfm(stream);
bt_bap_stream_unref(stream);
}
static bool bap_stream_io_link(const void *data, const void *user_data)
{
struct bt_bap_stream *stream = (void *)data;
struct bt_bap_stream *link = (void *)user_data;
return !bt_bap_stream_io_link(stream, link);
}
static void bap_stream_update_io_links(struct bt_bap_stream *stream)
{
struct bt_bap *bap = stream->bap;
DBG(bap, "stream %p", stream);
queue_find(bap->streams, bap_stream_io_link, stream);
}
static bool match_stream_io(const void *data, const void *user_data)
{
const struct bt_bap_stream *stream = data;
const struct bt_bap_stream_io *io = user_data;
if (!stream->io)
return false;
return stream->io == io;
}
static bool bap_stream_io_detach(struct bt_bap_stream *stream)
{
struct bt_bap_stream *link;
struct bt_bap_stream_io *io;
if (!stream->io)
return false;
DBG(stream->bap, "stream %p", stream);
io = stream->io;
stream->io = NULL;
link = queue_find(stream->links, match_stream_io, io);
if (link) {
/* Detach link if in QoS state */
if (link->ep->state == BT_ASCS_ASE_STATE_QOS)
bap_stream_io_detach(link);
}
stream_io_unref(io);
return true;
}
static void bap_stream_state_changed(struct bt_bap_stream *stream)
{
struct bt_bap *bap = stream->bap;
const struct queue_entry *entry;
/* Pre notification updates */
switch (stream->ep->state) {
case BT_ASCS_ASE_STATE_IDLE:
break;
case BT_ASCS_ASE_STATE_CONFIG:
bap_stream_update_io_links(stream);
break;
case BT_ASCS_ASE_STATE_DISABLING:
/* As client, we detach after Receiver Stop Ready */
if (!stream->client)
bap_stream_io_detach(stream);
break;
case BT_ASCS_ASE_STATE_QOS:
if (stream->io && !stream->io->connecting)
bap_stream_io_detach(stream);
else
bap_stream_update_io_links(stream);
break;
case BT_ASCS_ASE_STATE_ENABLING:
case BT_ASCS_ASE_STATE_STREAMING:
break;
}
for (entry = queue_get_entries(bap->state_cbs); entry;
entry = entry->next) {
struct bt_bap_state *state = entry->data;
if (state->func)
state->func(stream, stream->ep->old_state,
stream->ep->state, state->data);
}
/* Post notification updates */
switch (stream->ep->state) {
case BT_ASCS_ASE_STATE_IDLE:
if (bap->req && bap->req->stream == stream) {
bap_req_complete(bap->req, NULL);
bap->req = NULL;
}
if (stream->ops && stream->ops->detach)
stream->ops->detach(stream);
break;
case BT_ASCS_ASE_STATE_QOS:
break;
case BT_ASCS_ASE_STATE_ENABLING:
if (bt_bap_stream_get_io(stream))
bt_bap_stream_start(stream, NULL, NULL);
break;
case BT_ASCS_ASE_STATE_DISABLING:
/* Client may terminate CIS after Receiver Stop Ready completes
* successfully (BAP v1.0.2, 5.6.5.1). Do it when back to QOS.
* Ensure IO is detached also if CIS was not yet established.
*/
if (stream->client) {
bt_bap_stream_stop(stream, NULL, NULL);
if (stream->io)
stream->io->connecting = false;
}
break;
case BT_ASCS_ASE_STATE_RELEASING:
if (stream->client) {
bap_stream_clear_cfm(stream);
bap_stream_io_detach(stream);
bt_bap_stream_io_unlink(stream, NULL);
}
break;
}
}
/* Return false if the stream is being detached */
static bool stream_set_state(struct bt_bap_stream *stream, uint8_t state)
{
struct bt_bap *bap = stream->bap;
/* Check if ref_count is already 0 which means detaching is in
* progress.
*/
bap = bt_bap_ref_safe(bap);
if (!bap) {
if (stream->ops && stream->ops->detach)
stream->ops->detach(stream);
return false;
}
if (stream->ops && stream->ops->set_state)
stream->ops->set_state(stream, state);
bt_bap_unref(bap);
return true;
}
static void ep_config_cb(struct bt_bap_stream *stream, int err)
{
if (err)
return;
stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
}
static uint8_t stream_config(struct bt_bap_stream *stream, struct iovec *cc,
struct iovec *rsp)
{
struct bt_bap_pac *pac = stream->lpac;
DBG(stream->bap, "stream %p", stream);
if (!pac) {
ascs_ase_rsp_add(rsp, stream->ep->id, BT_ASCS_RSP_CONF_REJECTED,
BT_ASCS_REASON_CODEC);
return 0;
}
/* TODO: Wait for pac->ops response */
ascs_ase_rsp_success(rsp, stream->ep->id);
if (!util_iov_memcmp(stream->cc, cc)) {
stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
return 0;
}
util_iov_free(stream->cc, 1);
stream->cc = util_iov_dup(cc, 1);
if (pac->ops && pac->ops->config)
pac->ops->config(stream, cc, NULL, ep_config_cb,
pac->user_data);
return 0;
}
static struct bt_bap_req *bap_req_new(struct bt_bap_stream *stream,
uint8_t op, struct iovec *iov,
size_t len,
bt_bap_stream_func_t func,
void *user_data)
{
struct bt_bap_req *req;
static unsigned int id;
req = new0(struct bt_bap_req, 1);
req->id = ++id ? id : ++id;
req->stream = stream;
req->op = op;
req->iov = util_iov_dup(iov, len);
req->len = len;
req->func = func;
req->user_data = user_data;
return req;
}
static uint16_t bap_req_len(struct bt_bap_req *req)
{
uint16_t len = 0;
size_t i;
const struct queue_entry *e;
for (i = 0; i < req->len; i++)
len += req->iov[i].iov_len;
e = queue_get_entries(req->group);
for (; e; e = e->next)
len += bap_req_len(e->data);
return len;
}
static bool match_req(const void *data, const void *match_data)
{
const struct bt_bap_req *pend = data;
const struct bt_bap_req *req = match_data;
return pend->op == req->op;
}
static struct bt_ascs *bap_get_ascs(struct bt_bap *bap)
{
if (!bap || !bap->rdb)
return NULL;
if (bap->rdb->ascs)
return bap->rdb->ascs;
bap->rdb->ascs = new0(struct bt_ascs, 1);
bap->rdb->ascs->bdb = bap->rdb;
return bap->rdb->ascs;
}
static void append_group(void *data, void *user_data)
{
struct bt_bap_req *req = data;
struct iovec *iov = user_data;
size_t i;
for (i = 0; i < req->len; i++)
util_iov_push_mem(iov, req->iov[i].iov_len,
req->iov[i].iov_base);
}
static bool bap_send(struct bt_bap *bap, struct bt_bap_req *req)
{
struct bt_ascs *ascs = bap_get_ascs(bap);
int ret;
uint16_t handle;
struct bt_ascs_ase_hdr hdr;
struct iovec iov;
size_t i;
iov.iov_len = sizeof(hdr) + bap_req_len(req);
DBG(bap, "req %p len %u", req, iov.iov_len);
if (req->stream && !queue_find(bap->streams, NULL, req->stream)) {
DBG(bap, "stream %p detached, aborting op 0x%02x", req->stream,
req->op);
return false;
}
if (!gatt_db_attribute_get_char_data(ascs->ase_cp, NULL, &handle,
NULL, NULL, NULL)) {
DBG(bap, "Unable to find Control Point");
return false;
}
iov.iov_base = alloca(iov.iov_len);
iov.iov_len = 0;
hdr.op = req->op;
hdr.num = 1 + queue_length(req->group);
util_iov_push_mem(&iov, sizeof(hdr), &hdr);
for (i = 0; i < req->len; i++)
util_iov_push_mem(&iov, req->iov[i].iov_len,
req->iov[i].iov_base);
/* Append the request group with the same opcode */
queue_foreach(req->group, append_group, &iov);
ret = bt_gatt_client_write_without_response(bap->client, handle,
false, iov.iov_base,
iov.iov_len);
if (!ret) {
DBG(bap, "Unable to Write to Control Point");
return false;
}
bap->req = req;
return true;
}
static bool bap_process_queue(void *data)
{
struct bt_bap *bap = data;
struct bt_bap_req *req;
DBG(bap, "");
if (bap->process_id) {
timeout_remove(bap->process_id);
bap->process_id = 0;
}
while ((req = queue_pop_head(bap->reqs))) {
if (bap_send(bap, req))
break;
bap_req_complete(req, NULL);
}
return false;
}
static bool bap_queue_req(struct bt_bap *bap, struct bt_bap_req *req)
{
struct bt_bap_req *pend;
struct queue *queue;
struct bt_att *att = bt_bap_get_att(bap);
uint16_t mtu = bt_att_get_mtu(att);
uint16_t len = 3 + 2 + bap_req_len(req);
if (len > mtu) {
DBG(bap, "Unable to queue request: req len %u > %u mtu", len,
mtu);
return false;
}
pend = queue_find(bap->reqs, match_req, req);
/* Check if req can be grouped together and it fits in the MTU */
if (pend && (bap_req_len(pend) + len < mtu)) {
if (!pend->group)
pend->group = queue_new();
/* Group requests with the same opcode */
queue = pend->group;
} else {
queue = bap->reqs;
}
DBG(bap, "req %p (op 0x%2.2x) queue %p", req, req->op, queue);
if (!queue_push_tail(queue, req)) {
DBG(bap, "Unable to queue request");
return false;
}
/* Only attempot to process queue if there is no outstanding request
* and it has not been scheduled.
*/
if (!bap->req && !bap->process_id)
bap->process_id = timeout_add(BAP_PROCESS_TIMEOUT,
bap_process_queue, bap, NULL);
return true;
}
static void stream_notify(struct bt_bap_stream *stream, uint8_t state)
{
DBG(stream->bap, "stream %p state %d", stream, state);
switch (state) {
case BT_ASCS_ASE_STATE_IDLE:
stream_notify_idle(stream);
break;
case BT_ASCS_ASE_STATE_CONFIG:
stream_notify_config(stream);
break;
case BT_ASCS_ASE_STATE_QOS:
stream_notify_qos(stream);
break;
case BT_ASCS_ASE_STATE_ENABLING:
case BT_ASCS_ASE_STATE_STREAMING:
case BT_ASCS_ASE_STATE_DISABLING:
stream_notify_metadata(stream, state);
break;
case BT_ASCS_ASE_STATE_RELEASING:
stream_notify_release(stream);
break;
}
}
static bool stream_notify_state(void *data)
{
struct bt_bap_stream *stream = data;
struct bt_bap_endpoint *ep = stream->ep;
uint8_t state;
if (stream->state_id) {
timeout_remove(stream->state_id);
stream->state_id = 0;
}
/* Notify any pending states before notifying ep->state */
while ((state = PTR_TO_UINT(queue_pop_head(stream->pending_states))))
stream_notify(stream, state);
stream_notify(stream, ep->state);
return false;
}
static struct bt_bap_stream *bt_bap_stream_ref(struct bt_bap_stream *stream)
{
if (!stream)
return NULL;
__sync_fetch_and_add(&stream->ref_count, 1);
return stream;
}
static void bap_ucast_set_state(struct bt_bap_stream *stream, uint8_t state)
{
struct bt_bap_endpoint *ep = stream->ep;
ep->old_state = ep->state;
ep->state = state;
DBG(stream->bap, "stream %p dir 0x%02x: %s -> %s", stream,
bt_bap_stream_get_dir(stream),
bt_bap_stream_statestr(stream->ep->old_state),
bt_bap_stream_statestr(stream->ep->state));
if (stream->client)
goto done;
if (!stream->bap->in_cp_write)
stream_notify_state(stream);
else if (!stream->state_id)
stream->state_id = timeout_add(BAP_PROCESS_TIMEOUT,
stream_notify_state,
bt_bap_stream_ref(stream),
bt_bap_stream_unref);
else /* If a state_id is already pending then queue the old one */
queue_push_tail(stream->pending_states,
UINT_TO_PTR(ep->old_state));
done:
bap_stream_state_changed(stream);
}
static unsigned int bap_ucast_get_state(struct bt_bap_stream *stream)
{
return stream->ep->state;
}
static uint8_t bits_to_phy(uint8_t bits)
{
uint8_t phy = 0x00;
/* Convert PHY bits to PHY values on a ascending order. */
if (bits & BIT(0))
phy = 0x01; /* LE 1M */
if (bits & BIT(1))
phy = 0x02; /* LE 2M */
if (bits & BIT(2))
phy = 0x03; /* LE Coded */
return phy;
}
static unsigned int bap_ucast_config(struct bt_bap_stream *stream,
struct bt_bap_qos *qos,
struct iovec *data,
bt_bap_stream_func_t func,
void *user_data)
{
struct iovec iov[2];
struct bt_ascs_config config;
uint8_t iovlen = 1;
struct bt_bap_req *req;
if (!stream->client) {
stream_config(stream, data, NULL);
return -EINVAL;
}
memset(&config, 0, sizeof(config));
config.ase = stream->ep->id;
config.latency = qos->ucast.target_latency;
config.phy = bits_to_phy(qos->ucast.io_qos.phys);
config.codec = stream->rpac->codec;
if (config.codec.id == 0xff) {
config.codec.cid = cpu_to_le16(config.codec.cid);
config.codec.vid = cpu_to_le16(config.codec.vid);
}
iov[0].iov_base = &config;
iov[0].iov_len = sizeof(config);
if (data) {
if (!bt_bap_debug_config(data->iov_base, data->iov_len,
stream->bap->debug_func,
stream->bap->debug_data))
return 0;
config.cc_len = data->iov_len;
iov[1] = *data;
iovlen++;
}
req = bap_req_new(stream, BT_ASCS_CONFIG, iov, iovlen, func, user_data);
if (!bap_queue_req(stream->bap, req)) {
bap_req_free(req);
return 0;
}
stream->qos = *qos;
return req->id;
}
static unsigned int bap_ucast_qos(struct bt_bap_stream *stream,
struct bt_bap_qos *data,
bt_bap_stream_func_t func,
void *user_data)
{
struct iovec iov;
struct bt_ascs_qos qos;
struct bt_bap_req *req;
/* Table 3.2: ASE state machine transition
* Initiating device - client Only
*/
if (!stream->client)
return 0;
if (stream->need_reconfig)
return 0;
memset(&qos, 0, sizeof(qos));
/* TODO: Figure out how to pass these values around */
qos.ase = stream->ep->id;
qos.cig = data->ucast.cig_id;
qos.cis = data->ucast.cis_id;
put_le24(data->ucast.io_qos.interval, qos.interval);
qos.framing = data->ucast.framing;
qos.phys = data->ucast.io_qos.phys;
qos.sdu = cpu_to_le16(data->ucast.io_qos.sdu);
qos.rtn = data->ucast.io_qos.rtn;
qos.latency = cpu_to_le16(data->ucast.io_qos.latency);
put_le24(data->ucast.delay, qos.pd);
iov.iov_base = &qos;
iov.iov_len = sizeof(qos);
req = bap_req_new(stream, BT_ASCS_QOS, &iov, 1, func, user_data);
if (!bap_queue_req(stream->bap, req)) {
bap_req_free(req);
return 0;
}
stream->qos = *data;
return req->id;
}
static void bap_stream_get_context(size_t i, uint8_t l, uint8_t t, uint8_t *v,
void *user_data)
{
bool *found = user_data;
if (!v)
return;
*found = true;
}
static unsigned int bap_stream_metadata(struct bt_bap_stream *stream,
uint8_t op, struct iovec *data,
bt_bap_stream_func_t func,
void *user_data)
{
struct iovec iov[2];
struct bt_ascs_metadata meta;
struct bt_bap_req *req;
uint16_t value = cpu_to_le16(0x0001); /* Context = Unspecified */
memset(&meta, 0, sizeof(meta));
meta.ase = stream->ep->id;
iov[0].iov_base = &meta;
iov[0].iov_len = sizeof(meta);
if (data) {
util_iov_free(stream->meta, 1);
stream->meta = util_iov_dup(data, 1);
}
/* Check if metadata contains an Audio Context */
if (stream->meta) {
uint8_t type = 0x02;
bool found = false;
util_ltv_foreach(stream->meta->iov_base,
stream->meta->iov_len, &type,
bap_stream_get_context, &found);
if (!found)
util_ltv_push(stream->meta, sizeof(value), type,
&value);
}
/* If metadata doesn't contain an Audio Context, add one */
if (!stream->meta) {
stream->meta = new0(struct iovec, 1);
util_ltv_push(stream->meta, sizeof(value), 0x02, &value);
}
iov[1].iov_base = stream->meta->iov_base;
iov[1].iov_len = stream->meta->iov_len;
meta.len = iov[1].iov_len;
req = bap_req_new(stream, op, iov, 2, func, user_data);
if (!bap_queue_req(stream->bap, req)) {
bap_req_free(req);
return 0;
}
return req->id;
}
static unsigned int bap_bcast_qos(struct bt_bap_stream *stream,
struct bt_bap_qos *data,
bt_bap_stream_func_t func,
void *user_data)
{
stream->qos = *data;
return 1;
}
static unsigned int bap_bcast_config(struct bt_bap_stream *stream,
struct bt_bap_qos *qos, struct iovec *data,
bt_bap_stream_func_t func, void *user_data)
{
if (!stream->lpac)
return 0;
stream->qos = *qos;
stream->lpac->ops->config(stream, stream->cc, &stream->qos,
ep_config_cb, stream->lpac->user_data);
return 1;
}
static void bap_stream_enable_link(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
struct iovec *metadata = user_data;
bap_stream_metadata(stream, BT_ASCS_ENABLE, metadata, NULL, NULL);
}
static unsigned int bap_ucast_enable(struct bt_bap_stream *stream,
bool enable_links, struct iovec *data,
bt_bap_stream_func_t func,
void *user_data)
{
int ret;
/* Table 3.2: ASE state machine transition
* Initiating device - client Only
*/
if (!stream->client)
return 0;
ret = bap_stream_metadata(stream, BT_ASCS_ENABLE, data, func,
user_data);
if (!ret || !enable_links)
return ret;
queue_foreach(stream->links, bap_stream_enable_link, data);
return ret;
}
static uint8_t stream_start(struct bt_bap_stream *stream, struct iovec *rsp)
{
DBG(stream->bap, "stream %p", stream);
ascs_ase_rsp_success(rsp, stream->ep->id);
stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING);
return 0;
}
static unsigned int bap_ucast_start(struct bt_bap_stream *stream,
bt_bap_stream_func_t func,
void *user_data)
{
struct iovec iov;
struct bt_ascs_start start;
struct bt_bap_req *req;
if (!stream->client) {
if (stream->ep->dir == BT_BAP_SINK)
stream_start(stream, NULL);
return 0;
}
if (stream->ep->dir == BT_BAP_SINK)
return 0;
memset(&start, 0, sizeof(start));
start.ase = stream->ep->id;
iov.iov_base = &start;
iov.iov_len = sizeof(start);
req = bap_req_new(stream, BT_ASCS_START, &iov, 1, func, user_data);
if (!bap_queue_req(stream->bap, req)) {
bap_req_free(req);
return 0;
}
return req->id;
}
static uint8_t stream_disable(struct bt_bap_stream *stream, struct iovec *rsp)
{
if (!stream)
return 0;
switch (stream->ep->state) {
case BT_BAP_STREAM_STATE_ENABLING:
case BT_BAP_STREAM_STATE_STREAMING:
break;
default:
return 0;
}
DBG(stream->bap, "stream %p", stream);
ascs_ase_rsp_success(rsp, stream->ep->id);
/* Sink can autonomously transit to QOS while source needs to go to
* Disabling until BT_ASCS_STOP is received.
*/
if (stream->ep->dir == BT_BAP_SINK)
stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
if (stream->ep->dir == BT_BAP_SOURCE)
stream_set_state(stream, BT_BAP_STREAM_STATE_DISABLING);
return 0;
}
static void bap_stream_disable_link(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
bt_bap_stream_disable(stream, false, NULL, NULL);
}
static unsigned int bap_ucast_disable(struct bt_bap_stream *stream,
bool disable_links,
bt_bap_stream_func_t func,
void *user_data)
{
struct iovec iov;
struct bt_ascs_disable disable;
struct bt_bap_req *req;
if (!stream->client)
return stream_disable(stream, NULL);
memset(&disable, 0, sizeof(disable));
disable.ase = stream->ep->id;
iov.iov_base = &disable;
iov.iov_len = sizeof(disable);
req = bap_req_new(stream, BT_ASCS_DISABLE, &iov, 1, func, user_data);
if (!bap_queue_req(stream->bap, req)) {
bap_req_free(req);
return 0;
}
if (disable_links)
queue_foreach(stream->links, bap_stream_disable_link, NULL);
return req->id;
}
static uint8_t stream_stop(struct bt_bap_stream *stream, struct iovec *rsp)
{
if (!stream)
return 0;
DBG(stream->bap, "stream %p", stream);
ascs_ase_rsp_success(rsp, stream->ep->id);
stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
return 0;
}
static unsigned int bap_ucast_stop(struct bt_bap_stream *stream,
bt_bap_stream_func_t func,
void *user_data)
{
struct iovec iov;
struct bt_ascs_stop stop;
struct bt_bap_req *req;
if (!stream->client) {
if (stream->ep->dir == BT_BAP_SINK)
stream_stop(stream, NULL);
return 0;
}
if (stream->ep->dir == BT_BAP_SINK)
return 0;
memset(&stop, 0, sizeof(stop));
stop.ase = stream->ep->id;
iov.iov_base = &stop;
iov.iov_len = sizeof(stop);
req = bap_req_new(stream, BT_ASCS_STOP, &iov, 1, func, user_data);
if (!bap_queue_req(stream->bap, req)) {
bap_req_free(req);
return 0;
}
return req->id;
}
static uint8_t stream_metadata(struct bt_bap_stream *stream, struct iovec *meta,
struct iovec *rsp)
{
DBG(stream->bap, "stream %p", stream);
ascs_ase_rsp_success(rsp, stream->ep->id);
util_iov_free(stream->meta, 1);
stream->meta = util_iov_dup(meta, 1);
switch (bt_bap_stream_get_state(stream)) {
case BT_BAP_STREAM_STATE_IDLE:
/* Initial metadata */
break;
default:
/* Force state change to the same state to update metadata */
stream_set_state(stream, bt_bap_stream_get_state(stream));
}
return 0;
}
static unsigned int bap_ucast_metadata(struct bt_bap_stream *stream,
struct iovec *data,
bt_bap_stream_func_t func,
void *user_data)
{
if (!stream->client) {
stream_metadata(stream, data, NULL);
return 0;
}
switch (bt_bap_stream_get_state(stream)) {
/* Valid only if ASE_State field = 0x03 (Enabling) */
case BT_BAP_STREAM_STATE_ENABLING:
/* or 0x04 (Streaming) */
case BT_BAP_STREAM_STATE_STREAMING:
return bap_stream_metadata(stream, BT_ASCS_METADATA, data, func,
user_data);
}
stream_metadata(stream, data, NULL);
return 0;
}
static uint8_t stream_release(struct bt_bap_stream *stream, struct iovec *rsp)
{
DBG(stream->bap, "stream %p", stream);
ascs_ase_rsp_success(rsp, stream->ep->id);
/* In case the stream IO is already down the released transition needs
* to take action immeditely.
*/
if (!stream->io) {
bool cache_config = !stream->no_cache_config;
switch (bt_bap_stream_get_state(stream)) {
case BT_BAP_STREAM_STATE_CONFIG:
/* Released (no caching) */
cache_config = false;
break;
default:
/* Released (caching) */
break;
}
stream_set_state(stream, BT_BAP_STREAM_STATE_RELEASING);
if (cache_config)
stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
else
stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
} else
stream_set_state(stream, BT_BAP_STREAM_STATE_RELEASING);
return 0;
}
static bool bap_stream_valid(struct bt_bap_stream *stream)
{
if (!stream || !stream->bap)
return false;
return queue_find(stream->bap->streams, NULL, stream);
}
static unsigned int bap_ucast_get_dir(struct bt_bap_stream *stream)
{
return stream->ep->dir;
}
static unsigned int bap_ucast_get_location(struct bt_bap_stream *stream)
{
struct bt_pacs *pacs;
if (!stream)
return 0x00000000;
pacs = stream->client ? stream->bap->rdb->pacs : stream->bap->ldb->pacs;
if (stream->ep->dir == BT_BAP_SOURCE)
return pacs->source_loc_value;
else if (stream->ep->dir == BT_BAP_SINK)
return pacs->sink_loc_value;
return 0x00000000;
}
static unsigned int bap_ucast_release(struct bt_bap_stream *stream,
bt_bap_stream_func_t func,
void *user_data)
{
struct iovec iov;
struct bt_ascs_release rel;
struct bt_bap_req *req;
struct bt_bap *bap;
if (!stream->client) {
stream_release(stream, NULL);
return 0;
}
memset(&req, 0, sizeof(req));
rel.ase = stream->ep->id;
iov.iov_base = &rel;
iov.iov_len = sizeof(rel);
bap = stream->bap;
/* If stream does not belong to a client session, clean it up now */
if (!bap_stream_valid(stream)) {
stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
return 0;
}
req = bap_req_new(stream, BT_ASCS_RELEASE, &iov, 1, func, user_data);
if (!bap_queue_req(bap, req)) {
bap_req_free(req);
return 0;
}
return req->id;
}
static void bap_bcast_set_state(struct bt_bap_stream *stream, uint8_t state)
{
struct bt_bap *bap = stream->bap;
const struct queue_entry *entry;
stream->old_state = stream->state;
stream->state = state;
bt_bap_stream_ref(stream);
DBG(bap, "stream %p dir 0x%02x: %s -> %s", stream,
bt_bap_stream_get_dir(stream),
bt_bap_stream_statestr(stream->old_state),
bt_bap_stream_statestr(stream->state));
for (entry = queue_get_entries(bap->state_cbs); entry;
entry = entry->next) {
struct bt_bap_state *state = entry->data;
if (state->func)
state->func(stream, stream->old_state,
stream->state, state->data);
}
/* Post notification updates */
switch (stream->state) {
case BT_ASCS_ASE_STATE_IDLE:
if (stream->ops && stream->ops->detach)
stream->ops->detach(stream);
break;
case BT_ASCS_ASE_STATE_RELEASING:
bap_stream_io_detach(stream);
stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
break;
case BT_ASCS_ASE_STATE_ENABLING:
if (bt_bap_stream_get_io(stream))
/* Start stream if fd has already been set */
bt_bap_stream_start(stream, NULL, NULL);
break;
}
bt_bap_stream_unref(stream);
}
static unsigned int bap_bcast_get_state(struct bt_bap_stream *stream)
{
return stream->state;
}
static bool bcast_sink_stream_enabled(const void *data, const void *match_data)
{
struct bt_bap_stream *stream = (struct bt_bap_stream *)data;
struct bt_bap_stream *match = (struct bt_bap_stream *)match_data;
uint8_t state = bt_bap_stream_get_state(stream);
if (stream == match)
return false;
if (queue_find(stream->links, NULL, match))
return false;
/* Ignore streams that are not Broadcast Sink */
if (bt_bap_pac_get_type(stream->lpac) != BT_BAP_BCAST_SINK)
return false;
return ((state == BT_BAP_STREAM_STATE_ENABLING) ||
bt_bap_stream_get_io(stream));
}
static unsigned int bap_bcast_sink_enable(struct bt_bap_stream *stream,
bool enable_links, struct iovec *data,
bt_bap_stream_func_t func,
void *user_data)
{
struct bt_bap *bap = stream->bap;
/* The stream cannot be enabled if there is any other
* unlinked stream for the same source that is in the
* process of enabling or that has already been started.
*/
if (queue_find(bap->streams, bcast_sink_stream_enabled, stream))
return 0;
stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING);
return 1;
}
static unsigned int bap_bcast_src_enable(struct bt_bap_stream *stream,
bool enable_links, struct iovec *data,
bt_bap_stream_func_t func,
void *user_data)
{
stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING);
return 1;
}
static unsigned int bap_bcast_start(struct bt_bap_stream *stream,
bt_bap_stream_func_t func,
void *user_data)
{
stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING);
return 1;
}
static unsigned int bap_bcast_disable(struct bt_bap_stream *stream,
bool disable_links,
bt_bap_stream_func_t func,
void *user_data)
{
bap_stream_io_detach(stream);
stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
return 1;
}
static unsigned int bap_bcast_metadata(struct bt_bap_stream *stream,
struct iovec *data,
bt_bap_stream_func_t func,
void *user_data)
{
util_iov_free(stream->meta, 1);
stream->meta = util_iov_dup(data, 1);
return 1;
}
static unsigned int bap_bcast_src_get_dir(struct bt_bap_stream *stream)
{
return BT_BAP_BCAST_SINK;
}
static unsigned int bap_bcast_sink_get_dir(struct bt_bap_stream *stream)
{
return BT_BAP_BCAST_SOURCE;
}
static void bap_sink_get_allocation(size_t i, uint8_t l, uint8_t t,
uint8_t *v, void *user_data)
{
uint32_t location32;
if (!v)
return;
memcpy(&location32, v, l);
*((uint32_t *)user_data) = le32_to_cpu(location32);
}
static unsigned int bap_bcast_get_location(struct bt_bap_stream *stream)
{
uint8_t type = BAP_CHANNEL_ALLOCATION_LTV_TYPE;
uint32_t allocation = 0;
struct iovec *caps;
caps = bt_bap_stream_get_config(stream);
/* Get stream allocation from capabilities */
util_ltv_foreach(caps->iov_base, caps->iov_len, &type,
bap_sink_get_allocation, &allocation);
return allocation;
}
static unsigned int bap_bcast_release(struct bt_bap_stream *stream,
bt_bap_stream_func_t func,
void *user_data)
{
stream_set_state(stream, BT_BAP_STREAM_STATE_RELEASING);
return 1;
}
static bool bap_ucast_set_io(struct bt_bap_stream *stream, int fd)
{
if (!stream || (fd >= 0 && stream->io && !stream->io->connecting))
return false;
bap_stream_set_io(stream, INT_TO_PTR(fd));
queue_foreach(stream->links, bap_stream_set_io, INT_TO_PTR(fd));
return true;
}
static bool bap_bcast_set_io(struct bt_bap_stream *stream, int fd)
{
if (!stream || (fd >= 0 && stream->io && !stream->io->connecting))
return false;
bap_stream_set_io(stream, INT_TO_PTR(fd));
return true;
}
static struct bt_bap_stream_io *bap_ucast_get_io(struct bt_bap_stream *stream)
{
struct bt_bap_stream_io *io = NULL;
if (!stream)
return NULL;
if (stream->io)
return stream->io;
queue_foreach(stream->links, stream_find_io, &io);
return io;
}
static struct bt_bap_stream_io *bap_bcast_get_io(struct bt_bap_stream *stream)
{
if (!stream)
return NULL;
return stream->io;
}
static uint8_t bap_ucast_io_dir(struct bt_bap_stream *stream)
{
uint8_t dir;
if (!stream)
return 0x00;
dir = stream->ep->dir;
queue_foreach(stream->links, bap_stream_get_dir, &dir);
return dir;
}
static uint8_t bap_bcast_io_dir(struct bt_bap_stream *stream)
{
uint8_t dir;
uint8_t pac_type;
if (!stream)
return 0x00;
pac_type = bt_bap_pac_get_type(stream->lpac);
if (pac_type == BT_BAP_BCAST_SINK)
dir = BT_BAP_BCAST_SOURCE;
else
dir = BT_BAP_BCAST_SINK;
return dir;
}
static int bap_ucast_io_link(struct bt_bap_stream *stream,
struct bt_bap_stream *link)
{
struct bt_bap *bap;
if (!stream || !link || stream == link)
return -EINVAL;
bap = stream->bap;
if (!queue_isempty(stream->links) || !queue_isempty(link->links))
return -EALREADY;
if (stream->client != link->client ||
stream->qos.ucast.cig_id != link->qos.ucast.cig_id ||
stream->qos.ucast.cis_id != link->qos.ucast.cis_id ||
stream->ep->dir == link->ep->dir)
return -EINVAL;
if (stream->client && !(stream->locked && link->locked))
return -EINVAL;
if (!stream->links)
stream->links = queue_new();
if (!link->links)
link->links = queue_new();
queue_push_tail(stream->links, link);
queue_push_tail(link->links, stream);
/* Link IOs if already set on stream/link */
if (stream->io && !link->io)
link->io = stream_io_ref(stream->io);
else if (link->io && !stream->io)
stream->io = stream_io_ref(link->io);
DBG(bap, "stream %p link %p", stream, link);
return 0;
}
static void stream_unlink_ucast(void *data)
{
struct bt_bap_stream *link = data;
DBG(link->bap, "stream %p unlink", link);
queue_destroy(link->links, NULL);
link->links = NULL;
}
static int bap_ucast_io_unlink(struct bt_bap_stream *stream,
struct bt_bap_stream *link)
{
if (!stream)
return -EINVAL;
queue_destroy(stream->links, stream_unlink_ucast);
stream->links = NULL;
DBG(stream->bap, "stream %p unlink", stream);
return 0;
}
static void stream_link(void *data, void *user_data)
{
struct bt_bap_stream *stream = (void *)data;
struct bt_bap_stream *link = (void *)user_data;
bt_bap_stream_io_link(stream, link);
}
static int bap_bcast_io_link(struct bt_bap_stream *stream,
struct bt_bap_stream *link)
{
struct bt_bap *bap;
if (!stream || !link || stream == link)
return -EINVAL;
bap = stream->bap;
if (queue_find(stream->links, NULL, link) ||
queue_find(link->links, NULL, stream))
return -EALREADY;
if (!stream->links)
stream->links = queue_new();
if (!link->links)
link->links = queue_new();
queue_push_tail(stream->links, link);
queue_push_tail(link->links, stream);
DBG(bap, "stream %p link %p", stream, link);
queue_foreach(stream->links, stream_link, link);
return 0;
}
static void stream_unlink(void *data, void *user_data)
{
struct bt_bap_stream *stream = (void *)data;
struct bt_bap_stream *link = (void *)user_data;
bap_bcast_io_unlink(stream, link);
}
static int bap_bcast_io_unlink(struct bt_bap_stream *stream,
struct bt_bap_stream *link)
{
struct bt_bap *bap;
if (!stream || !link || stream == link)
return -EINVAL;
bap = stream->bap;
if (!queue_find(stream->links, NULL, link) ||
!queue_find(link->links, NULL, stream))
return -EALREADY;
queue_remove(stream->links, link);
queue_remove(link->links, stream);
DBG(bap, "stream %p unlink %p", stream, link);
queue_foreach(stream->links, stream_unlink, link);
return 0;
}
#define STREAM_OPS(_type, _set_state, _get_state, _config, _qos, _enable, \
_start, _disable, _stop, _metadata, _get_dir, _get_loc, _release, \
_detach, _set_io, _get_io, _io_dir, _io_link, _io_unlink) \
{ \
.type = _type, \
.set_state = _set_state, \
.get_state = _get_state, \
.config = _config, \
.qos = _qos, \
.enable = _enable, \
.start = _start, \
.disable = _disable, \
.stop = _stop, \
.metadata = _metadata, \
.get_dir = _get_dir,\
.get_loc = _get_loc, \
.release = _release, \
.detach = _detach, \
.set_io = _set_io, \
.get_io = _get_io, \
.io_dir = _io_dir, \
.io_link = _io_link, \
.io_unlink = _io_unlink, \
}
static const struct bt_bap_stream_ops stream_ops[] = {
STREAM_OPS(BT_BAP_SINK, bap_ucast_set_state,
bap_ucast_get_state,
bap_ucast_config, bap_ucast_qos, bap_ucast_enable,
bap_ucast_start, bap_ucast_disable, bap_ucast_stop,
bap_ucast_metadata, bap_ucast_get_dir,
bap_ucast_get_location,
bap_ucast_release, bap_ucast_detach,
bap_ucast_set_io, bap_ucast_get_io,
bap_ucast_io_dir, bap_ucast_io_link,
bap_ucast_io_unlink),
STREAM_OPS(BT_BAP_SOURCE, bap_ucast_set_state,
bap_ucast_get_state,
bap_ucast_config, bap_ucast_qos, bap_ucast_enable,
bap_ucast_start, bap_ucast_disable, bap_ucast_stop,
bap_ucast_metadata, bap_ucast_get_dir,
bap_ucast_get_location,
bap_ucast_release, bap_ucast_detach,
bap_ucast_set_io, bap_ucast_get_io,
bap_ucast_io_dir, bap_ucast_io_link,
bap_ucast_io_unlink),
STREAM_OPS(BT_BAP_BCAST_SINK, bap_bcast_set_state,
bap_bcast_get_state,
bap_bcast_config, bap_bcast_qos, bap_bcast_sink_enable,
bap_bcast_start, bap_bcast_disable, NULL,
bap_bcast_metadata, bap_bcast_sink_get_dir,
bap_bcast_get_location,
bap_bcast_release, bap_bcast_sink_detach,
bap_bcast_set_io, bap_bcast_get_io,
bap_bcast_io_dir, bap_bcast_io_link,
bap_bcast_io_unlink),
STREAM_OPS(BT_BAP_BCAST_SOURCE, bap_bcast_set_state,
bap_bcast_get_state,
bap_bcast_config, bap_bcast_qos, bap_bcast_src_enable,
bap_bcast_start, bap_bcast_disable, NULL,
bap_bcast_metadata, bap_bcast_src_get_dir,
bap_bcast_get_location,
bap_bcast_release, bap_bcast_src_detach,
bap_bcast_set_io, bap_bcast_get_io,
bap_bcast_io_dir, bap_bcast_io_link,
bap_bcast_io_unlink),
};
static const struct bt_bap_stream_ops *
bap_stream_new_ops(struct bt_bap_stream *stream)
{
const struct bt_bap_stream_ops *ops;
uint8_t type = bt_bap_pac_get_type(stream->lpac);
size_t i;
for (i = 0; i < ARRAY_SIZE(stream_ops); i++) {
ops = &stream_ops[i];
if (ops->type == type)
return ops;
}
return NULL;
}
static struct bt_bap_stream *bap_stream_new(struct bt_bap *bap,
struct bt_bap_endpoint *ep,
struct bt_bap_pac *lpac,
struct bt_bap_pac *rpac,
struct iovec *data,
bool client)
{
struct bt_bap_stream *stream;
stream = new0(struct bt_bap_stream, 1);
stream->bap = bap;
stream->ep = ep;
if (ep != NULL)
ep->stream = stream;
stream->lpac = lpac;
stream->rpac = rpac;
stream->cc = util_iov_dup(data, 1);
stream->client = client;
stream->ops = bap_stream_new_ops(stream);
stream->pending_states = queue_new();
queue_push_tail(bap->streams, stream);
return bt_bap_stream_ref(stream);
}
static struct bt_bap_stream_io *stream_io_ref(struct bt_bap_stream_io *io)
{
if (!io)
return NULL;
__sync_fetch_and_add(&io->ref_count, 1);
return io;
}
static struct bt_bap_stream_io *stream_io_new(struct bt_bap *bap, int fd)
{
struct io *io;
struct bt_bap_stream_io *sio;
io = io_new(fd);
if (!io)
return NULL;
DBG(bap, "fd %d", fd);
io_set_ignore_errqueue(io, true);
sio = new0(struct bt_bap_stream_io, 1);
sio->bap = bap;
sio->io = io;
return stream_io_ref(sio);
}
static void stream_find_io(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
struct bt_bap_stream_io **io = user_data;
if (*io)
return;
*io = stream->io;
}
static struct bt_bap_stream_io *stream_get_io(struct bt_bap_stream *stream)
{
struct bt_bap_stream_io *io;
struct bt_bap *bap;
if (!bap_stream_valid(stream))
return NULL;
if (!stream->ops || !stream->ops->get_io)
return NULL;
if (!bt_bap_ref_safe(stream->bap))
return NULL;
bap = stream->bap;
io = stream->ops->get_io(stream);
bt_bap_unref(bap);
return io;
}
static bool stream_io_disconnected(struct io *io, void *user_data);
static bool bap_stream_io_attach(struct bt_bap_stream *stream, int fd,
bool connecting)
{
struct bt_bap_stream_io *io;
io = stream_get_io(stream);
if (io) {
if (fd == stream_io_get_fd(io)) {
if (!stream->io)
stream->io = stream_io_ref(io);
io->connecting = connecting;
return true;
}
DBG(stream->bap, "stream %p io already set", stream);
return false;
}
DBG(stream->bap, "stream %p connecting %s", stream,
connecting ? "true" : "false");
io = stream_io_new(stream->bap, fd);
if (!io)
return false;
io->connecting = connecting;
stream->io = io;
io_set_disconnect_handler(io->io, stream_io_disconnected, stream, NULL);
return true;
}
static void bap_stream_set_io(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
int fd = PTR_TO_INT(user_data);
bool ret;
uint8_t state;
if (fd >= 0)
ret = bap_stream_io_attach(stream, fd, false);
else
ret = bap_stream_io_detach(stream);
if (!ret)
return;
if (bt_bap_stream_get_type(stream) == BT_BAP_STREAM_TYPE_BCAST)
state = stream->state;
else
state = stream->ep->state;
switch (state) {
case BT_BAP_STREAM_STATE_ENABLING:
if (fd < 0)
bt_bap_stream_disable(stream, false, NULL, NULL);
else
bt_bap_stream_start(stream, NULL, NULL);
break;
case BT_BAP_STREAM_STATE_DISABLING:
if (fd < 0)
bt_bap_stream_stop(stream, NULL, NULL);
break;
}
}
static void ascs_ase_rsp_add_errno(struct iovec *iov, uint8_t id, int err)
{
struct bt_ascs_cp_rsp *rsp = iov->iov_base;
switch (err) {
case -ENOBUFS:
case -ENOMEM:
return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_NO_MEM,
BT_ASCS_REASON_NONE);
case -EINVAL:
switch (rsp->op) {
case BT_ASCS_CONFIG:
/* Fallthrough */
case BT_ASCS_QOS:
return ascs_ase_rsp_add(iov, id,
BT_ASCS_RSP_CONF_INVALID,
BT_ASCS_REASON_NONE);
case BT_ASCS_ENABLE:
/* Fallthrough */
case BT_ASCS_METADATA:
return ascs_ase_rsp_add(iov, id,
BT_ASCS_RSP_METADATA_INVALID,
BT_ASCS_REASON_NONE);
default:
return ascs_ase_rsp_add(iov, id,
BT_ASCS_RSP_UNSPECIFIED,
BT_ASCS_REASON_NONE);
}
case -ENOTSUP:
switch (rsp->op) {
case BT_ASCS_CONFIG:
/* Fallthrough */
case BT_ASCS_QOS:
return ascs_ase_rsp_add(iov, id,
BT_ASCS_RSP_CONF_UNSUPPORTED,
BT_ASCS_REASON_NONE);
case BT_ASCS_ENABLE:
/* Fallthrough */
case BT_ASCS_METADATA:
return ascs_ase_rsp_add(iov, id,
BT_ASCS_RSP_METADATA_UNSUPPORTED,
BT_ASCS_REASON_NONE);
default:
return ascs_ase_rsp_add(iov, id,
BT_ASCS_RSP_NOT_SUPPORTED,
BT_ASCS_REASON_NONE);
}
case -EBADMSG:
return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
case -ENOMSG:
return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_TRUNCATED,
BT_ASCS_REASON_NONE);
default:
return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_UNSPECIFIED,
BT_ASCS_REASON_NONE);
}
}
static uint8_t ep_config(struct bt_bap_endpoint *ep, struct bt_bap *bap,
struct bt_ascs_config *req,
struct iovec *iov, struct iovec *rsp)
{
struct iovec cc;
const struct queue_entry *e;
struct bt_bap_codec codec;
DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
switch (ep->state) {
/* Valid only if ASE_State field = 0x00 (Idle) */
case BT_ASCS_ASE_STATE_IDLE:
/* or 0x01 (Codec Configured) */
case BT_ASCS_ASE_STATE_CONFIG:
/* or 0x02 (QoS Configured) */
case BT_ASCS_ASE_STATE_QOS:
break;
default:
DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state));
ascs_ase_rsp_add(rsp, req->ase,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
if (iov->iov_len < req->cc_len)
return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
cc.iov_base = util_iov_pull_mem(iov, req->cc_len);
cc.iov_len = req->cc_len;
if (!bt_bap_debug_config(cc.iov_base, cc.iov_len, bap->debug_func,
bap->debug_data)) {
ascs_ase_rsp_add(rsp, req->ase,
BT_ASCS_RSP_CONF_INVALID,
BT_ASCS_REASON_CODEC_DATA);
return 0;
}
switch (ep->dir) {
case BT_BAP_SINK:
e = queue_get_entries(bap->ldb->sinks);
break;
case BT_BAP_SOURCE:
e = queue_get_entries(bap->ldb->sources);
break;
default:
e = NULL;
}
/* Convert to native endianness before comparing */
memset(&codec, 0, sizeof(codec));
codec.id = req->codec.id;
codec.cid = le16_to_cpu(req->codec.cid);
codec.vid = le16_to_cpu(req->codec.vid);
for (; e; e = e->next) {
struct bt_bap_pac *pac = e->data;
if (!bap_codec_equal(&codec, &pac->codec))
continue;
if (!ep->stream)
ep->stream = bap_stream_new(bap, ep, pac, NULL, NULL,
false);
break;
}
if (!e) {
ascs_ase_rsp_add(rsp, req->ase,
BT_ASCS_RSP_CONF_INVALID,
BT_ASCS_REASON_CODEC);
return 0;
}
return stream_config(ep->stream, &cc, rsp);
}
static uint8_t ascs_config(struct bt_ascs *ascs, struct bt_bap *bap,
struct iovec *iov, struct iovec *rsp)
{
struct bt_bap_endpoint *ep;
struct bt_ascs_config *req;
req = util_iov_pull_mem(iov, sizeof(*req));
DBG(bap, "codec 0x%02x phy 0x%02x latency %u", req->codec.id,
req->phy, req->latency);
ep = bap_get_local_endpoint_id(bap, req->ase);
if (!ep) {
DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
ascs_ase_rsp_add(rsp, req->ase,
BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
return 0;
}
return ep_config(ep, bap, req, iov, rsp);
}
static uint8_t stream_qos(struct bt_bap_stream *stream, struct bt_bap_qos *qos,
struct iovec *rsp)
{
DBG(stream->bap, "stream %p", stream);
ascs_ase_rsp_success(rsp, stream->ep->id);
if (memcmp(&stream->qos, qos, sizeof(*qos)))
stream->qos = *qos;
stream_set_state(stream, BT_BAP_STREAM_STATE_QOS);
return 0;
}
static uint8_t ep_qos(struct bt_bap_endpoint *ep, struct bt_bap *bap,
struct bt_bap_qos *qos, struct iovec *rsp)
{
DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
switch (ep->state) {
/* Valid only if ASE_State field = 0x01 (Codec Configured) */
case BT_ASCS_ASE_STATE_CONFIG:
/* or 0x02 (QoS Configured) */
case BT_ASCS_ASE_STATE_QOS:
break;
default:
DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state));
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
if (!ep->stream) {
DBG(bap, "No stream found");
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
return stream_qos(ep->stream, qos, rsp);
}
static uint8_t ascs_qos(struct bt_ascs *ascs, struct bt_bap *bap,
struct iovec *iov, struct iovec *rsp)
{
struct bt_bap_endpoint *ep;
struct bt_ascs_qos *req;
struct bt_bap_qos qos;
req = util_iov_pull_mem(iov, sizeof(*req));
memset(&qos, 0, sizeof(qos));
qos.ucast.cig_id = req->cig;
qos.ucast.cis_id = req->cis;
qos.ucast.io_qos.interval = get_le24(req->interval);
qos.ucast.framing = req->framing;
qos.ucast.io_qos.phys = req->phys;
qos.ucast.io_qos.sdu = le16_to_cpu(req->sdu);
qos.ucast.io_qos.rtn = req->rtn;
qos.ucast.io_qos.latency = le16_to_cpu(req->latency);
qos.ucast.delay = get_le24(req->pd);
DBG(bap, "CIG 0x%02x CIS 0x%02x interval %u framing 0x%02x "
"phys 0x%02x SDU %u rtn %u latency %u pd %u",
req->cig, req->cis, qos.ucast.io_qos.interval,
qos.ucast.framing, qos.ucast.io_qos.phys,
qos.ucast.io_qos.sdu, qos.ucast.io_qos.rtn,
qos.ucast.io_qos.latency, qos.ucast.delay);
ep = bap_get_local_endpoint_id(bap, req->ase);
if (!ep) {
DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
ascs_ase_rsp_add(rsp, req->ase,
BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
return 0;
}
return ep_qos(ep, bap, &qos, rsp);
}
static uint8_t stream_enable(struct bt_bap_stream *stream, struct iovec *meta,
struct iovec *rsp)
{
DBG(stream->bap, "stream %p", stream);
ascs_ase_rsp_success(rsp, stream->ep->id);
util_iov_free(stream->meta, 1);
stream->meta = util_iov_dup(meta, 1);
if (!stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING))
return 1;
/* Sink can autonomously for to Streaming state if io already exits */
if (stream->io && stream->ep->dir == BT_BAP_SINK)
stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING);
return 0;
}
static uint8_t ep_enable(struct bt_bap_endpoint *ep, struct bt_bap *bap,
struct bt_ascs_enable *req, struct iovec *iov,
struct iovec *rsp)
{
struct iovec meta;
DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
switch (ep->state) {
/* Valid only if ASE_State field = 0x02 (QoS Configured) */
case BT_ASCS_ASE_STATE_QOS:
break;
default:
DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state));
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
meta.iov_base = util_iov_pull_mem(iov, req->meta.len);
meta.iov_len = req->meta.len;
if (!bt_bap_debug_metadata(meta.iov_base, meta.iov_len,
bap->debug_func, bap->debug_data)) {
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_METADATA_INVALID,
BT_ASCS_REASON_NONE);
return 0;
}
if (!ep->stream) {
DBG(bap, "No stream found");
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
return stream_enable(ep->stream, &meta, rsp);
}
static uint8_t ascs_enable(struct bt_ascs *ascs, struct bt_bap *bap,
struct iovec *iov, struct iovec *rsp)
{
struct bt_bap_endpoint *ep;
struct bt_ascs_enable *req;
req = util_iov_pull_mem(iov, sizeof(*req));
ep = bap_get_local_endpoint_id(bap, req->meta.ase);
if (!ep) {
DBG(bap, "Invalid ASE ID 0x%02x", req->meta.ase);
ascs_ase_rsp_add(rsp, req->meta.ase,
BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
return 0;
}
return ep_enable(ep, bap, req, iov, rsp);
}
static uint8_t ep_start(struct bt_bap_endpoint *ep, struct iovec *rsp)
{
struct bt_bap_stream *stream = ep->stream;
DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
switch (ep->state) {
/* Valid only if ASE_State field = 0x03 (Enabling) */
case BT_ASCS_ASE_STATE_ENABLING:
break;
default:
DBG(ep->stream->bap, "Invalid state %s",
bt_bap_stream_statestr(ep->state));
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
/* 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).
*/
if (ep->dir == BT_BAP_SINK) {
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_DIR, BT_ASCS_REASON_NONE);
return 0;
}
return stream_start(ep->stream, rsp);
}
static uint8_t ascs_start(struct bt_ascs *ascs, struct bt_bap *bap,
struct iovec *iov, struct iovec *rsp)
{
struct bt_bap_endpoint *ep;
struct bt_ascs_start *req;
req = util_iov_pull_mem(iov, sizeof(*req));
ep = bap_get_local_endpoint_id(bap, req->ase);
if (!ep) {
DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
ascs_ase_rsp_add(rsp, req->ase,
BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
return 0;
}
if (!ep->stream) {
DBG(bap, "No stream found for %p", ep);
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
return ep_start(ep, rsp);
}
static uint8_t ep_disable(struct bt_bap_endpoint *ep, struct iovec *rsp)
{
struct bt_bap_stream *stream = ep->stream;
DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
switch (ep->state) {
/* Valid only if ASE_State field = 0x03 (Enabling) */
case BT_ASCS_ASE_STATE_ENABLING:
/* or 0x04 (Streaming) */
case BT_ASCS_ASE_STATE_STREAMING:
break;
default:
DBG(stream->bap, "Invalid state %s",
bt_bap_stream_statestr(ep->state));
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
return stream_disable(ep->stream, rsp);
}
static uint8_t ascs_disable(struct bt_ascs *ascs, struct bt_bap *bap,
struct iovec *iov, struct iovec *rsp)
{
struct bt_bap_endpoint *ep;
struct bt_ascs_disable *req;
req = util_iov_pull_mem(iov, sizeof(*req));
ep = bap_get_local_endpoint_id(bap, req->ase);
if (!ep) {
DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
ascs_ase_rsp_add(rsp, req->ase,
BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
return 0;
}
if (!ep->stream) {
DBG(bap, "No stream found");
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
return ep_disable(ep, rsp);
}
static uint8_t ep_stop(struct bt_bap_endpoint *ep, struct iovec *rsp)
{
struct bt_bap_stream *stream = ep->stream;
DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
switch (ep->state) {
/* Valid only if ASE_State field = 0x05 (Disabling) */
case BT_ASCS_ASE_STATE_DISABLING:
break;
default:
DBG(stream->bap, "Invalid state %s",
bt_bap_stream_statestr(ep->state));
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
/* 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).
*/
if (ep->dir == BT_BAP_SINK) {
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_DIR, BT_ASCS_REASON_NONE);
return 0;
}
return stream_stop(ep->stream, rsp);
}
static uint8_t ascs_stop(struct bt_ascs *ascs, struct bt_bap *bap,
struct iovec *iov, struct iovec *rsp)
{
struct bt_bap_endpoint *ep;
struct bt_ascs_stop *req;
req = util_iov_pull_mem(iov, sizeof(*req));
ep = bap_get_local_endpoint_id(bap, req->ase);
if (!ep) {
DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
ascs_ase_rsp_add(rsp, req->ase,
BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
return 0;
}
if (!ep->stream) {
DBG(bap, "No stream found");
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
return ep_stop(ep, rsp);
}
static uint8_t ep_metadata(struct bt_bap_endpoint *ep,
struct bt_ascs_metadata *req,
struct iovec *iov, struct iovec *rsp)
{
struct bt_bap_stream *stream = ep->stream;
struct iovec meta;
DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir);
switch (ep->state) {
/* Valid only if ASE_State field = 0x03 (Enabling) */
case BT_ASCS_ASE_STATE_ENABLING:
/* or 0x04 (Streaming) */
case BT_ASCS_ASE_STATE_STREAMING:
break;
default:
DBG(stream->bap, "Invalid state %s",
bt_bap_stream_statestr(ep->state));
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
if (iov->iov_len < req->len)
return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
meta.iov_base = util_iov_pull_mem(iov, req->len);
meta.iov_len = req->len;
return stream_metadata(ep->stream, &meta, rsp);
}
static uint8_t ascs_metadata(struct bt_ascs *ascs, struct bt_bap *bap,
struct iovec *iov, struct iovec *rsp)
{
struct bt_bap_endpoint *ep;
struct bt_ascs_metadata *req;
req = util_iov_pull_mem(iov, sizeof(*req));
ep = bap_get_local_endpoint_id(bap, req->ase);
if (!ep) {
DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
ascs_ase_rsp_add(rsp, req->ase,
BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
return 0;
}
if (!ep->stream) {
DBG(bap, "No stream found");
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
return ep_metadata(ep, req, iov, rsp);
}
static uint8_t ascs_release(struct bt_ascs *ascs, struct bt_bap *bap,
struct iovec *iov, struct iovec *rsp)
{
struct bt_bap_endpoint *ep;
struct bt_ascs_release *req;
req = util_iov_pull_mem(iov, sizeof(*req));
ep = bap_get_local_endpoint_id(bap, req->ase);
if (!ep) {
DBG(bap, "Invalid ASE ID 0x%02x", req->ase);
ascs_ase_rsp_add(rsp, req->ase,
BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE);
return 0;
}
if (!ep->stream) {
DBG(bap, "No stream found");
ascs_ase_rsp_add(rsp, ep->id,
BT_ASCS_RSP_INVALID_ASE_STATE,
BT_ASCS_REASON_NONE);
return 0;
}
return stream_release(ep->stream, rsp);
}
#define ASCS_OP(_str, _op, _size, _func) \
{ \
.str = _str, \
.op = _op, \
.size = _size, \
.func = _func, \
}
struct ascs_op_handler {
const char *str;
uint8_t op;
size_t size;
uint8_t (*func)(struct bt_ascs *ascs, struct bt_bap *bap,
struct iovec *iov, struct iovec *rsp);
} handlers[] = {
ASCS_OP("Codec Config", BT_ASCS_CONFIG,
sizeof(struct bt_ascs_config), ascs_config),
ASCS_OP("QoS Config", BT_ASCS_QOS,
sizeof(struct bt_ascs_qos), ascs_qos),
ASCS_OP("Enable", BT_ASCS_ENABLE, sizeof(struct bt_ascs_enable),
ascs_enable),
ASCS_OP("Receiver Start Ready", BT_ASCS_START,
sizeof(struct bt_ascs_start), ascs_start),
ASCS_OP("Disable", BT_ASCS_DISABLE,
sizeof(struct bt_ascs_disable), ascs_disable),
ASCS_OP("Receiver Stop Ready", BT_ASCS_STOP,
sizeof(struct bt_ascs_stop), ascs_stop),
ASCS_OP("Update Metadata", BT_ASCS_METADATA,
sizeof(struct bt_ascs_metadata), ascs_metadata),
ASCS_OP("Release", BT_ASCS_RELEASE,
sizeof(struct bt_ascs_release), ascs_release),
{}
};
static struct iovec *ascs_ase_cp_rsp_new(uint8_t op)
{
struct bt_ascs_cp_rsp *rsp;
struct iovec *iov;
iov = new0(struct iovec, 1);
rsp = new0(struct bt_ascs_cp_rsp, 1);
rsp->op = op;
iov->iov_base = rsp;
iov->iov_len = sizeof(*rsp);
return iov;
}
static void ascs_ase_cp_write(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
const uint8_t *value, size_t len,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_ascs *ascs = user_data;
struct bt_bap *bap = bt_bap_get_session(att, ascs->bdb->db);
struct iovec iov = {
.iov_base = (void *) value,
.iov_len = len,
};
struct bt_ascs_ase_hdr *hdr;
struct ascs_op_handler *handler;
uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
struct iovec *rsp;
if (offset) {
DBG(bap, "invalid offset %u", offset);
gatt_db_attribute_write_result(attrib, id,
BT_ATT_ERROR_INVALID_OFFSET);
return;
}
if (len < sizeof(*hdr)) {
DBG(bap, "invalid len %u < %u sizeof(*hdr)", len,
sizeof(*hdr));
gatt_db_attribute_write_result(attrib, id,
BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
return;
}
hdr = util_iov_pull_mem(&iov, sizeof(*hdr));
rsp = ascs_ase_cp_rsp_new(hdr->op);
for (handler = handlers; handler && handler->str; handler++) {
if (handler->op != hdr->op)
continue;
if (iov.iov_len < hdr->num * handler->size) {
DBG(bap, "invalid len %u < %u "
"hdr->num * handler->size", len,
hdr->num * handler->size);
ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
goto respond;
}
break;
}
if (handler && handler->str) {
int i;
DBG(bap, "%s", handler->str);
/* Set in_cp_write so ASE notification are not sent ahead of
* CP notification.
*/
bap->in_cp_write = true;
for (i = 0; i < hdr->num; i++)
ret = handler->func(ascs, bap, &iov, rsp);
bap->in_cp_write = false;
} else {
DBG(bap, "Unknown opcode 0x%02x", hdr->op);
ascs_ase_rsp_add_errno(rsp, 0x00, -ENOTSUP);
}
respond:
if (ret == BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN)
ascs_ase_rsp_add_errno(rsp, 0x00, -ENOMSG);
gatt_db_attribute_notify(attrib, rsp->iov_base, rsp->iov_len, att);
gatt_db_attribute_write_result(attrib, id, ret);
util_iov_free(rsp, 1);
}
static struct bt_ascs *ascs_new(struct gatt_db *db)
{
struct bt_ascs *ascs;
bt_uuid_t uuid;
int i;
if (!db)
return NULL;
ascs = new0(struct bt_ascs, 1);
/* Populate DB with ASCS attributes */
bt_uuid16_create(&uuid, ASCS_UUID);
ascs->service = gatt_db_add_service(db, &uuid, true,
4 + (NUM_ASES * 3));
for (i = 0; i < NUM_ASES; i++)
ase_new(ascs, i);
bt_uuid16_create(&uuid, ASE_CP_UUID);
ascs->ase_cp = gatt_db_service_add_characteristic(ascs->service,
&uuid,
BT_ATT_PERM_WRITE,
BT_GATT_CHRC_PROP_WRITE |
BT_GATT_CHRC_PROP_NOTIFY,
NULL, ascs_ase_cp_write,
ascs);
ascs->ase_cp_ccc = gatt_db_service_add_ccc(ascs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
gatt_db_service_set_active(ascs->service, true);
return ascs;
}
static struct bt_bap_db *bap_db_new(struct gatt_db *db)
{
struct bt_bap_db *bdb;
if (!db)
return NULL;
bdb = new0(struct bt_bap_db, 1);
bdb->db = gatt_db_ref(db);
bdb->sinks = queue_new();
bdb->sources = queue_new();
bdb->broadcast_sources = queue_new();
bdb->broadcast_sinks = queue_new();
if (!bap_db)
bap_db = queue_new();
bdb->pacs = pacs_new(db);
bdb->pacs->bdb = bdb;
bdb->ascs = ascs_new(db);
bdb->ascs->bdb = bdb;
queue_push_tail(bap_db, bdb);
return bdb;
}
static struct bt_bap_db *bap_get_db(struct gatt_db *db)
{
struct bt_bap_db *bdb;
bdb = queue_find(bap_db, bap_db_match, db);
if (bdb)
return bdb;
return bap_db_new(db);
}
static struct bt_pacs *bap_get_pacs(struct bt_bap *bap)
{
if (!bap)
return NULL;
if (bap->rdb->pacs)
return bap->rdb->pacs;
bap->rdb->pacs = new0(struct bt_pacs, 1);
bap->rdb->pacs->bdb = bap->rdb;
return bap->rdb->pacs;
}
static bool match_codec(const void *data, const void *user_data)
{
const struct bt_bap_pac *pac = data;
const struct bt_bap_codec *codec = user_data;
return bap_codec_equal(&pac->codec, codec);
}
static struct bt_bap_pac *bap_pac_find(struct bt_bap_db *bdb, uint8_t type,
struct bt_bap_codec *codec)
{
switch (type) {
case BT_BAP_SOURCE:
return queue_find(bdb->sources, match_codec, codec);
case BT_BAP_SINK:
return queue_find(bdb->sinks, match_codec, codec);
case BT_BAP_BCAST_SOURCE:
return queue_find(bdb->broadcast_sources, match_codec, codec);
case BT_BAP_BCAST_SINK:
return queue_find(bdb->broadcast_sinks, match_codec, codec);
}
return NULL;
}
static void *ltv_merge(struct iovec *data, struct iovec *cont)
{
uint8_t delimiter = 0;
if (!data)
return NULL;
util_iov_append(data, &delimiter, sizeof(delimiter));
if (!cont || !cont->iov_len || !cont->iov_base)
return data->iov_base;
return util_iov_append(data, cont->iov_base, cont->iov_len);
}
static void bap_pac_merge(struct bt_bap_pac *pac, struct iovec *data,
struct iovec *metadata)
{
/* Merge data into existing record */
if (pac->data)
ltv_merge(pac->data, data);
else
pac->data = util_iov_dup(data, 1);
/* Merge metadata into existing record */
if (pac->metadata)
ltv_merge(pac->metadata, metadata);
else
pac->metadata = util_iov_dup(metadata, 1);
}
static struct bt_bap_pac *bap_pac_new(struct bt_bap_db *bdb, const char *name,
uint8_t type,
struct bt_bap_codec *codec,
struct bt_bap_pac_qos *qos,
struct iovec *data,
struct iovec *metadata)
{
struct bt_bap_pac *pac;
pac = new0(struct bt_bap_pac, 1);
pac->bdb = bdb;
pac->name = name ? strdup(name) : NULL;
pac->type = type;
if (codec)
pac->codec = *codec;
if (qos)
pac->qos = *qos;
bap_pac_merge(pac, data, metadata);
return pac;
}
static void bap_pac_free(void *data)
{
struct bt_bap_pac *pac = data;
free(pac->name);
util_iov_free(pac->metadata, 1);
util_iov_free(pac->data, 1);
queue_destroy(pac->channels, free);
free(pac);
}
static void pacs_sink_location_changed(struct bt_pacs *pacs)
{
uint32_t location = cpu_to_le32(pacs->sink_loc_value);
gatt_db_attribute_notify(pacs->sink_loc, (void *)&location,
sizeof(location), NULL);
}
static void pacs_add_sink_location(struct bt_pacs *pacs, uint32_t location)
{
/* Check if location value needs updating */
if (location == pacs->sink_loc_value)
return;
pacs->sink_loc_value |= location;
pacs_sink_location_changed(pacs);
}
static void pacs_supported_context_changed(struct bt_pacs *pacs)
{
struct bt_pacs_context ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.snk = cpu_to_le16(pacs->supported_sink_context_value);
ctx.src = cpu_to_le16(pacs->supported_source_context_value);
gatt_db_attribute_notify(pacs->supported_context, (void *)&ctx,
sizeof(ctx), NULL);
}
static void pacs_add_sink_supported_context(struct bt_pacs *pacs,
uint16_t context)
{
context |= pacs->supported_sink_context_value;
/* Check if context value needs updating */
if (context == pacs->supported_sink_context_value)
return;
pacs->supported_sink_context_value = context;
pacs_supported_context_changed(pacs);
}
static void pacs_context_changed(struct bt_pacs *pacs)
{
struct bt_pacs_context ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.snk = cpu_to_le16(pacs->sink_context_value);
ctx.src = cpu_to_le16(pacs->source_context_value);
gatt_db_attribute_notify(pacs->context, (void *)&ctx, sizeof(ctx),
NULL);
}
static void pacs_add_sink_context(struct bt_pacs *pacs, uint16_t context)
{
context |= pacs->supported_sink_context_value;
/* Check if context value needs updating */
if (context == pacs->sink_context_value)
return;
pacs->sink_context_value = context;
pacs_context_changed(pacs);
}
static void bap_add_sink(struct bt_bap_pac *pac)
{
struct iovec iov;
uint8_t value[512];
queue_push_tail(pac->bdb->sinks, pac);
memset(value, 0, sizeof(value));
iov.iov_base = value;
iov.iov_len = 0;
queue_foreach(pac->bdb->sinks, pac_foreach, &iov);
pacs_add_sink_location(pac->bdb->pacs, pac->qos.location);
pacs_add_sink_supported_context(pac->bdb->pacs,
pac->qos.supported_context);
pacs_add_sink_context(pac->bdb->pacs, pac->qos.context);
gatt_db_attribute_notify(pac->bdb->pacs->sink, iov.iov_base,
iov.iov_len, NULL);
}
static void pacs_source_location_changed(struct bt_pacs *pacs)
{
uint32_t location = cpu_to_le32(pacs->source_loc_value);
gatt_db_attribute_notify(pacs->source_loc, (void *)&location,
sizeof(location), NULL);
}
static void pacs_add_source_location(struct bt_pacs *pacs, uint32_t location)
{
location |= pacs->source_loc_value;
/* Check if location value needs updating */
if (location == pacs->source_loc_value)
return;
pacs->source_loc_value = location;
pacs_source_location_changed(pacs);
}
static void pacs_add_source_supported_context(struct bt_pacs *pacs,
uint16_t context)
{
context |= pacs->supported_source_context_value;
/* Check if context value needs updating */
if (context == pacs->supported_source_context_value)
return;
pacs->supported_source_context_value = context;
pacs_supported_context_changed(pacs);
}
static void pacs_add_source_context(struct bt_pacs *pacs, uint16_t context)
{
context |= pacs->source_context_value;
/* Check if context value needs updating */
if (context == pacs->source_context_value)
return;
pacs->source_context_value = context;
pacs_context_changed(pacs);
}
static void bap_add_source(struct bt_bap_pac *pac)
{
struct iovec iov;
uint8_t value[512];
queue_push_tail(pac->bdb->sources, pac);
memset(value, 0, sizeof(value));
iov.iov_base = value;
iov.iov_len = 0;
queue_foreach(pac->bdb->sources, pac_foreach, &iov);
pacs_add_source_location(pac->bdb->pacs, pac->qos.location);
pacs_add_source_supported_context(pac->bdb->pacs,
pac->qos.supported_context);
pacs_add_source_context(pac->bdb->pacs, pac->qos.context);
gatt_db_attribute_notify(pac->bdb->pacs->source, iov.iov_base,
iov.iov_len, NULL);
}
static void bap_add_broadcast_source(struct bt_bap_pac *pac)
{
queue_push_tail(pac->bdb->broadcast_sources, pac);
}
static void bap_add_broadcast_sink(struct bt_bap_pac *pac)
{
queue_push_tail(pac->bdb->broadcast_sinks, pac);
/* Update local PACS for broadcast sink also, when registering an
* endpoint
*/
pacs_add_sink_location(pac->bdb->pacs, pac->qos.location);
pacs_add_sink_supported_context(pac->bdb->pacs,
pac->qos.supported_context);
}
static void notify_pac_added(void *data, void *user_data)
{
struct bt_bap_pac_changed *changed = data;
struct bt_bap_pac *pac = user_data;
if (changed->added)
changed->added(pac, changed->data);
}
static void notify_session_pac_added(void *data, void *user_data)
{
struct bt_bap *bap = data;
queue_foreach(bap->pac_cbs, notify_pac_added, user_data);
}
struct bt_bap_pac *bt_bap_add_vendor_pac_full(struct gatt_db *db,
const char *name, uint8_t type,
uint8_t id, uint16_t cid, uint16_t vid,
struct bt_bap_pac_qos *qos,
struct iovec *data,
struct iovec *metadata,
struct bt_bap_pac_ops *ops,
void *ops_user_data)
{
struct bt_bap_db *bdb;
struct bt_bap_pac *pac;
struct bt_bap_codec codec;
if (!db)
return NULL;
bdb = bap_get_db(db);
if (!bdb)
return NULL;
if ((id != 0xff) && ((cid != 0U) || (vid != 0U)))
return NULL;
codec.id = id;
codec.cid = cid;
codec.vid = vid;
pac = bap_pac_new(bdb, name, type, &codec, qos, data, metadata);
if (ops)
bt_bap_pac_set_ops(pac, ops, ops_user_data);
switch (type) {
case BT_BAP_SINK:
bap_add_sink(pac);
break;
case BT_BAP_SOURCE:
bap_add_source(pac);
break;
case BT_BAP_BCAST_SOURCE:
bap_add_broadcast_source(pac);
break;
case BT_BAP_BCAST_SINK:
bap_add_broadcast_sink(pac);
break;
default:
bap_pac_free(pac);
return NULL;
}
queue_foreach(sessions, notify_session_pac_added, pac);
return pac;
}
struct bt_bap_pac *bt_bap_add_vendor_pac(struct gatt_db *db,
const char *name, uint8_t type,
uint8_t id, uint16_t cid, uint16_t vid,
struct bt_bap_pac_qos *qos,
struct iovec *data,
struct iovec *metadata)
{
return bt_bap_add_vendor_pac_full(db, name, type, id, cid, vid, qos,
data, metadata,
NULL, NULL);
}
struct bt_bap_pac *bt_bap_add_pac(struct gatt_db *db, const char *name,
uint8_t type, uint8_t id,
struct bt_bap_pac_qos *qos,
struct iovec *data,
struct iovec *metadata)
{
return bt_bap_add_vendor_pac(db, name, type, id, 0x0000, 0x0000, qos,
data, metadata);
}
uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac)
{
if (!pac)
return 0x00;
return pac->type;
}
uint32_t bt_bap_pac_get_locations(struct bt_bap_pac *pac)
{
struct bt_pacs *pacs;
if (!pac)
return 0;
if (pac->qos.location)
return pac->qos.location;
pacs = pac->bdb->pacs;
switch (pac->type) {
case BT_BAP_SOURCE:
return pacs->source_loc_value;
case BT_BAP_SINK:
return pacs->sink_loc_value;
default:
return 0;
}
}
uint16_t bt_bap_pac_get_supported_context(struct bt_bap_pac *pac)
{
struct bt_pacs *pacs;
if (!pac)
return 0;
pacs = pac->bdb->pacs;
switch (pac->type) {
case BT_BAP_SOURCE:
return pacs->supported_source_context_value;
case BT_BAP_SINK:
return pacs->supported_sink_context_value;
default:
return 0;
}
}
uint16_t bt_bap_pac_get_context(struct bt_bap_pac *pac)
{
struct bt_pacs *pacs;
if (!pac)
return 0;
pacs = pac->bdb->pacs;
switch (pac->type) {
case BT_BAP_SOURCE:
return pacs->source_context_value;
case BT_BAP_SINK:
return pacs->sink_context_value;
default:
return 0;
}
}
struct bt_bap_pac_qos *bt_bap_pac_get_qos(struct bt_bap_pac *pac)
{
if (!pac || !pac->qos.phys)
return NULL;
return &pac->qos;
}
struct iovec *bt_bap_pac_get_data(struct bt_bap_pac *pac)
{
return pac->data;
}
struct iovec *bt_bap_pac_get_metadata(struct bt_bap_pac *pac)
{
return pac->metadata;
}
uint8_t bt_bap_stream_get_type(struct bt_bap_stream *stream)
{
if (!stream)
return 0x00;
switch (bt_bap_pac_get_type(stream->lpac)) {
case BT_BAP_SINK:
case BT_BAP_SOURCE:
return BT_BAP_STREAM_TYPE_UCAST;
case BT_BAP_BCAST_SOURCE:
case BT_BAP_BCAST_SINK:
return BT_BAP_STREAM_TYPE_BCAST;
}
return 0x00;
}
static void notify_pac_removed(void *data, void *user_data)
{
struct bt_bap_pac_changed *changed = data;
struct bt_bap_pac *pac = user_data;
if (changed->removed)
changed->removed(pac, changed->data);
}
static void notify_session_pac_removed(void *data, void *user_data)
{
struct bt_bap *bap = data;
queue_foreach(bap->pac_cbs, notify_pac_removed, user_data);
}
bool bt_bap_pac_set_ops(struct bt_bap_pac *pac, struct bt_bap_pac_ops *ops,
void *user_data)
{
if (!pac)
return false;
pac->ops = ops;
pac->user_data = user_data;
return true;
}
static bool match_stream_lpac(const void *data, const void *user_data)
{
const struct bt_bap_stream *stream = data;
const struct bt_bap_pac *pac = user_data;
return stream->lpac == pac;
}
static void remove_lpac_streams(void *data, void *user_data)
{
struct bt_bap *bap = data;
struct bt_bap_pac *pac = user_data;
struct bt_bap_stream *stream;
while (1) {
stream = queue_remove_if(bap->streams, match_stream_lpac, pac);
if (!stream)
break;
bt_bap_stream_ref(stream);
stream->no_cache_config = true;
bt_bap_stream_release(stream, NULL, NULL);
stream->lpac = NULL;
bt_bap_stream_unref(stream);
}
}
static void bap_pac_sink_removed(void *data, void *user_data)
{
struct bt_bap_pac *pac = data;
struct bt_bap_pac_qos *qos = user_data;
qos->location |= pac->qos.location;
qos->supported_context |= pac->qos.supported_context;
qos->context |= pac->qos.context;
}
bool bt_bap_remove_pac(struct bt_bap_pac *pac)
{
if (!pac)
return false;
if (queue_remove_if(pac->bdb->sinks, NULL, pac)) {
struct bt_pacs *pacs = pac->bdb->pacs;
struct bt_bap_pac_qos qos;
memset(&qos, 0, sizeof(qos));
queue_foreach(pac->bdb->sinks, bap_pac_sink_removed, &qos);
if (pacs->sink_loc_value != qos.location) {
pacs->sink_loc_value = qos.location;
pacs_sink_location_changed(pacs);
}
if (pacs->supported_sink_context_value !=
qos.supported_context) {
pacs->supported_sink_context_value =
qos.supported_context;
pacs_supported_context_changed(pacs);
}
if (pacs->sink_context_value != qos.context) {
pacs->sink_context_value = qos.context;
pacs_context_changed(pacs);
}
goto found;
}
if (queue_remove_if(pac->bdb->sources, NULL, pac))
goto found;
if (queue_remove_if(pac->bdb->broadcast_sources, NULL, pac))
goto found;
return false;
found:
queue_foreach(sessions, remove_lpac_streams, pac);
queue_foreach(sessions, notify_session_pac_removed, pac);
bap_pac_free(pac);
return true;
}
static void bap_db_free(void *data)
{
struct bt_bap_db *bdb = data;
if (!bdb)
return;
queue_destroy(bdb->sinks, bap_pac_free);
queue_destroy(bdb->sources, bap_pac_free);
gatt_db_unref(bdb->db);
free(bdb->pacs);
free(bdb->ascs);
free(bdb);
}
static void bap_ready_free(void *data)
{
struct bt_bap_ready *ready = data;
if (ready->destroy)
ready->destroy(ready->data);
free(ready);
}
static void bap_state_free(void *data)
{
struct bt_bap_state *state = data;
if (state->destroy)
state->destroy(state->data);
free(state);
}
static void bap_bis_cb_free(void *data)
{
struct bt_bap_bis_cb *bis_cb = data;
if (bis_cb->destroy)
bis_cb->destroy(bis_cb->data);
free(bis_cb);
}
static void bap_bcode_cb_free(void *data)
{
struct bt_bap_bcode_cb *cb = data;
if (cb->destroy)
cb->destroy(cb->data);
free(cb);
}
static void bap_ep_free(void *data)
{
struct bt_bap_endpoint *ep = data;
if (ep && ep->stream)
ep->stream->ep = NULL;
free(ep);
}
static void bap_detached(void *data, void *user_data)
{
struct bt_bap_cb *cb = data;
struct bt_bap *bap = user_data;
if (!cb->detached)
return;
cb->detached(bap, cb->user_data);
}
static void bap_stream_unref(void *data)
{
struct bt_bap_stream *stream = data;
bt_bap_stream_unref(stream);
}
static void bap_free(void *data)
{
struct bt_bap *bap = data;
timeout_remove(bap->process_id);
bt_bap_detach(bap);
bap_db_free(bap->rdb);
queue_destroy(bap->pac_cbs, pac_changed_free);
queue_destroy(bap->ready_cbs, bap_ready_free);
queue_destroy(bap->state_cbs, bap_state_free);
queue_destroy(bap->bis_cbs, bap_bis_cb_free);
queue_destroy(bap->bcode_cbs, bap_bcode_cb_free);
queue_destroy(bap->local_eps, free);
queue_destroy(bap->remote_eps, bap_ep_free);
queue_destroy(bap->reqs, bap_req_free);
queue_destroy(bap->notify, NULL);
queue_destroy(bap->streams, bap_stream_unref);
free(bap);
}
unsigned int bt_bap_register(bt_bap_func_t attached, bt_bap_func_t detached,
void *user_data)
{
struct bt_bap_cb *cb;
static unsigned int id;
if (!attached && !detached)
return 0;
if (!bap_cbs)
bap_cbs = queue_new();
cb = new0(struct bt_bap_cb, 1);
cb->id = ++id ? id : ++id;
cb->attached = attached;
cb->detached = detached;
cb->user_data = user_data;
queue_push_tail(bap_cbs, cb);
return cb->id;
}
static bool match_id(const void *data, const void *match_data)
{
const struct bt_bap_cb *cb = data;
unsigned int id = PTR_TO_UINT(match_data);
return (cb->id == id);
}
bool bt_bap_unregister(unsigned int id)
{
struct bt_bap_cb *cb;
cb = queue_remove_if(bap_cbs, match_id, UINT_TO_PTR(id));
if (!cb)
return false;
free(cb);
return true;
}
static void bap_attached(void *data, void *user_data)
{
struct bt_bap_cb *cb = data;
struct bt_bap *bap = user_data;
if (!cb->attached)
return;
cb->attached(bap, cb->user_data);
}
struct bt_bap *bt_bap_new(struct gatt_db *ldb, struct gatt_db *rdb)
{
struct bt_bap *bap;
struct bt_bap_db *bdb;
if (!ldb)
return NULL;
bdb = bap_get_db(ldb);
if (!bdb)
return NULL;
bap = new0(struct bt_bap, 1);
bap->ldb = bdb;
bap->reqs = queue_new();
bap->notify = queue_new();
bap->pac_cbs = queue_new();
bap->ready_cbs = queue_new();
bap->streams = queue_new();
bap->state_cbs = queue_new();
bap->bis_cbs = queue_new();
bap->bcode_cbs = queue_new();
bap->local_eps = queue_new();
if (!rdb)
goto done;
bdb = new0(struct bt_bap_db, 1);
bdb->db = gatt_db_ref(rdb);
bdb->sinks = queue_new();
bdb->sources = queue_new();
bap->rdb = bdb;
bap->remote_eps = queue_new();
done:
return bt_bap_ref(bap);
}
bool bt_bap_set_user_data(struct bt_bap *bap, void *user_data)
{
if (!bap)
return false;
bap->user_data = user_data;
return true;
}
void *bt_bap_get_user_data(struct bt_bap *bap)
{
if (!bap)
return NULL;
return bap->user_data;
}
struct bt_att *bt_bap_get_att(struct bt_bap *bap)
{
if (!bap)
return NULL;
if (bap->att)
return bap->att;
return bt_gatt_client_get_att(bap->client);
}
struct gatt_db *bt_bap_get_db(struct bt_bap *bap, bool remote)
{
if (!bap)
return NULL;
if (remote)
return bap->rdb ? bap->rdb->db : NULL;
return bap->ldb ? bap->ldb->db : NULL;
}
struct bt_bap *bt_bap_ref(struct bt_bap *bap)
{
if (!bap)
return NULL;
__sync_fetch_and_add(&bap->ref_count, 1);
return bap;
}
void bt_bap_unref(struct bt_bap *bap)
{
if (!bap)
return;
if (__sync_sub_and_fetch(&bap->ref_count, 1))
return;
bap_free(bap);
}
static void bap_notify_ready(struct bt_bap *bap)
{
const struct queue_entry *entry;
if (!bt_bap_ref_safe(bap))
return;
for (entry = queue_get_entries(bap->ready_cbs); entry;
entry = entry->next) {
struct bt_bap_ready *ready = entry->data;
ready->func(bap, ready->data);
}
bt_bap_unref(bap);
}
static void bap_parse_pacs(struct bt_bap *bap, uint8_t type,
struct queue *queue,
const uint8_t *value,
uint16_t len)
{
struct bt_pacs_read_rsp *rsp;
struct iovec iov = {
.iov_base = (void *) value,
.iov_len = len,
};
int i;
rsp = util_iov_pull_mem(&iov, sizeof(*rsp));
if (!rsp) {
DBG(bap, "Unable to parse PAC");
return;
}
DBG(bap, "PAC(s) %u", rsp->num_pac);
for (i = 0; i < rsp->num_pac; i++) {
struct bt_bap_pac *pac;
struct bt_pac *p;
struct bt_ltv *cc, *m;
struct bt_pac_metadata *meta;
struct iovec data, metadata;
p = util_iov_pull_mem(&iov, sizeof(*p));
if (!p) {
DBG(bap, "Unable to parse PAC");
return;
}
if (p->codec.id == 0xff) {
p->codec.cid = le16_to_cpu(p->codec.cid);
p->codec.vid = le16_to_cpu(p->codec.vid);
}
pac = NULL;
cc = util_iov_pull_mem(&iov, p->cc_len);
if (!cc) {
DBG(bap, "Unable to parse PAC codec capabilities");
return;
}
if (!bt_bap_debug_caps(cc, p->cc_len, bap->debug_func,
bap->debug_data)) {
DBG(bap, "Invalid PAC codec capabilities LTV");
return;
}
meta = util_iov_pull_mem(&iov, sizeof(*meta));
if (!meta) {
DBG(bap, "Unable to parse PAC metadata");
return;
}
m = util_iov_pull_mem(&iov, meta->len);
if (!m) {
DBG(bap, "Unable to parse PAC metadata");
return;
}
if (!bt_bap_debug_metadata(meta->data, meta->len,
bap->debug_func, bap->debug_data)) {
DBG(bap, "Invalid PAC metadata LTV");
return;
}
data.iov_len = p->cc_len;
data.iov_base = cc;
metadata.iov_len = meta->len;
metadata.iov_base = m;
DBG(bap, "PAC #%u: type %u codec 0x%02x cc_len %u meta_len %u",
i, type, p->codec.id, p->cc_len, meta->len);
/* Check if there is already a PAC record for the codec */
pac = bap_pac_find(bap->rdb, type, &p->codec);
if (pac) {
bap_pac_merge(pac, &data, &metadata);
continue;
}
pac = bap_pac_new(bap->rdb, NULL, type, &p->codec, NULL, &data,
&metadata);
if (!pac)
continue;
queue_push_tail(queue, pac);
}
}
static void read_source_pac(bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct bt_bap *bap = user_data;
if (!success) {
DBG(bap, "Unable to read Source PAC: error 0x%02x", att_ecode);
return;
}
bap_parse_pacs(bap, BT_BAP_SOURCE, bap->rdb->sources, value, length);
}
static void read_sink_pac(bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct bt_bap *bap = user_data;
if (!success) {
DBG(bap, "Unable to read Sink PAC: error 0x%02x", att_ecode);
return;
}
bap_parse_pacs(bap, BT_BAP_SINK, bap->rdb->sinks, value, length);
}
static void read_source_pac_loc(bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct bt_bap *bap = user_data;
struct bt_pacs *pacs = bap_get_pacs(bap);
if (!success) {
DBG(bap, "Unable to read Source PAC Location: error 0x%02x",
att_ecode);
return;
}
if (length != sizeof(uint32_t)) {
DBG(bap, "Invalid Source PAC Location size: %d", length);
return;
}
pacs->source_loc_value = get_le32(value);
DBG(bap, "PACS Source Locations: 0x%08x", pacs->source_loc_value);
/* Resume reading sinks if supported but for some reason is empty */
if (pacs->source && queue_isempty(bap->rdb->sources)) {
uint16_t value_handle;
if (gatt_db_attribute_get_char_data(pacs->source,
NULL, &value_handle,
NULL, NULL, NULL))
bt_gatt_client_read_value(bap->client, value_handle,
read_source_pac, bap,
NULL);
}
}
static void read_sink_pac_loc(bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct bt_bap *bap = user_data;
struct bt_pacs *pacs = bap_get_pacs(bap);
if (!success) {
DBG(bap, "Unable to read Sink PAC Location: error 0x%02x",
att_ecode);
return;
}
if (length != sizeof(uint32_t)) {
DBG(bap, "Invalid Sink PAC Location size: %d", length);
return;
}
pacs->sink_loc_value = get_le32(value);
DBG(bap, "PACS Sink Locations: 0x%08x", pacs->sink_loc_value);
/* Resume reading sinks if supported but for some reason is empty */
if (pacs->sink && queue_isempty(bap->rdb->sinks)) {
uint16_t value_handle;
if (gatt_db_attribute_get_char_data(pacs->sink,
NULL, &value_handle,
NULL, NULL, NULL))
bt_gatt_client_read_value(bap->client, value_handle,
read_sink_pac, bap,
NULL);
}
}
static void read_pac_context(bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct bt_bap *bap = user_data;
struct bt_pacs *pacs = bap_get_pacs(bap);
const struct bt_pacs_context *ctx = (void *)value;
if (!success) {
DBG(bap, "Unable to read PAC Context: error 0x%02x", att_ecode);
return;
}
if (length != sizeof(*ctx)) {
DBG(bap, "Invalid PAC Context size: %d", length);
return;
}
pacs->sink_context_value = le16_to_cpu(ctx->snk);
pacs->source_context_value = le16_to_cpu(ctx->src);
DBG(bap, "PACS Sink Context: 0x%04x", pacs->sink_context_value);
DBG(bap, "PACS Source Context: 0x%04x", pacs->source_context_value);
}
static void read_pac_supported_context(bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct bt_bap *bap = user_data;
struct bt_pacs *pacs = bap_get_pacs(bap);
const struct bt_pacs_context *ctx = (void *)value;
if (!success) {
DBG(bap, "Unable to read PAC Supported Context: error 0x%02x",
att_ecode);
return;
}
if (length != sizeof(*ctx)) {
DBG(bap, "Invalid PAC Supported Context size: %d", length);
return;
}
pacs->supported_sink_context_value = le16_to_cpu(ctx->snk);
pacs->supported_source_context_value = le16_to_cpu(ctx->src);
DBG(bap, "PACS Supported Sink Context: 0x%04x",
pacs->supported_sink_context_value);
DBG(bap, "PACS Supported Source Context: 0x%04x",
pacs->supported_source_context_value);
}
static void foreach_pacs_char(struct gatt_db_attribute *attr, void *user_data)
{
struct bt_bap *bap = user_data;
uint16_t value_handle;
bt_uuid_t uuid, uuid_sink, uuid_source, uuid_sink_loc, uuid_source_loc;
bt_uuid_t uuid_context, uuid_supported_context;
struct bt_pacs *pacs;
if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
NULL, NULL, &uuid))
return;
bt_uuid16_create(&uuid_sink, PAC_SINK_CHRC_UUID);
bt_uuid16_create(&uuid_source, PAC_SOURCE_CHRC_UUID);
bt_uuid16_create(&uuid_sink_loc, PAC_SINK_LOC_CHRC_UUID);
bt_uuid16_create(&uuid_source_loc, PAC_SOURCE_LOC_CHRC_UUID);
bt_uuid16_create(&uuid_context, PAC_CONTEXT);
bt_uuid16_create(&uuid_supported_context, PAC_SUPPORTED_CONTEXT);
if (!bt_uuid_cmp(&uuid, &uuid_sink)) {
DBG(bap, "Sink PAC found: handle 0x%04x", value_handle);
pacs = bap_get_pacs(bap);
if (!pacs)
return;
if (!pacs->sink)
pacs->sink = attr;
bt_gatt_client_read_value(bap->client, value_handle,
read_sink_pac, bap, NULL);
}
if (!bt_uuid_cmp(&uuid, &uuid_source)) {
DBG(bap, "Source PAC found: handle 0x%04x", value_handle);
pacs = bap_get_pacs(bap);
if (!pacs)
return;
if (!pacs->source)
pacs->source = attr;
bt_gatt_client_read_value(bap->client, value_handle,
read_source_pac, bap, NULL);
}
if (!bt_uuid_cmp(&uuid, &uuid_sink_loc)) {
DBG(bap, "Sink PAC Location found: handle 0x%04x",
value_handle);
pacs = bap_get_pacs(bap);
if (!pacs || pacs->sink_loc)
return;
pacs->sink_loc = attr;
bt_gatt_client_read_value(bap->client, value_handle,
read_sink_pac_loc, bap, NULL);
}
if (!bt_uuid_cmp(&uuid, &uuid_source_loc)) {
DBG(bap, "Source PAC Location found: handle 0x%04x",
value_handle);
pacs = bap_get_pacs(bap);
if (!pacs || pacs->source_loc)
return;
pacs->source_loc = attr;
bt_gatt_client_read_value(bap->client, value_handle,
read_source_pac_loc, bap, NULL);
}
if (!bt_uuid_cmp(&uuid, &uuid_context)) {
DBG(bap, "PAC Context found: handle 0x%04x", value_handle);
pacs = bap_get_pacs(bap);
if (!pacs || pacs->context)
return;
pacs->context = attr;
bt_gatt_client_read_value(bap->client, value_handle,
read_pac_context, bap, NULL);
}
if (!bt_uuid_cmp(&uuid, &uuid_supported_context)) {
DBG(bap, "PAC Supported Context found: handle 0x%04x",
value_handle);
pacs = bap_get_pacs(bap);
if (!pacs || pacs->supported_context)
return;
pacs->supported_context = attr;
bt_gatt_client_read_value(bap->client, value_handle,
read_pac_supported_context,
bap, NULL);
}
}
static void foreach_pacs_service(struct gatt_db_attribute *attr,
void *user_data)
{
struct bt_bap *bap = user_data;
struct bt_pacs *pacs = bap_get_pacs(bap);
pacs->service = attr;
gatt_db_service_foreach_char(attr, foreach_pacs_char, bap);
}
struct match_pac {
struct bt_bap_codec codec;
struct bt_bap_pac *lpac;
struct bt_bap_pac *rpac;
struct bt_bap_endpoint *ep;
};
static bool match_stream_pac(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
void *user_data)
{
struct match_pac *match = user_data;
if (!bap_codec_equal(&match->codec, &lpac->codec))
return true;
match->lpac = lpac;
match->rpac = rpac;
return false;
}
static void ep_status_config(struct bt_bap *bap, struct bt_bap_endpoint *ep,
struct iovec *iov)
{
struct bt_ascs_ase_status_config *cfg;
struct bt_ltv *cc;
uint32_t pd_min, pd_max, ppd_min, ppd_max;
int i;
cfg = util_iov_pull_mem(iov, sizeof(*cfg));
if (!cfg) {
DBG(bap, "Unable to parse Config Status");
return;
}
pd_min = get_le24(cfg->pd_min);
pd_max = get_le24(cfg->pd_max);
ppd_min = get_le24(cfg->ppd_min);
ppd_max = get_le24(cfg->ppd_max);
DBG(bap, "codec 0x%02x framing 0x%02x.phys 0x%02x rtn %u "
"latency %u pd %u - %u ppd %u - %u", cfg->codec.id,
cfg->framing, cfg->phys, cfg->rtn,
le16_to_cpu(cfg->latency),
pd_min, pd_max, ppd_min, ppd_max);
if (iov->iov_len < cfg->cc_len) {
DBG(bap, "Unable to parse Config Status: len %zu < %u cc_len",
iov->iov_len, cfg->cc_len);
return;
}
for (i = 0; iov->iov_len >= sizeof(*cc); i++) {
cc = util_iov_pull_mem(iov, sizeof(*cc));
if (!cc)
break;
DBG(bap, "Codec Config #%u: type 0x%02x len %u", i,
cc->type, cc->len);
util_iov_pull_mem(iov, cc->len - 1);
}
/* Any previously applied codec configuration may be cached by the
* server.
*/
if (!ep->stream) {
struct match_pac match;
match.lpac = NULL;
match.rpac = NULL;
match.codec.id = cfg->codec.id;
match.codec.cid = le16_to_cpu(cfg->codec.cid);
match.codec.vid = le16_to_cpu(cfg->codec.vid);
bt_bap_foreach_pac(bap, ep->dir, match_stream_pac, &match);
if (!match.lpac || !match.rpac)
return;
bap_stream_new(bap, ep, match.lpac, match.rpac, NULL, true);
}
if (!ep->stream)
return;
/* Set preferred settings */
if (ep->stream->rpac) {
ep->stream->rpac->qos.framing = cfg->framing;
ep->stream->rpac->qos.phys = cfg->phys;
ep->stream->rpac->qos.rtn = cfg->rtn;
ep->stream->rpac->qos.latency = le16_to_cpu(cfg->latency);
ep->stream->rpac->qos.pd_min = pd_min;
ep->stream->rpac->qos.pd_max = pd_max;
ep->stream->rpac->qos.ppd_min = ppd_min;
ep->stream->rpac->qos.ppd_max = ppd_max;
}
if (!ep->stream->cc)
ep->stream->cc = new0(struct iovec, 1);
util_iov_memcpy(ep->stream->cc, cfg->cc, cfg->cc_len);
ep->stream->need_reconfig = false;
}
static void bap_stream_config_cfm_cb(struct bt_bap_stream *stream, int err)
{
struct bt_bap *bap = stream->bap;
if (err) {
DBG(bap, "Config Confirmation failed: %d", err);
bt_bap_stream_release(stream, NULL, NULL);
return;
}
}
static void bap_stream_config_cfm(struct bt_bap_stream *stream)
{
int err;
if (!stream->lpac || !stream->lpac->ops || !stream->lpac->ops->config)
return;
err = stream->lpac->ops->config(stream, stream->cc, &stream->qos,
bap_stream_config_cfm_cb,
stream->lpac->user_data);
if (err < 0) {
DBG(stream->bap, "Unable to send Config Confirmation: %d",
err);
bt_bap_stream_release(stream, NULL, NULL);
}
}
static void ep_status_qos(struct bt_bap *bap, struct bt_bap_endpoint *ep,
struct iovec *iov)
{
struct bt_ascs_ase_status_qos *qos;
uint32_t interval;
uint32_t pd;
uint16_t sdu;
uint16_t latency;
qos = util_iov_pull_mem(iov, sizeof(*qos));
if (!qos) {
DBG(bap, "Unable to parse QoS Status");
return;
}
interval = get_le24(qos->interval);
pd = get_le24(qos->pd);
sdu = le16_to_cpu(qos->sdu);
latency = le16_to_cpu(qos->latency);
DBG(bap, "CIG 0x%02x CIS 0x%02x interval %u framing 0x%02x "
"phys 0x%02x rtn %u latency %u pd %u", qos->cig_id,
qos->cis_id, interval, qos->framing, qos->phys,
qos->rtn, latency, pd);
if (!ep->stream)
return;
ep->stream->qos.ucast.io_qos.interval = interval;
ep->stream->qos.ucast.framing = qos->framing;
ep->stream->qos.ucast.io_qos.phys = qos->phys;
ep->stream->qos.ucast.io_qos.sdu = sdu;
ep->stream->qos.ucast.io_qos.rtn = qos->rtn;
ep->stream->qos.ucast.io_qos.latency = latency;
ep->stream->qos.ucast.delay = pd;
if (ep->old_state == BT_ASCS_ASE_STATE_CONFIG)
bap_stream_config_cfm(ep->stream);
}
static void ep_status_metadata(struct bt_bap *bap, struct bt_bap_endpoint *ep,
struct iovec *iov)
{
struct bt_ascs_ase_status_metadata *meta;
meta = util_iov_pull_mem(iov, sizeof(*meta));
if (!meta) {
DBG(bap, "Unable to parse Metadata Status");
return;
}
DBG(bap, "CIS 0x%02x CIG 0x%02x metadata len %u",
meta->cis_id, meta->cig_id, meta->len);
}
static void bap_ep_set_status(struct bt_bap *bap, struct bt_bap_endpoint *ep,
const uint8_t *value, uint16_t length)
{
struct bt_ascs_ase_status *rsp;
struct iovec iov = {
.iov_base = (void *) value,
.iov_len = length,
};
rsp = util_iov_pull_mem(&iov, sizeof(*rsp));
if (!rsp)
return;
ep->id = rsp->id;
ep->old_state = ep->state;
ep->state = rsp->state;
DBG(bap, "ASE status: ep %p id 0x%02x handle 0x%04x state %s "
"len %zu", ep, ep->id,
gatt_db_attribute_get_handle(ep->attr),
bt_bap_stream_statestr(ep->state), iov.iov_len);
switch (ep->state) {
case BT_ASCS_ASE_STATE_IDLE:
break;
case BT_ASCS_ASE_STATE_CONFIG:
ep_status_config(bap, ep, &iov);
break;
case BT_ASCS_ASE_STATE_QOS:
ep_status_qos(bap, ep, &iov);
break;
case BT_ASCS_ASE_STATE_ENABLING:
case BT_ASCS_ASE_STATE_STREAMING:
case BT_ASCS_ASE_STATE_DISABLING:
ep_status_metadata(bap, ep, &iov);
break;
case BT_ASCS_ASE_STATE_RELEASING:
break;
}
/* Only notifify if there is a stream */
if (!ep->stream)
return;
bap_stream_state_changed(ep->stream);
}
static void read_ase_status(bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct bt_bap_endpoint *ep = user_data;
struct bt_bap *bap = ep->bap;
if (!success) {
DBG(bap, "ASE read status failed: 0x%04x", att_ecode);
return;
}
bap_ep_set_status(bap, ep, value, length);
}
static void bap_register(uint16_t att_ecode, void *user_data)
{
struct bt_bap_notify *notify = user_data;
if (att_ecode)
DBG(notify->bap, "ASE register failed: 0x%04x", att_ecode);
}
static void bap_endpoint_notify(struct bt_bap *bap, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct bt_bap_endpoint *ep = user_data;
bap_ep_set_status(bap, ep, value, length);
}
static void bap_notify(uint16_t value_handle, const uint8_t *value,
uint16_t length, void *user_data)
{
struct bt_bap_notify *notify = user_data;
if (notify->func)
notify->func(notify->bap, value_handle, value, length,
notify->user_data);
}
static void bap_notify_destroy(void *data)
{
struct bt_bap_notify *notify = data;
struct bt_bap *bap = notify->bap;
if (queue_remove_if(bap->notify, NULL, notify))
free(notify);
}
static unsigned int bap_register_notify(struct bt_bap *bap,
uint16_t value_handle,
bap_notify_t func,
void *user_data)
{
struct bt_bap_notify *notify;
notify = new0(struct bt_bap_notify, 1);
notify->bap = bap;
notify->func = func;
notify->user_data = user_data;
notify->id = bt_gatt_client_register_notify(bap->client,
value_handle, bap_register,
bap_notify, notify,
bap_notify_destroy);
if (!notify->id) {
DBG(bap, "Unable to register for notifications");
free(notify);
return 0;
}
queue_push_tail(bap->notify, notify);
return notify->id;
}
static void bap_endpoint_attach(struct bt_bap *bap, struct bt_bap_endpoint *ep)
{
uint16_t value_handle;
if (!gatt_db_attribute_get_char_data(ep->attr, NULL, &value_handle,
NULL, NULL, NULL))
return;
DBG(bap, "ASE handle 0x%04x", value_handle);
ep->bap = bap;
bt_gatt_client_read_value(bap->client, value_handle, read_ase_status,
ep, NULL);
ep->state_id = bap_register_notify(bap, value_handle,
bap_endpoint_notify, ep);
}
static void bap_cp_notify(struct bt_bap *bap, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data)
{
const struct bt_ascs_cp_rsp *rsp = (void *)value;
const struct bt_ascs_ase_rsp *ase_rsp = NULL;
struct bt_bap_req *req;
int i;
if (!bap->req)
return;
req = bap->req;
bap->req = NULL;
if (length < sizeof(*rsp)) {
DBG(bap, "Invalid ASE CP notification: length %u < %zu",
length, sizeof(*rsp));
goto done;
}
if (rsp->op != req->op) {
DBG(bap, "Invalid ASE CP notification: op 0x%02x != 0x%02x",
rsp->op, req->op);
goto done;
}
length -= sizeof(*rsp);
if (rsp->num_ase == 0xff) {
ase_rsp = rsp->rsp;
goto done;
}
for (i = 0; i < rsp->num_ase; i++) {
if (length < sizeof(*ase_rsp)) {
DBG(bap, "Invalid ASE CP notification: length %u < %zu",
length, sizeof(*ase_rsp));
goto done;
}
ase_rsp = &rsp->rsp[i];
}
done:
bap_req_complete(req, ase_rsp);
bap_process_queue(bap);
}
static void bap_cp_attach(struct bt_bap *bap)
{
uint16_t value_handle;
struct bt_ascs *ascs = bap_get_ascs(bap);
if (!gatt_db_attribute_get_char_data(ascs->ase_cp, NULL,
&value_handle,
NULL, NULL, NULL))
return;
DBG(bap, "ASE CP handle 0x%04x", value_handle);
bap->cp_id = bap_register_notify(bap, value_handle, bap_cp_notify,
NULL);
}
static void foreach_ascs_char(struct gatt_db_attribute *attr, void *user_data)
{
struct bt_bap *bap = user_data;
uint16_t value_handle;
bt_uuid_t uuid, uuid_sink, uuid_source, uuid_cp;
struct bt_ascs *ascs;
if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
NULL, NULL, &uuid))
return;
bt_uuid16_create(&uuid_sink, ASE_SINK_UUID);
bt_uuid16_create(&uuid_source, ASE_SOURCE_UUID);
bt_uuid16_create(&uuid_cp, ASE_CP_UUID);
if (!bt_uuid_cmp(&uuid, &uuid_sink) ||
!bt_uuid_cmp(&uuid, &uuid_source)) {
struct bt_bap_endpoint *ep;
ep = bap_get_endpoint(bap->remote_eps, bap->rdb, attr);
if (!ep)
return;
if (!bt_uuid_cmp(&uuid, &uuid_sink))
DBG(bap, "ASE Sink found: handle 0x%04x", value_handle);
else
DBG(bap, "ASE Source found: handle 0x%04x",
value_handle);
bap_endpoint_attach(bap, ep);
return;
}
if (!bt_uuid_cmp(&uuid, &uuid_cp)) {
ascs = bap_get_ascs(bap);
if (!ascs || ascs->ase_cp)
return;
ascs->ase_cp = attr;
DBG(bap, "ASE Control Point found: handle 0x%04x",
value_handle);
bap_cp_attach(bap);
}
}
static void foreach_ascs_service(struct gatt_db_attribute *attr,
void *user_data)
{
struct bt_bap *bap = user_data;
struct bt_ascs *ascs = bap_get_ascs(bap);
ascs->service = attr;
gatt_db_service_set_claimed(attr, true);
gatt_db_service_foreach_char(attr, foreach_ascs_char, bap);
}
static void bap_endpoint_foreach(void *data, void *user_data)
{
struct bt_bap_endpoint *ep = data;
struct bt_bap *bap = user_data;
bap_endpoint_attach(bap, ep);
}
static void bap_attach_att(struct bt_bap *bap, struct bt_att *att)
{
if (bap->disconn_id) {
if (att == bt_bap_get_att(bap))
return;
bt_att_unregister_disconnect(bap->att, bap->disconn_id);
}
bap->disconn_id = bt_att_register_disconnect(bap->att,
bap_disconnected,
bap, NULL);
}
static void bap_idle(void *data)
{
struct bt_bap *bap = data;
bap->idle_id = 0;
bap_notify_ready(bap);
}
bool bt_bap_attach(struct bt_bap *bap, struct bt_gatt_client *client)
{
bt_uuid_t uuid;
if (queue_find(sessions, NULL, bap)) {
/* If instance already been set but there is no client proceed
* to clone it otherwise considered it already attached.
*/
if (client && !bap->client)
goto clone;
return true;
}
if (!sessions)
sessions = queue_new();
queue_push_tail(sessions, bap);
queue_foreach(bap_cbs, bap_attached, bap);
if (!client) {
if (bap->att)
bap_attach_att(bap, bap->att);
return true;
}
if (bap->client)
return false;
clone:
bap->client = bt_gatt_client_clone(client);
if (!bap->client)
return false;
bap_attach_att(bap, bt_gatt_client_get_att(client));
bap->idle_id = bt_gatt_client_idle_register(bap->client, bap_idle,
bap, NULL);
if (bap->rdb->pacs) {
uint16_t value_handle;
struct bt_pacs *pacs = bap->rdb->pacs;
/* Resume reading sinks if supported */
if (pacs->sink && queue_isempty(bap->rdb->sinks)) {
if (gatt_db_attribute_get_char_data(pacs->sink,
NULL, &value_handle,
NULL, NULL, NULL)) {
bt_gatt_client_read_value(bap->client,
value_handle,
read_sink_pac,
bap, NULL);
}
}
/* Resume reading sink locations if supported */
if (pacs->sink && pacs->sink_loc && !pacs->sink_loc_value) {
if (gatt_db_attribute_get_char_data(pacs->sink_loc,
NULL, &value_handle,
NULL, NULL, NULL)) {
bt_gatt_client_read_value(bap->client,
value_handle,
read_sink_pac_loc,
bap, NULL);
}
}
/* Resume reading sources if supported */
if (pacs->source && queue_isempty(bap->rdb->sources)) {
if (gatt_db_attribute_get_char_data(pacs->source,
NULL, &value_handle,
NULL, NULL, NULL)) {
bt_gatt_client_read_value(bap->client,
value_handle,
read_source_pac,
bap, NULL);
}
}
/* Resume reading source locations if supported */
if (pacs->source && pacs->source_loc &&
!pacs->source_loc_value) {
if (gatt_db_attribute_get_char_data(pacs->source_loc,
NULL, &value_handle,
NULL, NULL, NULL)) {
bt_gatt_client_read_value(bap->client,
value_handle,
read_source_pac_loc,
bap, NULL);
}
}
/* Resume reading supported contexts if supported */
if (pacs->sink && pacs->supported_context &&
!pacs->supported_sink_context_value &&
!pacs->supported_source_context_value) {
if (gatt_db_attribute_get_char_data(
pacs->supported_context,
NULL, &value_handle,
NULL, NULL, NULL)) {
bt_gatt_client_read_value(bap->client,
value_handle,
read_pac_supported_context,
bap, NULL);
}
}
/* Resume reading contexts if supported */
if (pacs->sink && pacs->context &&
!pacs->sink_context_value &&
!pacs->source_context_value) {
if (gatt_db_attribute_get_char_data(pacs->context,
NULL, &value_handle,
NULL, NULL, NULL)) {
bt_gatt_client_read_value(bap->client,
value_handle,
read_pac_context,
bap, NULL);
}
}
queue_foreach(bap->remote_eps, bap_endpoint_foreach, bap);
bap_cp_attach(bap);
return true;
}
bt_uuid16_create(&uuid, PACS_UUID);
gatt_db_foreach_service(bap->rdb->db, &uuid, foreach_pacs_service, bap);
bt_uuid16_create(&uuid, ASCS_UUID);
gatt_db_foreach_service(bap->rdb->db, &uuid, foreach_ascs_service, bap);
return true;
}
bool bt_bap_attach_broadcast(struct bt_bap *bap)
{
struct bt_bap_endpoint *ep;
if (queue_find(sessions, NULL, bap))
return true;
if (!sessions)
sessions = queue_new();
queue_push_tail(sessions, bap);
queue_foreach(bap_cbs, bap_attached, bap);
ep = bap_get_endpoint_bcast(bap->remote_eps, bap->ldb,
BT_BAP_BCAST_SOURCE);
if (ep)
ep->bap = bap;
return true;
}
static void stream_foreach_detach(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
}
static void bap_req_detach(void *data)
{
struct bt_bap_req *req = data;
bap_req_complete(req, NULL);
}
void bt_bap_detach(struct bt_bap *bap)
{
DBG(bap, "%p", bap);
if (!queue_remove(sessions, bap))
return;
/* Cancel ongoing request */
if (bap->req) {
bap_req_detach(bap->req);
bap->req = NULL;
}
bt_gatt_client_idle_unregister(bap->client, bap->idle_id);
/* Cancel queued requests */
queue_remove_all(bap->reqs, NULL, NULL, bap_req_detach);
bt_gatt_client_unref(bap->client);
bap->client = NULL;
bt_att_unregister_disconnect(bap->att, bap->disconn_id);
bap->att = NULL;
queue_foreach(bap->streams, stream_foreach_detach, bap);
queue_foreach(bap_cbs, bap_detached, bap);
}
bool bt_bap_set_debug(struct bt_bap *bap, bt_bap_debug_func_t func,
void *user_data, bt_bap_destroy_func_t destroy)
{
if (!bap)
return false;
if (bap->debug_destroy)
bap->debug_destroy(bap->debug_data);
bap->debug_func = func;
bap->debug_destroy = destroy;
bap->debug_data = user_data;
return true;
}
unsigned int bt_bap_ready_register(struct bt_bap *bap,
bt_bap_ready_func_t func, void *user_data,
bt_bap_destroy_func_t destroy)
{
struct bt_bap_ready *ready;
static unsigned int id;
if (!bap)
return 0;
ready = new0(struct bt_bap_ready, 1);
ready->id = ++id ? id : ++id;
ready->func = func;
ready->destroy = destroy;
ready->data = user_data;
queue_push_tail(bap->ready_cbs, ready);
return ready->id;
}
static bool match_ready_id(const void *data, const void *match_data)
{
const struct bt_bap_ready *ready = data;
unsigned int id = PTR_TO_UINT(match_data);
return (ready->id == id);
}
bool bt_bap_ready_unregister(struct bt_bap *bap, unsigned int id)
{
struct bt_bap_ready *ready;
ready = queue_remove_if(bap->ready_cbs, match_ready_id,
UINT_TO_PTR(id));
if (!ready)
return false;
bap_ready_free(ready);
return true;
}
unsigned int bt_bap_state_register(struct bt_bap *bap,
bt_bap_state_func_t func,
bt_bap_connecting_func_t connecting,
void *user_data, bt_bap_destroy_func_t destroy)
{
struct bt_bap_state *state;
static unsigned int id;
if (!bap)
return 0;
state = new0(struct bt_bap_state, 1);
state->id = ++id ? id : ++id;
state->func = func;
state->connecting = connecting;
state->destroy = destroy;
state->data = user_data;
queue_push_tail(bap->state_cbs, state);
return state->id;
}
static bool match_state_id(const void *data, const void *match_data)
{
const struct bt_bap_state *state = data;
unsigned int id = PTR_TO_UINT(match_data);
return (state->id == id);
}
bool bt_bap_state_unregister(struct bt_bap *bap, unsigned int id)
{
struct bt_bap_state *state;
if (!bap)
return false;
state = queue_remove_if(bap->state_cbs, match_state_id,
UINT_TO_PTR(id));
if (!state)
return false;
bap_state_free(state);
return false;
}
unsigned int bt_bap_bis_cb_register(struct bt_bap *bap,
bt_bap_bis_func_t probe,
bt_bap_func_t remove,
void *user_data,
bt_bap_destroy_func_t destroy)
{
struct bt_bap_bis_cb *bis_cb;
static unsigned int id;
if (!bap)
return 0;
bis_cb = new0(struct bt_bap_bis_cb, 1);
bis_cb->id = ++id ? id : ++id;
bis_cb->probe = probe;
bis_cb->remove = remove;
bis_cb->destroy = destroy;
bis_cb->data = user_data;
queue_push_tail(bap->bis_cbs, bis_cb);
return bis_cb->id;
}
static bool match_bis_cb_id(const void *data, const void *match_data)
{
const struct bt_bap_bis_cb *bis_cb = data;
unsigned int id = PTR_TO_UINT(match_data);
return (bis_cb->id == id);
}
bool bt_bap_bis_cb_unregister(struct bt_bap *bap, unsigned int id)
{
struct bt_bap_bis_cb *bis_cb;
if (!bap)
return false;
bis_cb = queue_remove_if(bap->bis_cbs, match_bis_cb_id,
UINT_TO_PTR(id));
if (!bis_cb)
return false;
bap_bis_cb_free(bis_cb);
return false;
}
void bt_bap_bis_probe(struct bt_bap *bap, uint8_t sid, uint8_t bis,
uint8_t sgrp, struct iovec *caps, struct iovec *meta,
struct bt_bap_qos *qos)
{
const struct queue_entry *entry;
if (!bt_bap_ref_safe(bap))
return;
entry = queue_get_entries(bap->bis_cbs);
while (entry) {
struct bt_bap_bis_cb *cb = entry->data;
entry = entry->next;
if (cb->probe)
cb->probe(sid, bis, sgrp, caps, meta, qos, cb->data);
}
bt_bap_unref(bap);
}
void bt_bap_bis_remove(struct bt_bap *bap)
{
const struct queue_entry *entry;
if (!bt_bap_ref_safe(bap))
return;
entry = queue_get_entries(bap->bis_cbs);
while (entry) {
struct bt_bap_bis_cb *cb = entry->data;
entry = entry->next;
if (cb->remove)
cb->remove(bap, cb->data);
}
bt_bap_unref(bap);
}
const char *bt_bap_stream_statestr(uint8_t state)
{
switch (state) {
case BT_BAP_STREAM_STATE_IDLE:
return "idle";
case BT_BAP_STREAM_STATE_CONFIG:
return "config";
case BT_BAP_STREAM_STATE_QOS:
return "qos";
case BT_BAP_STREAM_STATE_ENABLING:
return "enabling";
case BT_BAP_STREAM_STATE_STREAMING:
return "streaming";
case BT_BAP_STREAM_STATE_DISABLING:
return "disabling";
case BT_BAP_STREAM_STATE_RELEASING:
return "releasing";
}
return "unknown";
}
static void bap_foreach_pac(struct queue *l, struct queue *r,
bt_bap_pac_foreach_t func, void *user_data)
{
const struct queue_entry *el;
for (el = queue_get_entries(l); el; el = el->next) {
struct bt_bap_pac *lpac = el->data;
const struct queue_entry *er;
for (er = queue_get_entries(r); er; er = er->next) {
struct bt_bap_pac *rpac = er->data;
/* Skip checking codec for bcast source,
* it will be checked when BASE info are received
*/
if ((rpac->type != BT_BAP_BCAST_SOURCE) &&
(!bap_codec_equal(&lpac->codec, &rpac->codec)))
continue;
if (!func(lpac, rpac, user_data))
return;
}
}
}
void bt_bap_foreach_pac(struct bt_bap *bap, uint8_t type,
bt_bap_pac_foreach_t func, void *user_data)
{
if (!bap || !func || !bap->rdb || queue_isempty(bap_db))
return;
switch (type) {
case BT_BAP_SINK:
return bap_foreach_pac(bap->ldb->sources, bap->rdb->sinks,
func, user_data);
case BT_BAP_SOURCE:
return bap_foreach_pac(bap->ldb->sinks, bap->rdb->sources,
func, user_data);
case BT_BAP_BCAST_SOURCE:
case BT_BAP_BCAST_SINK:
return bap_foreach_pac(bap->ldb->broadcast_sinks,
bap->rdb->broadcast_sources,
func, user_data);
}
}
int bt_bap_pac_get_vendor_codec(struct bt_bap_pac *pac, uint8_t *id,
uint16_t *cid, uint16_t *vid,
struct iovec **data, struct iovec **metadata)
{
if (!pac)
return -EINVAL;
if (id)
*id = pac->codec.id;
if (cid)
*cid = pac->codec.cid;
if (vid)
*vid = pac->codec.cid;
if (data && pac->data)
*data = pac->data;
if (metadata && pac->metadata)
*metadata = pac->metadata;
return 0;
}
int bt_bap_pac_get_codec(struct bt_bap_pac *pac, uint8_t *id,
struct iovec **data, struct iovec **metadata)
{
return bt_bap_pac_get_vendor_codec(pac, id, NULL, NULL, data, metadata);
}
void bt_bap_pac_set_user_data(struct bt_bap_pac *pac, void *user_data)
{
pac->user_data = user_data;
}
void *bt_bap_pac_get_user_data(struct bt_bap_pac *pac)
{
return pac->user_data;
}
bool bt_bap_pac_bcast_is_local(struct bt_bap *bap, struct bt_bap_pac *pac)
{
if (!bap->ldb)
return false;
if (queue_find(bap->ldb->broadcast_sinks, NULL, pac))
return true;
if (queue_find(bap->ldb->broadcast_sources, NULL, pac))
return true;
return false;
}
static bool find_ep_source(const void *data, const void *user_data)
{
const struct bt_bap_endpoint *ep = data;
if (ep->dir == BT_BAP_BCAST_SINK)
return true;
else
return false;
}
unsigned int bt_bap_stream_config(struct bt_bap_stream *stream,
struct bt_bap_qos *qos,
struct iovec *data,
bt_bap_stream_func_t func,
void *user_data)
{
unsigned int id;
struct bt_bap *bap;
if (!bap_stream_valid(stream))
return 0;
if (!stream->ops || !stream->ops->config)
return 0;
if (!bt_bap_ref_safe(stream->bap))
return 0;
bap = stream->bap;
id = stream->ops->config(stream, qos, data, func, user_data);
bt_bap_unref(bap);
return id;
}
static bool match_pac(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
void *user_data)
{
struct match_pac *match = user_data;
if (match->lpac && match->lpac != lpac)
return true;
if (match->rpac && match->rpac != rpac)
return true;
match->lpac = lpac;
match->rpac = rpac;
return false;
}
static void bap_pac_ltv_ch_counts(size_t i, uint8_t l, uint8_t t, uint8_t *v,
void *user_data)
{
uint8_t *mask = user_data;
if (v)
*mask |= *v;
}
static uint8_t bap_pac_ch_counts(struct bt_bap_pac *pac)
{
uint8_t type = 0x03;
uint8_t mask = 0;
if (!pac->data)
return 0;
util_ltv_foreach(pac->data->iov_base, pac->data->iov_len, &type,
bap_pac_ltv_ch_counts, &mask);
if (!mask)
mask = 0x01; /* default (BAP v1.0.1 Sec 4.3.1) */
return mask;
}
static unsigned int bap_count_eps(struct queue *eps, uint8_t dir)
{
const struct queue_entry *entry;
unsigned int count = 0;
for (entry = queue_get_entries(eps); entry; entry = entry->next) {
struct bt_bap_endpoint *ep = entry->data;
if (ep->dir == dir)
count++;
}
return count;
}
int bt_bap_select(struct bt_bap *bap,
struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
unsigned int max_channels, int *count,
bt_bap_pac_select_t func, void *user_data)
{
uint32_t locations;
uint8_t ch_counts;
unsigned int num_ase;
if (!lpac || !rpac || !func)
return -EINVAL;
if (!lpac->ops || !lpac->ops->select)
return -EOPNOTSUPP;
if (!max_channels)
max_channels = 2; /* By default: don't go beyond BAP AC */
ch_counts = bap_pac_ch_counts(lpac) & bap_pac_ch_counts(rpac);
locations = bt_bap_pac_get_locations(rpac);
num_ase = bap_count_eps(bap->remote_eps, rpac->type);
/* Fallback to unspecified/mono allocation if nothing is matching */
if (!locations || !ch_counts) {
lpac->ops->select(lpac, rpac, 0, &rpac->qos, func, user_data,
lpac->user_data);
if (count)
(*count)++;
return 0;
}
/* Allocate all locations to streams */
while (num_ase) {
uint32_t allocation = 0, alloc = 0;
unsigned int i, n;
/* Put max number of channels per stream */
for (i = 0, n = 0; i < 32 && n < 8; ++i) {
uint32_t loc = (1LL << i);
if (!(locations & loc))
continue;
alloc |= loc;
if ((BIT(n) & ch_counts) && n < max_channels)
allocation = alloc;
++n;
}
if (!allocation)
break;
/* Configure stream */
lpac->ops->select(lpac, rpac, allocation, &rpac->qos,
func, user_data, lpac->user_data);
if (count)
(*count)++;
locations &= ~allocation;
max_channels -= __builtin_popcount(allocation);
num_ase--;
}
return 0;
}
void bt_bap_cancel_select(struct bt_bap_pac *lpac, bt_bap_pac_select_t func,
void *user_data)
{
if (!lpac || !func)
return;
if (!lpac->ops || !lpac->ops->cancel_select)
return;
lpac->ops->cancel_select(lpac, func, user_data, lpac->user_data);
}
static struct bt_bap_stream *bap_bcast_stream_new(struct bt_bap *bap,
struct bt_bap_pac *lpac,
struct bt_bap_qos *pqos,
struct iovec *data)
{
struct bt_bap_stream *stream = NULL;
struct bt_bap_endpoint *ep = NULL;
struct match_pac match;
if (!bap || !lpac)
return NULL;
if (lpac->type == BT_BAP_BCAST_SOURCE) {
match.lpac = lpac;
match.rpac = NULL;
memset(&match.codec, 0, sizeof(match.codec));
bt_bap_foreach_pac(bap, BT_BAP_BCAST_SINK, match_pac, &match);
if (!match.lpac)
return NULL;
lpac = match.lpac;
ep = queue_find(bap->remote_eps, find_ep_source, NULL);
if (!ep)
return NULL;
} else if (lpac->type != BT_BAP_BCAST_SINK) {
return NULL;
}
if (!stream)
stream = bap_stream_new(bap, ep, lpac, NULL, data, true);
return stream;
}
static bool find_ep_ucast(const void *data, const void *user_data)
{
const struct bt_bap_endpoint *ep = data;
const struct match_pac *match = user_data;
if (ep->stream) {
if (!ep->stream->client)
return false;
if (ep->stream->locked)
return false;
if (!queue_isempty(ep->stream->pending_states))
return false;
switch (ep->stream->state) {
case BT_BAP_STREAM_STATE_IDLE:
case BT_BAP_STREAM_STATE_CONFIG:
case BT_BAP_STREAM_STATE_QOS:
break;
default:
return false;
}
}
switch (ep->state) {
case BT_ASCS_ASE_STATE_IDLE:
case BT_ASCS_ASE_STATE_CONFIG:
case BT_ASCS_ASE_STATE_QOS:
break;
default:
return false;
}
if (ep->dir != match->rpac->type)
return false;
switch (match->lpac->type) {
case BT_BAP_SOURCE:
if (ep->dir != BT_BAP_SINK)
return false;
break;
case BT_BAP_SINK:
if (ep->dir != BT_BAP_SOURCE)
return false;
break;
default:
return false;
}
return true;
}
static struct bt_bap_stream *bap_ucast_stream_new(struct bt_bap *bap,
struct bt_bap_pac *lpac,
struct bt_bap_pac *rpac,
struct bt_bap_qos *pqos,
struct iovec *data)
{
struct bt_bap_stream *stream = NULL;
struct bt_bap_endpoint *ep = NULL;
struct match_pac match;
if (!lpac || !rpac || !bap_codec_equal(&lpac->codec, &rpac->codec))
return NULL;
memset(&match, 0, sizeof(match));
match.lpac = lpac;
match.rpac = rpac;
/* Get free ASE */
ep = queue_find(bap->remote_eps, find_ep_ucast, &match);
if (!ep) {
DBG(bap, "Unable to find usable ASE");
return NULL;
}
stream = ep->stream;
if (stream) {
/* Replace lpac: the stream generally needs to be reconfigured
* after this, otherwise things like codec config not match.
*/
bap_stream_clear_cfm(stream);
stream->lpac = lpac;
util_iov_free(stream->cc, 1);
stream->cc = util_iov_dup(data, 1);
stream->need_reconfig = true;
} else {
stream = bap_stream_new(bap, ep, lpac, rpac, data, true);
}
return stream;
}
struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap,
struct bt_bap_pac *lpac,
struct bt_bap_pac *rpac,
struct bt_bap_qos *pqos,
struct iovec *data)
{
if (!bap)
return NULL;
/* Check if ATT is attached then it must be a unicast stream */
if (bt_bap_get_att(bap))
return bap_ucast_stream_new(bap, lpac, rpac, pqos, data);
return bap_bcast_stream_new(bap, lpac, pqos, data);
}
void bt_bap_stream_lock(struct bt_bap_stream *stream)
{
if (!stream || !stream->client)
return;
/* Reserve stream ASE for use by upper level, so it won't get
* reallocated
*/
stream->locked = true;
}
void bt_bap_stream_unlock(struct bt_bap_stream *stream)
{
if (!stream || !stream->client)
return;
stream->locked = false;
}
struct bt_bap *bt_bap_stream_get_session(struct bt_bap_stream *stream)
{
if (!stream)
return NULL;
return stream->bap;
}
uint8_t bt_bap_stream_get_state(struct bt_bap_stream *stream)
{
if (!stream)
return BT_BAP_STREAM_STATE_IDLE;
return stream->ops->get_state(stream);
}
bool bt_bap_stream_set_user_data(struct bt_bap_stream *stream, void *user_data)
{
if (!stream)
return false;
stream->user_data = user_data;
return true;
}
void *bt_bap_stream_get_user_data(struct bt_bap_stream *stream)
{
if (!stream)
return NULL;
return stream->user_data;
}
unsigned int bt_bap_stream_qos(struct bt_bap_stream *stream,
struct bt_bap_qos *data,
bt_bap_stream_func_t func,
void *user_data)
{
unsigned int id;
if (!bap_stream_valid(stream))
return 0;
if (!stream->ops || !stream->ops->qos)
return 0;
if (!bt_bap_ref_safe(stream->bap))
return 0;
id = stream->ops->qos(stream, data, func, user_data);
bt_bap_unref(stream->bap);
return id;
}
unsigned int bt_bap_stream_enable(struct bt_bap_stream *stream,
bool enable_links,
struct iovec *metadata,
bt_bap_stream_func_t func,
void *user_data)
{
unsigned int id;
struct bt_bap *bap;
if (!bap_stream_valid(stream))
return 0;
if (!stream->ops || !stream->ops->enable)
return 0;
if (!bt_bap_ref_safe(stream->bap))
return 0;
bap = stream->bap;
id = stream->ops->enable(stream, enable_links, metadata, func,
user_data);
bt_bap_unref(bap);
return id;
}
unsigned int bt_bap_stream_start(struct bt_bap_stream *stream,
bt_bap_stream_func_t func,
void *user_data)
{
unsigned int id;
struct bt_bap *bap;
if (!bap_stream_valid(stream))
return 0;
if (!stream->ops || !stream->ops->start)
return 0;
if (!bt_bap_ref_safe(stream->bap))
return 0;
bap = stream->bap;
id = stream->ops->start(stream, func, user_data);
bt_bap_unref(bap);
return id;
}
unsigned int bt_bap_stream_disable(struct bt_bap_stream *stream,
bool disable_links,
bt_bap_stream_func_t func,
void *user_data)
{
unsigned int id;
struct bt_bap *bap;
if (!bap_stream_valid(stream))
return 0;
if (!stream->ops || !stream->ops->disable)
return 0;
if (!bt_bap_ref_safe(stream->bap))
return 0;
bap = stream->bap;
id = stream->ops->disable(stream, disable_links, func, user_data);
bt_bap_unref(bap);
return id;
}
unsigned int bt_bap_stream_stop(struct bt_bap_stream *stream,
bt_bap_stream_func_t func,
void *user_data)
{
unsigned int id;
if (!bap_stream_valid(stream))
return 0;
if (!stream->ops || !stream->ops->stop)
return 0;
if (!bt_bap_ref_safe(stream->bap))
return 0;
id = stream->ops->stop(stream, func, user_data);
bt_bap_unref(stream->bap);
return id;
}
unsigned int bt_bap_stream_metadata(struct bt_bap_stream *stream,
struct iovec *metadata,
bt_bap_stream_func_t func,
void *user_data)
{
unsigned int id;
if (!bap_stream_valid(stream))
return 0;
if (!stream->ops || !stream->ops->metadata)
return 0;
if (!bt_bap_ref_safe(stream->bap))
return 0;
id = stream->ops->metadata(stream, metadata, func, user_data);
bt_bap_unref(stream->bap);
return id;
}
unsigned int bt_bap_stream_release(struct bt_bap_stream *stream,
bt_bap_stream_func_t func,
void *user_data)
{
unsigned int id;
struct bt_bap *bap;
if (!stream || !stream->ops || !stream->ops->release)
return 0;
bap = stream->bap;
if (!bt_bap_ref_safe(bap))
return 0;
id = stream->ops->release(stream, func, user_data);
bt_bap_unref(bap);
return id;
}
uint8_t bt_bap_stream_get_dir(struct bt_bap_stream *stream)
{
if (!stream)
return 0x00;
return stream->ops->get_dir(stream);
}
uint32_t bt_bap_stream_get_location(struct bt_bap_stream *stream)
{
if (!stream)
return 0x00000000;
return stream->ops->get_loc(stream);
}
struct iovec *bt_bap_stream_get_config(struct bt_bap_stream *stream)
{
if (!stream)
return NULL;
return stream->cc;
}
struct bt_bap_qos *bt_bap_stream_get_qos(struct bt_bap_stream *stream)
{
if (!stream)
return NULL;
return &stream->qos;
}
struct iovec *bt_bap_stream_get_metadata(struct bt_bap_stream *stream)
{
if (!stream)
return NULL;
return stream->meta;
}
struct io *bt_bap_stream_get_io(struct bt_bap_stream *stream)
{
struct bt_bap_stream_io *io;
io = stream_get_io(stream);
if (!io || io->connecting)
return NULL;
return io->io;
}
bool bt_bap_match_bcast_sink_stream(const void *data, const void *user_data)
{
const struct bt_bap_stream *stream = data;
if (!stream->lpac)
return false;
return stream->lpac->type == BT_BAP_BCAST_SINK;
}
static bool stream_io_disconnected(struct io *io, void *user_data)
{
struct bt_bap_stream *stream = user_data;
DBG(stream->bap, "stream %p io disconnected", stream);
/* If the IO is for a broadcast sink has been disconnected both BIG Sync
* and PA Sync have been lost so switch to idle state to cleanup the
* stream.
*/
if (stream->lpac->type == BT_BAP_BCAST_SINK) {
stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE);
return false;
}
if (stream->ep->state == BT_ASCS_ASE_STATE_RELEASING)
stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG);
bt_bap_stream_set_io(stream, -1);
return false;
}
bool bt_bap_stream_set_io(struct bt_bap_stream *stream, int fd)
{
bool ret;
struct bt_bap *bap;
if (!bap_stream_valid(stream))
return false;
if (!stream->ops || !stream->ops->set_io)
return false;
if (!bt_bap_ref_safe(stream->bap))
return false;
bap = stream->bap;
ret = stream->ops->set_io(stream, fd);
bt_bap_unref(bap);
return ret;
}
static bool match_req_id(const void *data, const void *match_data)
{
const struct bt_bap_req *req = data;
unsigned int id = PTR_TO_UINT(match_data);
return (req->id == id);
}
static bool match_name(const void *data, const void *match_data)
{
const struct bt_bap_pac *pac = data;
const char *name = match_data;
return (!strcmp(pac->name, name));
}
int bt_bap_stream_cancel(struct bt_bap_stream *stream, unsigned int id)
{
struct bt_bap_req *req;
if (!stream)
return -EINVAL;
if (stream->bap->req && stream->bap->req->id == id) {
req = stream->bap->req;
stream->bap->req = NULL;
bap_req_free(req);
return 0;
}
req = queue_remove_if(stream->bap->reqs, match_req_id,
UINT_TO_PTR(id));
if (!req)
return 0;
bap_req_free(req);
return 0;
}
int bt_bap_stream_io_link(struct bt_bap_stream *stream,
struct bt_bap_stream *link)
{
int ret;
struct bt_bap *bap;
if (!bap_stream_valid(stream))
return -EINVAL;
if (!stream->ops || !stream->ops->io_link)
return -EINVAL;
if (!bt_bap_ref_safe(stream->bap))
return -EINVAL;
bap = stream->bap;
ret = stream->ops->io_link(stream, link);
bt_bap_unref(bap);
return ret;
}
int bt_bap_stream_io_unlink(struct bt_bap_stream *stream,
struct bt_bap_stream *link)
{
int ret;
struct bt_bap *bap;
if (!bap_stream_valid(stream))
return -EINVAL;
if (!stream->ops || !stream->ops->io_unlink)
return -EINVAL;
if (!bt_bap_ref_safe(stream->bap))
return -EINVAL;
bap = stream->bap;
ret = stream->ops->io_unlink(stream, link);
bt_bap_unref(bap);
return ret;
}
struct queue *bt_bap_stream_io_get_links(struct bt_bap_stream *stream)
{
if (!stream)
return NULL;
return stream->links;
}
static void bap_stream_get_in_qos(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
struct bt_bap_qos **qos = user_data;
if (!stream)
return;
if (!qos || *qos || stream->ep->dir != BT_BAP_SOURCE ||
!stream->qos.ucast.io_qos.sdu)
return;
*qos = &stream->qos;
}
static void bap_stream_get_out_qos(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
struct bt_bap_qos **qos = user_data;
if (!stream)
return;
if (!qos || *qos || stream->ep->dir != BT_BAP_SINK ||
!stream->qos.ucast.io_qos.sdu)
return;
*qos = &stream->qos;
}
static void bap_stream_bcast_get_out_qos(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
struct bt_bap_qos **qos = user_data;
if (!stream)
return;
if (!qos || *qos || stream->ep->dir != BT_BAP_BCAST_SINK ||
!stream->qos.bcast.io_qos.sdu)
return;
*qos = &stream->qos;
}
static void bap_stream_bcast_get_in_qos(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
struct bt_bap_qos **qos = user_data;
if (!stream)
return;
if (!qos || *qos || stream->ep->dir != BT_BAP_BCAST_SOURCE ||
!stream->qos.bcast.io_qos.sdu)
return;
*qos = &stream->qos;
}
bool bt_bap_stream_io_get_qos(struct bt_bap_stream *stream,
struct bt_bap_qos **in,
struct bt_bap_qos **out)
{
if (!stream || (!in && !out))
return false;
switch (stream->ep->dir) {
case BT_BAP_SOURCE:
bap_stream_get_in_qos(stream, in);
queue_foreach(stream->links, bap_stream_get_out_qos, out);
break;
case BT_BAP_SINK:
bap_stream_get_out_qos(stream, out);
queue_foreach(stream->links, bap_stream_get_in_qos, in);
break;
case BT_BAP_BCAST_SOURCE:
bap_stream_bcast_get_in_qos(stream, in);
break;
case BT_BAP_BCAST_SINK:
bap_stream_bcast_get_out_qos(stream, out);
break;
default:
return false;
}
DBG(stream->bap, "in %p out %p", in ? *in : NULL, out ? *out : NULL);
return (in && *in) || (out && *out);
}
static void bap_stream_get_dir(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
uint8_t *dir = user_data;
*dir |= stream->ep->dir;
}
uint8_t bt_bap_stream_io_dir(struct bt_bap_stream *stream)
{
uint8_t dir;
struct bt_bap *bap;
if (!bap_stream_valid(stream))
return 0;
if (!stream->ops || !stream->ops->set_io)
return 0;
if (!bt_bap_ref_safe(stream->bap))
return 00;
bap = stream->bap;
dir = stream->ops->io_dir(stream);
bt_bap_unref(bap);
return dir;
}
static void bap_stream_io_connecting(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
int fd = PTR_TO_INT(user_data);
const struct queue_entry *entry;
if (!stream)
return;
if (fd >= 0)
bap_stream_io_attach(stream, fd, true);
else
bap_stream_io_detach(stream);
for (entry = queue_get_entries(stream->bap->state_cbs); entry;
entry = entry->next) {
struct bt_bap_state *state = entry->data;
if (state->connecting)
state->connecting(stream, stream->io ? true : false,
fd, state->data);
}
}
int bt_bap_stream_io_connecting(struct bt_bap_stream *stream, int fd)
{
if (!stream)
return -EINVAL;
bap_stream_io_connecting(stream, INT_TO_PTR(fd));
queue_foreach(stream->links, bap_stream_io_connecting, INT_TO_PTR(fd));
return 0;
}
bool bt_bap_stream_io_is_connecting(struct bt_bap_stream *stream, int *fd)
{
struct bt_bap_stream_io *io;
if (!stream)
return false;
io = stream_get_io(stream);
if (!io)
return false;
if (fd)
*fd = stream_io_get_fd(io);
return io->connecting;
}
bool bt_bap_new_bcast_source(struct bt_bap *bap, const char *name)
{
struct bt_bap_endpoint *ep;
struct bt_bap_pac *pac_broadcast_source;
/* Add the remote source only if a local sink endpoint was registered */
if (queue_isempty(bap->ldb->broadcast_sinks))
return false;
/* Add remote source endpoint */
if (!bap->rdb->broadcast_sources)
bap->rdb->broadcast_sources = queue_new();
if (queue_find(bap->rdb->broadcast_sources, match_name, name))
return true;
pac_broadcast_source = bap_pac_new(bap->rdb, name, BT_BAP_BCAST_SOURCE,
NULL, NULL, NULL, NULL);
queue_push_tail(bap->rdb->broadcast_sources, pac_broadcast_source);
if (!pac_broadcast_source)
return false;
queue_foreach(bap->pac_cbs, notify_pac_added, pac_broadcast_source);
/* Push remote endpoint with direction sink */
ep = bap_endpoint_new_broadcast(bap->rdb, BT_BAP_BCAST_SINK);
if (ep)
queue_push_tail(bap->remote_eps, ep);
return true;
}
void bt_bap_update_bcast_source(struct bt_bap_pac *pac,
struct bt_bap_codec *codec,
struct iovec *data,
struct iovec *metadata)
{
bap_pac_merge(pac, data, metadata);
pac->codec = *codec;
}
static void destroy_base_bis(void *data)
{
struct bt_bis *bis = data;
if (!bis)
return;
if (bis->caps)
util_iov_free(bis->caps, 1);
free(bis);
}
static void generate_bis_base(void *data, void *user_data)
{
struct bt_bis *bis = data;
struct iovec *base_iov = user_data;
uint8_t cc_length = bis->caps->iov_len;
if (!util_iov_push_u8(base_iov, bis->index))
return;
if (!util_iov_push_u8(base_iov, cc_length))
return;
if (cc_length)
util_iov_push_mem(base_iov, bis->caps->iov_len,
bis->caps->iov_base);
}
static void generate_subgroup_base(void *data, void *user_data)
{
struct bt_subgroup *sgrp = data;
struct iovec *base_iov = user_data;
if (!util_iov_push_u8(base_iov, queue_length(sgrp->bises)))
return;
if (!util_iov_push_u8(base_iov, sgrp->codec.id))
return;
if (!util_iov_push_le16(base_iov, sgrp->codec.cid))
return;
if (!util_iov_push_le16(base_iov, sgrp->codec.vid))
return;
if (sgrp->caps) {
if (!util_iov_push_u8(base_iov, sgrp->caps->iov_len))
return;
if (sgrp->caps->iov_len)
util_iov_push_mem(base_iov, sgrp->caps->iov_len,
sgrp->caps->iov_base);
} else if (!util_iov_push_u8(base_iov, 0))
return;
if (sgrp->meta) {
if (!util_iov_push_u8(base_iov, sgrp->meta->iov_len))
return;
if (sgrp->meta->iov_len)
util_iov_push_mem(base_iov, sgrp->meta->iov_len,
sgrp->meta->iov_base);
} else if (!util_iov_push_u8(base_iov, 0))
return;
queue_foreach(sgrp->bises, generate_bis_base, base_iov);
}
static struct iovec *generate_base(struct bt_base *base)
{
struct iovec *base_iov = new0(struct iovec, 0x1);
base_iov->iov_base = util_malloc(BASE_MAX_LENGTH);
if (!util_iov_push_le24(base_iov, base->pres_delay)) {
free(base_iov->iov_base);
free(base_iov);
return NULL;
}
if (!util_iov_push_u8(base_iov,
queue_length(base->subgroups))) {
free(base_iov->iov_base);
free(base_iov);
return NULL;
}
queue_foreach(base->subgroups, generate_subgroup_base,
base_iov);
return base_iov;
}
static void add_new_bis(struct bt_subgroup *subgroup,
uint8_t bis_index, struct iovec *caps)
{
struct bt_bis *bis = new0(struct bt_bis, 1);
bis->index = bis_index;
if (caps)
bis->caps = caps;
else
bis->caps = new0(struct iovec, 1);
queue_push_tail(subgroup->bises, bis);
}
static void add_new_subgroup(struct bt_base *base,
struct bt_bap_stream *stream)
{
struct bt_bap_pac *lpac = stream->lpac;
struct bt_subgroup *sgrp;
uint16_t cid = 0;
uint16_t vid = 0;
if (!lpac)
return;
sgrp = new0(struct bt_subgroup, 1);
bt_bap_pac_get_vendor_codec(lpac, &sgrp->codec.id, &cid,
&vid, NULL, NULL);
sgrp->codec.cid = cid;
sgrp->codec.vid = vid;
sgrp->caps = util_iov_dup(stream->cc, 1);
sgrp->meta = util_iov_dup(stream->meta, 1);
sgrp->bises = queue_new();
stream->qos.bcast.bis = base->next_bis_index++;
add_new_bis(sgrp, stream->qos.bcast.bis,
NULL);
queue_push_tail(base->subgroups, sgrp);
}
struct bt_ltv_match {
uint8_t l;
void *data;
bool found;
uint32_t data32;
};
struct bt_ltv_search {
struct iovec *iov;
bool found;
};
static void match_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v,
void *user_data)
{
struct bt_ltv_match *ltv_match = user_data;
if (ltv_match->found == true)
return;
if (ltv_match->l != l)
return;
if (!memcmp(v, ltv_match->data, l))
ltv_match->found = true;
}
static void search_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v,
void *user_data)
{
struct bt_ltv_search *ltv_search = user_data;
struct bt_ltv_match ltv_match;
ltv_match.found = false;
ltv_match.l = l;
ltv_match.data = v;
util_ltv_foreach(ltv_search->iov->iov_base,
ltv_search->iov->iov_len, &t,
match_ltv, &ltv_match);
/* Once "found" has been updated to "false",
* do not overwrite it anymore.
* It means that an ltv was not found in the search list,
* and this should be detected back in the parent function.
*/
if (ltv_search->found)
ltv_search->found = ltv_match.found;
}
static bool compare_ltv(struct iovec *iov1,
struct iovec *iov2)
{
struct bt_ltv_search ltv_search;
if ((!iov1) && (!iov2))
return true;
if ((!iov1) || (!iov2))
return false;
/* Compare metadata length */
if (iov1->iov_len != iov2->iov_len)
return false;
ltv_search.found = true;
ltv_search.iov = iov2;
util_ltv_foreach(iov1->iov_base,
iov1->iov_len, NULL,
search_ltv, &ltv_search);
return ltv_search.found;
}
struct bt_ltv_extract {
struct iovec *src;
void *value;
uint8_t len;
struct iovec *result;
};
static void extract_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v,
void *user_data)
{
struct bt_ltv_extract *ext_data = user_data;
struct bt_ltv_match ltv_match;
uint8_t ltv_len = 0;
ltv_match.found = false;
ltv_match.l = l;
ltv_match.data = v;
/* Search each BIS caps ltv in subgroup caps
* to extract the one that are BIS specific
*/
util_ltv_foreach(ext_data->src->iov_base,
ext_data->src->iov_len, &t,
match_ltv, &ltv_match);
if (!ltv_match.found) {
ltv_len = l + 1;
util_iov_append(ext_data->result, &ltv_len, 1);
util_iov_append(ext_data->result, &t, 1);
util_iov_append(ext_data->result, v, l);
}
}
static struct iovec *extract_diff_caps(
struct iovec *subgroup_caps, struct iovec *bis_caps)
{
struct bt_ltv_extract ext_data;
ext_data.src = subgroup_caps;
ext_data.result = new0(struct iovec, 1);
util_ltv_foreach(bis_caps->iov_base,
bis_caps->iov_len, NULL,
extract_ltv, &ext_data);
return ext_data.result;
}
static void set_base_subgroup(void *data, void *user_data)
{
struct bt_bap_stream *stream = data;
struct bt_base *base = user_data;
/* BIS specific codec capabilities */
struct iovec *bis_caps;
if (bt_bap_pac_get_type(stream->lpac) != BT_BAP_BCAST_SOURCE)
return;
if (stream->qos.bcast.big != base->big_id)
return;
if (base->pres_delay < stream->qos.bcast.delay)
base->pres_delay = stream->qos.bcast.delay;
if (queue_isempty(base->subgroups)) {
add_new_subgroup(base, stream);
} else {
/* Verify if a subgroup has the same metadata */
const struct queue_entry *entry;
struct bt_subgroup *subgroup = NULL;
bool same_meta = false;
for (entry = queue_get_entries(base->subgroups);
entry; entry = entry->next) {
subgroup = entry->data;
same_meta = compare_ltv(subgroup->meta, stream->meta);
if (same_meta)
break;
}
if (!same_meta) {
/* No subgroup with the same metadata found.
* Create a new one.
*/
add_new_subgroup(base, stream);
} else {
/* Subgroup found with the same metadata.
* Extract different codec capabilities.
*/
bis_caps = extract_diff_caps(
subgroup->caps,
stream->cc);
stream->qos.bcast.bis = base->next_bis_index++;
add_new_bis(subgroup,
stream->qos.bcast.bis,
bis_caps);
}
}
}
static void destroy_base_subgroup(void *data)
{
struct bt_subgroup *subgroup = data;
if (!subgroup)
return;
if (subgroup->caps)
util_iov_free(subgroup->caps, 1);
if (subgroup->meta)
util_iov_free(subgroup->meta, 1);
queue_destroy(subgroup->bises, destroy_base_bis);
free(subgroup);
}
/*
* Function to update the BASE using configuration data
* from each BIS belonging to the same BIG
*/
struct iovec *bt_bap_stream_get_base(struct bt_bap_stream *stream)
{
struct bt_base base;
struct iovec *base_iov;
base.subgroups = queue_new();
base.next_bis_index = 1;
base.big_id = stream->qos.bcast.big;
base.pres_delay = stream->qos.bcast.delay;
/* If the BIG ID was explicitly set, create a BASE with information
* from all streams belonging to this BIG. Otherwise, create a BASE
* with only this BIS.
*/
if (stream->qos.bcast.big != 0xFF)
queue_foreach(stream->bap->streams, set_base_subgroup, &base);
else {
base.pres_delay = stream->qos.bcast.delay;
set_base_subgroup(stream, &base);
}
base_iov = generate_base(&base);
queue_destroy(base.subgroups, destroy_base_subgroup);
return base_iov;
}
/*
* This function compares PAC Codec Specific Capabilities, with the Codec
* Specific Configuration LTVs received in the BASE of the BAP Source. The
* result is accumulated in data32 which is a bitmask of types.
*/
static void check_pac_caps_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v,
void *user_data)
{
struct bt_ltv_match *compare_data = user_data;
uint8_t *bis_v = compare_data->data;
uint16_t mask;
uint16_t min;
uint16_t max;
uint16_t frame_len;
switch (t) {
case BAP_FREQ_LTV_TYPE:
mask = get_le16(v);
if (mask & (1 << (bis_v[0] - 1)))
compare_data->data32 |= 1<<t;
break;
case BAP_DURATION_LTV_TYPE:
if ((v[0]) & (1 << bis_v[0]))
compare_data->data32 |= 1<<t;
break;
case BAP_FRAME_LEN_LTV_TYPE:
min = get_le16(v);
max = get_le16(v + 2);
frame_len = get_le16(bis_v);
if ((frame_len >= min) &&
(frame_len <= max))
compare_data->data32 |= 1<<t;
break;
}
}
static void check_source_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v,
void *user_data)
{
struct bt_ltv_match *local_data = user_data;
struct iovec *pac_caps = local_data->data;
struct bt_ltv_match compare_data;
compare_data.data = v;
/* Search inside local PAC's caps for LTV of type t */
util_ltv_foreach(pac_caps->iov_base, pac_caps->iov_len, &t,
check_pac_caps_ltv, &compare_data);
local_data->data32 |= compare_data.data32;
}
static void bap_sink_check_level3_ltv(size_t i, uint8_t l, uint8_t t,
uint8_t *v, void *user_data)
{
struct bt_ltv_extract *merge_data = user_data;
merge_data->value = v;
merge_data->len = l;
}
static void bap_sink_check_level2_ltv(size_t i, uint8_t l, uint8_t t,
uint8_t *v, void *user_data)
{
struct bt_ltv_extract *merge_data = user_data;
merge_data->value = NULL;
util_ltv_foreach(merge_data->src->iov_base,
merge_data->src->iov_len,
&t,
bap_sink_check_level3_ltv, merge_data);
/* If the LTV at level 2 was found at level 3 add the one from level 3,
* otherwise add the one at level 2
*/
if (merge_data->value)
util_ltv_push(merge_data->result, merge_data->len,
t, merge_data->value);
else
util_ltv_push(merge_data->result, l, t, v);
}
static void bap_sink_append_level3_ltv(size_t i, uint8_t l, uint8_t t,
uint8_t *v, void *user_data)
{
struct bt_ltv_extract *merge_data = user_data;
merge_data->value = NULL;
util_ltv_foreach(merge_data->result->iov_base,
merge_data->result->iov_len,
&t,
bap_sink_check_level3_ltv, merge_data);
/* If the LTV at level 3 was not found in merged configuration,
* append value
*/
if (!merge_data->value)
util_ltv_push(merge_data->result, l, t, v);
}
static void check_local_pac(void *data, void *user_data)
{
struct bt_ltv_match *compare_data = user_data;
struct iovec *bis_data = (struct iovec *)compare_data->data;
const struct bt_bap_pac *pac = data;
/* Keep searching for a matching PAC if one wasn't found
* in previous PAC element
*/
if (compare_data->found == false) {
struct bt_ltv_match bis_compare_data = {
.data = pac->data,
.data32 = 0, /* LTVs bitmask result */
.found = false
};
/* loop each BIS LTV */
util_ltv_foreach(bis_data->iov_base, bis_data->iov_len, NULL,
check_source_ltv, &bis_compare_data);
/* We have a match if all selected LTVs have a match */
if ((bis_compare_data.data32 &
CODEC_SPECIFIC_CONFIGURATION_MASK) ==
CODEC_SPECIFIC_CONFIGURATION_MASK) {
compare_data->found = true;
compare_data->data = data;
}
}
}
static void bap_sink_match_allocation(size_t i, uint8_t l, uint8_t t,
uint8_t *v, void *user_data)
{
struct bt_ltv_match *data = user_data;
uint32_t location32;
if (!v)
return;
memcpy(&location32, v, l);
location32 = le32_to_cpu(location32);
/* If all the bits in the received bitmask are found in
* the local bitmask then we have a match
*/
if ((location32 & data->data32) == location32)
data->found = true;
else
data->found = false;
}
static struct bt_ltv_match bap_check_bis(uint32_t sink_loc, struct queue *pacs,
struct iovec *bis_data)
{
struct bt_ltv_match compare_data = {};
/* Check channel allocation against the PACS location.
* If we don't have a location set we can accept any BIS location.
* If the BIS doesn't have a location set we also accept it
*/
compare_data.found = true;
if (sink_loc) {
uint8_t type = BAP_CHANNEL_ALLOCATION_LTV_TYPE;
compare_data.data32 = sink_loc;
util_ltv_foreach(bis_data->iov_base, bis_data->iov_len, &type,
bap_sink_match_allocation, &compare_data);
}
/* Check remaining LTVs against the PACs list */
if (compare_data.found) {
compare_data.data = bis_data;
compare_data.found = false;
queue_foreach(pacs, check_local_pac, &compare_data);
}
return compare_data;
}
struct iovec *bt_bap_merge_caps(struct iovec *l2_caps, struct iovec *l3_caps)
{
struct bt_ltv_extract merge_data = {0};
if (!l2_caps)
/* Codec_Specific_Configuration parameters shall
* be present at Level 2.
*/
return NULL;
if (!l3_caps)
/* Codec_Specific_Configuration parameters may
* be present at Level 3.
*/
return util_iov_dup(l2_caps, 1);
merge_data.src = l3_caps;
merge_data.result = new0(struct iovec, 1);
/* Create a Codec Specific Configuration with LTVs at level 2 (subgroup)
* overwritten by LTVs at level 3 (BIS)
*/
util_ltv_foreach(l2_caps->iov_base,
l2_caps->iov_len,
NULL,
bap_sink_check_level2_ltv, &merge_data);
/* Append LTVs at level 3 (BIS) that were not found at
* level 2 (subgroup)
*/
util_ltv_foreach(l3_caps->iov_base,
l3_caps->iov_len,
NULL,
bap_sink_append_level3_ltv, &merge_data);
return merge_data.result;
}
void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index,
struct iovec *caps,
struct bt_bap_pac **lpac)
{
struct bt_ltv_match match_data;
uint32_t sink_loc;
struct queue *pacs;
if (!caps)
return;
/* If the bap session corresponds to a client connection with
* a BAP Server, bis caps should be checked against peer caps.
* If the bap session corresponds to a scanned broadcast source,
* bis caps should be checked against local broadcast sink caps.
*/
if (bap->client) {
sink_loc = bap->rdb->pacs->sink_loc_value;
pacs = bap->rdb->sinks;
} else {
sink_loc = bap->ldb->pacs->sink_loc_value;
pacs = bap->ldb->broadcast_sinks;
}
/* Check each BIS Codec Specific Configuration LTVs against our Codec
* Specific Capabilities and if the BIS matches create a PAC with it
*/
match_data = bap_check_bis(sink_loc, pacs, caps);
if (match_data.found == true) {
*lpac = match_data.data;
DBG(bap, "Matching BIS %i", bis_index);
} else {
*lpac = NULL;
}
}
bool bt_bap_parse_base(uint8_t sid, struct iovec *iov,
struct bt_bap_qos *qos,
util_debug_func_t func,
bt_bap_bis_func_t handler,
void *user_data)
{
uint32_t delay;
uint8_t sgrps;
bool ret = true;
util_debug(func, NULL, "BASE len: %zd", iov->iov_len);
if (!util_iov_pull_le24(iov, &delay))
return false;
util_debug(func, NULL, "PresentationDelay: %d", delay);
if (!util_iov_pull_u8(iov, &sgrps))
return false;
util_debug(func, NULL, "Number of Subgroups: %d", sgrps);
/* Loop subgroups */
for (int idx = 0; idx < sgrps; idx++) {
uint8_t num_bis;
struct bt_bap_codec *codec;
struct iovec l2_cc;
uint8_t l2_cc_len;
struct iovec meta;
uint8_t meta_len;
util_debug(func, NULL, "Subgroup #%d", idx);
if (!util_iov_pull_u8(iov, &num_bis)) {
ret = false;
goto done;
}
util_debug(func, NULL, "Number of BISes: %d", num_bis);
codec = util_iov_pull_mem(iov, sizeof(*codec));
if (!codec) {
ret = false;
goto done;
}
util_debug(func, NULL, "Codec: ID %d CID 0x%2.2x VID 0x%2.2x",
codec->id, codec->cid, codec->vid);
/* Level 2 */
/* Read Codec Specific Configuration */
if (!util_iov_pull_u8(iov, &l2_cc_len)) {
ret = false;
goto done;
}
l2_cc.iov_base = util_iov_pull_mem(iov, l2_cc_len);
if (!l2_cc.iov_base) {
ret = false;
goto done;
}
l2_cc.iov_len = l2_cc_len;
/* Print Codec Specific Configuration */
util_debug(func, NULL, "CC len: %zd", l2_cc.iov_len);
bt_bap_debug_config(l2_cc.iov_base, l2_cc.iov_len,
func, NULL);
/* Read Metadata */
if (!util_iov_pull_u8(iov, &meta_len)) {
ret = false;
goto done;
}
meta.iov_base = util_iov_pull_mem(iov, meta_len);
if (!meta.iov_base) {
ret = false;
goto done;
}
meta.iov_len = meta_len;
/* Print Metadata */
util_debug(func, NULL, "Metadata len: %i",
(uint8_t)meta.iov_len);
bt_bap_debug_metadata(meta.iov_base, meta.iov_len,
func, NULL);
/* Level 3 */
for (; num_bis; num_bis--) {
uint8_t bis_index;
struct iovec l3_cc;
uint8_t l3_cc_len;
struct iovec *bis_cc;
if (!util_iov_pull_u8(iov, &bis_index)) {
ret = false;
goto done;
}
util_debug(func, NULL, "BIS #%d", bis_index);
/* Read Codec Specific Configuration */
if (!util_iov_pull_u8(iov, &l3_cc_len)) {
ret = false;
goto done;
}
l3_cc.iov_base = util_iov_pull_mem(iov,
l3_cc_len);
if (!l3_cc.iov_base) {
ret = false;
goto done;
}
l3_cc.iov_len = l3_cc_len;
/* Print Codec Specific Configuration */
util_debug(func, NULL, "CC Len: %d",
(uint8_t)l3_cc.iov_len);
bt_bap_debug_config(l3_cc.iov_base,
l3_cc.iov_len,
func, NULL);
bis_cc = bt_bap_merge_caps(&l2_cc, &l3_cc);
if (!bis_cc)
continue;
handler(sid, bis_index, idx, bis_cc, &meta,
qos, user_data);
util_iov_free(bis_cc, 1);
}
}
done:
if (!ret)
util_debug(func, NULL, "Unable to parse Base");
return ret;
}
void bt_bap_req_bcode(struct bt_bap_stream *stream,
bt_bap_bcode_reply_t reply,
void *reply_data)
{
const struct queue_entry *entry;
if (!bap_stream_valid(stream))
return;
bt_bap_stream_ref(stream);
if (!bt_bap_ref_safe(stream->bap))
goto done;
entry = queue_get_entries(stream->bap->bcode_cbs);
while (entry) {
struct bt_bap_bcode_cb *cb = entry->data;
entry = entry->next;
if (cb->func)
cb->func(stream, reply, reply_data, cb->data);
}
bt_bap_unref(stream->bap);
done:
bt_bap_stream_unref(stream);
}
unsigned int bt_bap_bcode_cb_register(struct bt_bap *bap,
bt_bap_bcode_func_t func,
void *user_data,
bt_bap_destroy_func_t destroy)
{
struct bt_bap_bcode_cb *cb;
static unsigned int id;
if (!bap)
return 0;
cb = new0(struct bt_bap_bcode_cb, 1);
cb->id = ++id ? id : ++id;
cb->func = func;
cb->destroy = destroy;
cb->data = user_data;
queue_push_tail(bap->bcode_cbs, cb);
return cb->id;
}
static bool match_bcode_cb_id(const void *data, const void *match_data)
{
const struct bt_bap_bcode_cb *cb = data;
unsigned int id = PTR_TO_UINT(match_data);
return (cb->id == id);
}
bool bt_bap_bcode_cb_unregister(struct bt_bap *bap, unsigned int id)
{
struct bt_bap_bcode_cb *cb;
if (!bap)
return false;
cb = queue_remove_if(bap->bcode_cbs, match_bcode_cb_id,
UINT_TO_PTR(id));
if (!cb)
return false;
bap_bcode_cb_free(cb);
return false;
}
void bt_bap_iso_qos_to_bap_qos(struct bt_iso_qos *iso_qos,
struct bt_bap_qos *bap_qos)
{
bap_qos->bcast.big = iso_qos->bcast.big;
bap_qos->bcast.bis = iso_qos->bcast.bis;
bap_qos->bcast.sync_factor = iso_qos->bcast.sync_factor;
bap_qos->bcast.packing = iso_qos->bcast.packing;
bap_qos->bcast.framing = iso_qos->bcast.framing;
bap_qos->bcast.encryption = iso_qos->bcast.encryption;
if (bap_qos->bcast.encryption)
bap_qos->bcast.bcode = util_iov_new(iso_qos->bcast.bcode,
sizeof(iso_qos->bcast.bcode));
bap_qos->bcast.options = iso_qos->bcast.options;
bap_qos->bcast.skip = iso_qos->bcast.skip;
bap_qos->bcast.sync_timeout = iso_qos->bcast.sync_timeout;
bap_qos->bcast.sync_cte_type =
iso_qos->bcast.sync_cte_type;
bap_qos->bcast.mse = iso_qos->bcast.mse;
bap_qos->bcast.timeout = iso_qos->bcast.timeout;
bap_qos->bcast.io_qos.interval =
iso_qos->bcast.in.interval;
bap_qos->bcast.io_qos.latency = iso_qos->bcast.in.latency;
bap_qos->bcast.io_qos.phys = iso_qos->bcast.in.phys;
bap_qos->bcast.io_qos.rtn = iso_qos->bcast.in.rtn;
bap_qos->bcast.io_qos.sdu = iso_qos->bcast.in.sdu;
}
void bt_bap_qos_to_iso_qos(struct bt_bap_qos *bap_qos,
struct bt_iso_qos *iso_qos)
{
memset(iso_qos, 0, sizeof(*iso_qos));
iso_qos->bcast.big = bap_qos->bcast.big;
iso_qos->bcast.bis = bap_qos->bcast.bis;
iso_qos->bcast.sync_factor = bap_qos->bcast.sync_factor;
iso_qos->bcast.packing = bap_qos->bcast.packing;
iso_qos->bcast.framing = bap_qos->bcast.framing;
iso_qos->bcast.encryption = bap_qos->bcast.encryption;
if (bap_qos->bcast.bcode && bap_qos->bcast.bcode->iov_base)
memcpy(iso_qos->bcast.bcode, bap_qos->bcast.bcode->iov_base,
bap_qos->bcast.bcode->iov_len);
iso_qos->bcast.options = bap_qos->bcast.options;
iso_qos->bcast.skip = bap_qos->bcast.skip;
iso_qos->bcast.sync_timeout = bap_qos->bcast.sync_timeout;
iso_qos->bcast.sync_cte_type = bap_qos->bcast.sync_cte_type;
iso_qos->bcast.mse = bap_qos->bcast.mse;
iso_qos->bcast.timeout = bap_qos->bcast.timeout;
memcpy(&iso_qos->bcast.out, &bap_qos->bcast.io_qos,
sizeof(struct bt_iso_io_qos));
}