bluetooth: controller: Add ISO-AL TX unframed fragmentation

- Implemented ISO-AL TX interface functions for fragmentation of
  unframed PDUs
- Implemented ISO-AL source construct and its creation for an input
  data path

Signed-off-by: Nirosharn Amarasinghe <niag@demant.com>
This commit is contained in:
Nirosharn Amarasinghe 2022-03-18 09:35:17 +01:00 committed by Carles Cufí
commit 3cdaf72d9d
8 changed files with 914 additions and 36 deletions

View file

@ -225,6 +225,24 @@ config BT_CTLR_ISO_TX_BUFFER_SIZE
Size of the Isochronous Tx buffers and the value returned in HCI LE
Read Buffer Size V2 command response.
config BT_CTLR_ISOAL_SOURCES
int "Number of Isochronous Adaptation Layer sinks"
depends on BT_CTLR_ADV_ISO || BT_CTLR_CONN_ISO
range 1 64
default 1
help
Set the number of concurrently active sources supported by the
ISO AL.
config BT_CTLR_ISOAL_SINKS
int "Number of Isochronous Adaptation Layer sinks"
depends on BT_CTLR_SYNC_ISO || BT_CTLR_CONN_ISO
range 1 64
default 1
help
Set the number of concurrently active sinks supported by the
ISO AL.
config BT_CTLR_ISO_VENDOR_DATA_PATH
bool "Vendor-specific ISO data path"
depends on BT_CTLR_SYNC_ISO || BT_CTLR_CONN_ISO

View file

