From 7e7b4f00c46ce79adef4415d143369ec5f099b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Kopy=C5=9Bci=C5=84ski?= Date: Fri, 20 Oct 2023 12:44:04 +0200 Subject: [PATCH] nimble/host: initial Broadcast source implementation This patch implements Broadcast feature in host. It includes implementation of ISO in host, with application API, HCI commands and ISO event handling. Host is now capable of managing BIGs with subgroups and BISes, updating Broadcast announcements and pushing data to ISO, to send in BIS events. Maximum extended advertising size was set to 251 to fit Periodic advertising data connected with Broadcast Audio Announcement Service advertisements by default. This patch does not implement Broadcast sink, only source. --- nimble/host/include/host/ble_hs.h | 1 + nimble/host/include/host/ble_hs_adv.h | 3 + nimble/host/include/host/ble_iso.h | 3 - nimble/host/src/ble_audio_broadcast.c | 486 ++++++++++++++++++++++++++ nimble/host/src/ble_hs.c | 10 + nimble/host/src/ble_hs_adv.c | 11 + nimble/host/src/ble_hs_hci_evt.c | 44 +++ nimble/host/src/ble_iso.c | 386 ++++++++++++++++++++ nimble/host/src/ble_iso_priv.h | 38 ++ nimble/host/syscfg.yml | 14 + 10 files changed, 993 insertions(+), 3 deletions(-) create mode 100644 nimble/host/src/ble_audio_broadcast.c create mode 100644 nimble/host/src/ble_iso.c create mode 100644 nimble/host/src/ble_iso_priv.h diff --git a/nimble/host/include/host/ble_hs.h b/nimble/host/include/host/ble_hs.h index 09900fd39..5079648fa 100644 --- a/nimble/host/include/host/ble_hs.h +++ b/nimble/host/include/host/ble_hs.h @@ -43,6 +43,7 @@ #include "host/ble_sm.h" #include "host/ble_store.h" #include "host/ble_uuid.h" +#include "host/ble_iso.h" #include "nimble/nimble_npl.h" #ifdef __cplusplus diff --git a/nimble/host/include/host/ble_hs_adv.h b/nimble/host/include/host/ble_hs_adv.h index a644add69..b498f6754 100644 --- a/nimble/host/include/host/ble_hs_adv.h +++ b/nimble/host/include/host/ble_hs_adv.h @@ -249,6 +249,9 @@ struct ble_hs_adv_fields { /** Common Data Type: Mesh Beacon. */ #define BLE_HS_ADV_TYPE_MESH_BEACON 0x2b +/** Common Data Type: Broadcast Name. */ +#define BLE_HS_ADV_TYPE_BROADCAST_NAME 0x30 + /** Common Data Type: Manufacturer Specific Data. */ #define BLE_HS_ADV_TYPE_MFG_DATA 0xff diff --git a/nimble/host/include/host/ble_iso.h b/nimble/host/include/host/ble_iso.h index 653b0acc7..44877dca6 100644 --- a/nimble/host/include/host/ble_iso.h +++ b/nimble/host/include/host/ble_iso.h @@ -21,8 +21,6 @@ #define H_BLE_ISO_ #include "syscfg/syscfg.h" -#if MYNEWT_VAL(BLE_ISO) - /** ISO event: BIG Create Completed */ #define BLE_ISO_EVENT_BIG_CREATE_COMPLETE 0 @@ -124,4 +122,3 @@ int ble_iso_tx(uint16_t conn_handle, void *data, uint16_t data_len); int ble_iso_init(void); #endif -#endif diff --git a/nimble/host/src/ble_audio_broadcast.c b/nimble/host/src/ble_audio_broadcast.c new file mode 100644 index 000000000..b3ec29ce2 --- /dev/null +++ b/nimble/host/src/ble_audio_broadcast.c @@ -0,0 +1,486 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "host/ble_uuid.h" +#include "host/ble_audio_broadcast.h" + +#include "os/util.h" + +#if MYNEWT_VAL(BLE_ISO_BROADCASTER) +struct ble_audio_broadcast { + SLIST_ENTRY(ble_audio_broadcast) next; + uint8_t adv_instance; + struct ble_audio_base *base; + struct ble_iso_big_params *big_params; + ble_audio_broadcast_destroy_fn *destroy_cb; + void *args; +}; + +static SLIST_HEAD(, ble_audio_broadcast) ble_audio_broadcasts; +static struct os_mempool ble_audio_broadcast_pool; +static os_membuf_t ble_audio_broadcast_mem[ + OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_MAX_BIG), + sizeof(struct ble_audio_broadcast))]; + +static bool +broadcast_bis_idx_ok(uint8_t bis_idx, struct ble_audio_base *base) +{ + struct ble_audio_big_subgroup *big; + struct ble_audio_bis *bis; + uint8_t idx_cnt = 0; + + STAILQ_FOREACH(big, &base->subs, next) { + STAILQ_FOREACH(bis, &big->bises, next) { + if (bis_idx == bis->idx) { + idx_cnt++; + } + } + } + + return idx_cnt == 1; +} + +static bool +broadcast_ltv_ok(uint8_t total_len, uint8_t *data) +{ + if (total_len == 0 && data == NULL) { + return true; + } + uint8_t len = data[0]; + uint8_t sum_len = 0; + + while (total_len > sum_len) { + sum_len += len + 1; + len = data[sum_len]; + } + + return total_len == sum_len; +} + +static struct ble_audio_broadcast * +ble_audio_broadcast_find(uint8_t adv_instance) +{ + struct ble_audio_broadcast *broadcast; + + SLIST_FOREACH(broadcast, &ble_audio_broadcasts, next) { + if (broadcast->adv_instance == adv_instance) { + return broadcast; + } + } + + return NULL; +} + +static struct ble_audio_broadcast * +ble_audio_broadcast_find_last() +{ + struct ble_audio_broadcast *broadcast; + + SLIST_FOREACH(broadcast, &ble_audio_broadcasts, next) { + } + + return broadcast; +} + +int +ble_audio_broadcast_create(const struct ble_broadcast_create_params *params, + ble_audio_broadcast_destroy_fn *destroy_cb, + void *args, + ble_gap_event_fn *gap_cb) +{ + int rc; + uint8_t offset = 2; + uint8_t service_data[5] = {0x52, 0x18 }; + uint8_t per_svc_data[MYNEWT_VAL(BLE_EXT_ADV_MAX_SIZE)] = { 0x51, 0x18 }; + struct ble_audio_big_subgroup *subgroup; + struct ble_audio_bis *bis; + struct ble_hs_adv_fields adv_fields = { + .broadcast_name = (uint8_t *) params->name, + .broadcast_name_len = params->name != NULL ? strlen(params->name) : 0, + .svc_data_uuid16 = service_data, + .svc_data_uuid16_len = sizeof(service_data), + }; + struct ble_hs_adv_fields per_adv_fields = { 0 }; + struct os_mbuf *adv_data; + ble_uuid16_t audio_announcement_uuid[1]; + uint8_t broadcast_id[3]; + struct ble_audio_broadcast *broadcast; + + if (strlen(params->name) < 4 || strlen(params->name) > 32) { + return BLE_HS_EINVAL; + } + + broadcast = ble_audio_broadcast_find(params->adv_instance); + if (broadcast) { + return BLE_HS_EALREADY; + } + + ble_hs_hci_rand(broadcast_id, 3); + params->base->broadcast_id = get_le24(broadcast_id); + + broadcast = os_memblock_get(&ble_audio_broadcast_pool); + + broadcast->adv_instance = params->adv_instance; + broadcast->base = params->base; + broadcast->big_params = params->big_params; + broadcast->destroy_cb = destroy_cb; + broadcast->args = args; + + if (SLIST_EMPTY(&ble_audio_broadcasts)) { + SLIST_INSERT_HEAD(&ble_audio_broadcasts, broadcast, next); + } else { + SLIST_INSERT_AFTER(ble_audio_broadcast_find_last(), broadcast, next); + } + + if (params->base->num_subgroups == 0) { + /** + * BAP specification 3.7.2.2 Basic Audio Announcements: + * Rule 1: There shall be at least one subgroup. + */ + BLE_HS_LOG_ERROR("No subgroups in BASE!\n"); + return BLE_HS_EINVAL; + } + + put_le24(&service_data[2], broadcast->base->broadcast_id); + put_le24(&per_svc_data[offset], params->base->presentation_delay); + offset += 3; + per_svc_data[offset] = params->base->num_subgroups; + offset++; + + STAILQ_FOREACH(subgroup, ¶ms->base->subs, next) { + if (subgroup->bis_cnt == 0) { + /** + * BAP specification 3.7.2.2 Basic Audio Announcements: + * Rule 2: There shall be at least one BIS per subgroup. + */ + BLE_HS_LOG_ERROR("No BIS in BIG!\n"); + return BLE_HS_EINVAL; + } + + if (!broadcast_ltv_ok(subgroup->metadata_len, subgroup->metadata)) { + BLE_HS_LOG_ERROR("Invalid Metadata!\n"); + return BLE_HS_EINVAL; + } + + if (!broadcast_ltv_ok(subgroup->codec_spec_config_len, subgroup->codec_spec_config)) { + BLE_HS_LOG_ERROR("Invalid Codec Configuration in subgroup!\n"); + return BLE_HS_EINVAL; + } + + per_svc_data[offset] = subgroup->bis_cnt; + offset++; + memcpy(&per_svc_data[offset], &subgroup->codec_id, 5); + offset += 5; + per_svc_data[offset] = subgroup->codec_spec_config_len; + offset++; + memcpy(&per_svc_data[offset], &subgroup->codec_spec_config, + subgroup->codec_spec_config_len); + offset += subgroup->codec_spec_config_len; + per_svc_data[offset] = subgroup->metadata_len; + offset++; + memcpy(&per_svc_data[offset], &subgroup->metadata, + subgroup->metadata_len); + offset += subgroup->metadata_len; + STAILQ_FOREACH(bis, &subgroup->bises, next) { + if (!broadcast_bis_idx_ok(bis->idx, params->base)) { + /** + * BAP specification 3.7.2.2 Basic Audio Announcements: + * Rule 3: Every BIS in the BIG, denoted by its BIS_index + * value, shall only be present in one subgroup. + */ + BLE_HS_LOG_ERROR("Duplicated BIS index!\n"); + return BLE_HS_EINVAL; + } + + if (!broadcast_ltv_ok(bis->codec_spec_config_len, + bis->codec_spec_config)) { + BLE_HS_LOG_ERROR("Invalid Codec Configuration in BIS!\n"); + return BLE_HS_EINVAL; + } + per_svc_data[offset] = bis->idx; + offset++; + per_svc_data[offset] = bis->codec_spec_config_len; + offset++; + memcpy(&per_svc_data[offset], bis->codec_spec_config, + bis->codec_spec_config_len); + offset += bis->codec_spec_config_len; + } + } + + audio_announcement_uuid[0] = (ble_uuid16_t) BLE_UUID16_INIT(BLE_BROADCAST_AUDIO_ANNOUNCEMENT_SVC_UUID); + per_adv_fields.uuids16 = audio_announcement_uuid; + per_adv_fields.num_uuids16 = 1; + per_adv_fields.uuids16_is_complete = 1; + per_adv_fields.svc_data_uuid16 = per_svc_data; + per_adv_fields.svc_data_uuid16_len = offset; + + rc = ble_gap_ext_adv_configure(params->adv_instance, + params->extended_params, 0, + gap_cb, NULL); + if (rc) { + BLE_HS_LOG_ERROR("Could not configure the broadcast (rc=%d)\n", rc); + return 0; + } + + adv_data = os_msys_get_pkthdr(BLE_HCI_MAX_EXT_ADV_DATA_LEN, 0); + if (!adv_data) { + BLE_HS_LOG_ERROR("No memory\n"); + return BLE_HS_ENOMEM; + } + + /* Set ext advertising data */ + rc = ble_hs_adv_set_fields_mbuf(&adv_fields, adv_data); + if (rc) { + BLE_HS_LOG_ERROR("Failed to set extended advertising fields" + "(rc=%d)\n", rc); + return rc; + } + + os_mbuf_append(adv_data, params->svc_data, params->svc_data_len); + + rc = ble_gap_ext_adv_set_data(params->adv_instance, adv_data); + if (rc) { + BLE_HS_LOG_ERROR("Failed to set extended advertising data" + "(rc=%d)\n", rc); + return rc; + } + + /* Clear buffer */ + adv_data = os_msys_get_pkthdr(BLE_HCI_MAX_EXT_ADV_DATA_LEN, 0); + if (!adv_data) { + BLE_HS_LOG_ERROR("No memory\n"); + return BLE_HS_ENOMEM; + } + + rc = ble_gap_periodic_adv_configure(params->adv_instance, params->periodic_params); + if (rc) { + BLE_HS_LOG_ERROR("failed to configure periodic advertising" + "(rc=%d)\n", rc); + return rc; + } + + rc = ble_hs_adv_set_fields_mbuf(&per_adv_fields, adv_data); + if (rc) { + BLE_HS_LOG_ERROR("Failed to set periodic advertising fields" + "(rc=%d)\n", rc); + return rc; + } + + rc = ble_gap_periodic_adv_set_data(params->adv_instance, adv_data, NULL); + if (rc) { + BLE_HS_LOG_ERROR("Failed to set periodic advertising data" + "(rc=%d)\n", rc); + } + + return rc; +} + +int +ble_audio_broadcast_start(uint8_t adv_instance, + ble_iso_event_fn *cb, void *cb_arg) +{ + struct ble_audio_big_subgroup *subgroup; + struct ble_iso_create_big_params create_big_params = { 0 }; + struct ble_audio_broadcast *broadcast; + int rc; + + broadcast = ble_audio_broadcast_find(adv_instance); + if (!broadcast) { + return BLE_HS_ENOENT; + } + + STAILQ_FOREACH(subgroup, &broadcast->base->subs, next) { + create_big_params.bis_cnt += subgroup->bis_cnt; + } + + create_big_params.adv_handle = broadcast->adv_instance; + create_big_params.cb = cb; + create_big_params.cb_arg = cb_arg; + + rc = ble_gap_ext_adv_start(broadcast->adv_instance, 0, 0); + if (rc) { + BLE_HS_LOG_ERROR("Failed to start extended advertising (rc=%d)\n", rc); + return rc; + } + + rc = ble_gap_periodic_adv_start(broadcast->adv_instance, NULL); + if (rc) { + BLE_HS_LOG_ERROR("Failed to start periodic advertising (rc=%d)\n", rc); + return rc; + } + + rc = ble_iso_create_big(&create_big_params, broadcast->big_params); + if (rc) { + BLE_HS_LOG_ERROR("Failed to create BIG (rc=%d)\n", rc); + return rc; + } + + return 0; +} + +int +ble_audio_broadcast_stop(uint8_t adv_instance) +{ + int rc; + + rc = ble_gap_ext_adv_stop(adv_instance); + if (rc) { + BLE_HS_LOG_ERROR("Failed to stop extended advertising (rc=%d)\n", rc); + return rc; + } + + rc = ble_gap_periodic_adv_stop(adv_instance); + if (rc) { + BLE_HS_LOG_ERROR("Failed to stop periodic advertising (rc=%d)\n", rc); + return rc; + } + + return 0; +} + +int +ble_audio_broadcast_destroy(uint8_t adv_instance) +{ + struct ble_audio_broadcast *broadcast; + int rc; + + broadcast = ble_audio_broadcast_find(adv_instance); + if (!broadcast) { + return BLE_HS_ENOENT; + } + + rc = ble_gap_ext_adv_remove(adv_instance); + if (rc) { + BLE_HS_LOG_ERROR("Failed to remove extended advertising\n"); + return rc; + } + + rc = ble_iso_terminate_big(adv_instance); + if (rc) { + BLE_HS_LOG_ERROR("Failed to terminate BIG\n"); + return rc; + } + + os_memblock_put(&ble_audio_broadcast_pool, broadcast); + SLIST_REMOVE(&ble_audio_broadcasts, broadcast, ble_audio_broadcast, next); + + return 0; +} + +int +ble_audio_broadcast_update(const struct ble_broadcast_update_params *params) +{ + uint8_t service_data[5] = {0x52, 0x18 }; + struct ble_hs_adv_fields adv_fields = { + .broadcast_name = (uint8_t *) params->name, + .broadcast_name_len = params->name != NULL ? strlen(params->name) : 0, + .svc_data_uuid16 = service_data, + .svc_data_uuid16_len = sizeof(service_data) + }; + struct os_mbuf *adv_data; + int rc; + + if (strlen(params->name) < 4 || strlen(params->name) > 32) { + return BLE_HS_EINVAL; + } + + memcpy(&service_data[2], ¶ms->broadcast_id, 3); + + adv_data = os_msys_get_pkthdr(BLE_HCI_MAX_EXT_ADV_DATA_LEN, 0); + + os_mbuf_append(adv_data, params->svc_data, params->svc_data_len); + + /* Set ext advertising data */ + rc = ble_hs_adv_set_fields_mbuf(&adv_fields, adv_data); + if (rc) { + BLE_HS_LOG_ERROR("Failed to set extended advertising fields\n"); + return rc; + } + + rc = ble_gap_ext_adv_set_data(params->adv_instance, adv_data); + if (rc) { + BLE_HS_LOG_ERROR("Failed to set extended advertising data\n"); + return rc; + } + + return 0; +} + +int +ble_audio_broadcast_build_sub(struct ble_audio_base *base, + struct ble_audio_big_subgroup *sub, + const struct ble_broadcast_subgroup_params + *params) +{ + sub->codec_id = *params->codec_id; + memcpy(sub->codec_spec_config, params->codec_spec_config, + params->codec_spec_config_len); + sub->codec_spec_config_len = params->codec_spec_config_len; + memcpy(sub->metadata, params->metadata, + params->metadata_len); + sub->metadata_len = params->metadata_len; + + if (STAILQ_EMPTY(&base->subs)) { + STAILQ_INSERT_HEAD(&base->subs, sub, next); + } else { + STAILQ_INSERT_TAIL(&base->subs, sub, next); + } + + base->num_subgroups++; + return 0; +} + +int +ble_audio_broadcast_build_bis(struct ble_audio_big_subgroup *sub, + struct ble_audio_bis *bis, + const struct ble_broadcast_bis_params + *params) +{ + bis->idx = params->idx; + memcpy(bis->codec_spec_config, params->codec_spec_config, + params->codec_spec_config_len); + bis->codec_spec_config_len = params->codec_spec_config_len; + + if (STAILQ_EMPTY(&sub->bises)) { + STAILQ_INSERT_HEAD(&sub->bises, bis, next); + } else { + STAILQ_INSERT_TAIL(&sub->bises, bis, next); + } + + sub->bis_cnt++; + return 0; +} + + +int +ble_audio_broadcast_init(void) +{ + int rc; + + SLIST_INIT(&ble_audio_broadcasts); + + rc = os_mempool_init(&ble_audio_broadcast_pool, + MYNEWT_VAL(BLE_MAX_BIG), + sizeof(struct ble_audio_broadcast), + ble_audio_broadcast_mem, "ble_audio_broadcast_pool"); + SYSINIT_PANIC_ASSERT(rc == 0); + + return 0; +} +#endif diff --git a/nimble/host/src/ble_hs.c b/nimble/host/src/ble_hs.c index bab8fe111..41bf5e2c3 100644 --- a/nimble/host/src/ble_hs.c +++ b/nimble/host/src/ble_hs.c @@ -24,6 +24,7 @@ #include "syscfg/syscfg.h" #include "stats/stats.h" #include "host/ble_hs.h" +#include "host/ble_audio_broadcast.h" #include "ble_hs_priv.h" #include "nimble/nimble_npl.h" #ifndef MYNEWT @@ -752,6 +753,15 @@ ble_hs_init(void) SYSINIT_PANIC_ASSERT(rc == 0); #endif +#if MYNEWT_VAL(BLE_ISO) + rc = ble_iso_init(); + SYSINIT_PANIC_ASSERT(rc == 0); +#if MYNEWT_VAL(BLE_ISO_BROADCASTER) + rc = ble_audio_broadcast_init(); + SYSINIT_PANIC_ASSERT(rc == 0); +#endif +#endif + ble_hs_stop_init(); ble_mqueue_init(&ble_hs_rx_q, ble_hs_event_rx_data, NULL); diff --git a/nimble/host/src/ble_hs_adv.c b/nimble/host/src/ble_hs_adv.c index 1d938b958..44c28c0cc 100644 --- a/nimble/host/src/ble_hs_adv.c +++ b/nimble/host/src/ble_hs_adv.c @@ -354,6 +354,17 @@ adv_set_fields(const struct ble_hs_adv_fields *adv_fields, } } + /*** 0x30 - Broadcast name. */ + if (adv_fields->broadcast_name != NULL && adv_fields->broadcast_name_len > 0) { + rc = ble_hs_adv_set_flat_mbuf(BLE_HS_ADV_TYPE_BROADCAST_NAME, + adv_fields->broadcast_name_len, + adv_fields->broadcast_name, dst, + &dst_len_local, max_len, om); + if (rc != 0) { + return rc; + } + } + /*** 0x16 - Service data - 16-bit UUID. */ if (adv_fields->svc_data_uuid16 != NULL && adv_fields->svc_data_uuid16_len) { rc = ble_hs_adv_set_flat_mbuf(BLE_HS_ADV_TYPE_SVC_DATA_UUID16, diff --git a/nimble/host/src/ble_hs_hci_evt.c b/nimble/host/src/ble_hs_hci_evt.c index a128e3ab6..49a0993c3 100644 --- a/nimble/host/src/ble_hs_hci_evt.c +++ b/nimble/host/src/ble_hs_hci_evt.c @@ -23,7 +23,9 @@ #include "os/os.h" #include "nimble/hci_common.h" #include "host/ble_gap.h" +#include "host/ble_iso.h" #include "ble_hs_priv.h" +#include "ble_iso_priv.h" _Static_assert(sizeof (struct hci_data_hdr) == BLE_HCI_DATA_HDR_SZ, "struct hci_data_hdr must be 4 bytes"); @@ -63,6 +65,10 @@ static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_periodic_adv_rpt; static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_periodic_adv_sync_lost; static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_scan_req_rcvd; static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_periodic_adv_sync_transfer; +#if MYNEWT_VAL(BLE_ISO_BROADCASTER) +static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_create_big_complete; +static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_terminate_big_complete; +#endif #if MYNEWT_VAL(BLE_PERIODIC_ADV_SYNC_BIGINFO_REPORTS) static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_biginfo_adv_report; #endif @@ -131,6 +137,12 @@ static ble_hs_hci_evt_le_fn * const ble_hs_hci_evt_le_dispatch[] = { [BLE_HCI_LE_SUBEV_ADV_SET_TERMINATED] = ble_hs_hci_evt_le_adv_set_terminated, [BLE_HCI_LE_SUBEV_SCAN_REQ_RCVD] = ble_hs_hci_evt_le_scan_req_rcvd, [BLE_HCI_LE_SUBEV_PERIODIC_ADV_SYNC_TRANSFER] = ble_hs_hci_evt_le_periodic_adv_sync_transfer, +#if MYNEWT_VAL(BLE_ISO_BROADCASTER) + [BLE_HCI_LE_SUBEV_CREATE_BIG_COMPLETE] = + ble_hs_hci_evt_le_create_big_complete, + [BLE_HCI_LE_SUBEV_TERMINATE_BIG_COMPLETE] = + ble_hs_hci_evt_le_terminate_big_complete, +#endif #if MYNEWT_VAL(BLE_PERIODIC_ADV_SYNC_BIGINFO_REPORTS) [BLE_HCI_LE_SUBEV_BIGINFO_ADV_REPORT] = ble_hs_hci_evt_le_biginfo_adv_report, #endif @@ -735,6 +747,38 @@ ble_hs_hci_evt_le_periodic_adv_sync_transfer(uint8_t subevent, const void *data, return 0; } +#if MYNEWT_VAL(BLE_ISO_BROADCASTER) +static int +ble_hs_hci_evt_le_create_big_complete(uint8_t subevent, const void *data, + unsigned int len) +{ + const struct ble_hci_ev_le_subev_create_big_complete *ev = data; + + if (len != sizeof(*ev) + (ev->num_bis * sizeof(ev->conn_handle[0]))) { + return BLE_HS_EBADDATA; + } + + ble_iso_rx_create_big_complete(ev); + + return 0; +} + +static int +ble_hs_hci_evt_le_terminate_big_complete(uint8_t subevent, const void *data, + unsigned int len) +{ + const struct ble_hci_ev_le_subev_terminate_big_complete *ev = data; + + if (len != sizeof(*ev)) { + return BLE_HS_EBADDATA; + } + + ble_iso_rx_terminate_big_complete(ev); + + return 0; +} +#endif + #if MYNEWT_VAL(BLE_PERIODIC_ADV_SYNC_BIGINFO_REPORTS) static int ble_hs_hci_evt_le_biginfo_adv_report(uint8_t subevent, const void *data, diff --git a/nimble/host/src/ble_iso.c b/nimble/host/src/ble_iso.c new file mode 100644 index 000000000..ba0fd3056 --- /dev/null +++ b/nimble/host/src/ble_iso.c @@ -0,0 +1,386 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include "syscfg/syscfg.h" +#include "ble_hs_mbuf_priv.h" + +#if MYNEWT_VAL(BLE_ISO) + +#include "os/os_mbuf.h" +#include "host/ble_hs_log.h" +#include "host/ble_hs.h" +#include "host/ble_iso.h" +#include "nimble/hci_common.h" +#include "sys/queue.h" +#include "ble_hs_hci_priv.h" + +struct ble_iso_big { + SLIST_ENTRY(ble_iso_big) next; + uint8_t id; + uint16_t max_pdu; + uint8_t num_bis; + uint16_t conn_handles[MYNEWT_VAL(BLE_MAX_BIS)]; + + ble_iso_event_fn *cb; + void *cb_arg; +}; + +static SLIST_HEAD(, ble_iso_big) ble_iso_bigs; +static struct os_mempool ble_iso_big_pool; +static os_membuf_t ble_iso_big_mem[ + OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_MAX_BIG), sizeof (struct ble_iso_big))]; + +static struct ble_iso_big * +ble_iso_big_alloc(uint8_t adv_handle) +{ + struct ble_iso_big *new_big; + struct ble_iso_big *big; + + if (adv_handle >= BLE_ADV_INSTANCES) { + BLE_HS_LOG_ERROR("Invalid advertising instance"); + return NULL; + } + + if (!ble_gap_ext_adv_active(adv_handle)) { + BLE_HS_LOG_ERROR("Instance not active"); + return NULL; + } + + new_big = os_memblock_get(&ble_iso_big_pool); + if (new_big == NULL) { + BLE_HS_LOG_ERROR("No more memory in pool"); + /* Out of memory. */ + return NULL; + } + + memset(new_big, 0, sizeof *new_big); + + SLIST_FOREACH(big, &ble_iso_bigs, next) { + if (big->id == adv_handle) { + BLE_HS_LOG_ERROR("Advertising instance (%d) already in use", + adv_handle); + return NULL; + } + } + + new_big->id = adv_handle; + + if (SLIST_EMPTY(&ble_iso_bigs)) { + SLIST_INSERT_HEAD(&ble_iso_bigs, new_big, next); + } else { + SLIST_INSERT_AFTER(big, new_big, next); + } + + return new_big; +} + +static struct ble_iso_big * +ble_iso_big_find_by_id(uint8_t big_id) +{ + struct ble_iso_big *big; + + SLIST_FOREACH(big, &ble_iso_bigs, next) { + if (big->id == big_id) { + return big; + } + } + + return NULL; +} + +static int +ble_iso_big_free(struct ble_iso_big *big) +{ + SLIST_REMOVE(&ble_iso_bigs, big, ble_iso_big, next); + os_memblock_put(&ble_iso_big_pool, big); + return 0; +} + +int +ble_iso_create_big(const struct ble_iso_create_big_params *create_params, + const struct ble_iso_big_params *big_params) +{ + struct ble_hci_le_create_big_cp cp = { 0 }; + struct ble_iso_big *big; + + big = ble_iso_big_alloc(create_params->adv_handle); + if (big == NULL) { + return BLE_HS_ENOENT; + } + + big->cb = create_params->cb; + big->cb_arg = create_params->cb_arg; + + cp.big_handle = create_params->adv_handle; + + cp.adv_handle = create_params->adv_handle; + if (create_params->bis_cnt > MYNEWT_VAL(BLE_MAX_BIS)) { + return BLE_HS_EINVAL; + } + + cp.num_bis = create_params->bis_cnt; + put_le24(cp.sdu_interval, big_params->sdu_interval); + cp.max_sdu = big_params->max_sdu; + cp.max_transport_latency = big_params->max_transport_latency; + cp.rtn = big_params->rtn; + cp.phy = big_params->phy; + cp.packing = big_params->packing; + cp.framing = big_params->framing; + cp.encryption = big_params->encryption; + if (big_params->encryption) { + memcpy(cp.broadcast_code, big_params->broadcast_code, 16); + } + + return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE, + BLE_HCI_OCF_LE_CREATE_BIG), + &cp, sizeof(cp),NULL, 0); +} + +int +ble_iso_terminate_big(uint8_t big_id) +{ + struct ble_hci_le_terminate_big_cp cp; + struct ble_iso_big *big; + int rc; + + big = ble_iso_big_find_by_id(big_id); + if (big == NULL) { + BLE_HS_LOG_ERROR("No BIG with id=%d\n", big_id); + return BLE_HS_ENOENT; + } + + cp.big_handle = big->id; + cp.reason = BLE_ERR_CONN_TERM_LOCAL; + + rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE, + BLE_HCI_OCF_LE_TERMINATE_BIG), + &cp, sizeof(cp),NULL, 0); + + return rc; +} + +int +ble_iso_init(void) +{ + int rc; + + SLIST_INIT(&ble_iso_bigs); + + rc = os_mempool_init(&ble_iso_big_pool, + MYNEWT_VAL(BLE_MAX_BIG), + sizeof (struct ble_iso_big), + ble_iso_big_mem, "ble_iso_big_pool"); + SYSINIT_PANIC_ASSERT(rc == 0); + + return 0; +} + +void +ble_iso_rx_create_big_complete(const struct ble_hci_ev_le_subev_create_big_complete *ev) +{ + struct ble_iso_event event; + + struct ble_iso_big *big; + int i; + + big = ble_iso_big_find_by_id(ev->big_handle); + + big->num_bis = ev->num_bis; + + for (i = 0; i < ev->num_bis; i++) { + big->conn_handles[i] = ev->conn_handle[i]; + } + + big->max_pdu = ev->max_pdu; + + event.type = BLE_ISO_EVENT_BIG_CREATE_COMPLETE; + event.big_created.desc.big_handle = ev->big_handle; + event.big_created.desc.big_sync_delay = get_le24(ev->big_sync_delay); + event.big_created.desc.transport_latency_big = + get_le24(ev->transport_latency_big); + event.big_created.desc.phy = ev->phy; + event.big_created.desc.nse = ev->nse; + event.big_created.desc.bn = ev->bn; + event.big_created.desc.pto = ev->pto; + event.big_created.desc.irc = ev->irc; + event.big_created.desc.max_pdu = ev->max_pdu; + event.big_created.desc.iso_interval = ev->iso_interval; + event.big_created.desc.num_bis = ev->num_bis; + memcpy(event.big_created.desc.conn_handle, ev->conn_handle, + ev->num_bis * sizeof(uint16_t)); + + if (big->cb != NULL) { + big->cb(&event, big->cb_arg); + } +} + +void +ble_iso_rx_terminate_big_complete(const struct ble_hci_ev_le_subev_terminate_big_complete *ev) +{ + struct ble_iso_event event; + struct ble_iso_big *big; + + big = ble_iso_big_find_by_id(ev->big_handle); + + event.type = BLE_ISO_EVENT_BIG_TERMINATE_COMPLETE; + event.big_terminated.big_handle = ev->big_handle; + event.big_terminated.reason = ev->reason; + + if (big->cb != NULL) { + big->cb(&event, big->cb_arg); + } + + ble_iso_big_free(big); +} + +static int +ble_iso_tx_complete(uint16_t conn_handle, const uint8_t *data, + uint16_t data_len) +{ + struct os_mbuf *om; + int rc; + + om = ble_hs_mbuf_bare_pkt(); + if (!om) { + return BLE_HS_ENOMEM; + } + + os_mbuf_extend(om, 8); + /* Connection_Handle, PB_Flag, TS_Flag */ + put_le16(&om->om_data[0], + BLE_HCI_ISO_HANDLE(conn_handle, BLE_HCI_ISO_PB_COMPLETE, 0)); + /* Data_Total_Length = Data length + Packet_Sequence_Number placeholder */ + put_le16(&om->om_data[2], data_len + 4); + /* Packet_Sequence_Number placeholder */ + put_le16(&om->om_data[4], 0); + /* ISO_SDU_Length */ + put_le16(&om->om_data[6], data_len); + + rc = os_mbuf_append(om, data, data_len); + if (rc) { + return rc; + } + + return ble_transport_to_ll_iso(om); +} + +static int +ble_iso_tx_segmented(uint16_t conn_handle, uint16_t max_pdu, + const uint8_t *data, uint16_t data_len) +{ + struct os_mbuf *om; + uint16_t data_left = data_len; + uint16_t packet_len; + uint16_t offset = 0; + uint8_t pb; + uint8_t ts; + int rc; + + while (data_left) { + packet_len = min(max_pdu - (BLE_HCI_DATA_HDR_SZ + 4), data_left); + if (data_left == data_len) { + pb = BLE_HCI_ISO_PB_FIRST; + ts = 1; + } else if (packet_len == data_left) { + pb = BLE_HCI_ISO_PB_LAST; + ts = 0; + } else { + pb = BLE_HCI_ISO_PB_CONTINUATION; + ts = 0; + } + + om = ble_hs_mbuf_bare_pkt(); + if (!om) { + return BLE_HS_ENOMEM; + } + + os_mbuf_extend(om, ts == 1 ? 10: 8); + + /* Connection_Handle, PB_Flag, TS_Flag */ + put_le16(&om->om_data[0], + BLE_HCI_ISO_HANDLE(conn_handle, pb, ts)); + /* Data_Total_Length = Data length + + * Packet_Sequence_Number placeholder + + * optional timestamp*/ + put_le16(&om->om_data[2], packet_len + 4 + 2 * ts); + + if (ts) { + /* Timestamp placeholder */ + put_le16(&om->om_data[4], 0); + + /* Packet_Sequence_Number placeholder */ + put_le16(&om->om_data[6], 0); + + /* ISO_SDU_Length */ + put_le16(&om->om_data[8], packet_len); + } + + /* Packet_Sequence_Number placeholder */ + put_le16(&om->om_data[4], 0); + /* ISO_SDU_Length */ + put_le16(&om->om_data[6], packet_len); + + rc = os_mbuf_append(om, data + offset, packet_len); + if (rc) { + return rc; + } + + ble_transport_to_ll_iso(om); + + offset += packet_len; + data_len -= packet_len; + } + + return 0; +} + +int +ble_iso_tx(uint16_t conn_handle, const uint8_t *data, uint16_t data_len) +{ + struct ble_iso_big *big; + uint16_t max_pdu = 0; + int rc; + int i; + + SLIST_FOREACH(big, &ble_iso_bigs, next) { + for (i = 0; i < big->num_bis; i++) { + if (big->conn_handles[i] == conn_handle) { + max_pdu = big->max_pdu; + break; + } + } + if (max_pdu > 0) { + break; + } + } + + if (!max_pdu) { + return BLE_HS_ENOENT; + } + + if (data_len <= max_pdu) { + rc = ble_iso_tx_complete(conn_handle, data, data_len); + } else { + rc = ble_iso_tx_segmented(conn_handle, max_pdu, data, data_len); + } + + return rc; +} +#endif diff --git a/nimble/host/src/ble_iso_priv.h b/nimble/host/src/ble_iso_priv.h new file mode 100644 index 000000000..fb0a6aab3 --- /dev/null +++ b/nimble/host/src/ble_iso_priv.h @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef H_BLE_ISO_PRIV_ +#define H_BLE_ISO_PRIV_ + +#include "nimble/hci_common.h" +#ifdef __cplusplus +extern "C" { +#endif + +void +ble_iso_rx_create_big_complete(const struct ble_hci_ev_le_subev_create_big_complete *ev); + +void +ble_iso_rx_terminate_big_complete(const struct ble_hci_ev_le_subev_terminate_big_complete *ev); + +#ifdef __cplusplus +} +#endif + +#endif /* H_BLE_ISO_PRIV_ */ diff --git a/nimble/host/syscfg.yml b/nimble/host/syscfg.yml index f667eee98..558e31e8d 100644 --- a/nimble/host/syscfg.yml +++ b/nimble/host/syscfg.yml @@ -486,6 +486,20 @@ syscfg.defs: supported by host. value: 0 + BLE_MAX_BIG: + desciptrion: > + Number of available BIGs + value: 'MYNEWT_VAL_BLE_MULTI_ADV_INSTANCES' + restrictions: + - 'BLE_ISO_BROADCASTER if 0' + + BLE_MAX_BIS: + description: > + Number of supported BISes + value: 4 + restrictions: + - 'BLE_ISO_BROADCASTER if 0' + ### Log settings. BLE_HS_LOG_MOD: