Message ID | 20240108164258.77332-3-silviu.barbulescu@nxp.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | Add support for multiple BISes | expand |
Context | Check | Description |
---|---|---|
tedd_an/pre-ci_am | success | Success |
tedd_an/CheckPatch | success | CheckPatch PASS |
tedd_an/GitLint | success | Gitlint PASS |
tedd_an/IncrementalBuild | success | Incremental Build PASS |
Hi Silviu, On Mon, Jan 8, 2024 at 11:43 AM Silviu Florian Barbulescu <silviu.barbulescu@nxp.com> wrote: > > Update broadcast to support the new setup structure. > Change broadcast code from iovec struct to byte array. > Add adapter property for broadcast endpoint. > Make broadcast sink to select broadcast source BISes > based on pac broadcast sink properties. > > --- > profiles/audio/bap.c | 1273 ++++++++++++++++++++++++++++++------ > profiles/audio/transport.c | 25 +- > src/shared/bap.c | 441 ++++++++++++- > src/shared/bap.h | 36 +- > 4 files changed, 1543 insertions(+), 232 deletions(-) > > diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c > index b88876485..8bc292946 100644 > --- a/profiles/audio/bap.c > +++ b/profiles/audio/bap.c > @@ -45,6 +45,7 @@ > #include "src/shared/gatt-client.h" > #include "src/shared/gatt-server.h" > #include "src/shared/bap.h" > +#include "src/shared/lc3.h" > > #include "btio/btio.h" > #include "src/plugin.h" > @@ -75,6 +76,8 @@ struct bap_setup { > unsigned int id; > struct iovec *base; > DBusMessage *msg; > + struct queue *same_big_setups; > + struct bt_bap_qos combined_qos; Perhaps we could use the same concept of links for broadcast as well? But Id put that logic inside bt_bap instead because I recall there are streaming tests for them, which is in my TODO list btw, which we likely want to cover in unit/test-bap. > }; > > struct bap_ep { > @@ -102,6 +105,64 @@ struct bap_data { > void *user_data; > }; > > +struct stream_base_data { > + struct queue *ltv_caps; > + struct queue *ltv_meta; > + struct bap_setup *setup; > +}; > + > +struct base_data { > + uint32_t pres_delay; > + struct queue *base_data_subgroup; > +}; > + > +struct base_data_subgroup { > + uint8_t subgroup_index; > + struct bt_bap_codec codec; > + struct queue *ltv_caps; > + struct queue *ltv_meta; > + struct queue *bises; > +}; > + > +struct base_data_bis { > + uint8_t bis_index; > + struct queue *ltv_caps; > +}; > + > +#define DEFAULT_IO_QOS \ > +{ \ > + .interval = 10000, \ > + .latency = 10, \ > + .sdu = 40, \ > + .phy = 0x02, \ > + .rtn = 2, \ > +} > + > +static struct bt_iso_qos default_qos = { > + .bcast = { > + .big = BT_ISO_QOS_BIG_UNSET, > + .bis = BT_ISO_QOS_BIS_UNSET, > + .sync_factor = 0x07, > + .packing = 0x00, > + .framing = 0x00, > + .in = DEFAULT_IO_QOS, > + .out = DEFAULT_IO_QOS, > + .encryption = 0x00, > + .bcode = {0x00}, > + .options = 0x00, > + .skip = 0x0000, > + .sync_timeout = 0x4000, > + .sync_cte_type = 0x00, > + .mse = 0x00, > + .timeout = 0x4000, > + } > +}; > +struct ltv_compare_data { > + bool match; > + void *data; > + uint32_t data32; > +}; > + Id prefer we create helper functions in shared/bap.c to manipulate the base data, or perhaps have it as part of shared/ad.c, either way I think this should be unit testable. > static struct queue *sessions; > > static bool bap_data_set_user_data(struct bap_data *data, void *user_data) > @@ -258,10 +319,20 @@ static gboolean get_device(const GDBusPropertyTable *property, > struct bap_ep *ep = data; > const char *path; > > - if (bt_bap_pac_get_type(ep->lpac) == BT_BAP_BCAST_SOURCE) > - path = adapter_get_path(ep->data->adapter); > - else > - path = device_get_path(ep->data->device); > + path = device_get_path(ep->data->device); > + > + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); > + > + return TRUE; > +} > + > +static gboolean get_adapter(const GDBusPropertyTable *property, > + DBusMessageIter *iter, void *data) > +{ > + struct bap_ep *ep = data; > + const char *path; > + > + path = adapter_get_path(ep->data->adapter); > > dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); > > @@ -370,6 +441,26 @@ static const GDBusPropertyTable ep_properties[] = { > { } > }; > > +static const GDBusPropertyTable ep_properties_bcast_source[] = { > + { "UUID", "s", get_uuid, NULL, NULL, > + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, > + { "Codec", "y", get_codec, NULL, NULL, > + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, > + { "Capabilities", "ay", get_capabilities, NULL, has_capabilities, > + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, > + { "Adapter", "o", get_adapter, NULL, NULL, > + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, > + { "Locations", "u", get_locations, NULL, NULL, > + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, > + { "SupportedContext", "q", get_supported_context, NULL, NULL, > + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, > + { "Context", "q", get_context, NULL, NULL, > + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, > + { "QoS", "a{sv}", get_qos, NULL, qos_exists, > + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, > + { } > +}; > + > static int parse_array(DBusMessageIter *iter, struct iovec *iov) > { > DBusMessageIter array; > @@ -384,101 +475,755 @@ static int parse_array(DBusMessageIter *iter, struct iovec *iov) > return 0; > } > > -static bool parse_base(void *data, size_t len, util_debug_func_t func, > - uint32_t *presDelay, uint8_t *numSubgroups, uint8_t *numBis, > - struct bt_bap_codec *codec, struct iovec **caps, > - struct iovec **meta) > +static void cleanup_subgroup(struct bt_bap_base_subgroup *subgroup) > { > - struct iovec iov = { > - .iov_base = data, > - .iov_len = len, > - }; > + if (!subgroup) > + return; > > - uint8_t capsLen, metaLen; > - uint8_t *hexstream; > + if (subgroup->meta) > + util_iov_free(subgroup->meta, 1); > > - if (presDelay) { > - if (!util_iov_pull_le24(&iov, presDelay)) > - return false; > - util_debug(func, NULL, "PresentationDelay %d", *presDelay); > + if (subgroup->caps) > + util_iov_free(subgroup->caps, 1); > + > + while (!queue_isempty(subgroup->bises)) { > + struct bt_bap_bis *bis = queue_peek_head(subgroup->bises); > + > + if (bis->caps) > + util_iov_free(bis->caps, 1); > + queue_remove(subgroup->bises, bis); > + subgroup->num_bises--; > } > + queue_destroy(subgroup->bises, NULL); > > - if (numSubgroups) { > - if (!util_iov_pull_u8(&iov, numSubgroups)) > - return false; > - util_debug(func, NULL, "NumSubgroups %d", *numSubgroups); > + if (subgroup) > + free(subgroup); > +} > + > +static void destroy_ltv(void *data) > +{ > + struct bt_ltv *ltv = data; > + > + if (!ltv) > + return; > + > + free(ltv); > +} > + > +static void parse_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v, > + void *user_data) > +{ > + struct queue **ltv_queue = user_data; > + struct bt_ltv *q_ltv_elem = malloc(sizeof(struct bt_ltv) + l); > + > + q_ltv_elem->len = l + 1; > + q_ltv_elem->type = t; > + > + memcpy(q_ltv_elem->value, v, l); > + > + if (!*ltv_queue) > + *ltv_queue = queue_new(); > + queue_push_tail(*ltv_queue, q_ltv_elem); > +} > + > +static struct stream_base_data *get_stream_base_info( > + struct bap_setup *setup, util_debug_func_t func) > +{ > + struct stream_base_data *sbd = new0(struct stream_base_data, 1); > + struct iovec *stream_caps_iov = util_iov_dup( > + setup->caps, 1); > + struct iovec *stream_meta_iov = util_iov_dup( > + setup->metadata, 1); > + > + /* > + * Copy the Codec Specific configurations from stream > + */ > + if (stream_caps_iov != NULL) { > + if (!util_ltv_foreach(stream_caps_iov->iov_base, > + stream_caps_iov->iov_len, NULL, > + parse_ltv, &sbd->ltv_caps)) { > + util_debug(func, > + NULL, "Unable to parse Codec Specific configurations"); > + goto fail; > + } > } > > - if (numBis) { > - if (!util_iov_pull_u8(&iov, numBis)) > - return false; > - util_debug(func, NULL, "NumBis %d", *numBis); > + /* > + * Copy the Metadata from stream > + */ > + if (stream_meta_iov != NULL) { > + if (!util_ltv_foreach(stream_meta_iov->iov_base, > + stream_meta_iov->iov_len, NULL, > + parse_ltv, &sbd->ltv_meta)) { > + util_debug(func, > + NULL, "Unable to parse Codec Specific configurations"); > + goto fail; > + } > } > > - if (codec) { > - codec = util_iov_pull_mem(&iov, sizeof(*codec)); > - if (!codec) > + sbd->setup = setup; > + > + util_iov_free(stream_caps_iov, 1); > + util_iov_free(stream_meta_iov, 1); > + > + return sbd; > + > +fail: > + util_iov_free(stream_caps_iov, 1); > + util_iov_free(stream_meta_iov, 1); > + > + if (sbd->ltv_caps) > + queue_destroy(sbd->ltv_caps, destroy_ltv); > + > + if (sbd->ltv_meta) > + queue_destroy(sbd->ltv_meta, destroy_ltv); > + > + free(sbd); > + > + return NULL; > +} > + > +static void get_stream_base_data(void *data, void *user_data) > +{ > + struct bap_setup *setup = data; > + struct queue *streams_base_data = user_data; > + struct stream_base_data *sbd = get_stream_base_info(setup, bap_debug); > + > + if (sbd) > + queue_push_tail(streams_base_data, sbd); > +} > + > +static void set_device_presentation_delay(void *data, void *user_data) > +{ > + struct stream_base_data *sbd = data; > + struct base_data *base = user_data; > + struct bt_bap_qos *qos = &sbd->setup->qos; > + > + if (base->pres_delay < qos->bcast.delay) > + base->pres_delay = qos->bcast.delay; > +} > + > +static bool ltv_match(const void *data, const void *match_data) > +{ > + const struct bt_ltv *ltv1 = data; > + const struct bt_ltv *ltv2 = match_data; > + > + if (ltv1->len == ltv2->len) > + if (ltv1->type == ltv2->type) > + if (memcmp(ltv1->value, ltv2->value, ltv1->len - 1) > + == 0) > + return true; > + return false; > +} > + > +static bool compare_ltv_lists(struct queue *ltv_list1, struct queue *ltv_list2) > +{ > + const struct queue_entry *entry; > + /* Compare metadata length */ > + if (queue_length(ltv_list1) != queue_length(ltv_list2)) > + return false; > + > + /* Compare metadata ltvs */ > + for (entry = queue_get_entries(ltv_list1); entry; entry = entry->next) { > + struct bt_ltv *ltv = entry->data; > + > + if (!queue_find(ltv_list2, ltv_match, ltv)) > return false; > - util_debug(func, NULL, "%s: ID %d CID 0x%2.2x VID 0x%2.2x", > - "Codec", codec->id, codec->cid, codec->vid); > } > > - if (!util_iov_pull_u8(&iov, &capsLen)) > - return false; > - util_debug(func, NULL, "CC Len %d", capsLen); > + return true; > +} > > - if (!capsLen) > - return false; > - if (caps) { > - if (!(*caps)) > - *caps = new0(struct iovec, 1); > - (*caps)->iov_len = capsLen; > - (*caps)->iov_base = iov.iov_base; > +static struct queue *compare_caps_ltv_lists( > + struct queue *subgroup_caps, struct queue *bis_caps) > +{ > + struct queue *ltv_caps = queue_new(); > + const struct queue_entry *entry; > + > + /* Compare metadata ltvs */ > + for (entry = queue_get_entries(bis_caps); entry; > + entry = entry->next) { > + struct bt_ltv *ltv = entry->data; > + > + if (!queue_find(subgroup_caps, ltv_match, ltv)) > + queue_push_tail(ltv_caps, ltv); > } > > - for (int i = 0; capsLen > 1; i++) { > - struct bt_ltv *ltv = util_iov_pull_mem(&iov, sizeof(*ltv)); > - uint8_t *caps; > + if (queue_isempty(ltv_caps)) { > + queue_destroy(ltv_caps, NULL); > + return NULL; > + } else > + return ltv_caps; > +} > > - if (!ltv) { > - util_debug(func, NULL, "Unable to parse %s", > - "Capabilities"); > - return false; > - } > +static void get_max_bises_index(void *data, void *user_data) > +{ > + struct base_data_bis *bdb = data; > + uint8_t *bis_index = user_data; > > - util_debug(func, NULL, "%s #%u: len %u type %u", > - "CC", i, ltv->len, ltv->type); > + if (bdb->bis_index > *bis_index) > + *bis_index = bdb->bis_index + 1; > + else if (bdb->bis_index == *bis_index) > + *bis_index = *bis_index + 1; > +} > > - caps = util_iov_pull_mem(&iov, ltv->len - 1); > - if (!caps) { > - util_debug(func, NULL, "Unable to parse %s", > - "CC"); > - return false; > +static void get_bises_index(void *data, void *user_data) > +{ > + struct base_data_subgroup *bds = data; > + uint8_t *bis_index = user_data; > + > + queue_foreach(bds->bises, get_max_bises_index, bis_index); > +} > + > +static uint8_t get_bis_index(struct queue *subgroups) > +{ > + uint8_t bis_index = 1; > + > + queue_foreach(subgroups, get_bises_index, &bis_index); > + return bis_index; > +} > + > +static void add_new_bis(struct base_data_subgroup *subgroup, > + uint8_t bis_index, struct queue *ltv_bis_caps) > +{ > + struct base_data_bis *bdb = new0(struct base_data_bis, 1); > + > + bdb->bis_index = bis_index; > + bdb->ltv_caps = ltv_bis_caps; > + queue_push_tail(subgroup->bises, bdb); > +} > + > +static void add_new_subgroup(struct queue *subgroups, > + struct stream_base_data *base_data) > +{ > + struct bt_bap_pac *lpac = base_data->setup->ep->lpac; > + struct base_data_subgroup *bds = new0( > + struct base_data_subgroup, 1); > + uint16_t cid; > + uint16_t vid; > + > + bt_bap_pac_get_vendor_codec(lpac, &bds->codec.id, &cid, > + &vid, NULL, NULL); > + bds->codec.cid = cid; > + bds->codec.vid = vid; > + bds->ltv_caps = base_data->ltv_caps; > + bds->ltv_meta = base_data->ltv_meta; > + base_data->ltv_caps = NULL; > + base_data->ltv_meta = NULL; > + bds->bises = queue_new(); > + base_data->setup->qos.bcast.bis = get_bis_index(subgroups); > + add_new_bis(bds, base_data->setup->qos.bcast.bis, > + queue_new()); > + queue_push_tail(subgroups, bds); > +} > + > +static void remove_ltv_form_list(void *data, void *user_data) > +{ > + struct bt_ltv *ltv = data; > + struct queue *ltv_caps = user_data; > + > + queue_remove(ltv_caps, ltv); > +} > + > +static void set_base_subgroup(void *data, void *user_data) > +{ > + struct stream_base_data *stream_base = data; > + struct base_data *base = user_data; > + struct queue *ltv_caps; > + > + if (queue_isempty(base->base_data_subgroup)) { > + add_new_subgroup(base->base_data_subgroup, stream_base); > + } else { > + /* Verify if a subgroup has the same metadata */ > + const struct queue_entry *entry; > + struct base_data_subgroup *subgroup_base = NULL; > + bool same_meta = false; > + > + for (entry = queue_get_entries(base->base_data_subgroup); > + entry; entry = entry->next) { > + subgroup_base = entry->data; > + if (compare_ltv_lists(subgroup_base->ltv_meta, > + stream_base->ltv_meta)) { > + same_meta = true; > + break; > + } > } > - util_hexdump(' ', caps, ltv->len - 1, func, NULL); > > - capsLen -= (ltv->len + 1); > + if (!same_meta) { > + /* No subgroup with the same metadata found. > + * Create a new one. > + */ > + add_new_subgroup(base->base_data_subgroup, > + stream_base); > + } else { > + /* Soubgroup found with the same metadata > + * get different capabilities > + */ > + ltv_caps = compare_caps_ltv_lists( > + subgroup_base->ltv_caps, > + stream_base->ltv_caps); > + > + queue_foreach(ltv_caps, remove_ltv_form_list, > + stream_base->ltv_caps); > + stream_base->setup->qos.bcast.bis = get_bis_index( > + base->base_data_subgroup); > + add_new_bis(subgroup_base, > + stream_base->setup->qos.bcast.bis, > + ltv_caps); > + } > } > +} > + > +static void get_tlv_size(void *data, void *user_data) > +{ > + struct bt_ltv *ltv = data; > + uint8_t *length = user_data; > + > + *length = *length + ltv->len + 1; > +} > + > +static uint8_t get_size_from_ltv_queue(struct queue *tlv_queue) > +{ > + uint8_t length = 0; > + > + queue_foreach(tlv_queue, get_tlv_size, &length); > + return length; > +} > + > +static void generate_ltv_base(void *data, void *user_data) > +{ > + struct bt_ltv *ltv = data; > + struct iovec *base_iov = user_data; > + > + if (!util_iov_push_u8(base_iov, ltv->len)) > + return; > + > + if (!util_iov_push_u8(base_iov, ltv->type)) > + return; > + > + if (!util_iov_push_mem(base_iov, ltv->len - 1, ltv->value)) > + return; > + util_debug(bap_debug, NULL, "ltv type %d", ltv->type); > +} > + > +static void generate_bis_base(void *data, void *user_data) > +{ > + struct base_data_bis *bis = data; > + struct iovec *base_iov = user_data; > + uint8_t cc_length = get_size_from_ltv_queue(bis->ltv_caps); > + > + if (!util_iov_push_u8(base_iov, bis->bis_index)) > + return; > + util_debug(bap_debug, NULL, "Bis Index %d", bis->bis_index); > + > + if (!util_iov_push_u8(base_iov, cc_length)) > + return; > + util_debug(bap_debug, NULL, "Codec Specific Configuration length %d", > + cc_length); > + > + queue_foreach(bis->ltv_caps, generate_ltv_base, base_iov); > +} > + > +static void generate_subgroup_base(void *data, void *user_data) > +{ > + struct base_data_subgroup *bds = data; > + struct iovec *base_iov = user_data; > + uint8_t cc_length = get_size_from_ltv_queue(bds->ltv_caps); > + uint8_t metadata_length = get_size_from_ltv_queue(bds->ltv_meta); > + > + if (!util_iov_push_u8(base_iov, queue_length(bds->bises))) > + return; > + > + util_debug(bap_debug, NULL, "Number of BIS %d", > + queue_length(bds->bises)); > + > + if (!util_iov_push_u8(base_iov, bds->codec.id)) > + return; > + > + util_debug(bap_debug, NULL, "Codec ID %d", bds->codec.id); > + > + if (!util_iov_push_le16(base_iov, bds->codec.cid)) > + return; > + > + util_debug(bap_debug, NULL, "Company ID %d", bds->codec.cid); > + > + if (!util_iov_push_le16(base_iov, bds->codec.vid)) > + return; > + > + util_debug(bap_debug, NULL, "Vendor specific codec_ID %d", > + bds->codec.vid); > + > + if (!util_iov_push_u8(base_iov, cc_length)) > + return; > + > + util_debug(bap_debug, NULL, "Codec Specific Configuration length %d", > + cc_length); > + > + queue_foreach(bds->ltv_caps, generate_ltv_base, base_iov); > + > + if (!util_iov_push_u8(base_iov, metadata_length)) > + return; > + > + util_debug(bap_debug, NULL, "Metadata length %d", metadata_length); > + > + queue_foreach(bds->ltv_meta, generate_ltv_base, base_iov); > + > + queue_foreach(bds->bises, generate_bis_base, base_iov); > +} > + > +static struct iovec *generate_base(struct base_data *base, > + util_debug_func_t func) > +{ > + struct iovec *base_iov = g_new0(struct iovec, 0x1); > + > + base_iov->iov_base = util_malloc(BASE_MAX_LENGTH); > + > + if (!util_iov_push_le24(base_iov, base->pres_delay)) > + return NULL; > + util_debug(func, NULL, "PresentationDelay %d", base->pres_delay); > + > + if (!util_iov_push_u8(base_iov, > + queue_length(base->base_data_subgroup))) > + return NULL; > + > + util_debug(func, NULL, "Number of Subgroups %d", > + queue_length(base->base_data_subgroup)); > + > + queue_foreach(base->base_data_subgroup, generate_subgroup_base, > + base_iov); > + > + return base_iov; > +} > + > +static void update_endpoint_base(struct bap_setup *setup, > + struct bap_data *data, struct iovec *base_iov) > +{ > + if (setup->base) > + util_iov_free(setup->base, 1); > + > + setup->base = util_iov_dup(base_iov, 1); > +} > + > +static void destroy_stream_base_data(void *data) > +{ > + struct stream_base_data *sbd = data; > + > + if (!sbd) > + return; > + > + queue_destroy(sbd->ltv_caps, destroy_ltv); > + queue_destroy(sbd->ltv_meta, destroy_ltv); > + sbd->setup = NULL; > + free(sbd); > +} > + > +static void destroy_base_data_bis(void *data) > +{ > + struct base_data_bis *bis = data; > + > + if (!bis) > + return; > + > + queue_destroy(bis->ltv_caps, destroy_ltv); > + free(bis); > +} > + > +static void destroy_base_data_subgroup(void *data) > +{ > + struct base_data_subgroup *subgroup = data; > + > + if (!subgroup) > + return; > + > + queue_destroy(subgroup->ltv_caps, destroy_ltv); > + queue_destroy(subgroup->ltv_meta, destroy_ltv); > + queue_destroy(subgroup->bises, destroy_base_data_bis); > + > + free(subgroup); > +} > + > +static void update_base(struct bap_setup *setup, > + struct bap_data *data, util_debug_func_t func) > +{ > + struct base_data *base; > + struct iovec *base_iov; > + const struct queue_entry *entry; > + struct queue *streams_base_data = queue_new(); > + struct stream_base_data *sbd = get_stream_base_info(setup, func); > + > + queue_foreach(setup->same_big_setups, get_stream_base_data, > + streams_base_data); > + > + queue_push_tail(streams_base_data, sbd); > + > + base = new0(struct base_data, 1); > + base->base_data_subgroup = queue_new(); > + > + queue_foreach(streams_base_data, set_device_presentation_delay, base); > + > + queue_foreach(streams_base_data, set_base_subgroup, base); > + > + base_iov = generate_base(base, bap_debug); > + > + /* Update the endpoints BASE iovec */ > + update_endpoint_base(setup, data, base_iov); > + > + for (entry = queue_get_entries(setup->same_big_setups); > + entry; entry = entry->next) { > + struct bap_setup *link = entry->data; > + > + update_endpoint_base(link, data, base_iov); > + } > + > + util_iov_free(base_iov, 1); > + > + queue_destroy(streams_base_data, destroy_stream_base_data); > + > + queue_destroy(base->base_data_subgroup, destroy_base_data_subgroup); > + > + free(base); > +} > > - if (!util_iov_pull_u8(&iov, &metaLen)) > +static void get_combined_qos(void *data, void *user_data) > +{ > + struct bap_setup *setup = data; > + struct bt_bap_qos **local_qos = user_data; > + struct bt_bap_qos *qos = &setup->qos; > + > + if (*local_qos == NULL) > + return; > + > + if (((*local_qos)->bcast.io_qos.interval != > + qos->bcast.io_qos.interval) || > + ((*local_qos)->bcast.io_qos.rtn != qos->bcast.io_qos.rtn) || > + ((*local_qos)->bcast.io_qos.latency != > + qos->bcast.io_qos.latency) || > + ((*local_qos)->bcast.io_qos.phy != qos->bcast.io_qos.phy) || > + ((*local_qos)->bcast.framing != qos->bcast.framing)) { > + util_debug(bap_debug, > + NULL, > + "Error: QOS is different between BISes in the BIG"); > + free(*local_qos); > + *local_qos = NULL; > + return; > + } > + > + if ((*local_qos)->bcast.io_qos.sdu != qos->bcast.io_qos.sdu) > + if ((*local_qos)->bcast.io_qos.sdu < qos->bcast.io_qos.sdu) > + (*local_qos)->bcast.io_qos.sdu = qos->bcast.io_qos.sdu; > + > + if ((*local_qos)->bcast.delay != qos->bcast.delay) > + if ((*local_qos)->bcast.delay < qos->bcast.delay) > + (*local_qos)->bcast.delay = qos->bcast.delay; > +} > + > +static void update_qos(struct bap_setup *setup, > + struct bap_data *data) > +{ > + const struct queue_entry *entry; > + struct bt_bap_qos *local_qos = new0(struct bt_bap_qos, 1); > + struct bt_bap_qos *qos = &setup->qos; > + > + memcpy(local_qos, qos, sizeof(struct bt_bap_qos)); > + > + queue_foreach(setup->same_big_setups, > + get_combined_qos, &local_qos); > + > + /* Update each endpoint QOS structure to have the > + * same values, as obtained in local_qos > + */ > + if (local_qos != NULL) { > + memcpy(&setup->combined_qos, local_qos, > + sizeof(struct bt_bap_qos)); > + > + for (entry = queue_get_entries(setup->same_big_setups); > + entry; entry = entry->next) { > + struct bap_setup *link = entry->data; > + > + memcpy(&link->combined_qos, local_qos, > + sizeof(struct bt_bap_qos)); > + } > + } > + > + if (local_qos != NULL) > + free(local_qos); > +} > + > +static bool parse_base(struct bt_bap *bap, void *data, size_t len, > + util_debug_func_t func, struct bt_bap_base *base) > +{ > + uint8_t num_subgroups; > + uint8_t num_bis; > + > + struct iovec iov = { > + .iov_base = data, > + .iov_len = len, > + }; > + util_debug(func, NULL, "BASE len %ld", len); > + if (!base) > return false; > - util_debug(func, NULL, "Metadata Len %d", metaLen); > > - if (!metaLen) > + if (!util_iov_pull_le24(&iov, &base->pres_delay)) > return false; > - if (meta) { > - if (!(*meta)) > - *meta = new0(struct iovec, 1); > - (*meta)->iov_len = metaLen; > - (*meta)->iov_base = iov.iov_base; > - } > + util_debug(func, NULL, "PresentationDelay %d", base->pres_delay); > > - hexstream = util_iov_pull_mem(&iov, metaLen); > - if (!hexstream) > + if (!util_iov_pull_u8(&iov, &base->num_subgroups)) > return false; > - util_hexdump(' ', hexstream, metaLen, func, NULL); > + util_debug(func, NULL, "NumSubgroups %d", base->num_subgroups); > + num_subgroups = base->num_subgroups; > + > + for (int sg = 0; sg < num_subgroups; sg++) { > + struct bt_bap_base_subgroup *sub_group = new0( > + struct bt_bap_base_subgroup, 1); > + uint8_t caps_len, metaLen; > + uint8_t *hexstream; > + > + sub_group->subgroup_index = sg; > + > + util_debug(func, NULL, "Subgroup #%d", sg); > + sub_group->bap = bap; > + sub_group->bises = queue_new(); > + > + if (!util_iov_pull_u8(&iov, &num_bis)) { > + cleanup_subgroup(sub_group); > + goto fail; > + } > + util_debug(func, NULL, "NumBis %d", num_bis); > + sub_group->num_bises = num_bis; > + > + memcpy(&sub_group->codec, util_iov_pull_mem(&iov, > + sizeof(struct bt_bap_codec)), sizeof(struct bt_bap_codec)); > + util_debug(func, NULL, "%s: ID %d CID 0x%2.2x VID 0x%2.2x", > + "Codec", sub_group->codec.id, sub_group->codec.cid, > + sub_group->codec.vid); > + if (!util_iov_pull_u8(&iov, &caps_len)) { > + cleanup_subgroup(sub_group); > + goto fail; > + } > + > + util_debug(func, NULL, "CC Len %d", caps_len); > + > + /* > + * Copy the Codec Specific configurations from base > + */ > + sub_group->caps = new0(struct iovec, 1); > + util_iov_memcpy(sub_group->caps, iov.iov_base, caps_len); > + DBG("subgroup caps len %ld", sub_group->caps->iov_len); > + > + for (int i = 0; caps_len > 1; i++) { > + struct bt_ltv *ltv = util_iov_pull_mem(&iov, > + sizeof(*ltv)); > + uint8_t *caps; > + > + if (!ltv) { > + util_debug(func, NULL, "Unable to parse %s", > + "Capabilities"); > + cleanup_subgroup(sub_group); > + goto fail; > + } > + > + util_debug(func, NULL, "%s #%u: len %u type %u", > + "CC", i, ltv->len, ltv->type); > + > + caps = util_iov_pull_mem(&iov, ltv->len - 1); > + if (!caps) { > + util_debug(func, NULL, "Unable to parse %s", > + "CC"); > + cleanup_subgroup(sub_group); > + goto fail; > + } > + util_hexdump(' ', caps, ltv->len - 1, func, NULL); > + > + caps_len -= (ltv->len + 1); > + } > + > + if (!util_iov_pull_u8(&iov, &metaLen)) { > + cleanup_subgroup(sub_group); > + goto fail; > + } > + util_debug(func, NULL, "Metadata Len %d", metaLen); > + > + sub_group->meta = new0(struct iovec, 1); > + sub_group->meta->iov_len = metaLen; > + sub_group->meta->iov_base = iov.iov_base; > + > + hexstream = util_iov_pull_mem(&iov, metaLen); > + if (!hexstream) { > + cleanup_subgroup(sub_group); > + goto fail; > + } > + util_hexdump(' ', hexstream, metaLen, func, NULL); > + > + for (int bis_sg = 0; bis_sg < sub_group->num_bises; bis_sg++) { > + struct bt_bap_bis *bis; > + uint8_t caps_len; > + uint8_t crt_bis; > + > + if (!util_iov_pull_u8(&iov, &crt_bis)) { > + cleanup_subgroup(sub_group); > + goto fail; > + } > + util_debug(func, NULL, "BIS #%d", crt_bis); > > + bis = new0(struct bt_bap_bis, 1); > + bis->bisIndex = crt_bis; > + > + if (!util_iov_pull_u8(&iov, &caps_len)) { > + cleanup_subgroup(sub_group); > + goto fail; > + } > + util_debug(func, NULL, "CC Len %d", caps_len); > + > + bis->caps = new0(struct iovec, 1); > + bis->caps->iov_len = caps_len; > + util_iov_memcpy(bis->caps, iov.iov_base, caps_len); > + DBG("bis caps len %ld", bis->caps->iov_len); > + > + for (int i = 0; caps_len > 1; i++) { > + struct bt_ltv *ltv = util_iov_pull_mem(&iov, > + sizeof(*ltv)); > + uint8_t *caps; > + > + if (!ltv) { > + util_debug(func, NULL, "Unable to parse %s", > + "Capabilities"); > + cleanup_subgroup(sub_group); > + goto fail; > + } > + > + util_debug(func, NULL, "%s #%u: len %u type %u", > + "CC", i, ltv->len, ltv->type); > + > + caps = util_iov_pull_mem(&iov, ltv->len - 1); > + if (!caps) { > + util_debug(func, NULL, > + "Unable to parse %s", "CC"); > + cleanup_subgroup(sub_group); > + goto fail; > + } > + util_hexdump(' ', caps, ltv->len - 1, func, > + NULL); > + > + caps_len -= (ltv->len + 1); > + } > + > + queue_push_tail(sub_group->bises, bis); > + } > + > + queue_push_tail(base->subgroups, sub_group); > + } > return true; > + > +fail: > + while (!queue_isempty(base->subgroups)) { > + struct bt_bap_base_subgroup *subGroup = > + queue_peek_head(base->subgroups); > + cleanup_subgroup(subGroup); > + base->num_subgroups--; > + } > + util_debug(func, NULL, "Unable to parse %s", "Base"); > + > + return false; > } > > static int parse_io_qos(const char *key, int var, DBusMessageIter *iter, > @@ -561,6 +1306,16 @@ static int parse_bcast_qos(const char *key, int var, DBusMessageIter *iter, > return -EINVAL; > > dbus_message_iter_get_basic(iter, &qos->bcast.encryption); > + } else if (!strcasecmp(key, "BIG")) { > + if (var != DBUS_TYPE_BYTE) > + return -EINVAL; > + > + dbus_message_iter_get_basic(iter, &qos->bcast.big); > + } else if (!strcasecmp(key, "BIS")) { > + if (var != DBUS_TYPE_BYTE) > + return -EINVAL; > + > + dbus_message_iter_get_basic(iter, &qos->bcast.bis); > } else if (!strcasecmp(key, "Options")) { > if (var != DBUS_TYPE_BYTE) > return -EINVAL; > @@ -596,6 +1351,11 @@ static int parse_bcast_qos(const char *key, int var, DBusMessageIter *iter, > return -EINVAL; > > dbus_message_iter_get_basic(iter, &qos->bcast.timeout); > + } else if (!strcasecmp(key, "PresentationDelay")) { > + if (var != DBUS_TYPE_UINT32) > + return -EINVAL; > + > + dbus_message_iter_get_basic(iter, &qos->bcast.delay); > } else if (!strcasecmp(key, "BCode")) { > struct iovec iov; > > @@ -604,8 +1364,8 @@ static int parse_bcast_qos(const char *key, int var, DBusMessageIter *iter, > > parse_array(iter, &iov); > > - util_iov_free(qos->bcast.bcode, 1); > - qos->bcast.bcode = util_iov_dup(&iov, 1); > + memcpy(qos->bcast.bcode, iov.iov_base, > + sizeof(qos->bcast.bcode)); > } else { > int err; > > @@ -618,14 +1378,15 @@ static int parse_bcast_qos(const char *key, int var, DBusMessageIter *iter, > } > > static int parse_qos(DBusMessageIter *iter, struct bt_bap_qos *qos, > - struct iovec **base) > + struct bt_bap_pac *lpac) > { > DBusMessageIter array; > const char *key; > int (*parser)(const char *key, int var, DBusMessageIter *iter, > struct bt_bap_qos *qos); > > - if (*base) > + if ((bt_bap_pac_get_type(lpac) == BT_BAP_BCAST_SOURCE) || > + (bt_bap_pac_get_type(lpac) == BT_BAP_BCAST_SINK)) > parser = parse_bcast_qos; > else > parser = parse_ucast_qos; > @@ -657,8 +1418,9 @@ static int parse_qos(DBusMessageIter *iter, struct bt_bap_qos *qos, > } > > static int parse_configuration(DBusMessageIter *props, struct iovec **caps, > - struct iovec **metadata, struct iovec **base, > - struct bt_bap_qos *qos) > + struct iovec **metadata, > + struct bt_bap_qos *qos, > + struct bt_bap_pac *lpac) > { > const char *key; > struct iovec iov; > @@ -699,24 +1461,13 @@ static int parse_configuration(DBusMessageIter *props, struct iovec **caps, > if (var != DBUS_TYPE_ARRAY) > goto fail; > > - if (parse_qos(&value, qos, base)) > + if (parse_qos(&value, qos, lpac)) > goto fail; > } > > dbus_message_iter_next(props); > } > > - if (*base) { > - uint32_t presDelay; > - uint8_t numSubgroups, numBis; > - struct bt_bap_codec codec; > - > - util_iov_memcpy(*base, (*caps)->iov_base, (*caps)->iov_len); > - parse_base((*caps)->iov_base, (*caps)->iov_len, bap_debug, > - &presDelay, &numSubgroups, &numBis, &codec, > - caps, NULL); > - } > - > return 0; > > fail: > @@ -838,18 +1589,81 @@ static void setup_free(void *data) > if (setup->ep) > queue_remove(setup->ep->setups, setup); > > + queue_destroy(setup->same_big_setups, NULL); > + > setup_io_close(setup, NULL); > > util_iov_free(setup->caps, 1); > util_iov_free(setup->metadata, 1); > util_iov_free(setup->base, 1); > > - if (bt_bap_stream_get_type(setup->stream) == BT_BAP_STREAM_TYPE_BCAST) > - util_iov_free(setup->qos.bcast.bcode, 1); > - > free(setup); > } > > +static void link_setups(void *data, void *user_data) > +{ > + struct bap_ep *ep = data; > + struct bap_setup *setup = user_data; > + const struct queue_entry *entry; > + > + for (entry = queue_get_entries(ep->setups); entry; > + entry = entry->next) { > + struct bap_setup *iter_setup = entry->data; > + > + if (iter_setup == setup) > + continue; > + > + if (setup->qos.bcast.big == iter_setup->qos.bcast.big) { > + if (setup->same_big_setups == NULL) > + setup->same_big_setups = queue_new(); > + > + if (iter_setup->same_big_setups == NULL) > + iter_setup->same_big_setups = queue_new(); > + > + queue_push_tail(setup->same_big_setups, iter_setup); > + queue_push_tail(iter_setup->same_big_setups, setup); > + } > + } > +} > + > +static void bap_pac_foreach_channel(size_t i, uint8_t l, uint8_t t, uint8_t *v, > + void *user_data) > +{ > + struct ltv_compare_data *data = user_data; > + uint32_t location32; > + > + if (!v) > + return; > + > + memcpy(&location32, v, l); > + > + /* If all the bits in the received bitmask are found > + * in the local bitmask we have a match > + */ > + if ((le32_to_cpu(location32) & data->data32) == > + le32_to_cpu(location32)) > + data->match = true; > + else > + data->match = false; > +} > + > +static bool verify_bis_location_suported(struct bap_setup *setup, > + struct bap_ep *ep) > +{ > + uint8_t type = LC3_CONFIG_CHAN_ALLOC; > + struct ltv_compare_data data; > + struct iovec *caps = util_iov_dup(setup->caps, 1); > + > + data.data32 = bt_bap_pac_get_locations(ep->lpac); > + > + util_ltv_foreach(caps->iov_base, caps->iov_len, &type, > + bap_pac_foreach_channel, &data); > + > + util_iov_free(caps, 1); > + > + return data.match; > +} > + > static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, > void *data) > { > @@ -882,13 +1696,52 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, > setup->qos.ucast.cis_id = BT_ISO_QOS_CIS_UNSET; > } > > + /* Populate setup with the received data */ > if (parse_configuration(&props, &setup->caps, &setup->metadata, > - &setup->base, &setup->qos) < 0) { > + &setup->qos, ep->lpac) < 0) { > DBG("Unable to parse configuration"); > setup_free(setup); > return btd_error_invalid_args(msg); > } > > + if (bt_bap_pac_get_type(ep->lpac) == BT_BAP_BCAST_SOURCE) { > + /* > + * Kernel uses bis id as advertising handler. > + * To open all BISes from a big on the same > + * advertisng handler set the bis > + * to BT_ISO_QOS_BIS_UNSET. > + */ > + setup->qos.bcast.bis = BT_ISO_QOS_BIS_UNSET; > + > + if (!verify_bis_location_suported(setup, ep)) { > + DBG("BIS Location is unsupported"); > + return btd_error_invalid_args(msg); > + } > + bt_bap_add_vendor_pac_from_pac(ep->lpac); > + > + /* Link all setups based on the BIG id */ > + queue_foreach(ep->data->bcast, link_setups, setup); > + > + update_base(setup, data, bap_debug); > + update_qos(setup, data); > + } else if ((bt_bap_pac_get_type(ep->rpac) == BT_BAP_BCAST_SOURCE) && > + (bt_bap_pac_get_type(ep->lpac) == BT_BAP_BCAST_SINK)) { > + struct iovec *metadata; > + struct iovec *caps; > + > + util_iov_free(setup->caps, 1); > + util_iov_free(setup->metadata, 1); > + > + setup->caps = new0(struct iovec, 1); > + setup->metadata = new0(struct iovec, 1); > + setup->io = ep->data->listen_io; > + > + bt_bap_pac_get_vendor_codec(ep->rpac, NULL, NULL, NULL, &caps, > + &metadata); > + bt_bap_ltv_merge(setup->caps, caps, false); > + bt_bap_ltv_merge(setup->metadata, metadata, false); > + } > + > setup->stream = bt_bap_stream_new(ep->data->bap, ep->lpac, ep->rpac, > &setup->qos, setup->caps); > > @@ -900,12 +1753,12 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, > return btd_error_invalid_args(msg); > } > > - bt_bap_stream_set_user_data(setup->stream, ep->path); > - > if (setup->metadata && setup->metadata->iov_len) > bt_bap_stream_metadata(setup->stream, setup->metadata, NULL, > NULL); > > + bt_bap_stream_set_user_data(setup->stream, ep->path); > + > switch (bt_bap_stream_get_type(setup->stream)) { > case BT_BAP_STREAM_TYPE_UCAST: > setup->msg = dbus_message_ref(msg); > @@ -944,30 +1797,21 @@ static void update_bcast_qos(struct bt_iso_qos *qos, > bap_qos->bcast.io_qos.sdu = qos->bcast.in.sdu; > bap_qos->bcast.io_qos.rtn = qos->bcast.in.rtn; > > - bap_qos->bcast.bcode = new0(struct iovec, 1); > - util_iov_memcpy(bap_qos->bcast.bcode, qos->bcast.bcode, > - sizeof(qos->bcast.bcode)); > + memcpy(bap_qos->bcast.bcode, qos->bcast.bcode, > + sizeof(bap_qos->bcast.bcode)); > } > > static void iso_bcast_confirm_cb(GIOChannel *io, GError *err, void *user_data) > { > struct bap_setup *setup = user_data; > struct bap_ep *ep = setup->ep; > - struct bap_data *data = ep->data; > struct bt_iso_qos qos; > - struct bt_iso_base base; > char address[18]; > int fd; > - struct iovec *base_io; > - uint32_t presDelay; > - uint8_t numSubgroups; > - uint8_t numBis; > - struct bt_bap_codec codec; > > bt_io_get(io, &err, > BT_IO_OPT_DEST, address, > BT_IO_OPT_QOS, &qos, > - BT_IO_OPT_BASE, &base, > BT_IO_OPT_INVALID); > if (err) { > error("%s", err->message); > @@ -976,25 +1820,15 @@ static void iso_bcast_confirm_cb(GIOChannel *io, GError *err, void *user_data) > } > > g_io_channel_ref(io); > - btd_service_connecting_complete(data->service, 0); > + btd_service_connecting_complete(ep->data->service, 0); > DBG("BCAST ISO: sync with %s (BIG 0x%02x BIS 0x%02x)", > address, qos.bcast.big, qos.bcast.bis); > > update_bcast_qos(&qos, &setup->qos); > > - base_io = new0(struct iovec, 1); > - util_iov_memcpy(base_io, base.base, base.base_len); > - > - parse_base(base_io->iov_base, base_io->iov_len, bap_debug, > - &presDelay, &numSubgroups, &numBis, > - &codec, &setup->caps, &setup->metadata); > - > - /* Update pac with BASE information */ > - bt_bap_update_bcast_source(ep->rpac, &codec, setup->caps, > - setup->metadata); > setup->id = bt_bap_stream_config(setup->stream, &setup->qos, > setup->caps, NULL, NULL); > - data->listen_io = io; > + setup->io = io; > > bt_bap_stream_set_user_data(setup->stream, ep->path); > > @@ -1006,25 +1840,39 @@ static void iso_bcast_confirm_cb(GIOChannel *io, GError *err, void *user_data) > return; > } > > - > return; > > drop: > g_io_channel_shutdown(io, TRUE, NULL); > - > } > > static void iso_pa_sync_confirm_cb(GIOChannel *io, void *user_data) > { > GError *err = NULL; > + struct bt_iso_base base; > + char address[18]; > + struct bt_bap_base base_s; > + struct bap_data *data = user_data; > + struct bt_iso_qos qos; > > - if (!bt_io_bcast_accept(io, iso_bcast_confirm_cb, > - user_data, NULL, &err, BT_IO_OPT_INVALID)) { > - error("bt_io_bcast_accept: %s", err->message); > + bt_io_get(io, &err, > + BT_IO_OPT_DEST, address, > + BT_IO_OPT_QOS, &qos, > + BT_IO_OPT_BASE, &base, > + BT_IO_OPT_INVALID); > + if (err) { > + error("%s", err->message); > g_error_free(err); > - g_io_channel_shutdown(io, TRUE, NULL); > + return; > } > + g_io_channel_ref(io); > + data->listen_io = io; > + btd_service_connecting_complete(data->service, 0); > + > + base_s.subgroups = queue_new(); > + parse_base(data->bap, base.base, base.base_len, bap_debug, &base_s); > > + bt_bap_update_bcast_source(NULL, &base_s); > } > > static bool match_data_bap_data(const void *data, const void *match_data) > @@ -1081,6 +1929,7 @@ static struct bap_ep *ep_register_bcast(struct bap_data *data, > int i, err = 0; > const char *suffix; > struct match_ep match = { lpac, rpac }; > + const GDBusPropertyTable *properties; > > switch (bt_bap_pac_get_type(lpac)) { > case BT_BAP_BCAST_SOURCE: > @@ -1109,11 +1958,15 @@ static struct bap_ep *ep_register_bcast(struct bap_data *data, > case BT_BAP_BCAST_SOURCE: > err = asprintf(&ep->path, "%s/pac_%s%d", > adapter_get_path(adapter), suffix, i); > + properties = ep_properties_bcast_source; > break; > case BT_BAP_BCAST_SINK: > err = asprintf(&ep->path, "%s/pac_%s%d", > device_get_path(device), suffix, i); > + properties = ep_properties; > break; > + default: > + return NULL; > } > > if (err < 0) { > @@ -1125,7 +1978,7 @@ static struct bap_ep *ep_register_bcast(struct bap_data *data, > > if (g_dbus_register_interface(btd_get_dbus_connection(), > ep->path, MEDIA_ENDPOINT_INTERFACE, > - ep_methods, NULL, ep_properties, > + ep_methods, NULL, properties, > ep, ep_free) == FALSE) { > error("Could not register remote ep %s", ep->path); > ep_free(ep); > @@ -1646,9 +2499,12 @@ static gboolean setup_io_disconnected(GIOChannel *io, GIOCondition cond, > > setup_io_close(setup, NULL); > > - /* Check if connecting recreate IO */ > - if (!is_cig_busy(setup->ep->data, setup->qos.ucast.cig_id)) > - recreate_cig(setup); > + if (bt_bap_stream_get_type(setup->stream) == > + BT_BAP_STREAM_TYPE_UCAST) { > + /* Check if connecting recreate IO */ > + if (!is_cig_busy(setup->ep->data, setup->qos.ucast.cig_id)) > + recreate_cig(setup); > + } > > return FALSE; > } > @@ -1741,7 +2597,7 @@ static void setup_connect_io(struct bap_data *data, struct bap_setup *setup, > static void setup_connect_io_broadcast(struct bap_data *data, > struct bap_setup *setup, > struct bt_bap_stream *stream, > - struct bt_iso_qos *qos) > + struct bt_iso_qos *qos, int defer) > { > struct btd_adapter *adapter = data->user_data; > GIOChannel *io = NULL; > @@ -1778,7 +2634,7 @@ static void setup_connect_io_broadcast(struct bap_data *data, > BT_IO_OPT_MODE, BT_IO_MODE_ISO, > BT_IO_OPT_QOS, qos, > BT_IO_OPT_BASE, &base, > - BT_IO_OPT_DEFER_TIMEOUT, false, > + BT_IO_OPT_DEFER_TIMEOUT, defer, > BT_IO_OPT_INVALID); > > if (!io) { > @@ -1832,41 +2688,28 @@ static void setup_listen_io_broadcast(struct bap_data *data, > struct bt_bap_stream *stream, > struct bt_iso_qos *qos) > { > - GIOChannel *io; > GError *err = NULL; > struct sockaddr_iso_bc iso_bc_addr; > > iso_bc_addr.bc_bdaddr_type = btd_device_get_bdaddr_type(data->device); > memcpy(&iso_bc_addr.bc_bdaddr, device_get_address(data->device), > sizeof(bdaddr_t)); > - iso_bc_addr.bc_bis[0] = 1; > iso_bc_addr.bc_num_bis = 1; > + iso_bc_addr.bc_bis[0] = strtol(bt_bap_pac_get_name(setup->ep->rpac), > + NULL, 10); > > DBG("stream %p", stream); > > - /* If IO already set skip creating it again */ > - if (bt_bap_stream_get_io(stream) || data->listen_io) > - return; > - > - io = bt_io_listen(NULL, iso_pa_sync_confirm_cb, setup, NULL, &err, > - BT_IO_OPT_SOURCE_BDADDR, > - btd_adapter_get_address(data->adapter), > - BT_IO_OPT_DEST_BDADDR, > - device_get_address(data->device), > - BT_IO_OPT_DEST_TYPE, > - btd_device_get_bdaddr_type(data->device), > - BT_IO_OPT_MODE, BT_IO_MODE_ISO, > - BT_IO_OPT_QOS, &qos->bcast, > - BT_IO_OPT_ISO_BC_NUM_BIS, iso_bc_addr.bc_num_bis, > - BT_IO_OPT_ISO_BC_BIS, iso_bc_addr.bc_bis, > - BT_IO_OPT_INVALID); > - if (!io) { > - error("%s", err->message); > + if (!bt_io_bcast_accept(setup->io, > + iso_bcast_confirm_cb, > + setup, NULL, &err, > + BT_IO_OPT_ISO_BC_NUM_BIS, > + iso_bc_addr.bc_num_bis, BT_IO_OPT_ISO_BC_BIS, > + iso_bc_addr.bc_bis, BT_IO_OPT_INVALID)) { > + error("bt_io_bcast_accept: %s", err->message); > g_error_free(err); > + g_io_channel_shutdown(setup->ep->data->listen_io, TRUE, NULL); > } > - setup->io = io; > - data->listen_io = io; > - > } > static void setup_create_ucast_io(struct bap_data *data, > struct bap_setup *setup, > @@ -1902,32 +2745,37 @@ static void setup_create_bcast_io(struct bap_data *data, > struct bt_bap_stream *stream, int defer) > { > struct bt_iso_qos iso_qos; > + struct bt_bap_qos *qos; > > - memset(&iso_qos, 0, sizeof(iso_qos)); > > - if (!defer) > - goto done; > + memset(&iso_qos, 0, sizeof(iso_qos)); > > - iso_qos.bcast.big = setup->qos.bcast.big; > - iso_qos.bcast.bis = setup->qos.bcast.bis; > - iso_qos.bcast.sync_factor = setup->qos.bcast.sync_factor; > - iso_qos.bcast.packing = setup->qos.bcast.packing; > - iso_qos.bcast.framing = setup->qos.bcast.framing; > - iso_qos.bcast.encryption = setup->qos.bcast.encryption; > - if (setup->qos.bcast.bcode) > - memcpy(iso_qos.bcast.bcode, setup->qos.bcast.bcode->iov_base, > - 16); > - iso_qos.bcast.options = setup->qos.bcast.options; > - iso_qos.bcast.skip = setup->qos.bcast.skip; > - iso_qos.bcast.sync_timeout = setup->qos.bcast.sync_timeout; > - iso_qos.bcast.sync_cte_type = setup->qos.bcast.sync_cte_type; > - iso_qos.bcast.mse = setup->qos.bcast.mse; > - iso_qos.bcast.timeout = setup->qos.bcast.timeout; > - memcpy(&iso_qos.bcast.out, &setup->qos.bcast.io_qos, > + if (bt_bap_pac_get_type(setup->ep->lpac) == > + BT_BAP_BCAST_SOURCE) > + qos = &setup->combined_qos; > + else > + qos = &setup->qos; > + > + iso_qos.bcast.big = qos->bcast.big; > + iso_qos.bcast.bis = qos->bcast.bis; > + iso_qos.bcast.sync_factor = qos->bcast.sync_factor; > + iso_qos.bcast.packing = qos->bcast.packing; > + iso_qos.bcast.framing = qos->bcast.framing; > + iso_qos.bcast.encryption = qos->bcast.encryption; > + memcpy(iso_qos.bcast.bcode, qos->bcast.bcode, > + sizeof(iso_qos.bcast.bcode)); > + iso_qos.bcast.options = qos->bcast.options; > + iso_qos.bcast.skip = qos->bcast.skip; > + iso_qos.bcast.sync_timeout = qos->bcast.sync_timeout; > + iso_qos.bcast.sync_cte_type = qos->bcast.sync_cte_type; > + iso_qos.bcast.mse = qos->bcast.mse; > + iso_qos.bcast.timeout = qos->bcast.timeout; > + memcpy(&iso_qos.bcast.out, &qos->bcast.io_qos, > sizeof(struct bt_iso_io_qos)); > -done: > + > if (bt_bap_pac_get_type(setup->ep->lpac) == BT_BAP_BCAST_SOURCE) > - setup_connect_io_broadcast(data, setup, stream, &iso_qos); > + setup_connect_io_broadcast(data, setup, stream, > + &iso_qos, defer); > else > setup_listen_io_broadcast(data, setup, stream, &iso_qos); > } > @@ -1953,6 +2801,30 @@ static void setup_create_io(struct bap_data *data, struct bap_setup *setup, > break; > } > } > +/* > + * Function test_linked_streams_state will check if all linked streams > + * have the test state. > + * If all streams are in test_state returns true; Else returns false > + */ > +static bool test_linked_streams_state(struct bap_setup *setup, > + uint8_t test_state) > +{ > + const struct queue_entry *entry; > + struct bap_setup *linked_setups; > + > + if (!setup->same_big_setups) > + return true; > + > + for (entry = queue_get_entries(setup->same_big_setups); entry; > + entry = entry->next) { > + linked_setups = entry->data; > + if (bt_bap_stream_get_state(linked_setups->stream) != > + test_state) > + return false; > + } > + > + return true; > +} > > static void bap_state(struct bt_bap_stream *stream, uint8_t old_state, > uint8_t new_state, void *user_data) > @@ -1983,17 +2855,29 @@ static void bap_state(struct bt_bap_stream *stream, uint8_t old_state, > break; > case BT_BAP_STREAM_STATE_CONFIG: > if (setup && !setup->id) { > - setup_create_io(data, setup, stream, true); > - if (!setup->io) { > - error("Unable to create io"); > - if (old_state != BT_BAP_STREAM_STATE_RELEASING) > - bt_bap_stream_release(stream, NULL, > - NULL); > - return; > - } > - > if (bt_bap_stream_get_type(stream) == > + BT_BAP_STREAM_TYPE_BCAST) { > + bool defer = !test_linked_streams_state(setup, > + BT_BAP_STREAM_STATE_CONFIG); > + setup_create_io(data, setup, stream, defer); > + if (!setup->io) { > + error("Unable to create io"); > + if (old_state != > + BT_BAP_STREAM_STATE_RELEASING) > + bt_bap_stream_release(stream, > + NULL, > + NULL); > + return; > + } > + } else if (bt_bap_stream_get_type(stream) == > BT_BAP_STREAM_TYPE_UCAST) { > + setup_create_io(data, setup, stream, true); > + if (!setup->io) { > + error("Unable to create io"); > + bt_bap_stream_release(stream, > + NULL, NULL); > + return; > + } > /* Wait QoS response to respond */ > setup->id = bt_bap_stream_qos(stream, > &setup->qos, > @@ -2311,6 +3195,12 @@ static int bap_bcast_probe(struct btd_service *service) > struct btd_gatt_database *database = btd_adapter_get_database(adapter); > struct bap_data *data = btd_service_get_user_data(service); > char addr[18]; > + GIOChannel *io; > + GError *err = NULL; > + struct sockaddr_iso_bc iso_bc_addr; > + > + iso_bc_addr.bc_bis[0] = 1; > + iso_bc_addr.bc_num_bis = 1; > > ba2str(device_get_address(device), addr); > > @@ -2349,7 +3239,24 @@ static int bap_bcast_probe(struct btd_service *service) > > bt_bap_set_user_data(data->bap, service); > > - bt_bap_new_bcast_source(data->bap, device_get_path(device)); > + DBG("Create PA sync with source"); > + io = bt_io_listen(NULL, iso_pa_sync_confirm_cb, data, NULL, &err, > + BT_IO_OPT_SOURCE_BDADDR, > + btd_adapter_get_address(data->adapter), > + BT_IO_OPT_DEST_BDADDR, > + device_get_address(data->device), > + BT_IO_OPT_DEST_TYPE, > + btd_device_get_bdaddr_type(data->device), > + BT_IO_OPT_MODE, BT_IO_MODE_ISO, > + BT_IO_OPT_QOS, &default_qos, > + BT_IO_OPT_ISO_BC_NUM_BIS, iso_bc_addr.bc_num_bis, > + BT_IO_OPT_ISO_BC_BIS, iso_bc_addr.bc_bis, > + BT_IO_OPT_INVALID); > + if (!io) { > + error("%s", err->message); > + g_error_free(err); > + } > + > return 0; > } > > diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c > index a4696154a..657474a67 100644 > --- a/profiles/audio/transport.c > +++ b/profiles/audio/transport.c > @@ -172,7 +172,9 @@ find_transport_by_bap_stream(const struct bt_bap_stream *stream) > struct bap_transport *bap; > > if (strcasecmp(uuid, PAC_SINK_UUID) && > - strcasecmp(uuid, PAC_SOURCE_UUID)) > + strcasecmp(uuid, PAC_SOURCE_UUID) && > + strcasecmp(uuid, BCAA_SERVICE_UUID) && > + strcasecmp(uuid, BAA_SERVICE_UUID)) > continue; > > bap = transport->data; > @@ -776,7 +778,11 @@ static gboolean get_device(const GDBusPropertyTable *property, > if (transport->device) > path = device_get_path(transport->device); > else > - path = adapter_get_path(transport->adapter); > + /* > + *Use remote endpoint path as fake device path > + *for broadcast source transport > + */ > + path = transport->remote_endpoint; > > dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); > > @@ -1132,6 +1138,7 @@ static gboolean get_bcast_qos(const GDBusPropertyTable *property, > struct media_transport *transport = data; > struct bap_transport *bap = transport->data; > DBusMessageIter dict; > + uint8_t *temp = bap->qos.bcast.bcode; > > dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, > DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING > @@ -1150,10 +1157,9 @@ static gboolean get_bcast_qos(const GDBusPropertyTable *property, > &bap->qos.bcast.packing); > dict_append_entry(&dict, "Framing", DBUS_TYPE_BYTE, > &bap->qos.bcast.framing); > - if (bap->qos.bcast.bcode) > - dict_append_array(&dict, "BCode", DBUS_TYPE_BYTE, > - &bap->qos.bcast.bcode->iov_base, > - bap->qos.bcast.bcode->iov_len); > + dict_append_array(&dict, "BCode", DBUS_TYPE_BYTE, > + &temp, > + sizeof(bap->qos.bcast.bcode)); > dict_append_entry(&dict, "Options", DBUS_TYPE_BYTE, > &bap->qos.bcast.options); > dict_append_entry(&dict, "Skip", DBUS_TYPE_UINT16, > @@ -1614,9 +1620,10 @@ static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, > /* If a request is pending wait it to complete */ > if (owner && owner->pending) > return; > - bap_update_links(transport); > - if (!media_endpoint_is_broadcast(transport->endpoint)) > + if (!media_endpoint_is_broadcast(transport->endpoint)) { > + bap_update_links(transport); > bap_update_qos(transport); > + } > else if (bt_bap_stream_io_dir(stream) != BT_BAP_BCAST_SOURCE) > bap_update_bcast_qos(transport); > transport_update_playing(transport, FALSE); > @@ -1633,7 +1640,7 @@ static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, > bap_update_bcast_qos(transport); > break; > case BT_BAP_STREAM_STATE_RELEASING: > - if (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SINK) > + if (!bt_bap_stream_io_dir(stream)) > return; > break; > } > diff --git a/src/shared/bap.c b/src/shared/bap.c > index 078d308dc..6934c6d2d 100644 > --- a/src/shared/bap.c > +++ b/src/shared/bap.c > @@ -30,6 +30,7 @@ > #include "src/shared/bap.h" > #include "src/shared/ascs.h" > #include "src/shared/bap-debug.h" > +#include "src/shared/lc3.h" > > /* Maximum number of ASE(s) */ > #define NUM_SINKS 2 > @@ -260,6 +261,8 @@ static struct queue *bap_db; > static struct queue *bap_cbs; > static struct queue *sessions; > > +static bool bt_bap_new_bcast_sink(struct bt_bap *bap); > + > static bool bap_db_match(const void *data, const void *match_data) > { > const struct bt_bap_db *bdb = data; > @@ -1343,6 +1346,12 @@ static void stream_set_state_broadcast(struct bt_bap_stream *stream, > > bt_bap_ref(bap); > > + switch (stream->ep->state) { > + case BT_ASCS_ASE_STATE_QOS: > + bap_stream_update_io_links(stream); > + break; > + } > + > for (entry = queue_get_entries(bap->state_cbs); entry; > entry = entry->next) { > struct bt_bap_state *state = entry->data; > @@ -2377,21 +2386,6 @@ static struct bt_bap_pac *bap_pac_find(struct bt_bap_db *bdb, uint8_t type, > return NULL; > } > > -static void *ltv_merge(struct iovec *data, struct iovec *cont) > -{ > - uint8_t delimiter = 0; > - > - if (!data) > - return NULL; > - > - if (!cont || !cont->iov_len || !cont->iov_base) > - return data->iov_base; > - > - iov_append(data, sizeof(delimiter), &delimiter); > - > - return iov_append(data, cont->iov_len, cont->iov_base); > -} > - > static void bap_pac_foreach_channel(size_t i, uint8_t l, uint8_t t, uint8_t *v, > void *user_data) > { > @@ -2427,7 +2421,7 @@ static void bap_pac_merge(struct bt_bap_pac *pac, struct iovec *data, > { > /* Merge data into existing record */ > if (pac->data) > - ltv_merge(pac->data, data); > + bt_bap_ltv_merge(pac->data, data, true); > else > pac->data = util_iov_dup(data, 1); > > @@ -2436,7 +2430,7 @@ static void bap_pac_merge(struct bt_bap_pac *pac, struct iovec *data, > > /* Merge metadata into existing record */ > if (pac->metadata) > - ltv_merge(pac->metadata, metadata); > + bt_bap_ltv_merge(pac->metadata, metadata, true); > else > pac->metadata = util_iov_dup(metadata, 1); > } > @@ -2650,6 +2644,8 @@ static void bap_add_broadcast_source(struct bt_bap_pac *pac) > static void bap_add_broadcast_sink(struct bt_bap_pac *pac) > { > queue_push_tail(pac->bdb->broadcast_sinks, pac); > + > + pacs_add_sink_location(pac->bdb->pacs, pac->qos.location); > } > > static void notify_pac_added(void *data, void *user_data) > @@ -2668,6 +2664,20 @@ static void notify_session_pac_added(void *data, void *user_data) > queue_foreach(bap->pac_cbs, notify_pac_added, user_data); > } > > +struct bt_bap_pac *bt_bap_add_vendor_pac_from_pac(struct bt_bap_pac *pac) > +{ > + struct bt_bap_pac *new_pac; > + > + new_pac = bt_bap_add_vendor_pac(pac->bdb->db, pac->name, pac->type, > + pac->codec.id, > + pac->codec.cid, > + pac->codec.cid, > + &pac->qos, pac->data, > + pac->metadata); > + bt_bap_pac_set_ops(new_pac, pac->ops, pac->user_data); > + return new_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, > @@ -2725,7 +2735,7 @@ struct bt_bap_pac *bt_bap_add_pac(struct gatt_db *db, const char *name, > struct iovec *metadata) > { > return bt_bap_add_vendor_pac(db, name, type, id, 0x0000, 0x0000, qos, > - data, metadata); > + data, metadata); > } > > uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac) > @@ -2748,13 +2758,24 @@ uint32_t bt_bap_pac_get_locations(struct bt_bap_pac *pac) > switch (pac->type) { > case BT_BAP_SOURCE: > return pacs->source_loc_value; > + case BT_BAP_BCAST_SOURCE: > + return pac->qos.location; > case BT_BAP_SINK: > + case BT_BAP_BCAST_SINK: > return pacs->sink_loc_value; > default: > return 0; > } > } > > +struct iovec *bt_bap_pac_get_data(struct bt_bap_pac *pac) > +{ > + if (!pac) > + return 0x00; > + > + return pac->data; > +} > + > uint16_t bt_bap_pac_get_supported_context(struct bt_bap_pac *pac) > { > struct bt_pacs *pacs; > @@ -2837,12 +2858,26 @@ static void notify_session_pac_removed(void *data, void *user_data) > bool bt_bap_pac_set_ops(struct bt_bap_pac *pac, struct bt_bap_pac_ops *ops, > void *user_data) > { > + const struct queue_entry *entry = NULL; > + struct bt_bap_pac *pac_entry; > + > if (!pac) > return false; > > pac->ops = ops; > pac->user_data = user_data; > > + if (pac->type == BT_BAP_BCAST_SOURCE) { > + for (entry = queue_get_entries(pac->bdb->broadcast_sources); > + entry != NULL; entry = entry->next) { > + pac_entry = entry->data; > + if (pac_entry != pac) { > + pac_entry->ops = ops; > + pac_entry->user_data = user_data; > + } > + } > + } > + > return true; > } > > @@ -4489,6 +4524,11 @@ bool bt_bap_pac_bcast_is_local(struct bt_bap *bap, struct bt_bap_pac *pac) > return false; > } > > +char *bt_bap_pac_get_name(struct bt_bap_pac *pac) > +{ > + return pac->name; > +} > + > static bool find_ep_unused(const void *data, const void *user_data) > { > const struct bt_bap_endpoint *ep = data; > @@ -4618,7 +4658,6 @@ unsigned int bt_bap_stream_config(struct bt_bap_stream *stream, > if (stream->lpac->type == BT_BAP_BCAST_SINK) { > if (data) > stream_config(stream, data, NULL); > - stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG); > } > return 1; > } > @@ -4763,7 +4802,7 @@ struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap, > return NULL; > > bt_bap_foreach_pac(bap, type, match_pac, &match); > - if (!match.lpac) > + if ((!match.lpac) || (!lpac)) > return NULL; > if (!match.rpac && (lpac->type != BT_BAP_BCAST_SOURCE)) > return NULL; > @@ -4780,7 +4819,15 @@ struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap, > if (!ep) { > /* Check for unused ASE */ > ep = queue_find(bap->remote_eps, find_ep_unused, &match); > - if (!ep) { > + if (!ep && lpac->type == BT_BAP_BCAST_SOURCE) { > + bt_bap_new_bcast_sink(bap); > + ep = queue_find(bap->remote_eps, find_ep_unused, > + &match); > + if (!ep) { > + DBG(bap, "Unable to find unused ASE"); > + return NULL; > + } > + } else if (!ep) { > DBG(bap, "Unable to find unused ASE"); > return NULL; > } > @@ -4943,11 +4990,10 @@ unsigned int bt_bap_stream_enable(struct bt_bap_stream *stream, > case BT_BAP_STREAM_TYPE_BCAST: > if (!bt_bap_stream_io_dir(stream)) > stream_set_state_broadcast(stream, > - BT_BAP_STREAM_STATE_CONFIG); > + BT_BAP_STREAM_STATE_CONFIG); > else if (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SOURCE) > stream_set_state_broadcast(stream, > BT_BAP_STREAM_STATE_STREAMING); > - > return 1; > } > > @@ -5094,13 +5140,21 @@ unsigned int bt_bap_stream_metadata(struct bt_bap_stream *stream, > if (!stream) > return 0; > > - if (!stream->client) { > - stream_metadata(stream, metadata, NULL); > + switch (bt_bap_stream_get_type(stream)) { > + case BT_BAP_STREAM_TYPE_UCAST: > + if (!stream->client) { > + stream_metadata(stream, metadata, NULL); > + return 0; > + } > + return bap_stream_metadata(stream, BT_ASCS_METADATA, > + metadata, func, user_data); > + case BT_BAP_STREAM_TYPE_BCAST: > + util_iov_free(stream->meta, 1); > + stream->meta = util_iov_dup(metadata, 1); > + return 1; > + default: > return 0; > } > - > - return bap_stream_metadata(stream, BT_ASCS_METADATA, metadata, func, > - user_data); > } > > unsigned int bt_bap_stream_release(struct bt_bap_stream *stream, > @@ -5207,6 +5261,14 @@ struct iovec *bt_bap_stream_get_metadata(struct bt_bap_stream *stream) > return stream->meta; > } > > +struct bt_bap_pac *bt_bap_stream_get_local_pac(struct bt_bap_stream *stream) > +{ > + if (!stream) > + return NULL; > + > + return stream->lpac; > +} > + > struct io *bt_bap_stream_get_io(struct bt_bap_stream *stream) > { > struct bt_bap_stream_io *io; > @@ -5265,6 +5327,23 @@ static bool match_name(const void *data, const void *match_data) > return (!strcmp(pac->name, name)); > } > > +void *bt_bap_ltv_merge(struct iovec *data, struct iovec *cont, > + bool add_delimiter) > +{ > + uint8_t delimiter = 0; > + > + if (!data) > + return NULL; > + > + if (!cont || !cont->iov_len || !cont->iov_base) > + return data->iov_base; > + > + if (add_delimiter) > + iov_append(data, sizeof(delimiter), &delimiter); > + > + return iov_append(data, cont->iov_len, cont->iov_base); > +} > + > int bt_bap_stream_cancel(struct bt_bap_stream *stream, unsigned int id) > { > struct bt_bap_req *req; > @@ -5301,9 +5380,9 @@ int bt_bap_stream_io_link(struct bt_bap_stream *stream, > 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) > + 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; > > stream->link = link; > @@ -5484,11 +5563,301 @@ bool bt_bap_new_bcast_source(struct bt_bap *bap, const char *name) > return true; > } > > +static bool bt_bap_new_bcast_sink(struct bt_bap *bap) > +{ > + struct bt_bap_endpoint *ep; > + > + /* Push remote endpoint with direction source */ > + ep = bap_endpoint_new_broadcast(bap->ldb, BT_BAP_BCAST_SOURCE); > + > + if (ep) > + queue_push_tail(bap->remote_eps, ep); > + > + ep->bap = bap; > + return true; > +} > + > +struct ltv_compare_data { > + bool match; > + void *data; > + uint32_t data32; > +}; > + > +static void bap_sink_match_allocation(size_t i, uint8_t l, uint8_t t, > + uint8_t *v, void *user_data) > +{ > + struct ltv_compare_data *data = user_data; > + uint32_t location32; > + > + if (!v) > + return; > + > + memcpy(&location32, v, l); > + > + /* If all the bits in the received bitmask are found in > + * the local bitmask then we have a match > + */ > + if ((le32_to_cpu(location32) & data->data32) == > + le32_to_cpu(location32)) > + data->match = true; > + else > + data->match = false; > +} > + > +static void bap_sink_match_caps_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v, > + void *user_data) > +{ > + struct ltv_compare_data *local_data = user_data; > + struct iovec local_caps = *((struct iovec *)local_data->data); > + struct bt_ltv *ltv = NULL; > + bool found = false; > + > + /* Search for {l,t,v} in local_caps */ > + do { > + ltv = util_iov_pull_mem(&local_caps, sizeof(*ltv)); > + > + if (ltv != NULL) { > + /* If the LTV is found, continue */ > + if (ltv->type == t) { > + switch (ltv->type) { > + case LC3_FREQ: > + { > + uint16_t val16 = > + *((uint16_t *)ltv->value); > + val16 = le16_to_cpu(val16); > + if (val16 & (1 << (v[0] - 1))) > + found = true; > + } > + break; > + case LC3_DURATION: > + if ((ltv->value[0]) & (1 << v[0])) > + found = true; > + break; > + /* LC3_CHAN_COUNT */ > + case LC3_FRAME_LEN: > + { > + uint16_t min = > + *((uint16_t *)ltv->value); > + uint16_t max = > + *((uint16_t *)(<v->value[2])); > + uint16_t frame_len = *((uint16_t *)v); > + > + min = le16_to_cpu(min); > + max = le16_to_cpu(max); > + frame_len = le16_to_cpu(frame_len); > + if ((frame_len >= min) && > + (frame_len <= max)) > + found = true; > + } > + /* LC3_FRAME_COUNT */ > + break; > + } > + > + if (found) > + /* Matching LTV found -> mark it > + * in the output bitmask > + */ > + local_data->data32 |= 1<<t; > + } > + > + /* Advance over data field */ > + (void)util_iov_pull_mem(&local_caps, ltv->len - 1); > + } > + } while ((local_caps.iov_len > 0) && (ltv != NULL) && > + (found == false)); > +} > + > +static void check_capabilities(void *data, void *user_data) > +{ > + struct ltv_compare_data *ltv_data = user_data; > + > + #define Codec_Specific_Configuration_Check_Mask (\ > + (1<<LC3_FREQ)|\ > + (1<<LC3_DURATION)|\ > + /*(1<<LC3_CONFIG_CHAN_ALLOC)|\*/\ > + (1<<LC3_FRAME_LEN)) > + > + /* Keep searching for a matching PAC if one wasn't found > + * in previous PAC elements > + */ > + if (ltv_data->match == false) { > + const struct bt_bap_pac *pac = data; > + struct iovec *in_caps = (struct iovec *)ltv_data->data; > + struct ltv_compare_data local_ltv_data = { > + .data = pac->data, > + .data32 = 0, /* LTVs bitmask result */ > + .match = false > + }; > + > + util_ltv_foreach(in_caps->iov_base, in_caps->iov_len, NULL, > + bap_sink_match_caps_ltv, &local_ltv_data); > + > + /* We have a match if all selected LTVs have a match */ > + if (local_ltv_data.data32 & > + Codec_Specific_Configuration_Check_Mask) > + ltv_data->match = true; > + } > +} > + > +static bool bap_compare_ltv_capabilities(struct iovec *in_caps, > + uint32_t location, > + struct queue *local_data) > +{ > + struct ltv_compare_data ltv_data = {0}; > + uint8_t type = 0; > + > + /* Check Audio_Channel_Allocation LTV from PACS data */ > + if (location) { > + ltv_data.data32 = location; > + type = LC3_CONFIG_CHAN_ALLOC; > + > + util_ltv_foreach(in_caps->iov_base, in_caps->iov_len, &type, > + bap_sink_match_allocation, <v_data); > + } > + > + /* Check remaining LTVs from PACs list */ > + if (ltv_data.match) { > + ltv_data.data = in_caps; > + ltv_data.match = false; > + queue_foreach(local_data, check_capabilities, <v_data); > + } > + > + return ltv_data.match; > +} > + > +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 void print_caps(const char *str, void *user_data) > +{ > + printf("%s\n\r", str); > +} > + > +static void merge_codec_specific_config(struct iovec *caps_l2, > + struct iovec *caps_l3, struct iovec *result) > +{ > + struct iovec iov_l2 = {0}; > + struct iovec iov_l3 = {0}; > + struct bt_ltv *ltv2 = NULL; > + > + /* Loop Level 2 LTVs */ > + iov_l2 = *caps_l2; > + do { > + iov_l3 = *caps_l3; > + ltv2 = util_iov_pull_mem(&iov_l2, sizeof(*ltv2)); > + > + if (ltv2 != NULL) { > + struct bt_ltv *ltv3 = NULL; > + bool found = false; /* found at Level3? */ > + > + /* Loop Level 3 LTVs */ > + do { > + ltv3 = util_iov_pull_mem(&iov_l3, > + sizeof(*ltv3)); > + > + if (ltv3 != NULL) { > + /* If the LTV is found, > + * continue > + */ > + if (ltv3->type == ltv2->type) > + found = true; > + > + /* Advance over data field */ > + (void)util_iov_pull_mem(&iov_l3, > + ltv3->len - 1); > + } > + } while ((iov_l3.iov_len > 0) && (ltv3 != NULL) && > + (found == false)); > + > + /* In case LTV (L3) was not found at L2 add it */ > + if (!found) > + iov_append(result, ltv2->len + 1, ltv2); > + /* In case LTV (L3) was found at L2, > + * get the one at Level 3 > + */ > + else > + iov_append(result, ltv3->len + 1, ltv3); > + > + /* Advance over data field */ > + (void)util_iov_pull_mem(&iov_l2, ltv2->len - 1); > + } > + } while ((iov_l2.iov_len > 0) && (ltv2 != NULL)); > +} > + > +static void bis_to_pac(void *data, void *user_data) > +{ > + struct bt_bap_bis *bis = data; > + struct bt_bap_base_subgroup *subgroup = user_data; > + struct bt_bap_pac *pac_source_bis; > + struct bt_bap_endpoint *ep; > + int err = 0; > + struct bt_bap_pac_qos bis_qos = {0}; > + uint8_t type = 0; > + struct iovec *iov_bis = new0(struct iovec, 1); > + > + /* Create a Codec Specific Configuration with LTVs at level 2 > + * overwritten by LTVs at level 3 > + */ > + merge_codec_specific_config(subgroup->caps, bis->caps, iov_bis); > + > + /* Search in local database to see if this BIS > + * matches our capabilities > + */ > + if (bap_compare_ltv_capabilities(iov_bis, > + subgroup->bap->ldb->pacs->sink_loc_value, > + subgroup->bap->ldb->broadcast_sinks) != true) > + return; > + > + DBG(subgroup->bap, "Selected BIS %i", bis->bisIndex); > + bt_bap_debug_caps(iov_bis->iov_base, iov_bis->iov_len, > + print_caps, NULL); > + > + /* Create a QoS structure based on the received BIS information */ > + type = LC3_CONFIG_CHAN_ALLOC; > + util_ltv_foreach(iov_bis->iov_base, iov_bis->iov_len, &type, > + bap_sink_get_allocation, &bis_qos.location); > + > + /* Create a remote PAC */ > + pac_source_bis = bap_pac_new(subgroup->bap->rdb, NULL, > + BT_BAP_BCAST_SOURCE, &subgroup->codec, &bis_qos, > + iov_bis, subgroup->meta); > + > + err = asprintf(&pac_source_bis->name, "%d", bis->bisIndex); > + > + if (err < 0) > + DBG(subgroup->bap, "error in asprintf"); > + > + /* Add remote source endpoint */ > + if (!subgroup->bap->rdb->broadcast_sources) > + subgroup->bap->rdb->broadcast_sources = queue_new(); > + queue_push_tail(subgroup->bap->rdb->broadcast_sources, pac_source_bis); > + > + queue_foreach(subgroup->bap->pac_cbs, notify_pac_added, pac_source_bis); > + /* Push remote endpoint with direction sink */ > + ep = bap_endpoint_new_broadcast(subgroup->bap->rdb, BT_BAP_BCAST_SINK); > + > + if (ep) > + queue_push_tail(subgroup->bap->remote_eps, ep); > + > + util_iov_free(iov_bis, 1); > +} > +static void parse_bis(void *data, void *user_data) > +{ > + struct bt_bap_base_subgroup *subgroup = data; > + > + queue_foreach(subgroup->bises, bis_to_pac, subgroup); > +} > void bt_bap_update_bcast_source(struct bt_bap_pac *pac, > - struct bt_bap_codec *codec, > - struct iovec *data, > - struct iovec *metadata) > + struct bt_bap_base *base) > { > - bap_pac_merge(pac, data, metadata); > - pac->codec = *codec; > + queue_foreach(base->subgroups, parse_bis, NULL); > } > diff --git a/src/shared/bap.h b/src/shared/bap.h > index 51edc08ab..814b3af9e 100644 > --- a/src/shared/bap.h > +++ b/src/shared/bap.h > @@ -79,7 +79,7 @@ struct bt_bap_bcast_qos { > uint8_t packing; > uint8_t framing; > uint8_t encryption; > - struct iovec *bcode; > + uint8_t bcode[16]; > uint8_t options; > uint16_t skip; > uint16_t sync_timeout; > @@ -87,6 +87,7 @@ struct bt_bap_bcast_qos { > uint8_t mse; > uint16_t timeout; > uint8_t pa_sync; > + uint32_t delay; /* Presentation Delay */ > struct bt_bap_io_qos io_qos; > }; > > @@ -97,6 +98,27 @@ struct bt_bap_qos { > }; > }; > > +struct bt_bap_base { > + uint32_t pres_delay; > + uint8_t num_subgroups; > + struct queue *subgroups; > +}; > + > +struct bt_bap_base_subgroup { > + uint8_t subgroup_index; > + struct bt_bap *bap; > + uint8_t num_bises; > + struct bt_bap_codec codec; > + struct iovec *caps; > + struct iovec *meta; > + struct queue *bises; > +}; > + > +struct bt_bap_bis { > + uint8_t bisIndex; > + struct iovec *caps; > +}; > + > typedef void (*bt_bap_ready_func_t)(struct bt_bap *bap, void *user_data); > typedef void (*bt_bap_destroy_func_t)(void *user_data); > typedef void (*bt_bap_debug_func_t)(const char *str, void *user_data); > @@ -168,6 +190,8 @@ uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac); > > uint32_t bt_bap_pac_get_locations(struct bt_bap_pac *pac); > > +struct iovec *bt_bap_pac_get_data(struct bt_bap_pac *pac); > + > uint16_t bt_bap_pac_get_supported_context(struct bt_bap_pac *pac); > > uint16_t bt_bap_pac_get_context(struct bt_bap_pac *pac); > @@ -176,6 +200,8 @@ struct bt_bap_pac_qos *bt_bap_pac_get_qos(struct bt_bap_pac *pac); > > uint8_t bt_bap_stream_get_type(struct bt_bap_stream *stream); > > +struct bt_bap_pac *bt_bap_add_vendor_pac_from_pac(struct bt_bap_pac *pac); > + > struct bt_bap_stream *bt_bap_pac_get_stream(struct bt_bap_pac *pac); > > /* Session related function */ > @@ -231,6 +257,7 @@ int bt_bap_pac_get_codec(struct bt_bap_pac *pac, uint8_t *id, > > void bt_bap_pac_set_user_data(struct bt_bap_pac *pac, void *user_data); > void *bt_bap_pac_get_user_data(struct bt_bap_pac *pac); > +char *bt_bap_pac_get_name(struct bt_bap_pac *pac); > > /* Stream related functions */ > int bt_bap_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, > @@ -294,6 +321,7 @@ uint32_t bt_bap_stream_get_location(struct bt_bap_stream *stream); > struct iovec *bt_bap_stream_get_config(struct bt_bap_stream *stream); > struct bt_bap_qos *bt_bap_stream_get_qos(struct bt_bap_stream *stream); > struct iovec *bt_bap_stream_get_metadata(struct bt_bap_stream *stream); > +struct bt_bap_pac *bt_bap_stream_get_local_pac(struct bt_bap_stream *stream); > > struct io *bt_bap_stream_get_io(struct bt_bap_stream *stream); > bool bt_bap_match_bcast_sink_stream(const void *data, const void *user_data); > @@ -315,9 +343,9 @@ bool bt_bap_stream_io_is_connecting(struct bt_bap_stream *stream, int *fd); > > bool bt_bap_new_bcast_source(struct bt_bap *bap, const char *name); > void bt_bap_update_bcast_source(struct bt_bap_pac *pac, > - struct bt_bap_codec *codec, > - struct iovec *data, > - struct iovec *metadata); > + struct bt_bap_base *base); > +void *bt_bap_ltv_merge(struct iovec *data, struct iovec *cont, > + bool add_delimiter); > > bool bt_bap_pac_bcast_is_local(struct bt_bap *bap, struct bt_bap_pac *pac); > > -- > 2.39.2 >
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c index b88876485..8bc292946 100644 --- a/profiles/audio/bap.c +++ b/profiles/audio/bap.c @@ -45,6 +45,7 @@ #include "src/shared/gatt-client.h" #include "src/shared/gatt-server.h" #include "src/shared/bap.h" +#include "src/shared/lc3.h" #include "btio/btio.h" #include "src/plugin.h" @@ -75,6 +76,8 @@ struct bap_setup { unsigned int id; struct iovec *base; DBusMessage *msg; + struct queue *same_big_setups; + struct bt_bap_qos combined_qos; }; struct bap_ep { @@ -102,6 +105,64 @@ struct bap_data { void *user_data; }; +struct stream_base_data { + struct queue *ltv_caps; + struct queue *ltv_meta; + struct bap_setup *setup; +}; + +struct base_data { + uint32_t pres_delay; + struct queue *base_data_subgroup; +}; + +struct base_data_subgroup { + uint8_t subgroup_index; + struct bt_bap_codec codec; + struct queue *ltv_caps; + struct queue *ltv_meta; + struct queue *bises; +}; + +struct base_data_bis { + uint8_t bis_index; + struct queue *ltv_caps; +}; + +#define DEFAULT_IO_QOS \ +{ \ + .interval = 10000, \ + .latency = 10, \ + .sdu = 40, \ + .phy = 0x02, \ + .rtn = 2, \ +} + +static struct bt_iso_qos default_qos = { + .bcast = { + .big = BT_ISO_QOS_BIG_UNSET, + .bis = BT_ISO_QOS_BIS_UNSET, + .sync_factor = 0x07, + .packing = 0x00, + .framing = 0x00, + .in = DEFAULT_IO_QOS, + .out = DEFAULT_IO_QOS, + .encryption = 0x00, + .bcode = {0x00}, + .options = 0x00, + .skip = 0x0000, + .sync_timeout = 0x4000, + .sync_cte_type = 0x00, + .mse = 0x00, + .timeout = 0x4000, + } +}; +struct ltv_compare_data { + bool match; + void *data; + uint32_t data32; +}; + static struct queue *sessions; static bool bap_data_set_user_data(struct bap_data *data, void *user_data) @@ -258,10 +319,20 @@ static gboolean get_device(const GDBusPropertyTable *property, struct bap_ep *ep = data; const char *path; - if (bt_bap_pac_get_type(ep->lpac) == BT_BAP_BCAST_SOURCE) - path = adapter_get_path(ep->data->adapter); - else - path = device_get_path(ep->data->device); + path = device_get_path(ep->data->device); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + + return TRUE; +} + +static gboolean get_adapter(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct bap_ep *ep = data; + const char *path; + + path = adapter_get_path(ep->data->adapter); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); @@ -370,6 +441,26 @@ static const GDBusPropertyTable ep_properties[] = { { } }; +static const GDBusPropertyTable ep_properties_bcast_source[] = { + { "UUID", "s", get_uuid, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "Codec", "y", get_codec, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "Capabilities", "ay", get_capabilities, NULL, has_capabilities, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "Adapter", "o", get_adapter, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "Locations", "u", get_locations, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "SupportedContext", "q", get_supported_context, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "Context", "q", get_context, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "QoS", "a{sv}", get_qos, NULL, qos_exists, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { } +}; + static int parse_array(DBusMessageIter *iter, struct iovec *iov) { DBusMessageIter array; @@ -384,101 +475,755 @@ static int parse_array(DBusMessageIter *iter, struct iovec *iov) return 0; } -static bool parse_base(void *data, size_t len, util_debug_func_t func, - uint32_t *presDelay, uint8_t *numSubgroups, uint8_t *numBis, - struct bt_bap_codec *codec, struct iovec **caps, - struct iovec **meta) +static void cleanup_subgroup(struct bt_bap_base_subgroup *subgroup) { - struct iovec iov = { - .iov_base = data, - .iov_len = len, - }; + if (!subgroup) + return; - uint8_t capsLen, metaLen; - uint8_t *hexstream; + if (subgroup->meta) + util_iov_free(subgroup->meta, 1); - if (presDelay) { - if (!util_iov_pull_le24(&iov, presDelay)) - return false; - util_debug(func, NULL, "PresentationDelay %d", *presDelay); + if (subgroup->caps) + util_iov_free(subgroup->caps, 1); + + while (!queue_isempty(subgroup->bises)) { + struct bt_bap_bis *bis = queue_peek_head(subgroup->bises); + + if (bis->caps) + util_iov_free(bis->caps, 1); + queue_remove(subgroup->bises, bis); + subgroup->num_bises--; } + queue_destroy(subgroup->bises, NULL); - if (numSubgroups) { - if (!util_iov_pull_u8(&iov, numSubgroups)) - return false; - util_debug(func, NULL, "NumSubgroups %d", *numSubgroups); + if (subgroup) + free(subgroup); +} + +static void destroy_ltv(void *data) +{ + struct bt_ltv *ltv = data; + + if (!ltv) + return; + + free(ltv); +} + +static void parse_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v, + void *user_data) +{ + struct queue **ltv_queue = user_data; + struct bt_ltv *q_ltv_elem = malloc(sizeof(struct bt_ltv) + l); + + q_ltv_elem->len = l + 1; + q_ltv_elem->type = t; + + memcpy(q_ltv_elem->value, v, l); + + if (!*ltv_queue) + *ltv_queue = queue_new(); + queue_push_tail(*ltv_queue, q_ltv_elem); +} + +static struct stream_base_data *get_stream_base_info( + struct bap_setup *setup, util_debug_func_t func) +{ + struct stream_base_data *sbd = new0(struct stream_base_data, 1); + struct iovec *stream_caps_iov = util_iov_dup( + setup->caps, 1); + struct iovec *stream_meta_iov = util_iov_dup( + setup->metadata, 1); + + /* + * Copy the Codec Specific configurations from stream + */ + if (stream_caps_iov != NULL) { + if (!util_ltv_foreach(stream_caps_iov->iov_base, + stream_caps_iov->iov_len, NULL, + parse_ltv, &sbd->ltv_caps)) { + util_debug(func, + NULL, "Unable to parse Codec Specific configurations"); + goto fail; + } } - if (numBis) { - if (!util_iov_pull_u8(&iov, numBis)) - return false; - util_debug(func, NULL, "NumBis %d", *numBis); + /* + * Copy the Metadata from stream + */ + if (stream_meta_iov != NULL) { + if (!util_ltv_foreach(stream_meta_iov->iov_base, + stream_meta_iov->iov_len, NULL, + parse_ltv, &sbd->ltv_meta)) { + util_debug(func, + NULL, "Unable to parse Codec Specific configurations"); + goto fail; + } } - if (codec) { - codec = util_iov_pull_mem(&iov, sizeof(*codec)); - if (!codec) + sbd->setup = setup; + + util_iov_free(stream_caps_iov, 1); + util_iov_free(stream_meta_iov, 1); + + return sbd; + +fail: + util_iov_free(stream_caps_iov, 1); + util_iov_free(stream_meta_iov, 1); + + if (sbd->ltv_caps) + queue_destroy(sbd->ltv_caps, destroy_ltv); + + if (sbd->ltv_meta) + queue_destroy(sbd->ltv_meta, destroy_ltv); + + free(sbd); + + return NULL; +} + +static void get_stream_base_data(void *data, void *user_data) +{ + struct bap_setup *setup = data; + struct queue *streams_base_data = user_data; + struct stream_base_data *sbd = get_stream_base_info(setup, bap_debug); + + if (sbd) + queue_push_tail(streams_base_data, sbd); +} + +static void set_device_presentation_delay(void *data, void *user_data) +{ + struct stream_base_data *sbd = data; + struct base_data *base = user_data; + struct bt_bap_qos *qos = &sbd->setup->qos; + + if (base->pres_delay < qos->bcast.delay) + base->pres_delay = qos->bcast.delay; +} + +static bool ltv_match(const void *data, const void *match_data) +{ + const struct bt_ltv *ltv1 = data; + const struct bt_ltv *ltv2 = match_data; + + if (ltv1->len == ltv2->len) + if (ltv1->type == ltv2->type) + if (memcmp(ltv1->value, ltv2->value, ltv1->len - 1) + == 0) + return true; + return false; +} + +static bool compare_ltv_lists(struct queue *ltv_list1, struct queue *ltv_list2) +{ + const struct queue_entry *entry; + /* Compare metadata length */ + if (queue_length(ltv_list1) != queue_length(ltv_list2)) + return false; + + /* Compare metadata ltvs */ + for (entry = queue_get_entries(ltv_list1); entry; entry = entry->next) { + struct bt_ltv *ltv = entry->data; + + if (!queue_find(ltv_list2, ltv_match, ltv)) return false; - util_debug(func, NULL, "%s: ID %d CID 0x%2.2x VID 0x%2.2x", - "Codec", codec->id, codec->cid, codec->vid); } - if (!util_iov_pull_u8(&iov, &capsLen)) - return false; - util_debug(func, NULL, "CC Len %d", capsLen); + return true; +} - if (!capsLen) - return false; - if (caps) { - if (!(*caps)) - *caps = new0(struct iovec, 1); - (*caps)->iov_len = capsLen; - (*caps)->iov_base = iov.iov_base; +static struct queue *compare_caps_ltv_lists( + struct queue *subgroup_caps, struct queue *bis_caps) +{ + struct queue *ltv_caps = queue_new(); + const struct queue_entry *entry; + + /* Compare metadata ltvs */ + for (entry = queue_get_entries(bis_caps); entry; + entry = entry->next) { + struct bt_ltv *ltv = entry->data; + + if (!queue_find(subgroup_caps, ltv_match, ltv)) + queue_push_tail(ltv_caps, ltv); } - for (int i = 0; capsLen > 1; i++) { - struct bt_ltv *ltv = util_iov_pull_mem(&iov, sizeof(*ltv)); - uint8_t *caps; + if (queue_isempty(ltv_caps)) { + queue_destroy(ltv_caps, NULL); + return NULL; + } else + return ltv_caps; +} - if (!ltv) { - util_debug(func, NULL, "Unable to parse %s", - "Capabilities"); - return false; - } +static void get_max_bises_index(void *data, void *user_data) +{ + struct base_data_bis *bdb = data; + uint8_t *bis_index = user_data; - util_debug(func, NULL, "%s #%u: len %u type %u", - "CC", i, ltv->len, ltv->type); + if (bdb->bis_index > *bis_index) + *bis_index = bdb->bis_index + 1; + else if (bdb->bis_index == *bis_index) + *bis_index = *bis_index + 1; +} - caps = util_iov_pull_mem(&iov, ltv->len - 1); - if (!caps) { - util_debug(func, NULL, "Unable to parse %s", - "CC"); - return false; +static void get_bises_index(void *data, void *user_data) +{ + struct base_data_subgroup *bds = data; + uint8_t *bis_index = user_data; + + queue_foreach(bds->bises, get_max_bises_index, bis_index); +} + +static uint8_t get_bis_index(struct queue *subgroups) +{ + uint8_t bis_index = 1; + + queue_foreach(subgroups, get_bises_index, &bis_index); + return bis_index; +} + +static void add_new_bis(struct base_data_subgroup *subgroup, + uint8_t bis_index, struct queue *ltv_bis_caps) +{ + struct base_data_bis *bdb = new0(struct base_data_bis, 1); + + bdb->bis_index = bis_index; + bdb->ltv_caps = ltv_bis_caps; + queue_push_tail(subgroup->bises, bdb); +} + +static void add_new_subgroup(struct queue *subgroups, + struct stream_base_data *base_data) +{ + struct bt_bap_pac *lpac = base_data->setup->ep->lpac; + struct base_data_subgroup *bds = new0( + struct base_data_subgroup, 1); + uint16_t cid; + uint16_t vid; + + bt_bap_pac_get_vendor_codec(lpac, &bds->codec.id, &cid, + &vid, NULL, NULL); + bds->codec.cid = cid; + bds->codec.vid = vid; + bds->ltv_caps = base_data->ltv_caps; + bds->ltv_meta = base_data->ltv_meta; + base_data->ltv_caps = NULL; + base_data->ltv_meta = NULL; + bds->bises = queue_new(); + base_data->setup->qos.bcast.bis = get_bis_index(subgroups); + add_new_bis(bds, base_data->setup->qos.bcast.bis, + queue_new()); + queue_push_tail(subgroups, bds); +} + +static void remove_ltv_form_list(void *data, void *user_data) +{ + struct bt_ltv *ltv = data; + struct queue *ltv_caps = user_data; + + queue_remove(ltv_caps, ltv); +} + +static void set_base_subgroup(void *data, void *user_data) +{ + struct stream_base_data *stream_base = data; + struct base_data *base = user_data; + struct queue *ltv_caps; + + if (queue_isempty(base->base_data_subgroup)) { + add_new_subgroup(base->base_data_subgroup, stream_base); + } else { + /* Verify if a subgroup has the same metadata */ + const struct queue_entry *entry; + struct base_data_subgroup *subgroup_base = NULL; + bool same_meta = false; + + for (entry = queue_get_entries(base->base_data_subgroup); + entry; entry = entry->next) { + subgroup_base = entry->data; + if (compare_ltv_lists(subgroup_base->ltv_meta, + stream_base->ltv_meta)) { + same_meta = true; + break; + } } - util_hexdump(' ', caps, ltv->len - 1, func, NULL); - capsLen -= (ltv->len + 1); + if (!same_meta) { + /* No subgroup with the same metadata found. + * Create a new one. + */ + add_new_subgroup(base->base_data_subgroup, + stream_base); + } else { + /* Soubgroup found with the same metadata + * get different capabilities + */ + ltv_caps = compare_caps_ltv_lists( + subgroup_base->ltv_caps, + stream_base->ltv_caps); + + queue_foreach(ltv_caps, remove_ltv_form_list, + stream_base->ltv_caps); + stream_base->setup->qos.bcast.bis = get_bis_index( + base->base_data_subgroup); + add_new_bis(subgroup_base, + stream_base->setup->qos.bcast.bis, + ltv_caps); + } } +} + +static void get_tlv_size(void *data, void *user_data) +{ + struct bt_ltv *ltv = data; + uint8_t *length = user_data; + + *length = *length + ltv->len + 1; +} + +static uint8_t get_size_from_ltv_queue(struct queue *tlv_queue) +{ + uint8_t length = 0; + + queue_foreach(tlv_queue, get_tlv_size, &length); + return length; +} + +static void generate_ltv_base(void *data, void *user_data) +{ + struct bt_ltv *ltv = data; + struct iovec *base_iov = user_data; + + if (!util_iov_push_u8(base_iov, ltv->len)) + return; + + if (!util_iov_push_u8(base_iov, ltv->type)) + return; + + if (!util_iov_push_mem(base_iov, ltv->len - 1, ltv->value)) + return; + util_debug(bap_debug, NULL, "ltv type %d", ltv->type); +} + +static void generate_bis_base(void *data, void *user_data) +{ + struct base_data_bis *bis = data; + struct iovec *base_iov = user_data; + uint8_t cc_length = get_size_from_ltv_queue(bis->ltv_caps); + + if (!util_iov_push_u8(base_iov, bis->bis_index)) + return; + util_debug(bap_debug, NULL, "Bis Index %d", bis->bis_index); + + if (!util_iov_push_u8(base_iov, cc_length)) + return; + util_debug(bap_debug, NULL, "Codec Specific Configuration length %d", + cc_length); + + queue_foreach(bis->ltv_caps, generate_ltv_base, base_iov); +} + +static void generate_subgroup_base(void *data, void *user_data) +{ + struct base_data_subgroup *bds = data; + struct iovec *base_iov = user_data; + uint8_t cc_length = get_size_from_ltv_queue(bds->ltv_caps); + uint8_t metadata_length = get_size_from_ltv_queue(bds->ltv_meta); + + if (!util_iov_push_u8(base_iov, queue_length(bds->bises))) + return; + + util_debug(bap_debug, NULL, "Number of BIS %d", + queue_length(bds->bises)); + + if (!util_iov_push_u8(base_iov, bds->codec.id)) + return; + + util_debug(bap_debug, NULL, "Codec ID %d", bds->codec.id); + + if (!util_iov_push_le16(base_iov, bds->codec.cid)) + return; + + util_debug(bap_debug, NULL, "Company ID %d", bds->codec.cid); + + if (!util_iov_push_le16(base_iov, bds->codec.vid)) + return; + + util_debug(bap_debug, NULL, "Vendor specific codec_ID %d", + bds->codec.vid); + + if (!util_iov_push_u8(base_iov, cc_length)) + return; + + util_debug(bap_debug, NULL, "Codec Specific Configuration length %d", + cc_length); + + queue_foreach(bds->ltv_caps, generate_ltv_base, base_iov); + + if (!util_iov_push_u8(base_iov, metadata_length)) + return; + + util_debug(bap_debug, NULL, "Metadata length %d", metadata_length); + + queue_foreach(bds->ltv_meta, generate_ltv_base, base_iov); + + queue_foreach(bds->bises, generate_bis_base, base_iov); +} + +static struct iovec *generate_base(struct base_data *base, + util_debug_func_t func) +{ + struct iovec *base_iov = g_new0(struct iovec, 0x1); + + base_iov->iov_base = util_malloc(BASE_MAX_LENGTH); + + if (!util_iov_push_le24(base_iov, base->pres_delay)) + return NULL; + util_debug(func, NULL, "PresentationDelay %d", base->pres_delay); + + if (!util_iov_push_u8(base_iov, + queue_length(base->base_data_subgroup))) + return NULL; + + util_debug(func, NULL, "Number of Subgroups %d", + queue_length(base->base_data_subgroup)); + + queue_foreach(base->base_data_subgroup, generate_subgroup_base, + base_iov); + + return base_iov; +} + +static void update_endpoint_base(struct bap_setup *setup, + struct bap_data *data, struct iovec *base_iov) +{ + if (setup->base) + util_iov_free(setup->base, 1); + + setup->base = util_iov_dup(base_iov, 1); +} + +static void destroy_stream_base_data(void *data) +{ + struct stream_base_data *sbd = data; + + if (!sbd) + return; + + queue_destroy(sbd->ltv_caps, destroy_ltv); + queue_destroy(sbd->ltv_meta, destroy_ltv); + sbd->setup = NULL; + free(sbd); +} + +static void destroy_base_data_bis(void *data) +{ + struct base_data_bis *bis = data; + + if (!bis) + return; + + queue_destroy(bis->ltv_caps, destroy_ltv); + free(bis); +} + +static void destroy_base_data_subgroup(void *data) +{ + struct base_data_subgroup *subgroup = data; + + if (!subgroup) + return; + + queue_destroy(subgroup->ltv_caps, destroy_ltv); + queue_destroy(subgroup->ltv_meta, destroy_ltv); + queue_destroy(subgroup->bises, destroy_base_data_bis); + + free(subgroup); +} + +static void update_base(struct bap_setup *setup, + struct bap_data *data, util_debug_func_t func) +{ + struct base_data *base; + struct iovec *base_iov; + const struct queue_entry *entry; + struct queue *streams_base_data = queue_new(); + struct stream_base_data *sbd = get_stream_base_info(setup, func); + + queue_foreach(setup->same_big_setups, get_stream_base_data, + streams_base_data); + + queue_push_tail(streams_base_data, sbd); + + base = new0(struct base_data, 1); + base->base_data_subgroup = queue_new(); + + queue_foreach(streams_base_data, set_device_presentation_delay, base); + + queue_foreach(streams_base_data, set_base_subgroup, base); + + base_iov = generate_base(base, bap_debug); + + /* Update the endpoints BASE iovec */ + update_endpoint_base(setup, data, base_iov); + + for (entry = queue_get_entries(setup->same_big_setups); + entry; entry = entry->next) { + struct bap_setup *link = entry->data; + + update_endpoint_base(link, data, base_iov); + } + + util_iov_free(base_iov, 1); + + queue_destroy(streams_base_data, destroy_stream_base_data); + + queue_destroy(base->base_data_subgroup, destroy_base_data_subgroup); + + free(base); +} - if (!util_iov_pull_u8(&iov, &metaLen)) +static void get_combined_qos(void *data, void *user_data) +{ + struct bap_setup *setup = data; + struct bt_bap_qos **local_qos = user_data; + struct bt_bap_qos *qos = &setup->qos; + + if (*local_qos == NULL) + return; + + if (((*local_qos)->bcast.io_qos.interval != + qos->bcast.io_qos.interval) || + ((*local_qos)->bcast.io_qos.rtn != qos->bcast.io_qos.rtn) || + ((*local_qos)->bcast.io_qos.latency != + qos->bcast.io_qos.latency) || + ((*local_qos)->bcast.io_qos.phy != qos->bcast.io_qos.phy) || + ((*local_qos)->bcast.framing != qos->bcast.framing)) { + util_debug(bap_debug, + NULL, + "Error: QOS is different between BISes in the BIG"); + free(*local_qos); + *local_qos = NULL; + return; + } + + if ((*local_qos)->bcast.io_qos.sdu != qos->bcast.io_qos.sdu) + if ((*local_qos)->bcast.io_qos.sdu < qos->bcast.io_qos.sdu) + (*local_qos)->bcast.io_qos.sdu = qos->bcast.io_qos.sdu; + + if ((*local_qos)->bcast.delay != qos->bcast.delay) + if ((*local_qos)->bcast.delay < qos->bcast.delay) + (*local_qos)->bcast.delay = qos->bcast.delay; +} + +static void update_qos(struct bap_setup *setup, + struct bap_data *data) +{ + const struct queue_entry *entry; + struct bt_bap_qos *local_qos = new0(struct bt_bap_qos, 1); + struct bt_bap_qos *qos = &setup->qos; + + memcpy(local_qos, qos, sizeof(struct bt_bap_qos)); + + queue_foreach(setup->same_big_setups, + get_combined_qos, &local_qos); + + /* Update each endpoint QOS structure to have the + * same values, as obtained in local_qos + */ + if (local_qos != NULL) { + memcpy(&setup->combined_qos, local_qos, + sizeof(struct bt_bap_qos)); + + for (entry = queue_get_entries(setup->same_big_setups); + entry; entry = entry->next) { + struct bap_setup *link = entry->data; + + memcpy(&link->combined_qos, local_qos, + sizeof(struct bt_bap_qos)); + } + } + + if (local_qos != NULL) + free(local_qos); +} + +static bool parse_base(struct bt_bap *bap, void *data, size_t len, + util_debug_func_t func, struct bt_bap_base *base) +{ + uint8_t num_subgroups; + uint8_t num_bis; + + struct iovec iov = { + .iov_base = data, + .iov_len = len, + }; + util_debug(func, NULL, "BASE len %ld", len); + if (!base) return false; - util_debug(func, NULL, "Metadata Len %d", metaLen); - if (!metaLen) + if (!util_iov_pull_le24(&iov, &base->pres_delay)) return false; - if (meta) { - if (!(*meta)) - *meta = new0(struct iovec, 1); - (*meta)->iov_len = metaLen; - (*meta)->iov_base = iov.iov_base; - } + util_debug(func, NULL, "PresentationDelay %d", base->pres_delay); - hexstream = util_iov_pull_mem(&iov, metaLen); - if (!hexstream) + if (!util_iov_pull_u8(&iov, &base->num_subgroups)) return false; - util_hexdump(' ', hexstream, metaLen, func, NULL); + util_debug(func, NULL, "NumSubgroups %d", base->num_subgroups); + num_subgroups = base->num_subgroups; + + for (int sg = 0; sg < num_subgroups; sg++) { + struct bt_bap_base_subgroup *sub_group = new0( + struct bt_bap_base_subgroup, 1); + uint8_t caps_len, metaLen; + uint8_t *hexstream; + + sub_group->subgroup_index = sg; + + util_debug(func, NULL, "Subgroup #%d", sg); + sub_group->bap = bap; + sub_group->bises = queue_new(); + + if (!util_iov_pull_u8(&iov, &num_bis)) { + cleanup_subgroup(sub_group); + goto fail; + } + util_debug(func, NULL, "NumBis %d", num_bis); + sub_group->num_bises = num_bis; + + memcpy(&sub_group->codec, util_iov_pull_mem(&iov, + sizeof(struct bt_bap_codec)), sizeof(struct bt_bap_codec)); + util_debug(func, NULL, "%s: ID %d CID 0x%2.2x VID 0x%2.2x", + "Codec", sub_group->codec.id, sub_group->codec.cid, + sub_group->codec.vid); + if (!util_iov_pull_u8(&iov, &caps_len)) { + cleanup_subgroup(sub_group); + goto fail; + } + + util_debug(func, NULL, "CC Len %d", caps_len); + + /* + * Copy the Codec Specific configurations from base + */ + sub_group->caps = new0(struct iovec, 1); + util_iov_memcpy(sub_group->caps, iov.iov_base, caps_len); + DBG("subgroup caps len %ld", sub_group->caps->iov_len); + + for (int i = 0; caps_len > 1; i++) { + struct bt_ltv *ltv = util_iov_pull_mem(&iov, + sizeof(*ltv)); + uint8_t *caps; + + if (!ltv) { + util_debug(func, NULL, "Unable to parse %s", + "Capabilities"); + cleanup_subgroup(sub_group); + goto fail; + } + + util_debug(func, NULL, "%s #%u: len %u type %u", + "CC", i, ltv->len, ltv->type); + + caps = util_iov_pull_mem(&iov, ltv->len - 1); + if (!caps) { + util_debug(func, NULL, "Unable to parse %s", + "CC"); + cleanup_subgroup(sub_group); + goto fail; + } + util_hexdump(' ', caps, ltv->len - 1, func, NULL); + + caps_len -= (ltv->len + 1); + } + + if (!util_iov_pull_u8(&iov, &metaLen)) { + cleanup_subgroup(sub_group); + goto fail; + } + util_debug(func, NULL, "Metadata Len %d", metaLen); + + sub_group->meta = new0(struct iovec, 1); + sub_group->meta->iov_len = metaLen; + sub_group->meta->iov_base = iov.iov_base; + + hexstream = util_iov_pull_mem(&iov, metaLen); + if (!hexstream) { + cleanup_subgroup(sub_group); + goto fail; + } + util_hexdump(' ', hexstream, metaLen, func, NULL); + + for (int bis_sg = 0; bis_sg < sub_group->num_bises; bis_sg++) { + struct bt_bap_bis *bis; + uint8_t caps_len; + uint8_t crt_bis; + + if (!util_iov_pull_u8(&iov, &crt_bis)) { + cleanup_subgroup(sub_group); + goto fail; + } + util_debug(func, NULL, "BIS #%d", crt_bis); + bis = new0(struct bt_bap_bis, 1); + bis->bisIndex = crt_bis; + + if (!util_iov_pull_u8(&iov, &caps_len)) { + cleanup_subgroup(sub_group); + goto fail; + } + util_debug(func, NULL, "CC Len %d", caps_len); + + bis->caps = new0(struct iovec, 1); + bis->caps->iov_len = caps_len; + util_iov_memcpy(bis->caps, iov.iov_base, caps_len); + DBG("bis caps len %ld", bis->caps->iov_len); + + for (int i = 0; caps_len > 1; i++) { + struct bt_ltv *ltv = util_iov_pull_mem(&iov, + sizeof(*ltv)); + uint8_t *caps; + + if (!ltv) { + util_debug(func, NULL, "Unable to parse %s", + "Capabilities"); + cleanup_subgroup(sub_group); + goto fail; + } + + util_debug(func, NULL, "%s #%u: len %u type %u", + "CC", i, ltv->len, ltv->type); + + caps = util_iov_pull_mem(&iov, ltv->len - 1); + if (!caps) { + util_debug(func, NULL, + "Unable to parse %s", "CC"); + cleanup_subgroup(sub_group); + goto fail; + } + util_hexdump(' ', caps, ltv->len - 1, func, + NULL); + + caps_len -= (ltv->len + 1); + } + + queue_push_tail(sub_group->bises, bis); + } + + queue_push_tail(base->subgroups, sub_group); + } return true; + +fail: + while (!queue_isempty(base->subgroups)) { + struct bt_bap_base_subgroup *subGroup = + queue_peek_head(base->subgroups); + cleanup_subgroup(subGroup); + base->num_subgroups--; + } + util_debug(func, NULL, "Unable to parse %s", "Base"); + + return false; } static int parse_io_qos(const char *key, int var, DBusMessageIter *iter, @@ -561,6 +1306,16 @@ static int parse_bcast_qos(const char *key, int var, DBusMessageIter *iter, return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.encryption); + } else if (!strcasecmp(key, "BIG")) { + if (var != DBUS_TYPE_BYTE) + return -EINVAL; + + dbus_message_iter_get_basic(iter, &qos->bcast.big); + } else if (!strcasecmp(key, "BIS")) { + if (var != DBUS_TYPE_BYTE) + return -EINVAL; + + dbus_message_iter_get_basic(iter, &qos->bcast.bis); } else if (!strcasecmp(key, "Options")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; @@ -596,6 +1351,11 @@ static int parse_bcast_qos(const char *key, int var, DBusMessageIter *iter, return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.timeout); + } else if (!strcasecmp(key, "PresentationDelay")) { + if (var != DBUS_TYPE_UINT32) + return -EINVAL; + + dbus_message_iter_get_basic(iter, &qos->bcast.delay); } else if (!strcasecmp(key, "BCode")) { struct iovec iov; @@ -604,8 +1364,8 @@ static int parse_bcast_qos(const char *key, int var, DBusMessageIter *iter, parse_array(iter, &iov); - util_iov_free(qos->bcast.bcode, 1); - qos->bcast.bcode = util_iov_dup(&iov, 1); + memcpy(qos->bcast.bcode, iov.iov_base, + sizeof(qos->bcast.bcode)); } else { int err; @@ -618,14 +1378,15 @@ static int parse_bcast_qos(const char *key, int var, DBusMessageIter *iter, } static int parse_qos(DBusMessageIter *iter, struct bt_bap_qos *qos, - struct iovec **base) + struct bt_bap_pac *lpac) { DBusMessageIter array; const char *key; int (*parser)(const char *key, int var, DBusMessageIter *iter, struct bt_bap_qos *qos); - if (*base) + if ((bt_bap_pac_get_type(lpac) == BT_BAP_BCAST_SOURCE) || + (bt_bap_pac_get_type(lpac) == BT_BAP_BCAST_SINK)) parser = parse_bcast_qos; else parser = parse_ucast_qos; @@ -657,8 +1418,9 @@ static int parse_qos(DBusMessageIter *iter, struct bt_bap_qos *qos, } static int parse_configuration(DBusMessageIter *props, struct iovec **caps, - struct iovec **metadata, struct iovec **base, - struct bt_bap_qos *qos) + struct iovec **metadata, + struct bt_bap_qos *qos, + struct bt_bap_pac *lpac) { const char *key; struct iovec iov; @@ -699,24 +1461,13 @@ static int parse_configuration(DBusMessageIter *props, struct iovec **caps, if (var != DBUS_TYPE_ARRAY) goto fail; - if (parse_qos(&value, qos, base)) + if (parse_qos(&value, qos, lpac)) goto fail; } dbus_message_iter_next(props); } - if (*base) { - uint32_t presDelay; - uint8_t numSubgroups, numBis; - struct bt_bap_codec codec; - - util_iov_memcpy(*base, (*caps)->iov_base, (*caps)->iov_len); - parse_base((*caps)->iov_base, (*caps)->iov_len, bap_debug, - &presDelay, &numSubgroups, &numBis, &codec, - caps, NULL); - } - return 0; fail: @@ -838,18 +1589,81 @@ static void setup_free(void *data) if (setup->ep) queue_remove(setup->ep->setups, setup); + queue_destroy(setup->same_big_setups, NULL); + setup_io_close(setup, NULL); util_iov_free(setup->caps, 1); util_iov_free(setup->metadata, 1); util_iov_free(setup->base, 1); - if (bt_bap_stream_get_type(setup->stream) == BT_BAP_STREAM_TYPE_BCAST) - util_iov_free(setup->qos.bcast.bcode, 1); - free(setup); } +static void link_setups(void *data, void *user_data) +{ + struct bap_ep *ep = data; + struct bap_setup *setup = user_data; + const struct queue_entry *entry; + + for (entry = queue_get_entries(ep->setups); entry; + entry = entry->next) { + struct bap_setup *iter_setup = entry->data; + + if (iter_setup == setup) + continue; + + if (setup->qos.bcast.big == iter_setup->qos.bcast.big) { + if (setup->same_big_setups == NULL) + setup->same_big_setups = queue_new(); + + if (iter_setup->same_big_setups == NULL) + iter_setup->same_big_setups = queue_new(); + + queue_push_tail(setup->same_big_setups, iter_setup); + queue_push_tail(iter_setup->same_big_setups, setup); + } + } +} + +static void bap_pac_foreach_channel(size_t i, uint8_t l, uint8_t t, uint8_t *v, + void *user_data) +{ + struct ltv_compare_data *data = user_data; + uint32_t location32; + + if (!v) + return; + + memcpy(&location32, v, l); + + /* If all the bits in the received bitmask are found + * in the local bitmask we have a match + */ + if ((le32_to_cpu(location32) & data->data32) == + le32_to_cpu(location32)) + data->match = true; + else + data->match = false; +} + +static bool verify_bis_location_suported(struct bap_setup *setup, + struct bap_ep *ep) +{ + uint8_t type = LC3_CONFIG_CHAN_ALLOC; + struct ltv_compare_data data; + struct iovec *caps = util_iov_dup(setup->caps, 1); + + data.data32 = bt_bap_pac_get_locations(ep->lpac); + + util_ltv_foreach(caps->iov_base, caps->iov_len, &type, + bap_pac_foreach_channel, &data); + + util_iov_free(caps, 1); + + return data.match; +} + static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, void *data) { @@ -882,13 +1696,52 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, setup->qos.ucast.cis_id = BT_ISO_QOS_CIS_UNSET; } + /* Populate setup with the received data */ if (parse_configuration(&props, &setup->caps, &setup->metadata, - &setup->base, &setup->qos) < 0) { + &setup->qos, ep->lpac) < 0) { DBG("Unable to parse configuration"); setup_free(setup); return btd_error_invalid_args(msg); } + if (bt_bap_pac_get_type(ep->lpac) == BT_BAP_BCAST_SOURCE) { + /* + * Kernel uses bis id as advertising handler. + * To open all BISes from a big on the same + * advertisng handler set the bis + * to BT_ISO_QOS_BIS_UNSET. + */ + setup->qos.bcast.bis = BT_ISO_QOS_BIS_UNSET; + + if (!verify_bis_location_suported(setup, ep)) { + DBG("BIS Location is unsupported"); + return btd_error_invalid_args(msg); + } + bt_bap_add_vendor_pac_from_pac(ep->lpac); + + /* Link all setups based on the BIG id */ + queue_foreach(ep->data->bcast, link_setups, setup); + + update_base(setup, data, bap_debug); + update_qos(setup, data); + } else if ((bt_bap_pac_get_type(ep->rpac) == BT_BAP_BCAST_SOURCE) && + (bt_bap_pac_get_type(ep->lpac) == BT_BAP_BCAST_SINK)) { + struct iovec *metadata; + struct iovec *caps; + + util_iov_free(setup->caps, 1); + util_iov_free(setup->metadata, 1); + + setup->caps = new0(struct iovec, 1); + setup->metadata = new0(struct iovec, 1); + setup->io = ep->data->listen_io; + + bt_bap_pac_get_vendor_codec(ep->rpac, NULL, NULL, NULL, &caps, + &metadata); + bt_bap_ltv_merge(setup->caps, caps, false); + bt_bap_ltv_merge(setup->metadata, metadata, false); + } + setup->stream = bt_bap_stream_new(ep->data->bap, ep->lpac, ep->rpac, &setup->qos, setup->caps); @@ -900,12 +1753,12 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, return btd_error_invalid_args(msg); } - bt_bap_stream_set_user_data(setup->stream, ep->path); - if (setup->metadata && setup->metadata->iov_len) bt_bap_stream_metadata(setup->stream, setup->metadata, NULL, NULL); + bt_bap_stream_set_user_data(setup->stream, ep->path); + switch (bt_bap_stream_get_type(setup->stream)) { case BT_BAP_STREAM_TYPE_UCAST: setup->msg = dbus_message_ref(msg); @@ -944,30 +1797,21 @@ static void update_bcast_qos(struct bt_iso_qos *qos, bap_qos->bcast.io_qos.sdu = qos->bcast.in.sdu; bap_qos->bcast.io_qos.rtn = qos->bcast.in.rtn; - bap_qos->bcast.bcode = new0(struct iovec, 1); - util_iov_memcpy(bap_qos->bcast.bcode, qos->bcast.bcode, - sizeof(qos->bcast.bcode)); + memcpy(bap_qos->bcast.bcode, qos->bcast.bcode, + sizeof(bap_qos->bcast.bcode)); } static void iso_bcast_confirm_cb(GIOChannel *io, GError *err, void *user_data) { struct bap_setup *setup = user_data; struct bap_ep *ep = setup->ep; - struct bap_data *data = ep->data; struct bt_iso_qos qos; - struct bt_iso_base base; char address[18]; int fd; - struct iovec *base_io; - uint32_t presDelay; - uint8_t numSubgroups; - uint8_t numBis; - struct bt_bap_codec codec; bt_io_get(io, &err, BT_IO_OPT_DEST, address, BT_IO_OPT_QOS, &qos, - BT_IO_OPT_BASE, &base, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); @@ -976,25 +1820,15 @@ static void iso_bcast_confirm_cb(GIOChannel *io, GError *err, void *user_data) } g_io_channel_ref(io); - btd_service_connecting_complete(data->service, 0); + btd_service_connecting_complete(ep->data->service, 0); DBG("BCAST ISO: sync with %s (BIG 0x%02x BIS 0x%02x)", address, qos.bcast.big, qos.bcast.bis); update_bcast_qos(&qos, &setup->qos); - base_io = new0(struct iovec, 1); - util_iov_memcpy(base_io, base.base, base.base_len); - - parse_base(base_io->iov_base, base_io->iov_len, bap_debug, - &presDelay, &numSubgroups, &numBis, - &codec, &setup->caps, &setup->metadata); - - /* Update pac with BASE information */ - bt_bap_update_bcast_source(ep->rpac, &codec, setup->caps, - setup->metadata); setup->id = bt_bap_stream_config(setup->stream, &setup->qos, setup->caps, NULL, NULL); - data->listen_io = io; + setup->io = io; bt_bap_stream_set_user_data(setup->stream, ep->path); @@ -1006,25 +1840,39 @@ static void iso_bcast_confirm_cb(GIOChannel *io, GError *err, void *user_data) return; } - return; drop: g_io_channel_shutdown(io, TRUE, NULL); - } static void iso_pa_sync_confirm_cb(GIOChannel *io, void *user_data) { GError *err = NULL; + struct bt_iso_base base; + char address[18]; + struct bt_bap_base base_s; + struct bap_data *data = user_data; + struct bt_iso_qos qos; - if (!bt_io_bcast_accept(io, iso_bcast_confirm_cb, - user_data, NULL, &err, BT_IO_OPT_INVALID)) { - error("bt_io_bcast_accept: %s", err->message); + bt_io_get(io, &err, + BT_IO_OPT_DEST, address, + BT_IO_OPT_QOS, &qos, + BT_IO_OPT_BASE, &base, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); g_error_free(err); - g_io_channel_shutdown(io, TRUE, NULL); + return; } + g_io_channel_ref(io); + data->listen_io = io; + btd_service_connecting_complete(data->service, 0); + + base_s.subgroups = queue_new(); + parse_base(data->bap, base.base, base.base_len, bap_debug, &base_s); + bt_bap_update_bcast_source(NULL, &base_s); } static bool match_data_bap_data(const void *data, const void *match_data) @@ -1081,6 +1929,7 @@ static struct bap_ep *ep_register_bcast(struct bap_data *data, int i, err = 0; const char *suffix; struct match_ep match = { lpac, rpac }; + const GDBusPropertyTable *properties; switch (bt_bap_pac_get_type(lpac)) { case BT_BAP_BCAST_SOURCE: @@ -1109,11 +1958,15 @@ static struct bap_ep *ep_register_bcast(struct bap_data *data, case BT_BAP_BCAST_SOURCE: err = asprintf(&ep->path, "%s/pac_%s%d", adapter_get_path(adapter), suffix, i); + properties = ep_properties_bcast_source; break; case BT_BAP_BCAST_SINK: err = asprintf(&ep->path, "%s/pac_%s%d", device_get_path(device), suffix, i); + properties = ep_properties; break; + default: + return NULL; } if (err < 0) { @@ -1125,7 +1978,7 @@ static struct bap_ep *ep_register_bcast(struct bap_data *data, if (g_dbus_register_interface(btd_get_dbus_connection(), ep->path, MEDIA_ENDPOINT_INTERFACE, - ep_methods, NULL, ep_properties, + ep_methods, NULL, properties, ep, ep_free) == FALSE) { error("Could not register remote ep %s", ep->path); ep_free(ep); @@ -1646,9 +2499,12 @@ static gboolean setup_io_disconnected(GIOChannel *io, GIOCondition cond, setup_io_close(setup, NULL); - /* Check if connecting recreate IO */ - if (!is_cig_busy(setup->ep->data, setup->qos.ucast.cig_id)) - recreate_cig(setup); + if (bt_bap_stream_get_type(setup->stream) == + BT_BAP_STREAM_TYPE_UCAST) { + /* Check if connecting recreate IO */ + if (!is_cig_busy(setup->ep->data, setup->qos.ucast.cig_id)) + recreate_cig(setup); + } return FALSE; } @@ -1741,7 +2597,7 @@ static void setup_connect_io(struct bap_data *data, struct bap_setup *setup, static void setup_connect_io_broadcast(struct bap_data *data, struct bap_setup *setup, struct bt_bap_stream *stream, - struct bt_iso_qos *qos) + struct bt_iso_qos *qos, int defer) { struct btd_adapter *adapter = data->user_data; GIOChannel *io = NULL; @@ -1778,7 +2634,7 @@ static void setup_connect_io_broadcast(struct bap_data *data, BT_IO_OPT_MODE, BT_IO_MODE_ISO, BT_IO_OPT_QOS, qos, BT_IO_OPT_BASE, &base, - BT_IO_OPT_DEFER_TIMEOUT, false, + BT_IO_OPT_DEFER_TIMEOUT, defer, BT_IO_OPT_INVALID); if (!io) { @@ -1832,41 +2688,28 @@ static void setup_listen_io_broadcast(struct bap_data *data, struct bt_bap_stream *stream, struct bt_iso_qos *qos) { - GIOChannel *io; GError *err = NULL; struct sockaddr_iso_bc iso_bc_addr; iso_bc_addr.bc_bdaddr_type = btd_device_get_bdaddr_type(data->device); memcpy(&iso_bc_addr.bc_bdaddr, device_get_address(data->device), sizeof(bdaddr_t)); - iso_bc_addr.bc_bis[0] = 1; iso_bc_addr.bc_num_bis = 1; + iso_bc_addr.bc_bis[0] = strtol(bt_bap_pac_get_name(setup->ep->rpac), + NULL, 10); DBG("stream %p", stream); - /* If IO already set skip creating it again */ - if (bt_bap_stream_get_io(stream) || data->listen_io) - return; - - io = bt_io_listen(NULL, iso_pa_sync_confirm_cb, setup, NULL, &err, - BT_IO_OPT_SOURCE_BDADDR, - btd_adapter_get_address(data->adapter), - BT_IO_OPT_DEST_BDADDR, - device_get_address(data->device), - BT_IO_OPT_DEST_TYPE, - btd_device_get_bdaddr_type(data->device), - BT_IO_OPT_MODE, BT_IO_MODE_ISO, - BT_IO_OPT_QOS, &qos->bcast, - BT_IO_OPT_ISO_BC_NUM_BIS, iso_bc_addr.bc_num_bis, - BT_IO_OPT_ISO_BC_BIS, iso_bc_addr.bc_bis, - BT_IO_OPT_INVALID); - if (!io) { - error("%s", err->message); + if (!bt_io_bcast_accept(setup->io, + iso_bcast_confirm_cb, + setup, NULL, &err, + BT_IO_OPT_ISO_BC_NUM_BIS, + iso_bc_addr.bc_num_bis, BT_IO_OPT_ISO_BC_BIS, + iso_bc_addr.bc_bis, BT_IO_OPT_INVALID)) { + error("bt_io_bcast_accept: %s", err->message); g_error_free(err); + g_io_channel_shutdown(setup->ep->data->listen_io, TRUE, NULL); } - setup->io = io; - data->listen_io = io; - } static void setup_create_ucast_io(struct bap_data *data, struct bap_setup *setup, @@ -1902,32 +2745,37 @@ static void setup_create_bcast_io(struct bap_data *data, struct bt_bap_stream *stream, int defer) { struct bt_iso_qos iso_qos; + struct bt_bap_qos *qos; - memset(&iso_qos, 0, sizeof(iso_qos)); - if (!defer) - goto done; + memset(&iso_qos, 0, sizeof(iso_qos)); - iso_qos.bcast.big = setup->qos.bcast.big; - iso_qos.bcast.bis = setup->qos.bcast.bis; - iso_qos.bcast.sync_factor = setup->qos.bcast.sync_factor; - iso_qos.bcast.packing = setup->qos.bcast.packing; - iso_qos.bcast.framing = setup->qos.bcast.framing; - iso_qos.bcast.encryption = setup->qos.bcast.encryption; - if (setup->qos.bcast.bcode) - memcpy(iso_qos.bcast.bcode, setup->qos.bcast.bcode->iov_base, - 16); - iso_qos.bcast.options = setup->qos.bcast.options; - iso_qos.bcast.skip = setup->qos.bcast.skip; - iso_qos.bcast.sync_timeout = setup->qos.bcast.sync_timeout; - iso_qos.bcast.sync_cte_type = setup->qos.bcast.sync_cte_type; - iso_qos.bcast.mse = setup->qos.bcast.mse; - iso_qos.bcast.timeout = setup->qos.bcast.timeout; - memcpy(&iso_qos.bcast.out, &setup->qos.bcast.io_qos, + if (bt_bap_pac_get_type(setup->ep->lpac) == + BT_BAP_BCAST_SOURCE) + qos = &setup->combined_qos; + else + qos = &setup->qos; + + iso_qos.bcast.big = qos->bcast.big; + iso_qos.bcast.bis = qos->bcast.bis; + iso_qos.bcast.sync_factor = qos->bcast.sync_factor; + iso_qos.bcast.packing = qos->bcast.packing; + iso_qos.bcast.framing = qos->bcast.framing; + iso_qos.bcast.encryption = qos->bcast.encryption; + memcpy(iso_qos.bcast.bcode, qos->bcast.bcode, + sizeof(iso_qos.bcast.bcode)); + iso_qos.bcast.options = qos->bcast.options; + iso_qos.bcast.skip = qos->bcast.skip; + iso_qos.bcast.sync_timeout = qos->bcast.sync_timeout; + iso_qos.bcast.sync_cte_type = qos->bcast.sync_cte_type; + iso_qos.bcast.mse = qos->bcast.mse; + iso_qos.bcast.timeout = qos->bcast.timeout; + memcpy(&iso_qos.bcast.out, &qos->bcast.io_qos, sizeof(struct bt_iso_io_qos)); -done: + if (bt_bap_pac_get_type(setup->ep->lpac) == BT_BAP_BCAST_SOURCE) - setup_connect_io_broadcast(data, setup, stream, &iso_qos); + setup_connect_io_broadcast(data, setup, stream, + &iso_qos, defer); else setup_listen_io_broadcast(data, setup, stream, &iso_qos); } @@ -1953,6 +2801,30 @@ static void setup_create_io(struct bap_data *data, struct bap_setup *setup, break; } } +/* + * Function test_linked_streams_state will check if all linked streams + * have the test state. + * If all streams are in test_state returns true; Else returns false + */ +static bool test_linked_streams_state(struct bap_setup *setup, + uint8_t test_state) +{ + const struct queue_entry *entry; + struct bap_setup *linked_setups; + + if (!setup->same_big_setups) + return true; + + for (entry = queue_get_entries(setup->same_big_setups); entry; + entry = entry->next) { + linked_setups = entry->data; + if (bt_bap_stream_get_state(linked_setups->stream) != + test_state) + return false; + } + + return true; +} static void bap_state(struct bt_bap_stream *stream, uint8_t old_state, uint8_t new_state, void *user_data) @@ -1983,17 +2855,29 @@ static void bap_state(struct bt_bap_stream *stream, uint8_t old_state, break; case BT_BAP_STREAM_STATE_CONFIG: if (setup && !setup->id) { - setup_create_io(data, setup, stream, true); - if (!setup->io) { - error("Unable to create io"); - if (old_state != BT_BAP_STREAM_STATE_RELEASING) - bt_bap_stream_release(stream, NULL, - NULL); - return; - } - if (bt_bap_stream_get_type(stream) == + BT_BAP_STREAM_TYPE_BCAST) { + bool defer = !test_linked_streams_state(setup, + BT_BAP_STREAM_STATE_CONFIG); + setup_create_io(data, setup, stream, defer); + if (!setup->io) { + error("Unable to create io"); + if (old_state != + BT_BAP_STREAM_STATE_RELEASING) + bt_bap_stream_release(stream, + NULL, + NULL); + return; + } + } else if (bt_bap_stream_get_type(stream) == BT_BAP_STREAM_TYPE_UCAST) { + setup_create_io(data, setup, stream, true); + if (!setup->io) { + error("Unable to create io"); + bt_bap_stream_release(stream, + NULL, NULL); + return; + } /* Wait QoS response to respond */ setup->id = bt_bap_stream_qos(stream, &setup->qos, @@ -2311,6 +3195,12 @@ static int bap_bcast_probe(struct btd_service *service) struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct bap_data *data = btd_service_get_user_data(service); char addr[18]; + GIOChannel *io; + GError *err = NULL; + struct sockaddr_iso_bc iso_bc_addr; + + iso_bc_addr.bc_bis[0] = 1; + iso_bc_addr.bc_num_bis = 1; ba2str(device_get_address(device), addr); @@ -2349,7 +3239,24 @@ static int bap_bcast_probe(struct btd_service *service) bt_bap_set_user_data(data->bap, service); - bt_bap_new_bcast_source(data->bap, device_get_path(device)); + DBG("Create PA sync with source"); + io = bt_io_listen(NULL, iso_pa_sync_confirm_cb, data, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(data->adapter), + BT_IO_OPT_DEST_BDADDR, + device_get_address(data->device), + BT_IO_OPT_DEST_TYPE, + btd_device_get_bdaddr_type(data->device), + BT_IO_OPT_MODE, BT_IO_MODE_ISO, + BT_IO_OPT_QOS, &default_qos, + BT_IO_OPT_ISO_BC_NUM_BIS, iso_bc_addr.bc_num_bis, + BT_IO_OPT_ISO_BC_BIS, iso_bc_addr.bc_bis, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + } + return 0; } diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c index a4696154a..657474a67 100644 --- a/profiles/audio/transport.c +++ b/profiles/audio/transport.c @@ -172,7 +172,9 @@ find_transport_by_bap_stream(const struct bt_bap_stream *stream) struct bap_transport *bap; if (strcasecmp(uuid, PAC_SINK_UUID) && - strcasecmp(uuid, PAC_SOURCE_UUID)) + strcasecmp(uuid, PAC_SOURCE_UUID) && + strcasecmp(uuid, BCAA_SERVICE_UUID) && + strcasecmp(uuid, BAA_SERVICE_UUID)) continue; bap = transport->data; @@ -776,7 +778,11 @@ static gboolean get_device(const GDBusPropertyTable *property, if (transport->device) path = device_get_path(transport->device); else - path = adapter_get_path(transport->adapter); + /* + *Use remote endpoint path as fake device path + *for broadcast source transport + */ + path = transport->remote_endpoint; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); @@ -1132,6 +1138,7 @@ static gboolean get_bcast_qos(const GDBusPropertyTable *property, struct media_transport *transport = data; struct bap_transport *bap = transport->data; DBusMessageIter dict; + uint8_t *temp = bap->qos.bcast.bcode; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING @@ -1150,10 +1157,9 @@ static gboolean get_bcast_qos(const GDBusPropertyTable *property, &bap->qos.bcast.packing); dict_append_entry(&dict, "Framing", DBUS_TYPE_BYTE, &bap->qos.bcast.framing); - if (bap->qos.bcast.bcode) - dict_append_array(&dict, "BCode", DBUS_TYPE_BYTE, - &bap->qos.bcast.bcode->iov_base, - bap->qos.bcast.bcode->iov_len); + dict_append_array(&dict, "BCode", DBUS_TYPE_BYTE, + &temp, + sizeof(bap->qos.bcast.bcode)); dict_append_entry(&dict, "Options", DBUS_TYPE_BYTE, &bap->qos.bcast.options); dict_append_entry(&dict, "Skip", DBUS_TYPE_UINT16, @@ -1614,9 +1620,10 @@ static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, /* If a request is pending wait it to complete */ if (owner && owner->pending) return; - bap_update_links(transport); - if (!media_endpoint_is_broadcast(transport->endpoint)) + if (!media_endpoint_is_broadcast(transport->endpoint)) { + bap_update_links(transport); bap_update_qos(transport); + } else if (bt_bap_stream_io_dir(stream) != BT_BAP_BCAST_SOURCE) bap_update_bcast_qos(transport); transport_update_playing(transport, FALSE); @@ -1633,7 +1640,7 @@ static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, bap_update_bcast_qos(transport); break; case BT_BAP_STREAM_STATE_RELEASING: - if (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SINK) + if (!bt_bap_stream_io_dir(stream)) return; break; } diff --git a/src/shared/bap.c b/src/shared/bap.c index 078d308dc..6934c6d2d 100644 --- a/src/shared/bap.c +++ b/src/shared/bap.c @@ -30,6 +30,7 @@ #include "src/shared/bap.h" #include "src/shared/ascs.h" #include "src/shared/bap-debug.h" +#include "src/shared/lc3.h" /* Maximum number of ASE(s) */ #define NUM_SINKS 2 @@ -260,6 +261,8 @@ static struct queue *bap_db; static struct queue *bap_cbs; static struct queue *sessions; +static bool bt_bap_new_bcast_sink(struct bt_bap *bap); + static bool bap_db_match(const void *data, const void *match_data) { const struct bt_bap_db *bdb = data; @@ -1343,6 +1346,12 @@ static void stream_set_state_broadcast(struct bt_bap_stream *stream, bt_bap_ref(bap); + switch (stream->ep->state) { + case BT_ASCS_ASE_STATE_QOS: + bap_stream_update_io_links(stream); + break; + } + for (entry = queue_get_entries(bap->state_cbs); entry; entry = entry->next) { struct bt_bap_state *state = entry->data; @@ -2377,21 +2386,6 @@ static struct bt_bap_pac *bap_pac_find(struct bt_bap_db *bdb, uint8_t type, return NULL; } -static void *ltv_merge(struct iovec *data, struct iovec *cont) -{ - uint8_t delimiter = 0; - - if (!data) - return NULL; - - if (!cont || !cont->iov_len || !cont->iov_base) - return data->iov_base; - - iov_append(data, sizeof(delimiter), &delimiter); - - return iov_append(data, cont->iov_len, cont->iov_base); -} - static void bap_pac_foreach_channel(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { @@ -2427,7 +2421,7 @@ static void bap_pac_merge(struct bt_bap_pac *pac, struct iovec *data, { /* Merge data into existing record */ if (pac->data) - ltv_merge(pac->data, data); + bt_bap_ltv_merge(pac->data, data, true); else pac->data = util_iov_dup(data, 1); @@ -2436,7 +2430,7 @@ static void bap_pac_merge(struct bt_bap_pac *pac, struct iovec *data, /* Merge metadata into existing record */ if (pac->metadata) - ltv_merge(pac->metadata, metadata); + bt_bap_ltv_merge(pac->metadata, metadata, true); else pac->metadata = util_iov_dup(metadata, 1); } @@ -2650,6 +2644,8 @@ static void bap_add_broadcast_source(struct bt_bap_pac *pac) static void bap_add_broadcast_sink(struct bt_bap_pac *pac) { queue_push_tail(pac->bdb->broadcast_sinks, pac); + + pacs_add_sink_location(pac->bdb->pacs, pac->qos.location); } static void notify_pac_added(void *data, void *user_data) @@ -2668,6 +2664,20 @@ static void notify_session_pac_added(void *data, void *user_data) queue_foreach(bap->pac_cbs, notify_pac_added, user_data); } +struct bt_bap_pac *bt_bap_add_vendor_pac_from_pac(struct bt_bap_pac *pac) +{ + struct bt_bap_pac *new_pac; + + new_pac = bt_bap_add_vendor_pac(pac->bdb->db, pac->name, pac->type, + pac->codec.id, + pac->codec.cid, + pac->codec.cid, + &pac->qos, pac->data, + pac->metadata); + bt_bap_pac_set_ops(new_pac, pac->ops, pac->user_data); + return new_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, @@ -2725,7 +2735,7 @@ struct bt_bap_pac *bt_bap_add_pac(struct gatt_db *db, const char *name, struct iovec *metadata) { return bt_bap_add_vendor_pac(db, name, type, id, 0x0000, 0x0000, qos, - data, metadata); + data, metadata); } uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac) @@ -2748,13 +2758,24 @@ uint32_t bt_bap_pac_get_locations(struct bt_bap_pac *pac) switch (pac->type) { case BT_BAP_SOURCE: return pacs->source_loc_value; + case BT_BAP_BCAST_SOURCE: + return pac->qos.location; case BT_BAP_SINK: + case BT_BAP_BCAST_SINK: return pacs->sink_loc_value; default: return 0; } } +struct iovec *bt_bap_pac_get_data(struct bt_bap_pac *pac) +{ + if (!pac) + return 0x00; + + return pac->data; +} + uint16_t bt_bap_pac_get_supported_context(struct bt_bap_pac *pac) { struct bt_pacs *pacs; @@ -2837,12 +2858,26 @@ static void notify_session_pac_removed(void *data, void *user_data) bool bt_bap_pac_set_ops(struct bt_bap_pac *pac, struct bt_bap_pac_ops *ops, void *user_data) { + const struct queue_entry *entry = NULL; + struct bt_bap_pac *pac_entry; + if (!pac) return false; pac->ops = ops; pac->user_data = user_data; + if (pac->type == BT_BAP_BCAST_SOURCE) { + for (entry = queue_get_entries(pac->bdb->broadcast_sources); + entry != NULL; entry = entry->next) { + pac_entry = entry->data; + if (pac_entry != pac) { + pac_entry->ops = ops; + pac_entry->user_data = user_data; + } + } + } + return true; } @@ -4489,6 +4524,11 @@ bool bt_bap_pac_bcast_is_local(struct bt_bap *bap, struct bt_bap_pac *pac) return false; } +char *bt_bap_pac_get_name(struct bt_bap_pac *pac) +{ + return pac->name; +} + static bool find_ep_unused(const void *data, const void *user_data) { const struct bt_bap_endpoint *ep = data; @@ -4618,7 +4658,6 @@ unsigned int bt_bap_stream_config(struct bt_bap_stream *stream, if (stream->lpac->type == BT_BAP_BCAST_SINK) { if (data) stream_config(stream, data, NULL); - stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG); } return 1; } @@ -4763,7 +4802,7 @@ struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap, return NULL; bt_bap_foreach_pac(bap, type, match_pac, &match); - if (!match.lpac) + if ((!match.lpac) || (!lpac)) return NULL; if (!match.rpac && (lpac->type != BT_BAP_BCAST_SOURCE)) return NULL; @@ -4780,7 +4819,15 @@ struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap, if (!ep) { /* Check for unused ASE */ ep = queue_find(bap->remote_eps, find_ep_unused, &match); - if (!ep) { + if (!ep && lpac->type == BT_BAP_BCAST_SOURCE) { + bt_bap_new_bcast_sink(bap); + ep = queue_find(bap->remote_eps, find_ep_unused, + &match); + if (!ep) { + DBG(bap, "Unable to find unused ASE"); + return NULL; + } + } else if (!ep) { DBG(bap, "Unable to find unused ASE"); return NULL; } @@ -4943,11 +4990,10 @@ unsigned int bt_bap_stream_enable(struct bt_bap_stream *stream, case BT_BAP_STREAM_TYPE_BCAST: if (!bt_bap_stream_io_dir(stream)) stream_set_state_broadcast(stream, - BT_BAP_STREAM_STATE_CONFIG); + BT_BAP_STREAM_STATE_CONFIG); else if (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SOURCE) stream_set_state_broadcast(stream, BT_BAP_STREAM_STATE_STREAMING); - return 1; } @@ -5094,13 +5140,21 @@ unsigned int bt_bap_stream_metadata(struct bt_bap_stream *stream, if (!stream) return 0; - if (!stream->client) { - stream_metadata(stream, metadata, NULL); + switch (bt_bap_stream_get_type(stream)) { + case BT_BAP_STREAM_TYPE_UCAST: + if (!stream->client) { + stream_metadata(stream, metadata, NULL); + return 0; + } + return bap_stream_metadata(stream, BT_ASCS_METADATA, + metadata, func, user_data); + case BT_BAP_STREAM_TYPE_BCAST: + util_iov_free(stream->meta, 1); + stream->meta = util_iov_dup(metadata, 1); + return 1; + default: return 0; } - - return bap_stream_metadata(stream, BT_ASCS_METADATA, metadata, func, - user_data); } unsigned int bt_bap_stream_release(struct bt_bap_stream *stream, @@ -5207,6 +5261,14 @@ struct iovec *bt_bap_stream_get_metadata(struct bt_bap_stream *stream) return stream->meta; } +struct bt_bap_pac *bt_bap_stream_get_local_pac(struct bt_bap_stream *stream) +{ + if (!stream) + return NULL; + + return stream->lpac; +} + struct io *bt_bap_stream_get_io(struct bt_bap_stream *stream) { struct bt_bap_stream_io *io; @@ -5265,6 +5327,23 @@ static bool match_name(const void *data, const void *match_data) return (!strcmp(pac->name, name)); } +void *bt_bap_ltv_merge(struct iovec *data, struct iovec *cont, + bool add_delimiter) +{ + uint8_t delimiter = 0; + + if (!data) + return NULL; + + if (!cont || !cont->iov_len || !cont->iov_base) + return data->iov_base; + + if (add_delimiter) + iov_append(data, sizeof(delimiter), &delimiter); + + return iov_append(data, cont->iov_len, cont->iov_base); +} + int bt_bap_stream_cancel(struct bt_bap_stream *stream, unsigned int id) { struct bt_bap_req *req; @@ -5301,9 +5380,9 @@ int bt_bap_stream_io_link(struct bt_bap_stream *stream, 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) + 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; stream->link = link; @@ -5484,11 +5563,301 @@ bool bt_bap_new_bcast_source(struct bt_bap *bap, const char *name) return true; } +static bool bt_bap_new_bcast_sink(struct bt_bap *bap) +{ + struct bt_bap_endpoint *ep; + + /* Push remote endpoint with direction source */ + ep = bap_endpoint_new_broadcast(bap->ldb, BT_BAP_BCAST_SOURCE); + + if (ep) + queue_push_tail(bap->remote_eps, ep); + + ep->bap = bap; + return true; +} + +struct ltv_compare_data { + bool match; + void *data; + uint32_t data32; +}; + +static void bap_sink_match_allocation(size_t i, uint8_t l, uint8_t t, + uint8_t *v, void *user_data) +{ + struct ltv_compare_data *data = user_data; + uint32_t location32; + + if (!v) + return; + + memcpy(&location32, v, l); + + /* If all the bits in the received bitmask are found in + * the local bitmask then we have a match + */ + if ((le32_to_cpu(location32) & data->data32) == + le32_to_cpu(location32)) + data->match = true; + else + data->match = false; +} + +static void bap_sink_match_caps_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v, + void *user_data) +{ + struct ltv_compare_data *local_data = user_data; + struct iovec local_caps = *((struct iovec *)local_data->data); + struct bt_ltv *ltv = NULL; + bool found = false; + + /* Search for {l,t,v} in local_caps */ + do { + ltv = util_iov_pull_mem(&local_caps, sizeof(*ltv)); + + if (ltv != NULL) { + /* If the LTV is found, continue */ + if (ltv->type == t) { + switch (ltv->type) { + case LC3_FREQ: + { + uint16_t val16 = + *((uint16_t *)ltv->value); + val16 = le16_to_cpu(val16); + if (val16 & (1 << (v[0] - 1))) + found = true; + } + break; + case LC3_DURATION: + if ((ltv->value[0]) & (1 << v[0])) + found = true; + break; + /* LC3_CHAN_COUNT */ + case LC3_FRAME_LEN: + { + uint16_t min = + *((uint16_t *)ltv->value); + uint16_t max = + *((uint16_t *)(<v->value[2])); + uint16_t frame_len = *((uint16_t *)v); + + min = le16_to_cpu(min); + max = le16_to_cpu(max); + frame_len = le16_to_cpu(frame_len); + if ((frame_len >= min) && + (frame_len <= max)) + found = true; + } + /* LC3_FRAME_COUNT */ + break; + } + + if (found) + /* Matching LTV found -> mark it + * in the output bitmask + */ + local_data->data32 |= 1<<t; + } + + /* Advance over data field */ + (void)util_iov_pull_mem(&local_caps, ltv->len - 1); + } + } while ((local_caps.iov_len > 0) && (ltv != NULL) && + (found == false)); +} + +static void check_capabilities(void *data, void *user_data) +{ + struct ltv_compare_data *ltv_data = user_data; + + #define Codec_Specific_Configuration_Check_Mask (\ + (1<<LC3_FREQ)|\ + (1<<LC3_DURATION)|\ + /*(1<<LC3_CONFIG_CHAN_ALLOC)|\*/\ + (1<<LC3_FRAME_LEN)) + + /* Keep searching for a matching PAC if one wasn't found + * in previous PAC elements + */ + if (ltv_data->match == false) { + const struct bt_bap_pac *pac = data; + struct iovec *in_caps = (struct iovec *)ltv_data->data; + struct ltv_compare_data local_ltv_data = { + .data = pac->data, + .data32 = 0, /* LTVs bitmask result */ + .match = false + }; + + util_ltv_foreach(in_caps->iov_base, in_caps->iov_len, NULL, + bap_sink_match_caps_ltv, &local_ltv_data); + + /* We have a match if all selected LTVs have a match */ + if (local_ltv_data.data32 & + Codec_Specific_Configuration_Check_Mask) + ltv_data->match = true; + } +} + +static bool bap_compare_ltv_capabilities(struct iovec *in_caps, + uint32_t location, + struct queue *local_data) +{ + struct ltv_compare_data ltv_data = {0}; + uint8_t type = 0; + + /* Check Audio_Channel_Allocation LTV from PACS data */ + if (location) { + ltv_data.data32 = location; + type = LC3_CONFIG_CHAN_ALLOC; + + util_ltv_foreach(in_caps->iov_base, in_caps->iov_len, &type, + bap_sink_match_allocation, <v_data); + } + + /* Check remaining LTVs from PACs list */ + if (ltv_data.match) { + ltv_data.data = in_caps; + ltv_data.match = false; + queue_foreach(local_data, check_capabilities, <v_data); + } + + return ltv_data.match; +} + +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 void print_caps(const char *str, void *user_data) +{ + printf("%s\n\r", str); +} + +static void merge_codec_specific_config(struct iovec *caps_l2, + struct iovec *caps_l3, struct iovec *result) +{ + struct iovec iov_l2 = {0}; + struct iovec iov_l3 = {0}; + struct bt_ltv *ltv2 = NULL; + + /* Loop Level 2 LTVs */ + iov_l2 = *caps_l2; + do { + iov_l3 = *caps_l3; + ltv2 = util_iov_pull_mem(&iov_l2, sizeof(*ltv2)); + + if (ltv2 != NULL) { + struct bt_ltv *ltv3 = NULL; + bool found = false; /* found at Level3? */ + + /* Loop Level 3 LTVs */ + do { + ltv3 = util_iov_pull_mem(&iov_l3, + sizeof(*ltv3)); + + if (ltv3 != NULL) { + /* If the LTV is found, + * continue + */ + if (ltv3->type == ltv2->type) + found = true; + + /* Advance over data field */ + (void)util_iov_pull_mem(&iov_l3, + ltv3->len - 1); + } + } while ((iov_l3.iov_len > 0) && (ltv3 != NULL) && + (found == false)); + + /* In case LTV (L3) was not found at L2 add it */ + if (!found) + iov_append(result, ltv2->len + 1, ltv2); + /* In case LTV (L3) was found at L2, + * get the one at Level 3 + */ + else + iov_append(result, ltv3->len + 1, ltv3); + + /* Advance over data field */ + (void)util_iov_pull_mem(&iov_l2, ltv2->len - 1); + } + } while ((iov_l2.iov_len > 0) && (ltv2 != NULL)); +} + +static void bis_to_pac(void *data, void *user_data) +{ + struct bt_bap_bis *bis = data; + struct bt_bap_base_subgroup *subgroup = user_data; + struct bt_bap_pac *pac_source_bis; + struct bt_bap_endpoint *ep; + int err = 0; + struct bt_bap_pac_qos bis_qos = {0}; + uint8_t type = 0; + struct iovec *iov_bis = new0(struct iovec, 1); + + /* Create a Codec Specific Configuration with LTVs at level 2 + * overwritten by LTVs at level 3 + */ + merge_codec_specific_config(subgroup->caps, bis->caps, iov_bis); + + /* Search in local database to see if this BIS + * matches our capabilities + */ + if (bap_compare_ltv_capabilities(iov_bis, + subgroup->bap->ldb->pacs->sink_loc_value, + subgroup->bap->ldb->broadcast_sinks) != true) + return; + + DBG(subgroup->bap, "Selected BIS %i", bis->bisIndex); + bt_bap_debug_caps(iov_bis->iov_base, iov_bis->iov_len, + print_caps, NULL); + + /* Create a QoS structure based on the received BIS information */ + type = LC3_CONFIG_CHAN_ALLOC; + util_ltv_foreach(iov_bis->iov_base, iov_bis->iov_len, &type, + bap_sink_get_allocation, &bis_qos.location); + + /* Create a remote PAC */ + pac_source_bis = bap_pac_new(subgroup->bap->rdb, NULL, + BT_BAP_BCAST_SOURCE, &subgroup->codec, &bis_qos, + iov_bis, subgroup->meta); + + err = asprintf(&pac_source_bis->name, "%d", bis->bisIndex); + + if (err < 0) + DBG(subgroup->bap, "error in asprintf"); + + /* Add remote source endpoint */ + if (!subgroup->bap->rdb->broadcast_sources) + subgroup->bap->rdb->broadcast_sources = queue_new(); + queue_push_tail(subgroup->bap->rdb->broadcast_sources, pac_source_bis); + + queue_foreach(subgroup->bap->pac_cbs, notify_pac_added, pac_source_bis); + /* Push remote endpoint with direction sink */ + ep = bap_endpoint_new_broadcast(subgroup->bap->rdb, BT_BAP_BCAST_SINK); + + if (ep) + queue_push_tail(subgroup->bap->remote_eps, ep); + + util_iov_free(iov_bis, 1); +} +static void parse_bis(void *data, void *user_data) +{ + struct bt_bap_base_subgroup *subgroup = data; + + queue_foreach(subgroup->bises, bis_to_pac, subgroup); +} void bt_bap_update_bcast_source(struct bt_bap_pac *pac, - struct bt_bap_codec *codec, - struct iovec *data, - struct iovec *metadata) + struct bt_bap_base *base) { - bap_pac_merge(pac, data, metadata); - pac->codec = *codec; + queue_foreach(base->subgroups, parse_bis, NULL); } diff --git a/src/shared/bap.h b/src/shared/bap.h index 51edc08ab..814b3af9e 100644 --- a/src/shared/bap.h +++ b/src/shared/bap.h @@ -79,7 +79,7 @@ struct bt_bap_bcast_qos { uint8_t packing; uint8_t framing; uint8_t encryption; - struct iovec *bcode; + uint8_t bcode[16]; uint8_t options; uint16_t skip; uint16_t sync_timeout; @@ -87,6 +87,7 @@ struct bt_bap_bcast_qos { uint8_t mse; uint16_t timeout; uint8_t pa_sync; + uint32_t delay; /* Presentation Delay */ struct bt_bap_io_qos io_qos; }; @@ -97,6 +98,27 @@ struct bt_bap_qos { }; }; +struct bt_bap_base { + uint32_t pres_delay; + uint8_t num_subgroups; + struct queue *subgroups; +}; + +struct bt_bap_base_subgroup { + uint8_t subgroup_index; + struct bt_bap *bap; + uint8_t num_bises; + struct bt_bap_codec codec; + struct iovec *caps; + struct iovec *meta; + struct queue *bises; +}; + +struct bt_bap_bis { + uint8_t bisIndex; + struct iovec *caps; +}; + typedef void (*bt_bap_ready_func_t)(struct bt_bap *bap, void *user_data); typedef void (*bt_bap_destroy_func_t)(void *user_data); typedef void (*bt_bap_debug_func_t)(const char *str, void *user_data); @@ -168,6 +190,8 @@ uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac); uint32_t bt_bap_pac_get_locations(struct bt_bap_pac *pac); +struct iovec *bt_bap_pac_get_data(struct bt_bap_pac *pac); + uint16_t bt_bap_pac_get_supported_context(struct bt_bap_pac *pac); uint16_t bt_bap_pac_get_context(struct bt_bap_pac *pac); @@ -176,6 +200,8 @@ struct bt_bap_pac_qos *bt_bap_pac_get_qos(struct bt_bap_pac *pac); uint8_t bt_bap_stream_get_type(struct bt_bap_stream *stream); +struct bt_bap_pac *bt_bap_add_vendor_pac_from_pac(struct bt_bap_pac *pac); + struct bt_bap_stream *bt_bap_pac_get_stream(struct bt_bap_pac *pac); /* Session related function */ @@ -231,6 +257,7 @@ int bt_bap_pac_get_codec(struct bt_bap_pac *pac, uint8_t *id, void bt_bap_pac_set_user_data(struct bt_bap_pac *pac, void *user_data); void *bt_bap_pac_get_user_data(struct bt_bap_pac *pac); +char *bt_bap_pac_get_name(struct bt_bap_pac *pac); /* Stream related functions */ int bt_bap_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, @@ -294,6 +321,7 @@ uint32_t bt_bap_stream_get_location(struct bt_bap_stream *stream); struct iovec *bt_bap_stream_get_config(struct bt_bap_stream *stream); struct bt_bap_qos *bt_bap_stream_get_qos(struct bt_bap_stream *stream); struct iovec *bt_bap_stream_get_metadata(struct bt_bap_stream *stream); +struct bt_bap_pac *bt_bap_stream_get_local_pac(struct bt_bap_stream *stream); struct io *bt_bap_stream_get_io(struct bt_bap_stream *stream); bool bt_bap_match_bcast_sink_stream(const void *data, const void *user_data); @@ -315,9 +343,9 @@ bool bt_bap_stream_io_is_connecting(struct bt_bap_stream *stream, int *fd); bool bt_bap_new_bcast_source(struct bt_bap *bap, const char *name); void bt_bap_update_bcast_source(struct bt_bap_pac *pac, - struct bt_bap_codec *codec, - struct iovec *data, - struct iovec *metadata); + struct bt_bap_base *base); +void *bt_bap_ltv_merge(struct iovec *data, struct iovec *cont, + bool add_delimiter); bool bt_bap_pac_bcast_is_local(struct bt_bap *bap, struct bt_bap_pac *pac);