@ -9,27 +9,36 @@
#include <toolchain.h>
#include <sys/util.h>
#include <zephyr.h>
#include <bluetooth/bluetooth.h>
#include "util/memq.h"
#include "pdu.h"
#include "ll.h"
#include "lll.h"
#include "lll_conn_iso.h"
#include "lll_iso_tx.h"
#include "isoal.h"
#include "ull_iso_types.h"
#define LOG_MODULE_NAME bt_ctlr_isoal
#include "common/log.h"
#include "hal/debug.h"
/* TODO this must be taken from a Kconfig */
#define ISOAL_SINKS_MAX (4)
/** Allocation state */
typedef uint8_t isoal_alloc_state_t;
#define ISOAL_ALLOC_STATE_FREE ((isoal_alloc_state_t) 0x00)
#define ISOAL_ALLOC_STATE_TAKEN ((isoal_alloc_state_t) 0x01)
static struct
struct
{
isoal_alloc_state_t sink_allocated[ISOAL_SINKS_MAX];
struct isoal_sink sink_state[ISOAL_SINKS_MAX];
isoal_alloc_state_t sink_allocated[CONFIG_BT_CTLR_ISOAL_SINKS];
isoal_alloc_state_t source_allocated[CONFIG_BT_CTLR_ISOAL_SOURCES];
struct isoal_sink sink_state[CONFIG_BT_CTLR_ISOAL_SINKS];
struct isoal_source source_state[CONFIG_BT_CTLR_ISOAL_SOURCES];
} isoal_global;
@ -77,7 +86,7 @@ static isoal_status_t isoal_sink_allocate(isoal_sink_handle_t *hdl)
isoal_sink_handle_t i;
/* Very small linear search to find first free */
for (i = 0; i < ISOAL_SINKS_MAX; i++) {
for (i = 0; i < CONFIG_BT_CTLR_ISOAL_SINKS; i++) {
if (isoal_global.sink_allocated[i] == ISOAL_ALLOC_STATE_FREE) {
isoal_global.sink_allocated[i] = ISOAL_ALLOC_STATE_TAKEN;
*hdl = i;
@ -147,7 +156,8 @@ isoal_status_t isoal_sink_create(
*/
/* Note: sdu_interval unit is uS, iso_interval is a multiple of 1.25mS */
session->pdus_per_sdu = burst_number * (sdu_interval / (iso_interval * 1250));
session->pdus_per_sdu = (burst_number * sdu_interval) /
((uint32_t)iso_interval * CONN_INT_UNIT_US);
/* Computation of transport latency (constant part)
*
@ -212,6 +222,111 @@ isoal_status_t isoal_sink_create(
return err;
}
/**
* @brief Find free source from statically-sized pool and allocate it
* @details Implemented as linear search since pool is very small
*
* @param hdl[out] Handle to source
* @return ISOAL_STATUS_OK if we could allocate; otherwise ISOAL_STATUS_ERR_SOURCE_ALLOC
*/
static isoal_status_t isoal_source_allocate(isoal_source_handle_t *hdl)
{
isoal_source_handle_t i;
/* Very small linear search to find first free */
for (i = 0; i < CONFIG_BT_CTLR_ISOAL_SOURCES; i++) {
if (isoal_global.source_allocated[i] == ISOAL_ALLOC_STATE_FREE) {
isoal_global.source_allocated[i] = ISOAL_ALLOC_STATE_TAKEN;
*hdl = i;
return ISOAL_STATUS_OK;
}
}
return ISOAL_STATUS_ERR_SOURCE_ALLOC; /* All entries were taken */
}
/**
* @brief Mark a source as being free to allocate again
* @param hdl[in] Handle to source
*/
static void isoal_source_deallocate(isoal_source_handle_t hdl)
{
isoal_global.source_allocated[hdl] = ISOAL_ALLOC_STATE_FREE;
}
/**
* @brief Create a new source
*
* @param handle[in] Connection handle
* @param role[in] Peripheral, Central or Broadcast
* @param burst_number[in] Burst Number
* @param flush_timeout[in] Flush timeout
* @param max_octets[in] Maximum PDU size (Max_PDU_C_To_P / Max_PDU_P_To_C)
* @param sdu_interval[in] SDU interval
* @param iso_interval[in] ISO interval
* @param stream_sync_delay[in] CIS sync delay
* @param group_sync_delay[in] CIG sync delay
* @param pdu_alloc[in] Callback of PDU allocator
* @param pdu_write[in] Callback of PDU byte writer
* @param pdu_emit[in] Callback of PDU emitter
* @param pdu_release[in] Callback of PDU deallocator
* @param hdl[out] Handle to new source
*
* @return ISOAL_STATUS_OK if we could create a new sink; otherwise ISOAL_STATUS_ERR_SOURCE_ALLOC
*/
isoal_status_t isoal_source_create(
uint16_t handle,
uint8_t role,
uint8_t burst_number,
uint8_t flush_timeout,
uint8_t max_octets,
uint32_t sdu_interval,
uint16_t iso_interval,
uint32_t stream_sync_delay,
uint32_t group_sync_delay,
isoal_source_pdu_alloc_cb pdu_alloc,
isoal_source_pdu_write_cb pdu_write,
isoal_source_pdu_emit_cb pdu_emit,
isoal_source_pdu_release_cb pdu_release,
isoal_source_handle_t *hdl)
{
isoal_status_t err;
/* Allocate a new source */
err = isoal_source_allocate(hdl);
if (err) {
return err;
}
struct isoal_source_session *session = &isoal_global.source_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 */
session->pdus_per_sdu = (burst_number * sdu_interval) /
((uint32_t)iso_interval * CONN_INT_UNIT_US);
/* Set maximum PDU size */
session->max_pdu_size = max_octets;
/* Remember the platform-specific callbacks */
session->pdu_alloc = pdu_alloc;
session->pdu_write = pdu_write;
session->pdu_emit = pdu_emit;
session->pdu_release = pdu_release;
/* TODO: Constant need to be updated */
/* Initialize running seq number to zero */
session->seqn = 0;
return err;
}
/**
* @brief Get reference to configuration struct
*
@ -262,6 +377,56 @@ void isoal_sink_destroy(isoal_sink_handle_t hdl)
isoal_sink_deallocate(hdl);
}
/**
* @brief Get reference to configuration struct
*
* @param hdl[in] Handle to new source
* @return Reference to parameter struct, to be configured by caller
*/
struct isoal_source_config *isoal_get_source_param_ref(isoal_source_handle_t hdl)
{
LL_ASSERT(isoal_global.source_allocated[hdl] == ISOAL_ALLOC_STATE_TAKEN);
return &isoal_global.source_state[hdl].session.param;
}
/**
* @brief Atomically enable latch-in of packets and PDU production
* @param hdl[in] Handle of existing instance
*/
void isoal_source_enable(isoal_source_handle_t hdl)
{
/* Reset bookkeeping state */
memset(&isoal_global.source_state[hdl].pdu_production, 0,
sizeof(isoal_global.source_state[hdl].pdu_production));
/* Atomically enable */
isoal_global.source_state[hdl].pdu_production.mode = ISOAL_PRODUCTION_MODE_ENABLED;
}
/**
* @brief Atomically disable latch-in of packets and PDU production
* @param hdl[in] Handle of existing instance
*/
void isoal_source_disable(isoal_source_handle_t hdl)
{
/* Atomically disable */
isoal_global.source_state[hdl].pdu_production.mode = ISOAL_PRODUCTION_MODE_DISABLED;
}
/**
* @brief Disable and deallocate existing source
* @param hdl[in] Handle of existing instance
*/
void isoal_source_destroy(isoal_source_handle_t hdl)
{
/* Atomic disable */
isoal_source_disable(hdl);
/* Permit allocation anew */
isoal_source_deallocate(hdl);
}
/* Obtain destination SDU */
static isoal_status_t isoal_rx_allocate_sdu(struct isoal_sink *sink,
const struct isoal_pdu_rx *pdu_meta)
@ -812,3 +977,313 @@ isoal_status_t isoal_rx_pdu_recombine(isoal_sink_handle_t sink_hdl,
return err;
}
/**
* Queue the PDU in production in the relevant LL transmit queue. If the
* attmept to release the PDU fails, the buffer linked to the PDU will be released
* and it will not be possible to retry the emit operation on the same PDU.
* @param[in] source_ctx ISO-AL source reference for this CIS / BIS
* @param[in] produced_pdu PDU in production
* @param[in] pdu_ll_id LLID to be set indicating the type of fragment
* @param[in] payload_size Length of the data written to the PDU
* @return Error status of the operation
*/
static isoal_status_t isoal_tx_pdu_emit(const struct isoal_source *source_ctx,
const struct isoal_pdu_produced *produced_pdu,
const uint8_t pdu_ll_id,
const isoal_pdu_len_t payload_size)
{
struct node_tx_iso *node_tx;
isoal_status_t status;
uint16_t handle;
/* Retrieve CIS / BIS handle */
handle = bt_iso_handle(source_ctx->session.handle);
/* Retrieve Node handle */
node_tx = produced_pdu->contents.handle;
/* Set PDU LLID */
produced_pdu->contents.pdu->ll_id = pdu_ll_id;
/* Set PDU length */
produced_pdu->contents.pdu->length = (uint8_t)payload_size;
/* Attempt to enqueue the node towards the LL */
status = source_ctx->session.pdu_emit(node_tx, handle);
if (status != ISOAL_STATUS_OK) {
/* If it fails, the node will be released and no further attempt
* will be possible
*/
BT_ERR("Failed to enqueue node (%p)", node_tx);
source_ctx->session.pdu_release(node_tx, handle, status);
}
return status;
}
/* Allocates a new PDU only if the previous PDU was emitted */
static isoal_status_t isoal_tx_allocate_pdu(struct isoal_source *source,
const struct isoal_sdu_tx *tx_sdu)
{
struct isoal_source_session *session;
struct isoal_pdu_production *pp;
struct isoal_pdu_produced *pdu;
isoal_status_t err;
err = ISOAL_STATUS_OK;
session = &source->session;
pp = &source->pdu_production;
pdu = &pp->pdu;
/* Allocate a PDU if the previous was filled (thus sent) */
const bool pdu_complete = (pp->pdu_available == 0);
if (pdu_complete) {
/* Allocate new PDU buffer */
err = session->pdu_alloc(
&pdu->contents /* [out] Updated with pointer and size */
);
if (err) {
pdu->contents.handle = NULL;
pdu->contents.pdu = NULL;
pdu->contents.size = 0;
}
/* Get maximum buffer available */
const size_t available_len = MIN(
session->max_pdu_size,
pdu->contents.size
);
/* Nothing has been written into buffer yet */
pp->pdu_written = 0;
pp->pdu_available = available_len;
LL_ASSERT(available_len > 0);
pp->pdu_cnt++;
}
return err;
}
/**
* Attempt to emit the PDU in production if it is complete.
* @param[in] source ISO-AL source reference
* @param[in] end_of_sdu SDU end has been reached
* @param[in] pdu_ll_id LLID / PDU fragment type as Start, Cont, End, Single (Unframed) or Framed
* @return Error status of operation
*/
static isoal_status_t isoal_tx_try_emit_pdu(struct isoal_source *source,
bool end_of_sdu,
uint8_t pdu_ll_id)
{
struct isoal_pdu_production *pp;
struct isoal_pdu_produced *pdu;
isoal_status_t err;
err = ISOAL_STATUS_OK;
pp = &source->pdu_production;
pdu = &pp->pdu;
/* Emit a PDU */
const bool pdu_complete = (pp->pdu_available == 0) || end_of_sdu;
if (end_of_sdu) {
pp->pdu_available = 0;
}
if (pdu_complete) {
err = isoal_tx_pdu_emit(source, pdu, pdu_ll_id, pp->pdu_written);
}
return err;
}
/**
* @brief Fragment received SDU and produce unframed PDUs
* @details Destination source may have an already partially built PDU
*
* @param source[in,out] Destination source with bookkeeping state
* @param tx_sdu[in] SDU with packet boundary information
*
* @return Status
*/
static isoal_status_t isoal_tx_unframed_produce(struct isoal_source *source,
const struct isoal_sdu_tx *tx_sdu)
{
struct isoal_source_session *session;
isoal_sdu_len_t packet_available;
struct isoal_pdu_production *pp;
const uint8_t *sdu_payload;
bool zero_length_sdu;
isoal_status_t err;
bool padding_pdu;
uint8_t ll_id;
session = &source->session;
pp = &source->pdu_production;
padding_pdu = false;
err = ISOAL_STATUS_OK;
packet_available = tx_sdu->size;
sdu_payload = tx_sdu->dbuf;
LL_ASSERT(sdu_payload);
zero_length_sdu = (packet_available == 0 &&
tx_sdu->sdu_state == BT_ISO_SINGLE);
if (tx_sdu->sdu_state == BT_ISO_START ||
tx_sdu->sdu_state == BT_ISO_SINGLE) {
/* Start of a new SDU */
/* Update sequence number for received SDU
*
* BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
* 2 ISOAL Features :
* SDUs received by the ISOAL from the upper layer shall be
* given a sequence number which is initialized to 0 when the
* CIS or BIS is created.
*
* NOTE: The upper layer may synchronize its sequence number
* with the sequence number in the ISOAL once the Datapath is
* configured and the link is established.
*/
session->seqn++;
/* Reset PDU fragmentation count for this SDU */
pp->pdu_cnt = 0;
}
/* PDUs should be created until the SDU fragment has been fragmented or
* if this is the last fragment of the SDU, until the required padding
* PDU(s) are sent.
*/
while ((err == ISOAL_STATUS_OK) &&
((packet_available > 0) || padding_pdu || zero_length_sdu)) {
const isoal_status_t err_alloc = isoal_tx_allocate_pdu(source, tx_sdu);
struct isoal_pdu_produced *pdu = &pp->pdu;
err |= err_alloc;
/*
* For this PDU we can only consume of packet, bounded by:
* - What can fit in the destination PDU.
* - What remains of the packet.
*/
const size_t consume_len = MIN(
packet_available,
pp->pdu_available
);
if (consume_len > 0) {
err |= session->pdu_write(&pdu->contents,
pp->pdu_written,
sdu_payload,
consume_len);
sdu_payload += consume_len;
pp->pdu_written += consume_len;
pp->pdu_available -= consume_len;
packet_available -= consume_len;
}
/* End of the SDU is reached at the end of the last SDU fragment
* or if this is a single fragment SDU
*/
bool end_of_sdu = (packet_available == 0) &&
((tx_sdu->sdu_state == BT_ISO_SINGLE) ||
(tx_sdu->sdu_state == BT_ISO_END));
/* Decide PDU type
*
* BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
* 2.1 Unframed PDU :
* LLID 0b00 PDU_BIS_LLID_COMPLETE_END:
* (1) When the payload of the ISO Data PDU contains the end
* fragment of an SDU.
* (2) When the payload of the ISO Data PDU contains a complete
* SDU.
* (3) When an SDU contains zero length data, the corresponding
* PDU shall be of zero length and the LLID field shall be
* set to 0b00.
*
* LLID 0b01 PDU_BIS_LLID_COMPLETE_END:
* (1) When the payload of the ISO Data PDU contains a start or
* a continuation fragment of an SDU.
* (2) When the ISO Data PDU is used as padding.
*/
ll_id = PDU_BIS_LLID_COMPLETE_END;
if (!end_of_sdu || padding_pdu) {
ll_id = PDU_BIS_LLID_START_CONTINUE;
}
const isoal_status_t err_emit = isoal_tx_try_emit_pdu(source, end_of_sdu, ll_id);
err |= err_emit;
/* Send padding PDU(s) if required
*
* BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
* 2.1 Unframed PDU :
* Each SDU shall generate BN ÷ (ISO_Interval ÷ SDU_Interval)
* fragments. If an SDU generates less than this number of
* fragments, empty payloads shall be used to make up the
* number.
*/
padding_pdu = (end_of_sdu && (pp->pdu_cnt < session->pdus_per_sdu));
zero_length_sdu = false;
}
return err;
}
/**
* @brief Deep copy a SDU, fragment into PDU(s)
* @details Fragmentation will occur individually for every enabled source
*
* @param source_hdl[in] Handle of destination source
* @param tx_sdu[in] SDU along with packet boudary state
* @return Status
*/
isoal_status_t isoal_tx_sdu_fragment(isoal_source_handle_t source_hdl,
const struct isoal_sdu_tx *tx_sdu)
{
struct isoal_source *source = &isoal_global.source_state[source_hdl];
isoal_status_t err = ISOAL_STATUS_ERR_PDU_ALLOC;
if (source->pdu_production.mode != ISOAL_PRODUCTION_MODE_DISABLED) {
/* TODO: consider how to separate framed and unframed production
* BT Core V5.3 : Vol 6 Low Energy Controller : Part G IS0-AL:
* 2 ISOAL Features :
* (1) Unframed PDUs shall only be used when the ISO_Interval
* is equal to or an integer multiple of the SDU_Interval
* and a constant time offset alignment is maintained
* between the SDU generation and the timing in the
* isochronous transport.
* (2) When the Host requests the use of framed PDUs, the
* Controller shall use framed PDUs.
*/
bool pdu_framed = false;
if (pdu_framed) {
/* TODO: add framed handling */
} else {
err = isoal_tx_unframed_produce(source, tx_sdu);
}
}
return err;
}
void isoal_tx_pdu_release(isoal_source_handle_t source_hdl,
struct node_tx_iso *node_tx)
{
struct isoal_source *source = &isoal_global.source_state[source_hdl];
if (source && source->session.pdu_release) {
source->session.pdu_release(node_tx, source->session.handle,
ISOAL_STATUS_OK);
}
}

View file

@ -15,13 +15,18 @@ typedef uint8_t isoal_status_t;
#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_EMIT ((isoal_status_t) 0x08) /* SDU emission */
#define ISOAL_STATUS_ERR_UNSPECIFIED ((isoal_status_t) 0x10) /* Unspecified error */
#define ISOAL_STATUS_ERR_PDU_ALLOC ((isoal_status_t) 0x10) /* PDU allocation */
#define ISOAL_STATUS_ERR_PDU_EMIT ((isoal_status_t) 0x20) /* PDU emission */
#define ISOAL_STATUS_ERR_UNSPECIFIED ((isoal_status_t) 0x80) /* Unspecified error */
#define BT_ROLE_BROADCAST (BT_CONN_ROLE_PERIPHERAL + 1)
/** Handle to a registered ISO Sub-System sink */
typedef uint8_t isoal_sink_handle_t;
/** Handle to a registered ISO Sub-System source */
typedef uint8_t isoal_source_handle_t;
/** Byte length of an ISO SDU */
typedef uint16_t isoal_sdu_len_t;
@ -74,6 +79,11 @@ struct isoal_rx_origin {
} inst;
};
enum isoal_mode {
ISOAL_MODE_CIS,
ISOAL_MODE_BIS
};
/**
* @brief ISO frame SDU buffer - typically an Audio frame buffer
*
@ -93,6 +103,23 @@ struct isoal_sdu_buffer {
isoal_sdu_len_t size;
};
/**
* @brief ISO frame PDU buffer
*/
struct isoal_pdu_buffer {
/** Additional handle for the provided pdu */
void *handle;
/** PDU contents */
struct pdu_iso *pdu;
/** Maximum size of the data buffer allocated for the payload in the PDU.
* Should be at least Max_PDU_C_To_P / Max_PDU_P_To_C depending on
* the role.
*/
isoal_pdu_len_t size;
};
/** @brief Produced ISO SDU frame with associated meta data */
struct isoal_sdu_produced {
/** Status of contents, if valid or SDU was lost.
@ -117,6 +144,13 @@ struct isoal_sdu_produced {
};
/** @brief Produced ISO PDU encapsulation */
struct isoal_pdu_produced {
/** Contents information provided at PDU allocation */
struct isoal_pdu_buffer contents;
};
/** @brief Received ISO PDU with associated meta data */
struct isoal_pdu_rx {
/** Meta */
@ -125,9 +159,25 @@ struct isoal_pdu_rx {
struct pdu_iso *pdu;
};
/** @brief Received ISO SDU with associated meta data */
struct isoal_sdu_tx {
/** Code word buffer
* Type, location and alignment decided by ISO sub system
*/
void *dbuf;
/** Number of bytes accessible behind the dbuf pointer */
isoal_sdu_len_t size;
/** SDU packet boundary flags from HCI indicating the fragment type
* can be directly assigned to the SDU state
*/
uint8_t sdu_state;
};
/* Forward declaration */
struct isoal_sink;
struct node_tx_iso;
/**
* @brief Callback: Request memory for a new ISO SDU buffer
@ -174,10 +224,7 @@ typedef isoal_status_t (*isoal_sink_sdu_write_cb)(
struct isoal_sink_config {
enum {
ISOAL_MODE_CIS,
ISOAL_MODE_BIS
} mode;
enum isoal_mode mode;
/* TODO add SDU and PDU max length etc. */
};
@ -220,6 +267,104 @@ struct isoal_sink {
struct isoal_sdu_production sdu_production;
};
/* Forward declaration */
struct isoal_source;
/**
* @brief Callback: Request memory for a new ISO PDU buffer
*
* Proprietary ISO sub systems may have
* specific requirements or opinions on where to locate ISO PDUs; some
* memories may be faster, may be dynamically mapped in, etc.
*
* @return ISOAL_STATUS_ERR_ALLOC if size_request could not be fulfilled, otherwise
* ISOAL_STATUS_OK.
*/
typedef isoal_status_t (*isoal_source_pdu_alloc_cb)(
/*!< [out] Struct is modified. Must not be NULL */
struct isoal_pdu_buffer *pdu_buffer
);
/**
* @brief Callback: Release an ISO PDU
*/
typedef isoal_status_t (*isoal_source_pdu_release_cb)(
/*!< [in] PDU to be released */
struct node_tx_iso *node_tx,
/*!< [in] CIS/BIS handle */
const uint16_t handle,
/*!< [in] Cause of release. ISOAL_STATUS_OK means PDU was enqueued towards
* LLL and sent or flushed, and may need further handling before
* memory is released, e.g. forwarded to HCI thread for complete-
* counting. Any other status indicates cause of error during SDU
* fragmentation/segmentation, in which case the PDU will not be
* produced.
*/
const isoal_status_t status
);
/**
* @brief Callback: Write a number of bytes to PDU buffer
*/
typedef isoal_status_t (*isoal_source_pdu_write_cb)(
/*!< [out] PDU under production */
struct isoal_pdu_buffer *pdu_buffer,
/*!< [in] Offset within PDU buffer */
const size_t offset,
/*!< [in] Source data */
const uint8_t *sdu_payload,
/*!< [in] Number of bytes to be copied */
const size_t consume_len
);
/**
* @brief Callback: Enqueue an ISO PDU
*/
typedef isoal_status_t (*isoal_source_pdu_emit_cb)(
/*!< [in] PDU to be enqueued */
struct node_tx_iso *node_tx,
/*!< [in] CIS/BIS handle */
const uint16_t handle
);
struct isoal_source_config {
enum isoal_mode mode;
/* TODO add SDU and PDU max length etc. */
};
struct isoal_source_session {
isoal_source_pdu_alloc_cb pdu_alloc;
isoal_source_pdu_write_cb pdu_write;
isoal_source_pdu_emit_cb pdu_emit;
isoal_source_pdu_release_cb pdu_release;
struct isoal_source_config param;
isoal_sdu_cnt_t seqn;
uint16_t handle;
uint8_t pdus_per_sdu;
uint8_t max_pdu_size;
uint32_t latency_unframed;
uint32_t latency_framed;
};
struct isoal_pdu_production {
/* Permit atomic enable/disable of PDU production */
volatile isoal_production_mode_t mode;
/* We are constructing an PDU from {<1 or =1 or >1} SDUs */
struct isoal_pdu_produced pdu;
/* PDUs produced for current SDU */
uint8_t pdu_cnt;
isoal_pdu_len_t pdu_written;
isoal_pdu_len_t pdu_available;
};
struct isoal_source {
/* Session-constant */
struct isoal_source_session session;
/* State for PDU production */
struct isoal_pdu_production pdu_production;
};
isoal_status_t isoal_init(void);
@ -258,3 +403,32 @@ isoal_status_t sink_sdu_emit_hci(const struct isoal_sink *sink_ctx,
isoal_status_t sink_sdu_write_hci(void *dbuf,
const uint8_t *pdu_payload,
const size_t consume_len);
isoal_status_t isoal_source_create(uint16_t handle,
uint8_t role,
uint8_t burst_number,
uint8_t flush_timeout,
uint8_t max_octets,
uint32_t sdu_interval,
uint16_t iso_interval,
uint32_t stream_sync_delay,
uint32_t group_sync_delay,
isoal_source_pdu_alloc_cb pdu_alloc,
isoal_source_pdu_write_cb pdu_write,
isoal_source_pdu_emit_cb pdu_emit,
isoal_source_pdu_release_cb pdu_release,
isoal_source_handle_t *hdl);
struct isoal_source_config *isoal_get_source_param_ref(isoal_source_handle_t hdl);
void isoal_source_enable(isoal_source_handle_t hdl);
void isoal_source_disable(isoal_source_handle_t hdl);
void isoal_source_destroy(isoal_source_handle_t hdl);
isoal_status_t isoal_tx_sdu_fragment(isoal_source_handle_t source_hdl,
const struct isoal_sdu_tx *tx_sdu);
void isoal_tx_pdu_release(isoal_source_handle_t source_hdl,
struct node_tx_iso *node_tx);

View file

@ -42,6 +42,7 @@
#include "ull_conn_internal.h"
#include "ull_sync_iso_internal.h"
#include "ull_conn_iso_internal.h"
#include "ull_conn_types.h"
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER)
#define LOG_MODULE_NAME bt_ctlr_ull_iso
@ -62,6 +63,19 @@
static int init_reset(void);
#if defined(CONFIG_BT_CTLR_CONN_ISO)
static isoal_status_t ll_iso_pdu_alloc(struct isoal_pdu_buffer *pdu_buffer);
static isoal_status_t ll_iso_pdu_write(struct isoal_pdu_buffer *pdu_buffer,
const size_t offset,
const uint8_t *sdu_payload,
const size_t consume_len);
static isoal_status_t ll_iso_pdu_emit(struct node_tx_iso *node_tx,
const uint16_t handle);
static isoal_status_t ll_iso_pdu_release(struct node_tx_iso *node_tx,
const uint16_t handle,
const isoal_status_t status);
#endif /* CONFIG_BT_CTLR_CONN_ISO */
/* Allocate data path pools for RX/TX directions for each stream */
#define BT_CTLR_ISO_STREAMS ((2 * (BT_CTLR_CONN_ISO_STREAMS)) + \
BT_CTLR_SYNC_ISO_STREAMS)
@ -162,6 +176,22 @@ __weak bool ll_data_path_sink_create(struct ll_iso_datapath *datapath,
return false;
}
/* Could be implemented by vendor */
__weak bool ll_data_path_source_create(struct ll_iso_datapath *datapath,
isoal_source_pdu_alloc_cb *pdu_alloc,
isoal_source_pdu_write_cb *pdu_write,
isoal_source_pdu_emit_cb *pdu_emit,
isoal_source_pdu_release_cb *pdu_release)
{
ARG_UNUSED(datapath);
ARG_UNUSED(pdu_alloc);
ARG_UNUSED(pdu_write);
ARG_UNUSED(pdu_emit);
ARG_UNUSED(pdu_release);
return false;
}
static inline bool path_is_vendor_specific(uint8_t path_id)
{
return (path_id >= BT_HCI_DATAPATH_ID_VS &&
@ -181,6 +211,7 @@ uint8_t ll_setup_iso_path(uint16_t handle, uint8_t path_dir, uint8_t path_id,
return 0;
}
#if defined(CONFIG_BT_CTLR_SYNC_ISO)
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
@ -188,8 +219,13 @@ uint8_t ll_setup_iso_path(uint16_t handle, uint8_t path_dir, uint8_t path_id,
*/
return BT_HCI_ERR_SUCCESS;
}
#endif /* CONFIG_BT_CTLR_SYNC_ISO */
#if defined(CONFIG_BT_CTLR_SYNC_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
#if defined(CONFIG_BT_CTLR_CONN_ISO)
isoal_source_handle_t source_handle;
uint8_t max_octets;
#endif /* CONFIG_BT_CTLR_CONN_ISO */
isoal_sink_handle_t sink_handle;
uint32_t stream_sync_delay;
uint32_t group_sync_delay;
@ -281,22 +317,11 @@ uint8_t ll_setup_iso_path(uint16_t handle, uint8_t path_dir, uint8_t path_id,
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 {
if (path_dir == BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) {
/* Create sink for RX data path */
burst_number = cis->lll.rx.burst_number;
flush_timeout = cis->lll.rx.flush_timeout;
max_octets = cis->lll.rx.max_octets;
if (role) {
/* peripheral */
@ -307,8 +332,92 @@ uint8_t ll_setup_iso_path(uint16_t handle, uint8_t path_dir, uint8_t path_id,
}
cis->hdr.datapath_out = dp;
if (path_id == BT_HCI_DATAPATH_ID_HCI) {
/* Not vendor specific, thus alloc and emit functions known */
err = isoal_sink_create(handle, role,
burst_number, flush_timeout,
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;
/* 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;
}
#endif
}
if (!err) {
dp->sink_hdl = sink_handle;
isoal_sink_enable(sink_handle);
} else {
return BT_HCI_ERR_CMD_DISALLOWED;
}
} else {
/* path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR */
burst_number = cis->lll.tx.burst_number;
flush_timeout = cis->lll.tx.flush_timeout;
max_octets = cis->lll.tx.max_octets;
if (role) {
/* peripheral */
sdu_interval = cig->p_sdu_interval;
} else {
/* central */
sdu_interval = cig->c_sdu_interval;
}
cis->hdr.datapath_in = dp;
/* Create source for TX data path */
isoal_source_pdu_alloc_cb pdu_alloc;
isoal_source_pdu_write_cb pdu_write;
isoal_source_pdu_emit_cb pdu_emit;
isoal_source_pdu_release_cb pdu_release;
/* Set default callbacks assuming not vendor specific
* or that the vendor specific path is the same.
*/
pdu_alloc = ll_iso_pdu_alloc;
pdu_write = ll_iso_pdu_write;
pdu_emit = ll_iso_pdu_emit;
pdu_release = ll_iso_pdu_release;
if (path_is_vendor_specific(path_id)) {
if (!ll_data_path_source_create(dp, &pdu_alloc, &pdu_write,
&pdu_emit, &pdu_release)) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
}
err = isoal_source_create(handle, role,
burst_number, flush_timeout, max_octets,
sdu_interval, iso_interval,
stream_sync_delay, group_sync_delay,
pdu_alloc, pdu_write, pdu_emit,
pdu_release, &source_handle);
if (!err) {
dp->source_hdl = source_handle;
isoal_source_enable(source_handle);
} else {
return BT_HCI_ERR_CMD_DISALLOWED;
}
}
#endif /* CONFIG_BT_CTLR_CONN_ISO */
#if defined(CONFIG_BT_CTLR_SYNC_ISO)
struct ll_sync_iso_set *sync_iso;
@ -321,7 +430,6 @@ uint8_t ll_setup_iso_path(uint16_t handle, uint8_t path_dir, uint8_t path_id,
burst_number = lll_iso->bn;
sdu_interval = lll_iso->sdu_interval;
iso_interval = lll_iso->iso_interval;
#endif /* CONFIG_BT_CTLR_SYNC_ISO */
if (path_id == BT_HCI_DATAPATH_ID_HCI) {
/* Not vendor specific, thus alloc and emit functions known */
@ -353,9 +461,7 @@ uint8_t ll_setup_iso_path(uint16_t handle, uint8_t path_dir, uint8_t path_id,
}
if (!err) {
#if defined(CONFIG_BT_CTLR_SYNC_ISO)
stream->dp = dp;
#endif /* CONFIG_BT_CTLR_SYNC_ISO */
dp->sink_hdl = sink_handle;
isoal_sink_enable(sink_handle);
@ -364,6 +470,7 @@ uint8_t ll_setup_iso_path(uint16_t handle, uint8_t path_dir, uint8_t path_id,
return BT_HCI_ERR_CMD_DISALLOWED;
}
#endif /* CONFIG_BT_CTLR_SYNC_ISO */
#endif /* CONFIG_BT_CTLR_SYNC_ISO || CONFIG_BT_CTLR_CONN_ISO */
return 0;
@ -804,6 +911,94 @@ void ull_iso_datapath_release(struct ll_iso_datapath *dp)
mem_release(dp, &datapath_free);
}
#if defined(CONFIG_BT_CTLR_CONN_ISO)
/**
* Allocate a PDU from the LL and store the details in the given buffer. Allocation
* is not expected to fail as there must always be sufficient PDU buffers. Any
* failure will trigger the assert.
* @param[in] pdu_buffer Buffer to store PDU details in
* @return Error status of operation
*/
static isoal_status_t ll_iso_pdu_alloc(struct isoal_pdu_buffer *pdu_buffer)
{
ARG_UNUSED(pdu_buffer);
/* TODO: Function will be populated along with the data-path
* implementation
*/
return ISOAL_STATUS_OK;
}
/**
* Write the given SDU payload to the target PDU buffer at the given offset.
* @param[in,out] pdu_buffer Target PDU buffer
* @param[in] pdu_offset Offset / current write position within PDU
* @param[in] sdu_payload Location of source data
* @param[in] consume_len Length of data to copy
* @return Error status of write operation
*/
static isoal_status_t ll_iso_pdu_write(struct isoal_pdu_buffer *pdu_buffer,
const size_t pdu_offset,
const uint8_t *sdu_payload,
const size_t consume_len)
{
ARG_UNUSED(pdu_offset);
ARG_UNUSED(consume_len);
LL_ASSERT(pdu_buffer);
LL_ASSERT(pdu_buffer->pdu);
LL_ASSERT(sdu_payload);
/* TODO: Function will be populated along with the data-path
* implementation
*/
return ISOAL_STATUS_OK;
}
/**
* Emit the encoded node to the transmission queue
* @param node_tx TX node to enqueue
* @param handle CIS/BIS handle
* @return Error status of enqueue operation
*/
static isoal_status_t ll_iso_pdu_emit(struct node_tx_iso *node_tx,
const uint16_t handle)
{
ARG_UNUSED(node_tx);
ARG_UNUSED(handle);
/* TODO: Function will be populated along with the data-path
* implementation
*/
return ISOAL_STATUS_OK;
}
/**
* Release the given payload back to the memory pool.
* @param node_tx TX node to release or forward
* @param handle CIS/BIS handle
* @param status Reason for release
* @return Error status of release operation
*/
static isoal_status_t ll_iso_pdu_release(struct node_tx_iso *node_tx,
const uint16_t handle,
const isoal_status_t status)
{
ARG_UNUSED(node_tx);
ARG_UNUSED(handle);
ARG_UNUSED(status);
/* TODO: Function will be populated along with the data-path
* implementation
*/
return ISOAL_STATUS_OK;
}
#endif /* CONFIG_BT_CTLR_CONN_ISO */
static int init_reset(void)
{
#if defined(CONFIG_BT_CTLR_SYNC_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)

View file

@ -37,4 +37,5 @@ struct ll_iso_datapath {
uint8_t coding_format;
uint16_t company_id;
isoal_sink_handle_t sink_hdl;
isoal_source_handle_t source_hdl;
};

View file

@ -0,0 +1,12 @@
# Bluetooth Controller configuration options for ISO-AL Unit Tests
# Copyright (c) 2022 Oticon A/S
# SPDX-License-Identifier: Apache-2.0
config BT_CTLR_ISOAL_SINKS
int "Number of Isochronous Adaptation Layer sinks (for unittest)"
config BT_CTLR_ISOAL_SOURCES
int "Number of Isochronous Adaptation Layer sinks (for unittest)"
source "Kconfig.zephyr"

View file

@ -6,3 +6,6 @@ CONFIG_ZTEST_MOCKING=y
CONFIG_ZTEST_PARAMETER_COUNT=32
CONFIG_ZTEST_ASSERT_HOOK=y
CONFIG_BT_CTLR_ISOAL_SINKS=4
CONFIG_BT_CTLR_ISOAL_SOURCES=4

View file

@ -515,7 +515,7 @@ void test_unframed_long_pdu_short_sdu(void)
*/
void test_sink_create_destroy(void)
{
isoal_sink_handle_t hdl[ISOAL_SINKS_MAX];
isoal_sink_handle_t hdl[CONFIG_BT_CTLR_ISOAL_SINKS];
struct isoal_sink_config *config_ptr;
err = isoal_init();
@ -526,7 +526,7 @@ void test_sink_create_destroy(void)
uint8_t dummy_role = BT_CONN_ROLE_CENTRAL;
for (int i = 0; i < ISOAL_SINKS_MAX; i++) {
for (int i = 0; i < CONFIG_BT_CTLR_ISOAL_SINKS; i++) {
/* Create a sink based on global parameters */
err = isoal_sink_create(handle, dummy_role,
burst_number, flush_timeout,
@ -545,7 +545,7 @@ void test_sink_create_destroy(void)
dummy_role = (dummy_role + 1) % (BT_ROLE_BROADCAST + 1);
}
for (int i = 0; i < ISOAL_SINKS_MAX; i++) {
for (int i = 0; i < CONFIG_BT_CTLR_ISOAL_SINKS; i++) {
/* Destroy sink */
isoal_sink_destroy(hdl[i]);
}
@ -564,7 +564,7 @@ void test_sink_create_err(void)
isoal_sink_handle_t hdl;
for (int i = 0; i < ISOAL_SINKS_MAX; i++) {
for (int i = 0; i < CONFIG_BT_CTLR_ISOAL_SINKS; i++) {
/* Create a sink based on global parameters */
err = isoal_sink_create(handle, role,
burst_number, flush_timeout,