diff --git a/nimble/controller/src/ble_ll_iso_big.c b/nimble/controller/src/ble_ll_iso_big.c index b836fed0b8..f9ed9cc340 100644 --- a/nimble/controller/src/ble_ll_iso_big.c +++ b/nimble/controller/src/ble_ll_iso_big.c @@ -144,6 +144,10 @@ struct ble_ll_iso_big { struct ble_ll_iso_bis_q bis_q; +#if MYNEWT_VAL(BLE_LL_ISO_HCI_FEEDBACK_INTERVAL_MS) + uint32_t last_feedback; +#endif + uint8_t cstf : 1; uint8_t cssn : 4; uint8_t control_active : 3; @@ -398,8 +402,16 @@ ble_ll_iso_big_event_done(struct ble_ll_iso_big *big) { struct ble_ll_iso_bis *bis; struct ble_hci_ev *hci_ev; +#if MYNEWT_VAL(BLE_LL_ISO_HCI_FEEDBACK_INTERVAL_MS) + struct ble_hci_ev *fb_hci_ev = NULL; + struct ble_hci_ev_vs *fb_hci_ev_vs; + struct ble_hci_vs_subev_iso_hci_feedback *fb_hci_subev = NULL; + uint16_t exp; + uint32_t now; +#endif struct ble_hci_ev_num_comp_pkts *hci_ev_ncp; int num_completed_pkt; + int idx; int rc; ble_ll_rfmgmt_release(); @@ -417,18 +429,49 @@ ble_ll_iso_big_event_done(struct ble_ll_iso_big *big) hci_ev_ncp->count = 0; } +#if MYNEWT_VAL(BLE_LL_ISO_HCI_FEEDBACK_INTERVAL_MS) + now = os_time_get(); + if (OS_TIME_TICK_GEQ(now, big->last_feedback + + os_time_ms_to_ticks32(MYNEWT_VAL(BLE_LL_ISO_HCI_FEEDBACK_INTERVAL_MS)))) { + fb_hci_ev = ble_transport_alloc_evt(1); + if (fb_hci_ev) { + fb_hci_ev->opcode = BLE_HCI_EVCODE_VS; + fb_hci_ev->length = sizeof(*fb_hci_ev_vs) + sizeof(*fb_hci_subev); + fb_hci_ev_vs = (void *)fb_hci_ev->data; + fb_hci_ev_vs->id = BLE_HCI_VS_SUBEV_ISO_HCI_FEEDBACK; + fb_hci_subev = (void *)fb_hci_ev_vs->data; + fb_hci_subev->big_handle = big->handle; + fb_hci_subev->count = 0; + } + } +#endif + STAILQ_FOREACH(bis, &big->bis_q, bis_q_next) { num_completed_pkt = ble_ll_isoal_mux_event_done(&bis->mux); if (hci_ev && num_completed_pkt) { - hci_ev_ncp->completed[hci_ev_ncp->count].handle = - htole16(bis->conn_handle); - hci_ev_ncp->completed[hci_ev_ncp->count].packets = - htole16(num_completed_pkt + bis->num_completed_pkt); + idx = hci_ev_ncp->count++; + hci_ev_ncp->completed[idx].handle = htole16(bis->conn_handle); + hci_ev_ncp->completed[idx].packets = htole16(num_completed_pkt + + bis->num_completed_pkt); bis->num_completed_pkt = 0; - hci_ev_ncp->count++; } else { bis->num_completed_pkt += num_completed_pkt; } + +#if MYNEWT_VAL(BLE_LL_ISO_HCI_FEEDBACK_INTERVAL_MS) + if (fb_hci_ev) { + /* Expected SDUs in queue after an event -> host should send + * sdu_per_interval SDUs until next event so there are sdu_per_event + * SDUs queued at next event. Feedback value is the difference between + * expected and actual SDUs count. + */ + exp = bis->mux.sdu_per_event - bis->mux.sdu_per_interval; + idx = fb_hci_subev->count++; + fb_hci_subev->feedback[idx].handle = htole16(bis->conn_handle); + fb_hci_subev->feedback[idx].sdu_per_interval = bis->mux.sdu_per_interval; + fb_hci_subev->feedback[idx].diff = (int8_t)(bis->mux.sdu_q_len - exp); + } +#endif } if (hci_ev) { @@ -441,6 +484,15 @@ ble_ll_iso_big_event_done(struct ble_ll_iso_big *big) } } +#if MYNEWT_VAL(BLE_LL_ISO_HCI_FEEDBACK_INTERVAL_MS) + if (fb_hci_ev) { + fb_hci_ev->length = sizeof(*fb_hci_ev_vs) + sizeof(*fb_hci_subev) + + fb_hci_subev->count * sizeof(fb_hci_subev->feedback[0]); + ble_transport_to_hs_evt(fb_hci_ev); + big->last_feedback = now; + } +#endif + big->sch.start_time = big->event_start; big->sch.remainder = big->event_start_us; @@ -760,6 +812,8 @@ ble_ll_iso_big_event_sched_cb(struct ble_ll_sched_item *sch) ble_phy_mode_set(phy_mode, phy_mode); #endif + ble_ll_tx_power_set(g_ble_ll_tx_power); + BLE_LL_ASSERT(!big->framed); /* XXX calculate this in advance at the end of previous event? */ diff --git a/nimble/controller/syscfg.yml b/nimble/controller/syscfg.yml index f02b04dfe4..ad364884d0 100644 --- a/nimble/controller/syscfg.yml +++ b/nimble/controller/syscfg.yml @@ -488,6 +488,23 @@ syscfg.defs: - BLE_LL_ISO if 1 value: 0 state: experimental + BLE_LL_ISO_HCI_FEEDBACK_INTERVAL_MS: + description: > + Enables ISO synchronization feedback using vendor-specific HCI event. + The event is sent at configured interval after completed ISO event + and contains BIG handle, number of expected SDUs per ISO interval + and difference between expected vs actual number of SDUs queued in + controller. + The expected number of SDUs queued after each event is number of + SDUs required for each ISO event (i.e. including pre-transmissions) + minus number of SDUs expected per each ISO interval. + The host can use feedback to e.g. adjust for jitter between audio + clock and LL clock. + Set to 0 to disable. + value: 0 + experimental: 1 + restrictions: + - BLE_LL_HCI_VS || BLE_LL_ISO_HCI_FEEDBACK_INTERVAL_MS == 0 BLE_LL_SYSINIT_STAGE: description: > diff --git a/nimble/include/nimble/hci_common.h b/nimble/include/nimble/hci_common.h index 742824d877..3753f6387d 100644 --- a/nimble/include/nimble/hci_common.h +++ b/nimble/include/nimble/hci_common.h @@ -1611,6 +1611,18 @@ struct ble_hci_ev_vs_css_slot_changed { uint16_t slot_idx; }; +#define BLE_HCI_VS_SUBEV_ISO_HCI_FEEDBACK (0x03) +struct feedback_pkt { + uint16_t handle; + uint8_t sdu_per_interval; + int8_t diff; +} __attribute__((packed)); +struct ble_hci_vs_subev_iso_hci_feedback { + uint8_t big_handle; + uint8_t count; + struct feedback_pkt feedback[0]; +} __attribute__((packed)); + #define BLE_HCI_VS_SUBEV_ID_LLCP_TRACE (0x17) /* LE sub-event codes */