zephyr/subsys/bluetooth/controller/ll_sw/ull_central_iso.c
Vinayak Kariappa Chettimada 4d3f8697a3 Bluetooth: Controller: Make PDU octet3 vendor specific
Make Data, ISO, BIS and CIS PDU structure's octet 3 vendor
specific.

This will allow vendors not supporting the octet 3 or
CTEInfo (8 bits) to save 1 octet in their PDU structures.

Signed-off-by: Vinayak Kariappa Chettimada <vich@nordicsemi.no>
2023-01-26 13:25:11 +01:00

720 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2020 Demant
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/buf.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/iso.h>
#include "util/memq.h"
#include "util/mayfly.h"
#include "util/util.h"
#include "hal/ccm.h"
#include "hal/ticker.h"
#include "ticker/ticker.h"
#include "pdu_df.h"
#include "lll/pdu_vendor.h"
#include "pdu.h"
#include "lll.h"
#include "lll/lll_vendor.h"
#include "lll_conn.h"
#include "lll_conn_iso.h"
#include "lll_clock.h"
#include "isoal.h"
#include "ull_iso_types.h"
#include "ull_tx_queue.h"
#include "ull_internal.h"
#include "ull_conn_types.h"
#include "ull_conn_internal.h"
#include "ull_conn_iso_types.h"
#include "ull_conn_iso_internal.h"
#include "ull_llcp.h"
#include "lll_central_iso.h"
#include "ll.h"
#include <zephyr/bluetooth/hci.h>
#include "hal/debug.h"
#define SDU_MAX_DRIFT_PPM 100
/* Setup cache for CIG commit transaction */
static struct {
struct ll_conn_iso_group group;
uint8_t c_ft;
uint8_t p_ft;
uint8_t cis_idx;
struct ll_conn_iso_stream stream[CONFIG_BT_CTLR_CONN_ISO_STREAMS_PER_GROUP];
} ll_iso_setup;
uint8_t ll_cig_parameters_open(uint8_t cig_id,
uint32_t c_interval, uint32_t p_interval,
uint8_t sca, uint8_t packing, uint8_t framing,
uint16_t c_latency, uint16_t p_latency,
uint8_t num_cis)
{
memset(&ll_iso_setup, 0, sizeof(ll_iso_setup));
ll_iso_setup.group.cig_id = cig_id;
ll_iso_setup.group.c_sdu_interval = c_interval;
ll_iso_setup.group.p_sdu_interval = p_interval;
ll_iso_setup.group.c_latency = c_latency * 1000;
ll_iso_setup.group.p_latency = p_latency * 1000;
ll_iso_setup.group.cis_count = num_cis;
ll_iso_setup.group.central.sca = sca;
ll_iso_setup.group.central.packing = packing;
ll_iso_setup.group.central.framing = framing;
return BT_HCI_ERR_SUCCESS;
}
uint8_t ll_cis_parameters_set(uint8_t cis_id,
uint16_t c_sdu, uint16_t p_sdu,
uint8_t c_phy, uint8_t p_phy,
uint8_t c_rtn, uint8_t p_rtn)
{
uint8_t cis_idx = ll_iso_setup.cis_idx;
if (cis_idx >= CONFIG_BT_CTLR_CONN_ISO_STREAMS_PER_GROUP) {
return BT_HCI_ERR_INSUFFICIENT_RESOURCES;
}
memset(&ll_iso_setup.stream[cis_idx], 0, sizeof(struct ll_conn_iso_stream));
ll_iso_setup.stream[cis_idx].cis_id = cis_id;
ll_iso_setup.stream[cis_idx].c_max_sdu = c_sdu;
ll_iso_setup.stream[cis_idx].p_max_sdu = p_sdu;
ll_iso_setup.stream[cis_idx].lll.tx.phy = c_phy;
ll_iso_setup.stream[cis_idx].lll.rx.phy = p_phy;
ll_iso_setup.stream[cis_idx].central.c_rtn = c_rtn;
ll_iso_setup.stream[cis_idx].central.p_rtn = p_rtn;
ll_iso_setup.cis_idx++;
return BT_HCI_ERR_SUCCESS;
}
void ll_cis_create(uint16_t cis_handle, uint16_t acl_handle)
{
/* Handles have been verified prior to calling this function */
(void)ull_cp_cis_create(
ll_connected_get(acl_handle),
ll_conn_iso_stream_get(cis_handle));
}
uint8_t ll_cig_parameters_test_open(uint8_t cig_id,
uint32_t c_interval,
uint32_t p_interval,
uint8_t c_ft,
uint8_t p_ft,
uint16_t iso_interval,
uint8_t sca,
uint8_t packing,
uint8_t framing,
uint8_t num_cis)
{
memset(&ll_iso_setup, 0, sizeof(ll_iso_setup));
ll_iso_setup.group.cig_id = cig_id;
ll_iso_setup.group.c_sdu_interval = c_interval;
ll_iso_setup.group.p_sdu_interval = p_interval;
ll_iso_setup.group.iso_interval = iso_interval;
ll_iso_setup.group.cis_count = num_cis;
ll_iso_setup.group.central.sca = sca;
ll_iso_setup.group.central.packing = packing;
ll_iso_setup.group.central.framing = framing;
ll_iso_setup.group.central.test = 1U;
/* TODO: Perhaps move FT to LLL CIG */
ll_iso_setup.c_ft = c_ft;
ll_iso_setup.p_ft = p_ft;
return BT_HCI_ERR_SUCCESS;
}
uint8_t ll_cis_parameters_test_set(uint8_t cis_id, uint8_t nse,
uint16_t c_sdu, uint16_t p_sdu,
uint16_t c_pdu, uint16_t p_pdu,
uint8_t c_phy, uint8_t p_phy,
uint8_t c_bn, uint8_t p_bn)
{
uint8_t cis_idx = ll_iso_setup.cis_idx;
if (cis_idx >= CONFIG_BT_CTLR_CONN_ISO_STREAMS_PER_GROUP) {
return BT_HCI_ERR_INSUFFICIENT_RESOURCES;
}
memset(&ll_iso_setup.stream[cis_idx], 0, sizeof(struct ll_conn_iso_stream));
ll_iso_setup.stream[cis_idx].cis_id = cis_id;
ll_iso_setup.stream[cis_idx].c_max_sdu = c_sdu;
ll_iso_setup.stream[cis_idx].p_max_sdu = p_sdu;
ll_iso_setup.stream[cis_idx].lll.num_subevents = nse;
ll_iso_setup.stream[cis_idx].lll.tx.max_octets = c_bn ? c_pdu : 0;
ll_iso_setup.stream[cis_idx].lll.rx.max_octets = p_bn ? p_pdu : 0;
ll_iso_setup.stream[cis_idx].lll.tx.phy = c_phy;
ll_iso_setup.stream[cis_idx].lll.rx.phy = p_phy;
ll_iso_setup.stream[cis_idx].lll.tx.burst_number = c_bn;
ll_iso_setup.stream[cis_idx].lll.rx.burst_number = p_bn;
ll_iso_setup.cis_idx++;
return BT_HCI_ERR_SUCCESS;
}
static void set_bn_max_pdu(bool framed, uint32_t iso_interval, uint32_t sdu_interval,
uint16_t max_sdu, uint8_t *bn, uint8_t *max_pdu)
{
uint32_t ceil_f_x_max_sdu;
uint16_t max_pdu_bn1;
uint32_t max_drift;
uint32_t ceil_f;
if (framed) {
/* Framed (From ES-18002):
* Max_PDU >= ((ceil(F) x 5 + ceil(F x Max_SDU)) / BN) + 2
* F = (1 + MaxDrift) x ISO_Interval / SDU_Interval
* SegmentationHeader + TimeOffset = 5 bytes
* Continuation header = 2 bytes
* MaxDrift (Max. allowed SDU delivery timing drift) = 100 ppm
*/
max_drift = ceiling_fraction(SDU_MAX_DRIFT_PPM * sdu_interval, 1000000U);
ceil_f = ceiling_fraction(iso_interval + max_drift, sdu_interval);
ceil_f_x_max_sdu = ceiling_fraction(max_sdu * (iso_interval + max_drift),
sdu_interval);
/* Strategy: Keep lowest possible BN.
* TODO: Implement other strategies, possibly as policies.
*/
max_pdu_bn1 = ceil_f * 5 + ceil_f_x_max_sdu;
*bn = ceiling_fraction(max_pdu_bn1, CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE);
*max_pdu = ceiling_fraction(max_pdu_bn1, *bn) + 2;
} else {
/* For unframed, ISO_Interval must be N x SDU_Interval */
LL_ASSERT(iso_interval % sdu_interval == 0);
/* Core 5.3 Vol 6, Part G section 2.1:
* BN >= ceil(Max_SDU/Max_PDU * ISO_Interval/SDU_Interval)
*/
*bn = ceiling_fraction(max_sdu * iso_interval, (*max_pdu) * sdu_interval);
}
}
/* TODO:
* - Drop retransmissions to stay within Max_Transmission_Latency instead of asserting
* - Calculate ISO_Interval to allow SDU_Interval < ISO_Interval
*/
uint8_t ll_cig_parameters_commit(uint8_t cig_id)
{
struct ll_conn_iso_stream *cis;
struct ll_conn_iso_group *cig;
uint32_t iso_interval_us;
uint32_t cig_sync_delay;
uint32_t max_se_length;
uint32_t c_max_latency;
uint32_t p_max_latency;
uint16_t handle_iter;
uint8_t cis_count;
/* Intermediate subevent data */
struct {
uint32_t length;
uint8_t total_count;
} se[CONFIG_BT_CTLR_CONN_ISO_STREAMS_PER_GROUP];
/* If CIG already exists, controller and host are not in sync */
cig = ll_conn_iso_group_get_by_id(cig_id);
LL_ASSERT(!cig);
/* CIG does not exist - create it */
cig = ll_conn_iso_group_acquire();
if (!cig) {
/* No space for new CIG */
return BT_HCI_ERR_INSUFFICIENT_RESOURCES;
}
/* Transfer parameters from update cache and clear LLL fields */
memcpy(cig, &ll_iso_setup.group, sizeof(struct ll_conn_iso_group));
/* Setup LLL parameters */
cig->lll.handle = ll_conn_iso_group_handle_get(cig);
cig->lll.role = BT_HCI_ROLE_CENTRAL;
cig->lll.resume_cis = LLL_HANDLE_INVALID;
if (!cig->central.test) {
/* TODO: Calculate ISO_Interval based on SDU_Interval and Max_SDU vs Max_PDU,
* taking the policy into consideration. It may also be interesting to select an
* ISO_Interval which is less likely to collide with other connections.
* For instance:
*
* SDU_Interval ISO_Interval Max_SDU Max_SDU Collision risk (10 ms)
* ------------------------------------------------------------------------
* 10 ms 10 ms 40 40 100%
* 10 ms 12.5 ms 40 50 25%
*/
iso_interval_us = cig->c_sdu_interval;
cig->iso_interval = ceiling_fraction(iso_interval_us, ISO_INT_UNIT_US);
} else {
iso_interval_us = cig->iso_interval * ISO_INT_UNIT_US;
}
ull_hdr_init(&cig->ull);
lll_hdr_init(&cig->lll, cig);
max_se_length = 0;
cis_count = cig->cis_count;
/* 1) Acquire CIS instances and initialize instance data.
* 2) Calculate SE_Length for each CIS and store the largest
* 3) Calculate BN
* 4) Calculate total number of subevents needed to transfer payloads
*
* Sequential Interleaved
* CIS0 ___█_█_█_____________█_ ___█___█___█_________█_
* CIS1 _________█_█_█_________ _____█___█___█_________
* CIS_Sub_Interval |.| |...|
* CIG_Sync_Delay |............| |............|
* CIS_Sync_Delay 0 |............| |............|
* CIS_Sync_Delay 1 |......| |..........|
* ISO_Interval |.................|.. |.................|..
*/
for (uint8_t i = 0; i < cis_count; i++) {
uint32_t mpt_c;
uint32_t mpt_p;
bool tx;
bool rx;
/* Acquire new CIS */
cis = ll_conn_iso_stream_acquire();
if (cis == NULL) {
/* No space for new CIS */
return BT_HCI_ERR_INSUFFICIENT_RESOURCES;
}
/* Transfer parameters from update cache */
memcpy(cis, &ll_iso_setup.stream[i], sizeof(struct ll_conn_iso_stream));
cis->group = cig;
cis->framed = cig->central.framing;
cis->lll.handle = ll_conn_iso_stream_handle_get(cis);
if (cig->central.test) {
cis->lll.tx.flush_timeout = ll_iso_setup.c_ft;
cis->lll.rx.flush_timeout = ll_iso_setup.p_ft;
tx = cis->lll.tx.burst_number && cis->lll.tx.max_octets;
rx = cis->lll.rx.burst_number && cis->lll.rx.max_octets;
} else {
LL_ASSERT(iso_interval_us >= cig->c_sdu_interval);
tx = cig->c_sdu_interval && cis->c_max_sdu;
rx = cig->p_sdu_interval && cis->p_max_sdu;
/* Use Max_PDU = MIN(<buffer_size>, Max_SDU) as default. May be changed by
* set_bn_max_pdu.
*/
cis->lll.tx.max_octets = MIN(CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE,
cis->c_max_sdu);
cis->lll.rx.max_octets = MIN(251, cis->p_max_sdu);
/* Calculate BN and Max_PDU (framed) for both directions */
if (tx) {
set_bn_max_pdu(cis->framed, iso_interval_us, cig->c_sdu_interval,
cis->c_max_sdu, &cis->lll.tx.burst_number,
&cis->lll.tx.max_octets);
} else {
cis->lll.tx.burst_number = 0;
}
if (rx) {
set_bn_max_pdu(cis->framed, iso_interval_us, cig->p_sdu_interval,
cis->p_max_sdu, &cis->lll.rx.burst_number,
&cis->lll.rx.max_octets);
} else {
cis->lll.rx.burst_number = 0;
}
}
/* Calculate SE_Length */
mpt_c = PDU_CIS_MAX_US(cis->lll.tx.max_octets, tx ? 1 : 0, cis->lll.tx.phy);
mpt_p = PDU_CIS_MAX_US(cis->lll.rx.max_octets, rx ? 1 : 0, cis->lll.rx.phy);
se[i].length = mpt_c + EVENT_IFS_US + mpt_p + EVENT_MSS_US;
max_se_length = MAX(max_se_length, se[i].length);
/* Total number of subevents needed */
se[i].total_count = MAX((cis->central.c_rtn + 1) * cis->lll.tx.burst_number,
(cis->central.p_rtn + 1) * cis->lll.rx.burst_number);
/* Initialize TX link */
cis->lll.link_tx_free = &cis->lll.link_tx;
memq_init(cis->lll.link_tx_free, &cis->lll.memq_tx.head, &cis->lll.memq_tx.tail);
cis->lll.link_tx_free = NULL;
}
handle_iter = UINT16_MAX;
uint32_t total_time = 0;
/* 1) Prepare calculation of the flush timeout by adding up the total time needed to
* transfer all payloads, including retransmissions.
*/
for (uint8_t i = 0; i < cis_count; i++) {
cis = ll_conn_iso_stream_get_by_group(cig, &handle_iter);
if (cig->central.packing == BT_ISO_PACKING_SEQUENTIAL) {
/* Sequential CISes - add up the duration and set individual
* subinterval.
*/
total_time += se[i].total_count * se[i].length;
} else {
/* Interleaved CISes - find the largest total duration and
* set all subintervals to the largest SE_Length of any CIS x
* the number of interleaved CISes.
*/
total_time = MAX(total_time, se[i].total_count * cis->lll.sub_interval +
(i * cis->lll.sub_interval / cis_count));
}
}
handle_iter = UINT16_MAX;
cig_sync_delay = 0;
/* 1) Calculate the flush timeout either by dividing the total time needed to transfer all,
* payloads including retransmissions, and divide by the ISO_Interval (low latency
* policy), or by dividing the Max_Transmission_Latency by the ISO_Interval (reliability
* policy).
* 2) Calculate the number of subevents (NSE) by distributing total number of subevents into
* FT ISO_intervals.
* 3) Calculate subinterval as either individual CIS subinterval (sequential), or the
* largest SE_Length times number of CISes (interleaved). Min. subinterval is 400 us.
* 4) Calculate CIG_Sync_Delay
*/
for (uint8_t i = 0; i < cis_count; i++) {
cis = ll_conn_iso_stream_get_by_group(cig, &handle_iter);
if (!cig->central.test) {
#if defined(CONFIG_BT_CTLR_CONN_ISO_LOW_LATENCY_POLICY)
/* Use symmetric flush timeout */
cis->lll.tx.flush_timeout = ceiling_fraction(total_time, iso_interval_us);
cis->lll.rx.flush_timeout = cis->lll.tx.flush_timeout;
#elif defined(CONFIG_BT_CTLR_CONN_ISO_RELIABILITY_POLICY)
/* Utilize Max_Transmission_latency */
if (cis->framed) {
/* TL = CIG_Sync_Delay + FT x ISO_Interval + SDU_Interval.
* SDU_Interval <= CIG_Sync_Delay
*/
cis->lll.tx.flush_timeout =
ceiling_fraction(cig->c_latency - cig->c_sdu_interval -
iso_interval_us, iso_interval_us);
cis->lll.rx.flush_timeout =
ceiling_fraction(cig->p_latency - cig->p_sdu_interval -
iso_interval_us, iso_interval_us);
} else {
/* TL = CIG_Sync_Delay + FT x ISO_Interval - SDU_Interval.
* SDU_Interval <= CIG_Sync_Delay
*/
cis->lll.tx.flush_timeout =
ceiling_fraction(cig->c_latency + cig->c_sdu_interval -
iso_interval_us, iso_interval_us);
cis->lll.rx.flush_timeout =
ceiling_fraction(cig->p_latency + cig->p_sdu_interval -
iso_interval_us, iso_interval_us);
}
#else
LL_ASSERT(0);
#endif
cis->lll.num_subevents = ceiling_fraction(se[i].total_count,
cis->lll.tx.flush_timeout);
}
if (cig->central.packing == BT_ISO_PACKING_SEQUENTIAL) {
/* Accumulate CIG sync delay for sequential CISes */
cis->lll.sub_interval = MAX(400, se[i].length);
cig_sync_delay += cis->lll.num_subevents * cis->lll.sub_interval;
} else {
/* For interleaved CISes, offset each CIS by a fraction of a subinterval,
* positioning them evenly within the subinterval.
*/
cis->lll.sub_interval = MAX(400, cis_count * max_se_length);
cig_sync_delay = MAX(cig_sync_delay,
(cis->lll.num_subevents * cis->lll.sub_interval) +
(i * cis->lll.sub_interval / cis_count));
}
}
cig->sync_delay = cig_sync_delay;
handle_iter = UINT16_MAX;
c_max_latency = 0;
p_max_latency = 0;
/* 1) Calculate transport latencies for each CIS and validate against Max_Transport_Latency.
* 2) Lay out CISes by updating CIS_Sync_Delay, distributing according to the packing.
*/
for (uint8_t i = 0; i < cis_count; i++) {
uint32_t c_latency;
uint32_t p_latency;
cis = ll_conn_iso_stream_get_by_group(cig, &handle_iter);
if (cis->framed) {
/* Transport_Latency = CIG_Sync_Delay + FT x ISO_Interval + SDU_Interval */
c_latency = cig->sync_delay +
(cis->lll.tx.flush_timeout * iso_interval_us) +
cig->c_sdu_interval;
p_latency = cig->sync_delay +
(cis->lll.rx.flush_timeout * iso_interval_us) +
cig->p_sdu_interval;
} else {
/* Transport_Latency = CIG_Sync_Delay + FT x ISO_Interval - SDU_Interval */
c_latency = cig->sync_delay +
(cis->lll.tx.flush_timeout * iso_interval_us) -
cig->c_sdu_interval;
p_latency = cig->sync_delay +
(cis->lll.rx.flush_timeout * iso_interval_us) -
cig->p_sdu_interval;
}
if (!cig->central.test) {
/* Make sure specified Max_Transport_Latency is not exceeded */
LL_ASSERT(c_latency <= cig->c_latency);
LL_ASSERT(p_latency <= cig->p_latency);
}
c_max_latency = MAX(c_max_latency, c_latency);
p_max_latency = MAX(p_max_latency, p_latency);
if (cig->central.packing == BT_ISO_PACKING_SEQUENTIAL) {
/* Distribute CISes sequentially */
cis->sync_delay = cig_sync_delay;
cig_sync_delay -= cis->lll.num_subevents * cis->lll.sub_interval;
} else {
/* Distribute CISes interleaved */
cis->sync_delay = cig_sync_delay;
cig_sync_delay -= (cis->lll.sub_interval / cis_count);
}
if (cis->lll.num_subevents <= 1) {
cis->lll.sub_interval = 0;
}
}
/* Update actual latency */
cig->c_latency = c_max_latency;
cig->p_latency = p_max_latency;
cig->lll.num_cis = cis_count;
return BT_HCI_ERR_SUCCESS;
}
/* Core 5.3 Vol 6, Part B section 7.8.100:
* The HCI_LE_Remove_CIG command is used by the Centrals Host to remove the CIG
* identified by CIG_ID.
* This command shall delete the CIG_ID and also delete the Connection_Handles
* of the CIS configurations stored in the CIG.
* This command shall also remove the isochronous data paths that are associated
* with the Connection_Handles of the CIS configurations.
*/
uint8_t ll_cig_remove(uint8_t cig_id)
{
struct ll_conn_iso_stream *cis;
struct ll_conn_iso_group *cig;
uint16_t handle_iter;
cig = ll_conn_iso_group_get_by_id(cig_id);
if (!cig) {
/* Unknown CIG id */
return BT_HCI_ERR_UNKNOWN_CONN_ID;
}
if (cig->started) {
/* CIG is in active state */
return BT_HCI_ERR_CMD_DISALLOWED;
}
handle_iter = UINT16_MAX;
for (int i = 0; i < cig->cis_count; i++) {
struct ll_conn *conn;
cis = ll_conn_iso_stream_get_by_group(cig, &handle_iter);
if (!cis) {
break;
}
conn = ll_connected_get(cis->lll.acl_handle);
if (conn) {
if (ull_lp_cc_is_active(conn)) {
/* CIG creation is ongoing */
return BT_HCI_ERR_CMD_DISALLOWED;
}
}
}
/* CIG exists and is not active */
handle_iter = UINT16_MAX;
for (int i = 0; i < cig->cis_count; i++) {
cis = ll_conn_iso_stream_get_by_group(cig, &handle_iter);
if (!cis) {
continue;
}
/* Remove data path and ISOAL sink/source associated with this CIS
* for both directions.
*/
ll_remove_iso_path(cis->lll.handle, BT_HCI_DATAPATH_DIR_CTLR_TO_HOST);
ll_remove_iso_path(cis->lll.handle, BT_HCI_DATAPATH_DIR_HOST_TO_CTLR);
ll_conn_iso_stream_release(cis);
}
/* Clear configuration */
cig->cis_count = 0;
/* Release the CIG instance */
ll_conn_iso_group_release(cig);
return BT_HCI_ERR_SUCCESS;
}
uint8_t ll_cis_create_check(uint16_t cis_handle, uint16_t acl_handle)
{
struct ll_conn *conn;
conn = ll_connected_get(acl_handle);
if (conn) {
struct ll_conn_iso_stream *cis;
/* Verify handle validity and association */
cis = ll_conn_iso_stream_get(cis_handle);
if (cis->lll.handle == cis_handle && cis->lll.acl_handle == acl_handle) {
return BT_HCI_ERR_SUCCESS;
}
}
return BT_HCI_ERR_CMD_DISALLOWED;
}
int ull_central_iso_init(void)
{
return 0;
}
int ull_central_iso_reset(void)
{
return 0;
}
uint8_t ull_central_iso_setup(uint16_t cis_handle,
uint32_t *cig_sync_delay,
uint32_t *cis_sync_delay,
uint32_t *cis_offset_min,
uint32_t *cis_offset_max,
uint16_t *conn_event_count,
uint8_t *access_addr)
{
struct ll_conn_iso_stream *cis;
struct ll_conn_iso_group *cig;
struct ll_conn *conn;
uint16_t handle_iter;
uint32_t cis_offset;
uint16_t instant;
int err;
cis = ll_conn_iso_stream_get(cis_handle);
if (!cis) {
return BT_HCI_ERR_UNSPECIFIED;
}
cig = cis->group;
if (!cig) {
return BT_HCI_ERR_UNSPECIFIED;
}
conn = ll_conn_get(cis->lll.acl_handle);
instant = MAX(*conn_event_count, ull_conn_event_counter(conn) + 1);
handle_iter = UINT16_MAX;
cis_offset = *cis_offset_min;
/* Calculate offset for CIS */
for (uint8_t i = 0; i < cig->cis_count; i++) {
struct ll_conn_iso_stream *c;
int16_t conn_events_since_ref;
uint32_t iso_interval_us;
uint32_t time_since_ref;
c = ll_conn_iso_stream_get_by_group(cig, &handle_iter);
if (c->cis_id != cis->cis_id && c->lll.active) {
conn_events_since_ref = (int16_t)(instant - c->central.instant);
LL_ASSERT(conn_events_since_ref > 0);
time_since_ref = c->offset + conn_events_since_ref * conn->lll.interval *
CONN_INT_UNIT_US;
iso_interval_us = cig->iso_interval * ISO_INT_UNIT_US;
cis_offset = time_since_ref % iso_interval_us;
break;
}
}
cis->offset = cis_offset;
cis->central.instant = instant;
cis->lll.event_count = -1;
/* Create access address */
err = util_aa_le32(cis->lll.access_addr);
LL_ASSERT(!err);
/* Transfer to caller */
*cig_sync_delay = cig->sync_delay;
*cis_sync_delay = cis->sync_delay;
*cis_offset_min = cis->offset;
memcpy(access_addr, cis->lll.access_addr, sizeof(cis->lll.access_addr));
*conn_event_count = instant;
return 0;
}
uint16_t ull_central_iso_cis_offset_get(uint16_t cis_handle, uint32_t *cis_offset_min,
uint32_t *cis_offset_max)
{
struct ll_conn_iso_stream *cis;
struct ll_conn *conn;
cis = ll_conn_iso_stream_get(cis_handle);
LL_ASSERT(cis);
conn = ll_conn_get(cis->lll.acl_handle);
if (cis_offset_min && cis_offset_max) {
struct ll_conn_iso_group *cig;
cig = cis->group;
/* Provide CIS offset range
* CIS_Offset_Max < (connInterval - (CIG_Sync_Delay + T_MSS))
*/
*cis_offset_max = (conn->lll.interval * CONN_INT_UNIT_US) - cig->sync_delay;
*cis_offset_min = MAX(400, EVENT_OVERHEAD_CIS_SETUP_US);
}
cis->central.instant = ull_conn_event_counter(conn) + 3;
return cis->central.instant;
}