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: