Bluetooth: controller: ISO-AL CIS RX framed and ISO bugfixes

Adds reassembly of framed PDUs to ISO adaptation layer.
Reference times are also computed based on anchor points,
transmission latency components and a PDU specific time offset.

Some partial preparations for ISO broadcast can be found for
convenience. This needs to be properly merged, and currently will not
work with both CONFIG_BT_CTLR_CONN_ISO and CONFIG_BT_CTLR_SYNC_ISO
enabled.

Includes fixes and improvements for unframed mode.

Signed-off-by: Asger Munk Nielsen <asmk@oticon.com>
Signed-off-by: Morten Priess <mtpr@oticon.com>
This commit is contained in:
Morten Priess 2021-11-23 22:50:42 +01:00 committed by Christopher Friedt
commit 2d781ddfb2
11 changed files with 712 additions and 282 deletions

View file

@ -48,9 +48,10 @@
#include "ll.h" #include "ll.h"
#include "isoal.h" #include "isoal.h"
#include "ull_iso_types.h"
#include "lll_conn_iso.h" #include "lll_conn_iso.h"
#include "ull_conn_iso_types.h" #include "ull_conn_iso_types.h"
#include "ull_iso_types.h" #include "ull_iso_internal.h"
#include "ull_conn_internal.h" #include "ull_conn_internal.h"
#include "ull_conn_iso_internal.h" #include "ull_conn_iso_internal.h"
@ -85,7 +86,7 @@ isoal_status_t sink_sdu_alloc_hci(const struct isoal_sink *sink_ctx,
ARG_UNUSED(sink_ctx); ARG_UNUSED(sink_ctx);
ARG_UNUSED(valid_pdu); /* TODO copy valid pdu into netbuf ? */ ARG_UNUSED(valid_pdu); /* TODO copy valid pdu into netbuf ? */
struct net_buf *buf = bt_buf_get_rx(BT_BUF_ISO_IN, K_NO_WAIT); struct net_buf *buf = bt_buf_get_rx(BT_BUF_ISO_IN, K_FOREVER);
if (buf) { if (buf) {
/* Reserve space for headers */ /* Reserve space for headers */
@ -133,14 +134,30 @@ isoal_status_t sink_sdu_emit_hci(const struct isoal_sink *sink_ctx,
pb = sink_ctx->sdu_production.sdu_state; pb = sink_ctx->sdu_production.sdu_state;
ts = 1; /*TODO: Always assume timestamp? */ /*
* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 4, Part E
* 5.4.5 HCI ISO Data packets
*
* PB_Flag:
* Value Parameter Description
* 0b00 The ISO_Data_Load field contains a header and the first fragment
* of a fragmented SDU.
* 0b01 The ISO_Data_Load field contains a continuation fragment of an SDU.
* 0b10 The ISO_Data_Load field contains a header and a complete SDU.
* 0b11 The ISO_Data_Load field contains the last fragment of an SDU.
*
* The TS_Flag bit shall be set if the ISO_Data_Load field contains a
* Time_Stamp field. This bit shall only be set if the PB_Flag field equals 0b00 or
* 0b10.
*/
ts = !(pb & 1);
handle_packed = bt_iso_handle_pack(handle, pb, ts); handle_packed = bt_iso_handle_pack(handle, pb, ts);
len = sink_ctx->sdu_production.sdu_written + BT_HCI_ISO_TS_DATA_HDR_SIZE; len = sink_ctx->sdu_production.sdu_written + BT_HCI_ISO_TS_DATA_HDR_SIZE;
hdr->handle = sys_cpu_to_le16(handle_packed); hdr->handle = sys_cpu_to_le16(handle_packed);
hdr->len = sys_cpu_to_le16(len); hdr->len = sys_cpu_to_le16(len);
packet_status_flag = 0x0000; /* TODO: For now always assume "valid data" */ packet_status_flag = valid_sdu->status;
slen = sink_ctx->sdu_production.sdu_written; slen = sink_ctx->sdu_production.sdu_written;
slen_packed = bt_iso_pkt_len_pack(slen, packet_status_flag); slen_packed = bt_iso_pkt_len_pack(slen, packet_status_flag);
@ -308,17 +325,26 @@ static inline struct net_buf *encode_node(struct node_rx_pdu *node_rx,
#endif #endif
#if defined(CONFIG_BT_CTLR_ISO) #if defined(CONFIG_BT_CTLR_ISO)
case HCI_CLASS_ISO_DATA: { case HCI_CLASS_ISO_DATA: {
#if defined(CONFIG_BT_CTLR_CONN_ISO) || defined(CONFIG_BT_CTLR_SYNC_ISO)
uint8_t handle = node_rx->hdr.handle;
struct ll_iso_stream_hdr *hdr = NULL;
#if defined(CONFIG_BT_CTLR_CONN_ISO) #if defined(CONFIG_BT_CTLR_CONN_ISO)
struct ll_conn_iso_stream *cis = if (IS_CIS_HANDLE(handle)) {
ll_conn_iso_stream_get(node_rx->hdr.handle); struct ll_conn_iso_stream *cis =
struct ll_iso_datapath *dp = cis->datapath_out; ll_conn_iso_stream_get(handle);
hdr = &cis->hdr;
}
#endif
struct ll_iso_datapath *dp = hdr->datapath_out;
isoal_sink_handle_t sink = dp->sink_hdl; isoal_sink_handle_t sink = dp->sink_hdl;
if (dp->path_id == BT_HCI_DATAPATH_ID_HCI) { if (dp->path_id == BT_HCI_DATAPATH_ID_HCI) {
/* If HCI datapath pass to ISO AL here */ /* If HCI datapath pass to ISO AL here */
struct isoal_pdu_rx pckt_meta = { struct isoal_pdu_rx pckt_meta = {
.meta = &node_rx->hdr.rx_iso_meta, .meta = &node_rx->hdr.rx_iso_meta,
.pdu = (union isoal_pdu *) &node_rx->pdu[0] .pdu = (struct pdu_iso *) &node_rx->pdu[0]
}; };
/* Pass the ISO PDU through ISO-AL */ /* Pass the ISO PDU through ISO-AL */

View file

@ -51,9 +51,6 @@ isoal_status_t isoal_init(void)
isoal_status_t err = ISOAL_STATUS_OK; isoal_status_t err = ISOAL_STATUS_OK;
err = isoal_init_reset(); err = isoal_init_reset();
if (err) {
return err;
}
return err; return err;
} }
@ -64,9 +61,6 @@ isoal_status_t isoal_reset(void)
isoal_status_t err = ISOAL_STATUS_OK; isoal_status_t err = ISOAL_STATUS_OK;
err = isoal_init_reset(); err = isoal_init_reset();
if (err) {
return err;
}
return err; return err;
} }
@ -106,24 +100,34 @@ static void isoal_sink_deallocate(isoal_sink_handle_t hdl)
/** /**
* @brief Create a new sink * @brief Create a new sink
* *
* @param hdl[out] Handle to new sink * @param handle[in] Connection handle
* @param handle[in] Connection handle * @param role[in] Peripheral, Central or Broadcast
* @param burst_number[in] Burst Number * @param burst_number[in] Burst Number
* @param sdu_interval[in] SDU interval * @param flush_timeout[in] Flush timeout
* @param iso_interval[in] ISO interval * @param sdu_interval[in] SDU interval
* @param sdu_alloc[in] Callback of SDU allocator * @param iso_interval[in] ISO interval
* @param sdu_emit[in] Callback of SDU emitter * @param stream_sync_delay[in] CIS sync delay
* @param group_sync_delay[in] CIG sync delay
* @param sdu_alloc[in] Callback of SDU allocator
* @param sdu_emit[in] Callback of SDU emitter
* @param sdu_write[in] Callback of SDU byte writer
* @param hdl[out] Handle to new sink
*
* @return ISOAL_STATUS_OK if we could create a new sink; otherwise ISOAL_STATUS_ERR_SINK_ALLOC * @return ISOAL_STATUS_OK if we could create a new sink; otherwise ISOAL_STATUS_ERR_SINK_ALLOC
*/ */
isoal_status_t isoal_sink_create( isoal_status_t isoal_sink_create(
isoal_sink_handle_t *hdl,
uint16_t handle, uint16_t handle,
uint8_t role,
uint8_t burst_number, uint8_t burst_number,
uint8_t flush_timeout,
uint32_t sdu_interval, uint32_t sdu_interval,
uint16_t iso_interval, uint16_t iso_interval,
uint32_t stream_sync_delay,
uint32_t group_sync_delay,
isoal_sink_sdu_alloc_cb sdu_alloc, isoal_sink_sdu_alloc_cb sdu_alloc,
isoal_sink_sdu_emit_cb sdu_emit, isoal_sink_sdu_emit_cb sdu_emit,
isoal_sink_sdu_write_cb sdu_write) isoal_sink_sdu_write_cb sdu_write,
isoal_sink_handle_t *hdl)
{ {
isoal_status_t err; isoal_status_t err;
@ -133,17 +137,77 @@ isoal_status_t isoal_sink_create(
return err; return err;
} }
isoal_global.sink_state[*hdl].session.handle = handle; struct isoal_sink_session *session = &isoal_global.sink_state[*hdl].session;
session->handle = handle;
/* Todo: Next section computing various constants, should potentially be a
* function in itself as a number of the dependencies could be changed while
* a connection is active.
*/
/* Note: sdu_interval unit is uS, iso_interval is a multiple of 1.25mS */ /* Note: sdu_interval unit is uS, iso_interval is a multiple of 1.25mS */
isoal_global.sink_state[*hdl].session.pdus_per_sdu = session->pdus_per_sdu = burst_number * (sdu_interval / (iso_interval * 1250));
burst_number * (sdu_interval / (iso_interval * 1250));
/* Computation of transport latency (constant part)
*
* Unframed case:
*
* C->P: SDU_Synchronization_Reference =
* CIS reference anchor point + CIS_Sync_Delay + (FT_C_To_P - 1) * ISO_Interval
*
* P->C: SDU_Synchronization_Reference =
* CIS reference anchor point + CIS_Sync_Delay - CIG_Sync_Delay -
* ((ISO_Interval / SDU interval)-1) * SDU interval
*
* BIS: SDU_Synchronization_Reference =
* BIG reference anchor point + BIG_Sync_Delay
*
* Framed case:
*
* C->P: SDU_Synchronization_Reference =
* CIS Reference Anchor point +
* CIS_Sync_Delay + SDU_Interval_C_To_P + FT_C_To_P * ISO_Interval -
* Time_Offset
*
* P->C: synchronization reference SDU = CIS reference anchor point +
* CIS_Sync_Delay - CIG_Sync_Delay - Time_Offset
*
* BIS: SDU_Synchronization_Reference =
* BIG reference anchor point +
* BIG_Sync_Delay + SDU_interval + ISO_Interval - Time_Offset.
*/
if (role == BT_CONN_ROLE_PERIPHERAL) {
isoal_global.sink_state[*hdl].session.latency_unframed =
stream_sync_delay + ((flush_timeout - 1) * iso_interval);
isoal_global.sink_state[*hdl].session.latency_framed =
stream_sync_delay + sdu_interval + (flush_timeout * iso_interval);
} else if (role == BT_CONN_ROLE_CENTRAL) {
isoal_global.sink_state[*hdl].session.latency_unframed =
stream_sync_delay - group_sync_delay -
(((iso_interval / sdu_interval) - 1) * iso_interval);
isoal_global.sink_state[*hdl].session.latency_framed =
stream_sync_delay - group_sync_delay;
} else if (role == BT_ROLE_BROADCAST) {
isoal_global.sink_state[*hdl].session.latency_unframed =
group_sync_delay;
isoal_global.sink_state[*hdl].session.latency_framed =
group_sync_delay + sdu_interval + iso_interval;
} else {
LL_ASSERT(0);
}
/* Remember the platform-specific callbacks */ /* Remember the platform-specific callbacks */
isoal_global.sink_state[*hdl].session.sdu_alloc = sdu_alloc; session->sdu_alloc = sdu_alloc;
isoal_global.sink_state[*hdl].session.sdu_emit = sdu_emit; session->sdu_emit = sdu_emit;
isoal_global.sink_state[*hdl].session.sdu_write = sdu_write; session->sdu_write = sdu_write;
/* Initialize running seq number to zero */ /* Initialize running seq number to zero */
isoal_global.sink_state[*hdl].session.seqn = 0; session->seqn = 0;
return err; return err;
} }
@ -198,42 +262,41 @@ void isoal_sink_destroy(isoal_sink_handle_t hdl)
isoal_sink_deallocate(hdl); isoal_sink_deallocate(hdl);
} }
static uint8_t isoal_rx_packet_classify(const struct isoal_pdu_rx *pdu_meta)
{
/* LLID location is the same for both CIS and BIS PDUs */
uint8_t llid = pdu_meta->pdu->cis.ll_id;
return llid;
}
/* Obtain destination SDU */ /* Obtain destination SDU */
static isoal_status_t isoal_rx_allocate_sdu(struct isoal_sink *sink, static isoal_status_t isoal_rx_allocate_sdu(struct isoal_sink *sink,
const struct isoal_pdu_rx *pdu_meta) const struct isoal_pdu_rx *pdu_meta)
{ {
isoal_status_t err = ISOAL_STATUS_OK; struct isoal_sink_session *session;
struct isoal_sdu_produced *sdu = &sink->sdu_production.sdu; struct isoal_sdu_production *sp;
struct isoal_sdu_produced *sdu;
isoal_status_t err;
err = ISOAL_STATUS_OK;
session = &sink->session;
sp = &sink->sdu_production;
sdu = &sp->sdu;
/* Allocate a SDU if the previous was filled (thus sent) */ /* Allocate a SDU if the previous was filled (thus sent) */
const bool sdu_complete = (sink->sdu_production.sdu_available == 0); const bool sdu_complete = (sp->sdu_available == 0);
if (sdu_complete) { if (sdu_complete) {
/* Allocate new clean SDU buffer */ /* Allocate new clean SDU buffer */
err = sink->session.sdu_alloc( err = session->sdu_alloc(
sink, sink,
pdu_meta, /* [in] PDU origin may determine buffer */ pdu_meta, /* [in] PDU origin may determine buffer */
&sdu->contents /* [out] Updated with pointer and size */ &sdu->contents /* [out] Updated with pointer and size */
); );
/* Nothing has been written into buffer yet */ /* Nothing has been written into buffer yet */
sink->sdu_production.sdu_written = 0; sp->sdu_written = 0;
sink->sdu_production.sdu_available = sdu->contents.size; sp->sdu_available = sdu->contents.size;
LL_ASSERT(sdu->contents.size > 0); LL_ASSERT(sdu->contents.size > 0);
/* Remember meta data */ /* Remember meta data */
sdu->status = pdu_meta->meta->status; sdu->status = pdu_meta->meta->status;
sdu->timestamp = pdu_meta->meta->timestamp; sdu->timestamp = pdu_meta->meta->timestamp;
/* Get seq number from session counter, and increase SDU counter */ /* Get seq number from session counter */
sdu->seqn = sink->session.seqn++; sdu->seqn = session->seqn;
} }
return err; return err;
@ -241,46 +304,48 @@ static isoal_status_t isoal_rx_allocate_sdu(struct isoal_sink *sink,
static isoal_status_t isoal_rx_try_emit_sdu(struct isoal_sink *sink, bool end_of_sdu) static isoal_status_t isoal_rx_try_emit_sdu(struct isoal_sink *sink, bool end_of_sdu)
{ {
isoal_status_t err = ISOAL_STATUS_OK; struct isoal_sdu_production *sp;
struct isoal_sdu_produced *sdu = &sink->sdu_production.sdu; struct isoal_sdu_produced *sdu;
isoal_status_t err;
err = ISOAL_STATUS_OK;
sp = &sink->sdu_production;
sdu = &sp->sdu;
/* Emit a SDU */ /* Emit a SDU */
const bool sdu_complete = (sink->sdu_production.sdu_available == 0) || end_of_sdu; const bool sdu_complete = (sp->sdu_available == 0) || end_of_sdu;
if (end_of_sdu) { if (end_of_sdu) {
sink->sdu_production.sdu_available = 0; sp->sdu_available = 0;
} }
if (sdu_complete) { if (sdu_complete) {
uint8_t next_state = BT_ISO_START; uint8_t next_state = BT_ISO_START;
switch (sink->sdu_production.sdu_state) { switch (sp->sdu_state) {
case BT_ISO_START: case BT_ISO_START:
if (end_of_sdu) { if (end_of_sdu) {
sink->sdu_production.sdu_state = BT_ISO_SINGLE; sp->sdu_state = BT_ISO_SINGLE;
next_state = BT_ISO_START; next_state = BT_ISO_START;
} else { } else {
sink->sdu_production.sdu_state = BT_ISO_START; sp->sdu_state = BT_ISO_START;
next_state = BT_ISO_CONT; next_state = BT_ISO_CONT;
} }
break; break;
case BT_ISO_CONT: case BT_ISO_CONT:
if (end_of_sdu) { if (end_of_sdu) {
sink->sdu_production.sdu_state = BT_ISO_END; sp->sdu_state = BT_ISO_END;
next_state = BT_ISO_START; next_state = BT_ISO_START;
} else { } else {
sink->sdu_production.sdu_state = BT_ISO_CONT; sp->sdu_state = BT_ISO_CONT;
next_state = BT_ISO_CONT; next_state = BT_ISO_CONT;
} }
break; break;
case BT_ISO_END:
case BT_ISO_SINGLE:
default:
LL_ASSERT(0);
break;
} }
sdu->status = sink->sdu_production.sdu_status; sdu->status = sp->sdu_status;
err = sink->session.sdu_emit(sink, sdu); struct isoal_sink_session *session = &sink->session;
err = session->sdu_emit(sink, sdu);
/* update next state */ /* update next state */
sink->sdu_production.sdu_state = next_state; sink->sdu_production.sdu_state = next_state;
@ -291,24 +356,30 @@ static isoal_status_t isoal_rx_try_emit_sdu(struct isoal_sink *sink, bool end_of
static isoal_status_t isoal_rx_append_to_sdu(struct isoal_sink *sink, static isoal_status_t isoal_rx_append_to_sdu(struct isoal_sink *sink,
const struct isoal_pdu_rx *pdu_meta, const struct isoal_pdu_rx *pdu_meta,
uint8_t offset,
uint8_t length,
bool is_end_fragment) bool is_end_fragment)
{ {
const uint8_t *pdu_payload = pdu_meta->pdu->cis.payload; isoal_pdu_len_t packet_available;
isoal_pdu_len_t packet_available = pdu_meta->pdu->cis.length; const uint8_t *pdu_payload;
isoal_status_t err = ISOAL_STATUS_OK;
bool handle_error_case; bool handle_error_case;
isoal_status_t err;
/* Might get an empty packed due to errors, we will need to terminate /* Might get an empty packed due to errors, we will need to terminate
* and send something up anyhow * and send something up anyhow
*/ */
packet_available = length;
handle_error_case = (is_end_fragment && (packet_available == 0)); handle_error_case = (is_end_fragment && (packet_available == 0));
pdu_payload = pdu_meta->pdu->payload + offset;
LL_ASSERT(pdu_payload); LL_ASSERT(pdu_payload);
/* While there is something left of the packet to consume */ /* While there is something left of the packet to consume */
err = ISOAL_STATUS_OK;
while ((packet_available > 0) || handle_error_case) { while ((packet_available > 0) || handle_error_case) {
const isoal_status_t err_alloc = isoal_rx_allocate_sdu(sink, pdu_meta); const isoal_status_t err_alloc = isoal_rx_allocate_sdu(sink, pdu_meta);
struct isoal_sdu_produced *sdu = &sink->sdu_production.sdu; struct isoal_sdu_production *sp = &sink->sdu_production;
struct isoal_sdu_produced *sdu = &sp->sdu;
err |= err_alloc; err |= err_alloc;
@ -319,21 +390,21 @@ static isoal_status_t isoal_rx_append_to_sdu(struct isoal_sink *sink,
*/ */
const size_t consume_len = MIN( const size_t consume_len = MIN(
packet_available, packet_available,
sink->sdu_production.sdu_available sp->sdu_available
); );
LL_ASSERT(sdu->contents.dbuf);
if (consume_len > 0) { if (consume_len > 0) {
if (pdu_meta->meta->status == ISOAL_PDU_STATUS_VALID) { if (pdu_meta->meta->status == ISOAL_PDU_STATUS_VALID) {
err |= sink->session.sdu_write(sdu->contents.dbuf, struct isoal_sink_session *session = &sink->session;
pdu_payload,
consume_len); err |= session->sdu_write(sdu->contents.dbuf,
pdu_payload,
consume_len);
} }
pdu_payload += consume_len; pdu_payload += consume_len;
sink->sdu_production.sdu_written += consume_len; sp->sdu_written += consume_len;
sink->sdu_production.sdu_available -= consume_len; sp->sdu_available -= consume_len;
packet_available -= consume_len; packet_available -= consume_len;
} }
bool end_of_sdu = (packet_available == 0) && is_end_fragment; bool end_of_sdu = (packet_available == 0) && is_end_fragment;
@ -343,13 +414,12 @@ static isoal_status_t isoal_rx_append_to_sdu(struct isoal_sink *sink,
err |= err_emit; err |= err_emit;
} }
LL_ASSERT(packet_available == 0);
return err; return err;
} }
/** /**
* @brief Consume a PDU: Copy contents into SDU(s) and emit to a sink * @brief Consume an unframed PDU: Copy contents into SDU(s) and emit to a sink
* @details Destination sink may have an already partially built SDU * @details Destination sink may have an already partially built SDU
* *
* @param sink[in,out] Destination sink with bookkeeping state * @param sink[in,out] Destination sink with bookkeeping state
@ -357,39 +427,70 @@ static isoal_status_t isoal_rx_append_to_sdu(struct isoal_sink *sink,
* *
* @return Status * @return Status
*/ */
static isoal_status_t isoal_rx_packet_consume(struct isoal_sink *sink, static isoal_status_t isoal_rx_unframed_consume(struct isoal_sink *sink,
const struct isoal_pdu_rx *pdu_meta) const struct isoal_pdu_rx *pdu_meta)
{ {
struct isoal_sink_session *session;
struct isoal_sdu_production *sp;
struct node_rx_iso_meta *meta;
struct pdu_iso *pdu;
bool end_of_packet;
uint8_t next_state;
isoal_status_t err; isoal_status_t err;
uint8_t next_state, llid; bool pdu_padding;
bool pdu_err, pdu_padding, last_pdu, end_of_packet, seq_err; bool last_pdu;
bool pdu_err;
bool seq_err;
uint8_t llid;
sp = &sink->sdu_production;
session = &sink->session;
meta = pdu_meta->meta;
pdu = pdu_meta->pdu;
err = ISOAL_STATUS_OK; err = ISOAL_STATUS_OK;
next_state = ISOAL_START; next_state = ISOAL_START;
llid = isoal_rx_packet_classify(pdu_meta); llid = pdu_meta->pdu->ll_id;
pdu_err = (pdu_meta->meta->status != ISOAL_PDU_STATUS_VALID); pdu_err = (pdu_meta->meta->status != ISOAL_PDU_STATUS_VALID);
seq_err = (pdu_meta->meta->payload_number != sink->sdu_production.prev_pdu_id+1); pdu_padding = (pdu_meta->pdu->length == 0) && (llid == PDU_BIS_LLID_START_CONTINUE);
pdu_padding = (pdu_meta->pdu->cis.length == 0) && (llid == PDU_BIS_LLID_START_CONTINUE);
if (sink->sdu_production.fsm == ISOAL_START) { if (sp->fsm == ISOAL_START) {
sink->sdu_production.sdu_status = ISOAL_SDU_STATUS_VALID; struct isoal_sdu_produced *sdu;
sink->sdu_production.sdu_state = BT_ISO_START; uint32_t anchorpoint;
sink->sdu_production.pdu_cnt = 1; uint32_t latency;
} else {
sink->sdu_production.pdu_cnt++; sp->sdu_status = ISOAL_SDU_STATUS_VALID;
sp->sdu_state = BT_ISO_START;
sp->pdu_cnt = 1;
session->seqn++;
seq_err = false;
/* Todo: anchorpoint must be reference anchor point, should be fixed in LL */
anchorpoint = meta->timestamp;
latency = session->latency_unframed;
sdu = &sp->sdu;
sdu->timestamp = anchorpoint + latency;
} else {
sp->pdu_cnt++;
seq_err = (meta->payload_number != (sp->prev_pdu_id+1));
} }
last_pdu = (sink->sdu_production.pdu_cnt == sink->session.pdus_per_sdu); last_pdu = (sp->pdu_cnt == session->pdus_per_sdu);
end_of_packet = (llid == PDU_BIS_LLID_COMPLETE_END) || last_pdu; end_of_packet = (llid == PDU_BIS_LLID_COMPLETE_END) || last_pdu;
switch (sink->sdu_production.fsm) { switch (sp->fsm) {
case ISOAL_START: case ISOAL_START:
case ISOAL_CONTINUE: case ISOAL_CONTINUE:
if (pdu_err || seq_err) { if (pdu_err || seq_err) {
/* PDU contains errors */ /* PDU contains errors */
next_state = ISOAL_ERR_SPOOL; if (last_pdu) {
/* Last PDU all done */
next_state = ISOAL_START;
} else {
next_state = ISOAL_ERR_SPOOL;
}
} else if (llid == PDU_BIS_LLID_START_CONTINUE) { } else if (llid == PDU_BIS_LLID_START_CONTINUE) {
/* PDU contains a continuation (neither start of end) fragment of SDU */ /* PDU contains a continuation (neither start of end) fragment of SDU */
if (last_pdu) { if (last_pdu) {
@ -409,7 +510,7 @@ static isoal_status_t isoal_rx_packet_consume(struct isoal_sink *sink,
} }
} else { } else {
/* Unsupported case */ /* Unsupported case */
LL_ASSERT(0); err = ISOAL_STATUS_ERR_UNSPECIFIED;
} }
break; break;
@ -429,23 +530,221 @@ static isoal_status_t isoal_rx_packet_consume(struct isoal_sink *sink,
/* Update error state */ /* Update error state */
if (pdu_err && !pdu_padding) { if (pdu_err && !pdu_padding) {
sink->sdu_production.sdu_status |= pdu_meta->meta->status; sp->sdu_status |= meta->status;
} else if (last_pdu && (llid != PDU_BIS_LLID_COMPLETE_END) && } else if (last_pdu && (llid != PDU_BIS_LLID_COMPLETE_END) &&
(sink->sdu_production.fsm != ISOAL_ERR_SPOOL)) { (sp->fsm != ISOAL_ERR_SPOOL)) {
/* END fragment never seen */ /* END fragment never seen */
sink->sdu_production.sdu_status |= ISOAL_SDU_STATUS_ERRORS; sp->sdu_status |= ISOAL_SDU_STATUS_ERRORS;
} else if (seq_err) { } else if (seq_err) {
sink->sdu_production.sdu_status |= ISOAL_SDU_STATUS_LOST_DATA; sp->sdu_status |= ISOAL_SDU_STATUS_LOST_DATA;
} }
/* Append valid PDU to SDU */ /* Append valid PDU to SDU */
if (!pdu_padding && !pdu_err) { if (!pdu_padding) {
err |= isoal_rx_append_to_sdu(sink, pdu_meta, end_of_packet); err |= isoal_rx_append_to_sdu(sink, pdu_meta, 0,
pdu_meta->pdu->length,
end_of_packet);
} }
/* Update next stat */ /* Update next state */
sink->sdu_production.fsm = next_state; sp->fsm = next_state;
sink->sdu_production.prev_pdu_id = pdu_meta->meta->payload_number; sp->prev_pdu_id = meta->payload_number;
return err;
}
/**
* @brief Consume a framed PDU: Copy contents into SDU(s) and emit to a sink
* @details Destination sink may have an already partially built SDU
*
* @param sink[in,out] Destination sink with bookkeeping state
* @param pdu_meta[out] PDU with meta information (origin, timing, status)
*
* @return Status
*/
static isoal_status_t isoal_rx_framed_consume(struct isoal_sink *sink,
const struct isoal_pdu_rx *pdu_meta)
{
struct isoal_sink_session *session;
struct isoal_sdu_production *sp;
struct isoal_sdu_produced *sdu;
struct pdu_iso_sdu_sh *seg_hdr;
struct node_rx_iso_meta *meta;
uint32_t anchorpoint;
uint8_t *end_of_pdu;
uint32_t timeoffset;
isoal_status_t err;
uint8_t next_state;
uint32_t timestamp;
uint32_t latency;
bool pdu_padding;
bool pdu_err;
bool seq_err;
sp = &sink->sdu_production;
session = &sink->session;
meta = pdu_meta->meta;
sdu = &sp->sdu;
err = ISOAL_STATUS_OK;
next_state = ISOAL_START;
pdu_err = (pdu_meta->meta->status != ISOAL_PDU_STATUS_VALID);
pdu_padding = (pdu_meta->pdu->length == 0);
if (sp->fsm == ISOAL_START) {
seq_err = false;
} else {
seq_err = (meta->payload_number != (sp->prev_pdu_id + 1));
}
end_of_pdu = ((uint8_t *) pdu_meta->pdu->payload) + pdu_meta->pdu->length - 1;
seg_hdr = (struct pdu_iso_sdu_sh *) pdu_meta->pdu->payload;
if (pdu_err || seq_err) {
/* When one or more ISO Data PDUs are not received, the receiving device may
* discard all SDUs affected by the missing PDUs. Any partially received SDU
* may also be discarded.
*/
next_state = ISOAL_ERR_SPOOL;
/* Update next state */
sink->sdu_production.fsm = next_state;
if (pdu_err) {
sp->sdu_status |= meta->status;
} else if (seq_err) {
sp->sdu_status |= ISOAL_SDU_STATUS_LOST_DATA;
}
/* Flush current SDU with error if any */
err |= isoal_rx_append_to_sdu(sink, pdu_meta, 0, 0, true);
/* Skip searching this PDU */
seg_hdr = NULL;
}
if (pdu_padding) {
/* Skip searching this PDU */
seg_hdr = NULL;
}
while (seg_hdr) {
bool append = true;
const uint8_t sc = seg_hdr->sc;
const uint8_t cmplt = seg_hdr->cmplt;
if (sp->fsm == ISOAL_START) {
sp->sdu_status = ISOAL_SDU_STATUS_VALID;
sp->sdu_state = BT_ISO_START;
session->seqn++;
}
switch (sp->fsm) {
case ISOAL_START:
timeoffset = seg_hdr->timeoffset;
anchorpoint = meta->timestamp;
latency = session->latency_framed;
timestamp = anchorpoint + latency - timeoffset;
if (!sc && !cmplt) {
/* The start of a new SDU, where not all SDU data is included in
* the current PDU, and additional PDUs are required to complete
* the SDU.
*/
sdu->timestamp = timestamp;
next_state = ISOAL_CONTINUE;
} else if (!sc && cmplt) {
/* The start of a new SDU that contains the full SDU data in the
* current PDU.
*/
sdu->timestamp = timestamp;
next_state = ISOAL_START;
} else {
/* Unsupported case */
err = ISOAL_STATUS_ERR_UNSPECIFIED;
}
break;
case ISOAL_CONTINUE:
if (sc && !cmplt) {
/* The continuation of a previous SDU. The SDU payload is appended
* to the previous data and additional PDUs are required to
* complete the SDU.
*/
next_state = ISOAL_CONTINUE;
} else if (sc && cmplt) {
/* The continuation of a previous SDU.
* Frame data is appended to previously received SDU data and
* completes in the current PDU.
*/
next_state = ISOAL_START;
} else {
/* Unsupported case */
err = ISOAL_STATUS_ERR_UNSPECIFIED;
}
break;
case ISOAL_ERR_SPOOL:
/* In error state, search for valid next start of SDU */
timeoffset = seg_hdr->timeoffset;
anchorpoint = meta->timestamp;
latency = session->latency_framed;
timestamp = anchorpoint + latency - timeoffset;
if (!sc && !cmplt) {
/* The start of a new SDU, where not all SDU data is included in
* the current PDU, and additional PDUs are required to complete
* the SDU.
*/
sdu->timestamp = timestamp;
next_state = ISOAL_CONTINUE;
} else if (!sc && cmplt) {
/* The start of a new SDU that contains the full SDU data in the
* current PDU.
*/
sdu->timestamp = timestamp;
next_state = ISOAL_START;
} else {
/* Start not found yet, stay in Error state */
append = false;
next_state = ISOAL_ERR_SPOOL;
}
break;
}
if (append) {
/* Calculate offset of first payload byte from SDU based on assumption
* of No time_offset in header
*/
uint8_t offset = ((uint8_t *) seg_hdr) + PDU_ISO_SEG_HDR_SIZE -
pdu_meta->pdu->payload;
uint8_t length = seg_hdr->length;
if (!sc) {
/* time_offset included in header, don't copy offset field to SDU */
offset = offset + PDU_ISO_SEG_TIMEOFFSET_SIZE;
length = length - PDU_ISO_SEG_TIMEOFFSET_SIZE;
}
/* Todo: check if effective len=0 what happens then?
* We should possibly be able to send empty packets with only time stamp
*/
err |= isoal_rx_append_to_sdu(sink, pdu_meta, offset, length, cmplt);
}
/* Update next state */
sp->fsm = next_state;
/* Find next segment header, set to null if past end of PDU */
seg_hdr = (struct pdu_iso_sdu_sh *) (((uint8_t *) seg_hdr) +
seg_hdr->length + PDU_ISO_SEG_HDR_SIZE);
if (((uint8_t *) seg_hdr) > end_of_pdu) {
seg_hdr = NULL;
}
}
sp->prev_pdu_id = meta->payload_number;
return err; return err;
} }
@ -465,7 +764,13 @@ isoal_status_t isoal_rx_pdu_recombine(isoal_sink_handle_t sink_hdl,
isoal_status_t err = ISOAL_STATUS_ERR_SDU_ALLOC; isoal_status_t err = ISOAL_STATUS_ERR_SDU_ALLOC;
if (sink->sdu_production.mode != ISOAL_PRODUCTION_MODE_DISABLED) { if (sink->sdu_production.mode != ISOAL_PRODUCTION_MODE_DISABLED) {
err = isoal_rx_packet_consume(sink, pdu_meta); bool pdu_framed = (pdu_meta->pdu->ll_id == PDU_BIS_LLID_FRAMED);
if (pdu_framed) {
err = isoal_rx_framed_consume(sink, pdu_meta);
} else {
err = isoal_rx_unframed_consume(sink, pdu_meta);
}
} }
return err; return err;

View file

@ -15,6 +15,9 @@ typedef uint8_t isoal_status_t;
#define ISOAL_STATUS_ERR_SOURCE_ALLOC ((isoal_status_t) 0x02) /* Source pool full */ #define ISOAL_STATUS_ERR_SOURCE_ALLOC ((isoal_status_t) 0x02) /* Source pool full */
#define ISOAL_STATUS_ERR_SDU_ALLOC ((isoal_status_t) 0x04) /* SDU allocation */ #define ISOAL_STATUS_ERR_SDU_ALLOC ((isoal_status_t) 0x04) /* SDU allocation */
#define ISOAL_STATUS_ERR_SDU_EMIT ((isoal_status_t) 0x08) /* SDU emission */ #define ISOAL_STATUS_ERR_SDU_EMIT ((isoal_status_t) 0x08) /* SDU emission */
#define ISOAL_STATUS_ERR_UNSPECIFIED ((isoal_status_t) 0x10) /* Unspecified error */
#define BT_ROLE_BROADCAST (BT_CONN_ROLE_PERIPHERAL + 1)
/** Handle to a registered ISO Sub-System sink */ /** Handle to a registered ISO Sub-System sink */
typedef uint8_t isoal_sink_handle_t; typedef uint8_t isoal_sink_handle_t;
@ -108,19 +111,13 @@ struct isoal_sdu_produced {
void *ctx; void *ctx;
}; };
/** @brief ISO PDU. Covers both CIS and BIS */
union isoal_pdu {
struct pdu_cis cis;
struct pdu_bis bis;
};
/** @brief Received ISO PDU with associated meta data */ /** @brief Received ISO PDU with associated meta data */
struct isoal_pdu_rx { struct isoal_pdu_rx {
/** Meta */ /** Meta */
struct node_rx_iso_meta *meta; struct node_rx_iso_meta *meta;
/** PDU contents and length can only be trusted if status is valid */ /** PDU contents and length can only be trusted if status is valid */
union isoal_pdu *pdu; struct pdu_iso *pdu;
}; };
@ -179,38 +176,43 @@ struct isoal_sink_config {
/* TODO add SDU and PDU max length etc. */ /* TODO add SDU and PDU max length etc. */
}; };
struct isoal_sink_session {
isoal_sink_sdu_alloc_cb sdu_alloc;
isoal_sink_sdu_emit_cb sdu_emit;
isoal_sink_sdu_write_cb sdu_write;
struct isoal_sink_config param;
isoal_sdu_cnt_t seqn;
uint16_t handle;
uint8_t pdus_per_sdu;
uint32_t latency_unframed;
uint32_t latency_framed;
};
struct isoal_sdu_production {
/* Permit atomic enable/disable of SDU production */
volatile isoal_production_mode_t mode;
/* We are constructing an SDU from {<1 or =1 or >1} PDUs */
struct isoal_sdu_produced sdu;
/* Bookkeeping */
isoal_pdu_cnt_t prev_pdu_id : 39;
enum {
ISOAL_START,
ISOAL_CONTINUE,
ISOAL_ERR_SPOOL
} fsm;
uint8_t pdu_cnt;
uint8_t sdu_state;
isoal_sdu_len_t sdu_written;
isoal_sdu_len_t sdu_available;
isoal_sdu_status_t sdu_status;
};
struct isoal_sink { struct isoal_sink {
/* Session-constant */ /* Session-constant */
struct { struct isoal_sink_session session;
isoal_sink_sdu_alloc_cb sdu_alloc;
isoal_sink_sdu_emit_cb sdu_emit;
isoal_sink_sdu_write_cb sdu_write;
struct isoal_sink_config param;
isoal_sdu_cnt_t seqn;
uint16_t handle;
uint8_t pdus_per_sdu;
} session;
/* State for SDU production */ /* State for SDU production */
struct { struct isoal_sdu_production sdu_production;
/* Permit atomic enable/disable of SDU production */
volatile isoal_production_mode_t mode;
/* We are constructing an SDU from {<1 or =1 or >1} PDUs */
struct isoal_sdu_produced sdu;
/* Bookkeeping */
isoal_pdu_cnt_t prev_pdu_id : 39;
enum {
ISOAL_START,
ISOAL_CONTINUE,
ISOAL_ERR_SPOOL
} fsm;
uint8_t pdu_cnt;
uint8_t sdu_state;
isoal_sdu_len_t sdu_written;
isoal_sdu_len_t sdu_available;
isoal_sdu_status_t sdu_status;
} sdu_production;
}; };
@ -218,14 +220,18 @@ isoal_status_t isoal_init(void);
isoal_status_t isoal_reset(void); isoal_status_t isoal_reset(void);
isoal_status_t isoal_sink_create(isoal_sink_handle_t *hdl, isoal_status_t isoal_sink_create(uint16_t handle,
uint16_t handle, uint8_t role,
uint8_t burst_number, uint8_t burst_number,
uint8_t flush_timeout,
uint32_t sdu_interval, uint32_t sdu_interval,
uint16_t iso_interval, uint16_t iso_interval,
uint32_t stream_sync_delay,
uint32_t group_sync_delay,
isoal_sink_sdu_alloc_cb sdu_alloc, isoal_sink_sdu_alloc_cb sdu_alloc,
isoal_sink_sdu_emit_cb sdu_emit, isoal_sink_sdu_emit_cb sdu_emit,
isoal_sink_sdu_write_cb sdu_write); isoal_sink_sdu_write_cb sdu_write,
isoal_sink_handle_t *hdl);
struct isoal_sink_config *isoal_get_sink_param_ref(isoal_sink_handle_t hdl); struct isoal_sink_config *isoal_get_sink_param_ref(isoal_sink_handle_t hdl);

View file

@ -842,6 +842,52 @@ struct pdu_data {
} __packed; } __packed;
} __packed; } __packed;
/* Generic ISO pdu, could be CIS or BIS
* To be used when reffering to component withouth knowing CIS or BIS type
*/
struct pdu_iso {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint8_t ll_id:2;
uint8_t hdr_other:6;
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
uint8_t hdr_other:6;
uint8_t ll_id:2;
#else
#error "Unsupported endianness"
#endif /* __BYTE_ORDER__ */
uint8_t length;
uint8_t payload[0];
} __packed;
/* ISO SDU segmentation header */
#define PDU_ISO_SEG_HDR_SIZE 2
#define PDU_ISO_SEG_TIMEOFFSET_SIZE 3
struct pdu_iso_sdu_sh {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint8_t sc:1;
uint8_t cmplt:1;
uint8_t rfu:6;
uint8_t length;
/* Note, timeoffset only available in first segment of sdu */
uint32_t timeoffset:24;
uint32_t payload:8;
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
uint8_t rfu:6;
uint8_t cmplt:1;
uint8_t sc:1;
uint8_t length;
/* Note, timeoffset only available in first segment of sdu */
uint32_t payload:8;
uint32_t timeoffset:24;
#else
#error "Unsupported endianness"
#endif /* __BYTE_ORDER__ */
} __packed;
struct pdu_cis { struct pdu_cis {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint8_t ll_id:2; uint8_t ll_id:2;

View file

@ -61,10 +61,10 @@
#include "ull_sync_internal.h" #include "ull_sync_internal.h"
#include "ull_sync_iso_internal.h" #include "ull_sync_iso_internal.h"
#include "ull_central_internal.h" #include "ull_central_internal.h"
#include "ull_iso_types.h"
#include "ull_conn_internal.h" #include "ull_conn_internal.h"
#include "lll_conn_iso.h" #include "lll_conn_iso.h"
#include "ull_conn_iso_types.h" #include "ull_conn_iso_types.h"
#include "ull_iso_types.h"
#include "ull_central_iso_internal.h" #include "ull_central_iso_internal.h"
#include "ull_conn_iso_internal.h" #include "ull_conn_iso_internal.h"
@ -1490,7 +1490,7 @@ void ll_rx_mem_release(void **node_rx)
#endif /* CONFIG_BT_CTLR_SYNC_ISO */ #endif /* CONFIG_BT_CTLR_SYNC_ISO */
#endif /* CONFIG_BT_CTLR_SYNC_PERIODIC */ #endif /* CONFIG_BT_CTLR_SYNC_PERIODIC */
#if defined(CONFIG_BT_CONN) #if defined(CONFIG_BT_CONN) || defined(CONFIG_BT_CTLR_CONN_ISO)
case NODE_RX_TYPE_TERMINATE: case NODE_RX_TYPE_TERMINATE:
{ {
if (IS_ACL_HANDLE(rx_free->handle)) { if (IS_ACL_HANDLE(rx_free->handle)) {
@ -1509,7 +1509,7 @@ void ll_rx_mem_release(void **node_rx)
} }
} }
break; break;
#endif /* CONFIG_BT_CONN */ #endif /* CONFIG_BT_CONN || CONFIG_BT_CTLR_CONN_ISO */
case NODE_RX_TYPE_EVENT_DONE: case NODE_RX_TYPE_EVENT_DONE:
default: default:
@ -2543,7 +2543,7 @@ static inline int rx_demux_rx(memq_link_t *link, struct node_rx_hdr *rx)
struct node_rx_pdu *rx_pdu = (struct node_rx_pdu *)rx; struct node_rx_pdu *rx_pdu = (struct node_rx_pdu *)rx;
struct ll_conn_iso_stream *cis = struct ll_conn_iso_stream *cis =
ll_conn_iso_stream_get(rx_pdu->hdr.handle); ll_conn_iso_stream_get(rx_pdu->hdr.handle);
struct ll_iso_datapath *dp = cis->datapath_out; struct ll_iso_datapath *dp = cis->hdr.datapath_out;
isoal_sink_handle_t sink = dp->sink_hdl; isoal_sink_handle_t sink = dp->sink_hdl;
if (dp->path_id != BT_HCI_DATAPATH_ID_HCI) { if (dp->path_id != BT_HCI_DATAPATH_ID_HCI) {
@ -2553,7 +2553,7 @@ static inline int rx_demux_rx(memq_link_t *link, struct node_rx_hdr *rx)
*/ */
struct isoal_pdu_rx pckt_meta = { struct isoal_pdu_rx pckt_meta = {
.meta = &rx_pdu->hdr.rx_iso_meta, .meta = &rx_pdu->hdr.rx_iso_meta,
.pdu = (union isoal_pdu *) &rx_pdu->pdu[0] .pdu = (struct pdu_iso *) &rx_pdu->pdu[0]
}; };
/* Pass the ISO PDU through ISO-AL */ /* Pass the ISO PDU through ISO-AL */

View file

@ -36,6 +36,8 @@
#include "ull_tx_queue.h" #include "ull_tx_queue.h"
#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ #endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */
#include "isoal.h"
#include "ull_iso_types.h"
#include "ull_conn_types.h" #include "ull_conn_types.h"
#include "ull_conn_iso_types.h" #include "ull_conn_iso_types.h"
#include "ull_internal.h" #include "ull_internal.h"

View file

@ -18,6 +18,8 @@
#include "lll.h" #include "lll.h"
#include "lll_conn.h" #include "lll_conn.h"
#include "ull_conn_types.h" #include "ull_conn_types.h"
#include "isoal.h"
#include "ull_iso_types.h"
#include "lll_conn_iso.h" #include "lll_conn_iso.h"
#include "ull_conn_iso_types.h" #include "ull_conn_iso_types.h"
#include "ull_conn_internal.h" #include "ull_conn_internal.h"
@ -30,7 +32,6 @@
#include "common/log.h" #include "common/log.h"
#include "hal/debug.h" #include "hal/debug.h"
#define LAST_VALID_CIS_HANDLE (CONFIG_BT_CTLR_CONN_ISO_STREAMS + LL_CIS_HANDLE_BASE - 1)
static int init_reset(void); static int init_reset(void);
static void ticker_update_cig_op_cb(uint32_t status, void *param); static void ticker_update_cig_op_cb(uint32_t status, void *param);
@ -85,12 +86,19 @@ struct ll_conn_iso_group *ll_conn_iso_group_get_by_id(uint8_t id)
struct ll_conn_iso_stream *ll_conn_iso_stream_acquire(void) struct ll_conn_iso_stream *ll_conn_iso_stream_acquire(void)
{ {
return mem_acquire(&cis_free); struct ll_conn_iso_stream *cis = mem_acquire(&cis_free);
cis->hdr.datapath_in = NULL;
cis->hdr.datapath_out = NULL;
return cis;
} }
void ll_conn_iso_stream_release(struct ll_conn_iso_stream *cis) void ll_conn_iso_stream_release(struct ll_conn_iso_stream *cis)
{ {
mem_release(cis, &cig_free); cis->cis_id = 0;
cis->group = NULL;
mem_release(cis, &cis_free);
} }
uint16_t ll_conn_iso_stream_handle_get(struct ll_conn_iso_stream *cis) uint16_t ll_conn_iso_stream_handle_get(struct ll_conn_iso_stream *cis)

View file

@ -8,19 +8,14 @@ struct ll_conn;
typedef void (*ll_iso_stream_released_cb_t)(struct ll_conn *conn); typedef void (*ll_iso_stream_released_cb_t)(struct ll_conn *conn);
#define LL_CIS_HANDLE_BASE CONFIG_BT_MAX_CONN
#define LL_CIS_IDX_FROM_HANDLE(_handle) \
((_handle) - LL_CIS_HANDLE_BASE)
struct ll_conn_iso_stream { struct ll_conn_iso_stream {
struct ll_iso_stream_hdr hdr;
struct ll_conn_iso_group *group; struct ll_conn_iso_group *group;
struct lll_conn_iso_stream lll; struct lll_conn_iso_stream lll;
uint32_t sync_delay; uint32_t sync_delay;
uint8_t cis_id; uint8_t cis_id;
uint8_t terminate_reason; uint8_t terminate_reason;
struct ll_iso_datapath *datapath_in;
struct ll_iso_datapath *datapath_out;
uint32_t offset; /* Offset of CIS from ACL event in us */ uint32_t offset; /* Offset of CIS from ACL event in us */
ll_iso_stream_released_cb_t released_cb; /* CIS release callback */ ll_iso_stream_released_cb_t released_cb; /* CIS release callback */
uint8_t established : 1; /* 0 if CIS has not yet been established. uint8_t established : 1; /* 0 if CIS has not yet been established.

View file

@ -28,6 +28,8 @@
#include "ull_sync_types.h" #include "ull_sync_types.h"
#include "ull_iso_types.h" #include "ull_iso_types.h"
#include "ull_conn_iso_types.h" #include "ull_conn_iso_types.h"
#include "ull_internal.h"
#include "ull_iso_internal.h"
#include "ull_conn_internal.h" #include "ull_conn_internal.h"
#include "ull_sync_iso_internal.h" #include "ull_sync_iso_internal.h"
@ -136,37 +138,55 @@ uint8_t ll_setup_iso_path(uint16_t handle, uint8_t path_dir, uint8_t path_id,
ARG_UNUSED(codec_config_len); ARG_UNUSED(codec_config_len);
ARG_UNUSED(codec_config); ARG_UNUSED(codec_config);
isoal_sink_handle_t sink_handle;
uint32_t stream_sync_delay;
uint32_t group_sync_delay;
uint8_t flush_timeout;
uint16_t iso_interval;
uint32_t sdu_interval;
uint8_t burst_number;
isoal_status_t err;
uint8_t role;
if (path_id == BT_HCI_DATAPATH_ID_DISABLED) { if (path_id == BT_HCI_DATAPATH_ID_DISABLED) {
return 0; return 0;
} }
if (path_dir > BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) { if (path_dir != BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) {
return BT_HCI_ERR_CMD_DISALLOWED; /* FIXME: workaround to succeed datapath setup for ISO
* broadcaster until Tx datapath is implemented, in the
* future.
*/
return BT_HCI_ERR_SUCCESS;
} }
/* Todo: If the Host attempts to set a data path with a Connection Handle #if defined(CONFIG_BT_CTLR_CONN_ISO)
struct ll_iso_datapath *dp_in = NULL;
struct ll_iso_datapath *dp_out = NULL;
struct ll_conn_iso_stream *cis = NULL;
struct ll_conn_iso_group *cig = NULL;
if (IS_CIS_HANDLE(handle)) {
cis = ll_conn_iso_stream_get(handle);
cig = cis->group;
dp_in = cis->hdr.datapath_in;
dp_out = cis->hdr.datapath_out;
}
/* If the Host attempts to set a data path with a Connection Handle
* that does not exist or that is not for a CIS or a BIS, the Controller * that does not exist or that is not for a CIS or a BIS, the Controller
* shall return the error code Unknown Connection Identifier (0x02) * shall return the error code Unknown Connection Identifier (0x02)
*/ */
#if defined(CONFIG_BT_CTLR_CONN_ISO) if (!cis) {
struct ll_conn_iso_stream *cis; return BT_HCI_ERR_UNKNOWN_CONN_ID;
struct ll_conn_iso_group *cig; }
isoal_sink_handle_t sink_hdl;
isoal_status_t err = 0;
cis = ll_conn_iso_stream_get(handle); if ((path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR && dp_in) ||
/* TODO: Check valid cis */ (path_dir == BT_HCI_DATAPATH_DIR_CTLR_TO_HOST && dp_out)) {
cig = cis->group;
if ((path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR &&
cis->datapath_in) ||
(path_dir == BT_HCI_DATAPATH_DIR_CTLR_TO_HOST &&
cis->datapath_out)) {
/* Data path has been set up, can only do setup once */ /* Data path has been set up, can only do setup once */
return BT_HCI_ERR_CMD_DISALLOWED; return BT_HCI_ERR_CMD_DISALLOWED;
} }
#endif
if (path_is_vendor_specific(path_id) && if (path_is_vendor_specific(path_id) &&
!ll_data_path_configured(path_dir, path_id)) { !ll_data_path_configured(path_dir, path_id)) {
@ -181,93 +201,11 @@ uint8_t ll_setup_iso_path(uint16_t handle, uint8_t path_dir, uint8_t path_id,
if (codec_config_len && vs_codec_id == BT_HCI_CODING_FORMAT_TRANSPARENT) { if (codec_config_len && vs_codec_id == BT_HCI_CODING_FORMAT_TRANSPARENT) {
return BT_HCI_ERR_INVALID_PARAM; return BT_HCI_ERR_INVALID_PARAM;
} }
#endif /* CONFIG_BT_CTLR_CONN_ISO */
#if defined(CONFIG_BT_CTLR_CONN_ISO)
/* Allocate and configure datapath */
struct ll_iso_datapath *dp = mem_acquire(&datapath_free);
/* TODO: check valid dp buffer */
dp->path_dir = path_dir;
dp->path_id = path_id;
dp->coding_format = coding_format;
dp->company_id = company_id;
/* TODO dp->sync_delay = controller_delay; ?*/
uint32_t sdu_interval;
uint8_t burst_number;
if (path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR) {
burst_number = cis->lll.tx.burst_number;
if (cig->lll.role) {
/* peripheral */
sdu_interval = cig->p_sdu_interval;
} else {
/* central */
sdu_interval = cig->c_sdu_interval;
}
cis->datapath_in = dp;
} else {
burst_number = cis->lll.rx.burst_number;
if (cig->lll.role) {
/* peripheral */
sdu_interval = cig->c_sdu_interval;
} else {
/* central */
sdu_interval = cig->p_sdu_interval;
}
cis->datapath_out = dp;
}
if (path_id == BT_HCI_DATAPATH_ID_HCI) {
/* Set up HCI data path */
err = isoal_sink_create(&sink_hdl, handle, burst_number, sdu_interval,
cig->iso_interval, sink_sdu_alloc_hci,
sink_sdu_emit_hci, sink_sdu_write_hci);
} else {
/* Set up vendor specific data path */
isoal_sink_sdu_alloc_cb sdu_alloc;
isoal_sink_sdu_emit_cb sdu_emit;
isoal_sink_sdu_write_cb sdu_write;
/* Request vendor sink callbacks for path */
if (ll_data_path_sink_create(dp, &sdu_alloc, &sdu_emit, &sdu_write)) {
err = isoal_sink_create(&sink_hdl, handle, burst_number,
sdu_interval, cig->iso_interval,
sdu_alloc, sdu_emit, sdu_write);
} else {
return BT_HCI_ERR_CMD_DISALLOWED;
}
}
if (!err) {
dp->sink_hdl = sink_hdl;
isoal_sink_enable(sink_hdl);
} else {
return BT_HCI_ERR_CMD_DISALLOWED;
}
#endif
#if defined(CONFIG_BT_CTLR_SYNC_ISO) #if defined(CONFIG_BT_CTLR_SYNC_ISO)
struct lll_sync_iso_stream *stream; struct lll_sync_iso_stream *stream;
struct ll_sync_iso_set *sync_iso;
isoal_sink_handle_t sink_handle;
struct lll_sync_iso *lll_iso;
struct ll_iso_datapath *dp;
uint16_t stream_handle; uint16_t stream_handle;
uint32_t sdu_interval;
uint16_t iso_interval;
uint8_t burst_number;
int err;
if (path_dir != BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) {
/* FIXME: workaround to succeed datapath setup for ISO
* broadcaster until Tx datapath is implemented, in the
* future.
*/
return BT_HCI_ERR_SUCCESS;
}
if (handle < BT_CTLR_SYNC_ISO_STREAM_HANDLE_BASE) { if (handle < BT_CTLR_SYNC_ISO_STREAM_HANDLE_BASE) {
return BT_HCI_ERR_CMD_DISALLOWED; return BT_HCI_ERR_CMD_DISALLOWED;
@ -278,73 +216,148 @@ uint8_t ll_setup_iso_path(uint16_t handle, uint8_t path_dir, uint8_t path_id,
if (stream->dp) { if (stream->dp) {
return BT_HCI_ERR_CMD_DISALLOWED; return BT_HCI_ERR_CMD_DISALLOWED;
} }
#endif /* CONFIG_BT_CTLR_CONN_ISO */
/* Allocate and configure datapath */ /* Allocate and configure datapath */
dp = mem_acquire(&datapath_free); struct ll_iso_datapath *dp = mem_acquire(&datapath_free);
if (!dp) { if (!dp) {
return BT_HCI_ERR_CMD_DISALLOWED; return BT_HCI_ERR_CMD_DISALLOWED;
} }
dp->path_dir = path_dir; dp->path_dir = path_dir;
dp->path_id = path_id; dp->path_id = path_id;
dp->coding_format = coding_format; dp->coding_format = coding_format;
dp->company_id = company_id; dp->company_id = company_id;
/* TODO dp->sync_delay = controller_delay; ?*/ /* TODO dp->sync_delay = controller_delay; ?*/
flush_timeout = 0;
stream_sync_delay = 0;
group_sync_delay = 0;
#if defined(CONFIG_BT_CTLR_CONN_ISO)
role = cig->lll.role;
iso_interval = cig->iso_interval;
stream_sync_delay = cis->sync_delay;
group_sync_delay = cig->sync_delay;
if (path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR) {
burst_number = cis->lll.tx.burst_number;
flush_timeout = cis->lll.tx.flush_timeout;
if (role) {
/* peripheral */
sdu_interval = cig->p_sdu_interval;
} else {
/* central */
sdu_interval = cig->c_sdu_interval;
}
cis->hdr.datapath_in = dp;
} else {
burst_number = cis->lll.rx.burst_number;
flush_timeout = cis->lll.rx.flush_timeout;
if (role) {
/* peripheral */
sdu_interval = cig->c_sdu_interval;
} else {
/* central */
sdu_interval = cig->p_sdu_interval;
}
cis->hdr.datapath_out = dp;
}
#endif
#if defined(CONFIG_BT_CTLR_SYNC_ISO)
struct ll_sync_iso_set *sync_iso;
struct lll_sync_iso *lll_iso;
sync_iso = ull_sync_iso_by_stream_get(stream_handle); sync_iso = ull_sync_iso_by_stream_get(stream_handle);
lll_iso = &sync_iso->lll; lll_iso = &sync_iso->lll;
role = 1; /* FIXME: Set role from LLL struct */
burst_number = lll_iso->bn; burst_number = lll_iso->bn;
sdu_interval = lll_iso->sdu_interval; sdu_interval = lll_iso->sdu_interval;
iso_interval = lll_iso->iso_interval; iso_interval = lll_iso->iso_interval;
#endif /* CONFIG_BT_CTLR_SYNC_ISO */
err = isoal_sink_create(&sink_handle, handle, burst_number, if (path_id == BT_HCI_DATAPATH_ID_HCI) {
sdu_interval, iso_interval, sink_sdu_alloc_hci, /* Not vendor specific, thus alloc and emit functions known */
sink_sdu_emit_hci, sink_sdu_write_hci); err = isoal_sink_create(handle, role,
if (err) { burst_number, flush_timeout,
mem_release(dp, &datapath_free); sdu_interval, iso_interval,
stream_sync_delay, group_sync_delay,
sink_sdu_alloc_hci, sink_sdu_emit_hci,
sink_sdu_write_hci, &sink_handle);
} else {
/* Set up vendor specific data path */
isoal_sink_sdu_alloc_cb sdu_alloc;
isoal_sink_sdu_emit_cb sdu_emit;
isoal_sink_sdu_write_cb sdu_write;
return BT_HCI_ERR_CMD_DISALLOWED; /* Request vendor sink callbacks for path */
if (ll_data_path_sink_create(dp, &sdu_alloc, &sdu_emit, &sdu_write)) {
err = isoal_sink_create(handle, role,
burst_number, flush_timeout,
sdu_interval, iso_interval,
stream_sync_delay, group_sync_delay,
sdu_alloc, sdu_emit, sdu_write,
&sink_handle);
} else {
return BT_HCI_ERR_CMD_DISALLOWED;
}
} }
dp->sink_hdl = sink_handle; if (!err) {
stream->dp = dp; dp->sink_hdl = sink_handle;
isoal_sink_enable(sink_handle);
isoal_sink_enable(sink_handle); } else {
#endif return BT_HCI_ERR_CMD_DISALLOWED;
}
return 0; return 0;
} }
uint8_t ll_remove_iso_path(uint16_t handle, uint8_t path_dir) uint8_t ll_remove_iso_path(uint16_t handle, uint8_t path_dir)
{ {
/* TBD: If the Host issues this command with a Connection_Handle that does not exist struct ll_conn_iso_stream *cis = NULL;
struct ll_iso_stream_hdr *hdr = NULL;
#if defined(CONFIG_BT_CTLR_CONN_ISO)
if (IS_CIS_HANDLE(handle)) {
cis = ll_conn_iso_stream_get(handle);
hdr = &cis->hdr;
}
#endif
/* If the Host issues this command with a Connection_Handle that does not exist
* or is not for a CIS or a BIS, the Controller shall return the error code Unknown * or is not for a CIS or a BIS, the Controller shall return the error code Unknown
* Connection Identifier (0x02). * Connection Identifier (0x02).
*/ */
struct ll_iso_datapath *dp = NULL; if (!cis) {
return BT_HCI_ERR_UNKNOWN_CONN_ID;
}
#if defined(CONFIG_BT_CTLR_CONN_ISO) struct ll_iso_datapath *dp;
struct ll_conn_iso_stream *cis = ll_conn_iso_stream_get(handle);
if (path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR) { if (path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR) {
dp = cis->datapath_in; dp = hdr->datapath_in;
if (dp) { if (dp) {
cis->datapath_in = NULL; hdr->datapath_in = NULL;
mem_release(dp, &datapath_free); mem_release(dp, &datapath_free);
} }
} else if (path_dir == BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) { } else if (path_dir == BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) {
dp = cis->datapath_out; dp = hdr->datapath_out;
if (dp) { if (dp) {
cis->datapath_out = NULL; hdr->datapath_out = NULL;
mem_release(dp, &datapath_free); mem_release(dp, &datapath_free);
} }
} else { } else {
/* Reserved for future use */ /* Reserved for future use */
return BT_HCI_ERR_CMD_DISALLOWED; return BT_HCI_ERR_CMD_DISALLOWED;
} }
#endif /* CONFIG_BT_CTLR_CONN_ISO */
#if defined(CONFIG_BT_CTLR_SYNC_ISO) #if defined(CONFIG_BT_CTLR_SYNC_ISO)
struct lll_sync_iso_stream *stream; struct lll_sync_iso_stream *stream;
@ -364,7 +377,6 @@ uint8_t ll_remove_iso_path(uint16_t handle, uint8_t path_dir)
if (!dp) { if (!dp) {
/* Datapath was not previously set up */ /* Datapath was not previously set up */
return BT_HCI_ERR_CMD_DISALLOWED; return BT_HCI_ERR_CMD_DISALLOWED;
} }
return 0; return 0;

View file

@ -4,6 +4,33 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
/* CIS */
#define LL_CIS_HANDLE_BASE CONFIG_BT_MAX_CONN
#if defined(CONFIG_BT_CTLR_CONN_ISO_STREAMS)
#define LAST_VALID_CIS_HANDLE \
(CONFIG_BT_CTLR_CONN_ISO_STREAMS + LL_CIS_HANDLE_BASE - 1)
#else
#define LAST_VALID_CIS_HANDLE (LL_CIS_HANDLE_BASE - 1)
#endif /* CONFIG_BT_CTLR_CONN_ISO_STREAMS */
#define LL_CIS_IDX_FROM_HANDLE(_handle) \
((_handle) - LL_CIS_HANDLE_BASE)
#if defined(CONFIG_BT_CTLR_CONN_ISO)
#define IS_CIS_HANDLE(_handle) \
(((_handle) >= LL_CIS_HANDLE_BASE) && \
((_handle) <= LAST_VALID_CIS_HANDLE))
#else
#define IS_CIS_HANDLE(_handle) 0
#endif /* CONFIG_BT_CTLR_CONN_ISO */
/* Common memebers for ll_conn_iso_stream and ll_broadcast_iso_stream */
struct ll_iso_stream_hdr {
struct ll_iso_datapath *datapath_in;
struct ll_iso_datapath *datapath_out;
};
struct ll_iso_datapath { struct ll_iso_datapath {
uint8_t path_dir; uint8_t path_dir;
uint8_t path_id; uint8_t path_id;

View file

@ -23,6 +23,9 @@
#include "lll_conn.h" #include "lll_conn.h"
#include "lll_conn_iso.h" #include "lll_conn_iso.h"
#include "isoal.h"
#include "ull_iso_types.h"
#include "ull_conn_types.h" #include "ull_conn_types.h"
#include "ull_conn_iso_types.h" #include "ull_conn_iso_types.h"
#include "ull_internal.h" #include "ull_internal.h"