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 Size of the Isochronous Tx buffers and the value returned in HCI LE
Read Buffer Size V2 command response. 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 config BT_CTLR_ISO_VENDOR_DATA_PATH
bool "Vendor-specific ISO data path" bool "Vendor-specific ISO data path"
depends on BT_CTLR_SYNC_ISO || BT_CTLR_CONN_ISO depends on BT_CTLR_SYNC_ISO || BT_CTLR_CONN_ISO

View file

@ -9,27 +9,36 @@
#include <toolchain.h> #include <toolchain.h>
#include <sys/util.h> #include <sys/util.h>
#include <zephyr.h>
#include <bluetooth/bluetooth.h>
#include "util/memq.h" #include "util/memq.h"
#include "pdu.h" #include "pdu.h"
#include "ll.h"
#include "lll.h" #include "lll.h"
#include "lll_conn_iso.h"
#include "lll_iso_tx.h"
#include "isoal.h" #include "isoal.h"
#include "ull_iso_types.h"
#define LOG_MODULE_NAME bt_ctlr_isoal #define LOG_MODULE_NAME bt_ctlr_isoal
#include "common/log.h" #include "common/log.h"
#include "hal/debug.h" #include "hal/debug.h"
/* TODO this must be taken from a Kconfig */
#define ISOAL_SINKS_MAX (4)
/** Allocation state */ /** Allocation state */
typedef uint8_t isoal_alloc_state_t; typedef uint8_t isoal_alloc_state_t;
#define ISOAL_ALLOC_STATE_FREE ((isoal_alloc_state_t) 0x00) #define ISOAL_ALLOC_STATE_FREE ((isoal_alloc_state_t) 0x00)
#define ISOAL_ALLOC_STATE_TAKEN ((isoal_alloc_state_t) 0x01) #define ISOAL_ALLOC_STATE_TAKEN ((isoal_alloc_state_t) 0x01)
static struct struct
{ {
isoal_alloc_state_t sink_allocated[ISOAL_SINKS_MAX]; isoal_alloc_state_t sink_allocated[CONFIG_BT_CTLR_ISOAL_SINKS];
struct isoal_sink sink_state[ISOAL_SINKS_MAX]; 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; } isoal_global;
@ -77,7 +86,7 @@ static isoal_status_t isoal_sink_allocate(isoal_sink_handle_t *hdl)
isoal_sink_handle_t i; isoal_sink_handle_t i;
/* Very small linear search to find first free */ /* 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) { if (isoal_global.sink_allocated[i] == ISOAL_ALLOC_STATE_FREE) {
isoal_global.sink_allocated[i] = ISOAL_ALLOC_STATE_TAKEN; isoal_global.sink_allocated[i] = ISOAL_ALLOC_STATE_TAKEN;
*hdl = i; *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 */ /* 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) /* Computation of transport latency (constant part)
* *
@ -212,6 +222,111 @@ isoal_status_t isoal_sink_create(
return err; 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 * @brief Get reference to configuration struct
* *
@ -262,6 +377,56 @@ void isoal_sink_destroy(isoal_sink_handle_t hdl)
isoal_sink_deallocate(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 */ /* 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)
@ -812,3 +977,313 @@ isoal_status_t isoal_rx_pdu_recombine(isoal_sink_handle_t sink_hdl,
return err; 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_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 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) #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;
/** Handle to a registered ISO Sub-System source */
typedef uint8_t isoal_source_handle_t;
/** Byte length of an ISO SDU */ /** Byte length of an ISO SDU */
typedef uint16_t isoal_sdu_len_t; typedef uint16_t isoal_sdu_len_t;
@ -74,6 +79,11 @@ struct isoal_rx_origin {
} inst; } inst;
}; };
enum isoal_mode {
ISOAL_MODE_CIS,
ISOAL_MODE_BIS
};
/** /**
* @brief ISO frame SDU buffer - typically an Audio frame buffer * @brief ISO frame SDU buffer - typically an Audio frame buffer
* *
@ -93,6 +103,23 @@ struct isoal_sdu_buffer {
isoal_sdu_len_t size; 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 */ /** @brief Produced ISO SDU frame with associated meta data */
struct isoal_sdu_produced { struct isoal_sdu_produced {
/** Status of contents, if valid or SDU was lost. /** 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 */ /** @brief Received ISO PDU with associated meta data */
struct isoal_pdu_rx { struct isoal_pdu_rx {
/** Meta */ /** Meta */
@ -125,9 +159,25 @@ struct isoal_pdu_rx {
struct pdu_iso *pdu; 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 */ /* Forward declaration */
struct isoal_sink; struct isoal_sink;
struct node_tx_iso;
/** /**
* @brief Callback: Request memory for a new ISO SDU buffer * @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 { struct isoal_sink_config {
enum { enum isoal_mode mode;
ISOAL_MODE_CIS,
ISOAL_MODE_BIS
} mode;
/* TODO add SDU and PDU max length etc. */ /* TODO add SDU and PDU max length etc. */
}; };
@ -220,6 +267,104 @@ struct isoal_sink {
struct isoal_sdu_production sdu_production; 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); 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, isoal_status_t sink_sdu_write_hci(void *dbuf,
const uint8_t *pdu_payload, const uint8_t *pdu_payload,
const size_t consume_len); 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_conn_internal.h"
#include "ull_sync_iso_internal.h" #include "ull_sync_iso_internal.h"
#include "ull_conn_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 BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER)
#define LOG_MODULE_NAME bt_ctlr_ull_iso #define LOG_MODULE_NAME bt_ctlr_ull_iso
@ -62,6 +63,19 @@
static int init_reset(void); 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 */ /* Allocate data path pools for RX/TX directions for each stream */
#define BT_CTLR_ISO_STREAMS ((2 * (BT_CTLR_CONN_ISO_STREAMS)) + \ #define BT_CTLR_ISO_STREAMS ((2 * (BT_CTLR_CONN_ISO_STREAMS)) + \
BT_CTLR_SYNC_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; 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) static inline bool path_is_vendor_specific(uint8_t path_id)
{ {
return (path_id >= BT_HCI_DATAPATH_ID_VS && 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; return 0;
} }
#if defined(CONFIG_BT_CTLR_SYNC_ISO)
if (path_dir != BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) { if (path_dir != BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) {
/* FIXME: workaround to succeed datapath setup for ISO /* FIXME: workaround to succeed datapath setup for ISO
* broadcaster until Tx datapath is implemented, in the * 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; 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_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; isoal_sink_handle_t sink_handle;
uint32_t stream_sync_delay; uint32_t stream_sync_delay;
uint32_t group_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; stream_sync_delay = cis->sync_delay;
group_sync_delay = cig->sync_delay; group_sync_delay = cig->sync_delay;
if (path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR) { if (path_dir == BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) {
burst_number = cis->lll.tx.burst_number; /* Create sink for RX data path */
flush_timeout = cis->lll.tx.flush_timeout; burst_number = cis->lll.rx.burst_number;
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; flush_timeout = cis->lll.rx.flush_timeout;
max_octets = cis->lll.rx.max_octets;
if (role) { if (role) {
/* peripheral */ /* 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; 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;
}
}
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 #endif /* CONFIG_BT_CTLR_CONN_ISO */
#if defined(CONFIG_BT_CTLR_SYNC_ISO) #if defined(CONFIG_BT_CTLR_SYNC_ISO)
struct ll_sync_iso_set *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; 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 */
if (path_id == BT_HCI_DATAPATH_ID_HCI) { if (path_id == BT_HCI_DATAPATH_ID_HCI) {
/* Not vendor specific, thus alloc and emit functions known */ /* 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 (!err) {
#if defined(CONFIG_BT_CTLR_SYNC_ISO)
stream->dp = dp; stream->dp = dp;
#endif /* CONFIG_BT_CTLR_SYNC_ISO */
dp->sink_hdl = sink_handle; dp->sink_hdl = sink_handle;
isoal_sink_enable(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; return BT_HCI_ERR_CMD_DISALLOWED;
} }
#endif /* CONFIG_BT_CTLR_SYNC_ISO */
#endif /* CONFIG_BT_CTLR_SYNC_ISO || CONFIG_BT_CTLR_CONN_ISO */ #endif /* CONFIG_BT_CTLR_SYNC_ISO || CONFIG_BT_CTLR_CONN_ISO */
return 0; return 0;
@ -804,6 +911,94 @@ void ull_iso_datapath_release(struct ll_iso_datapath *dp)
mem_release(dp, &datapath_free); 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) static int init_reset(void)
{ {
#if defined(CONFIG_BT_CTLR_SYNC_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO) #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; uint8_t coding_format;
uint16_t company_id; uint16_t company_id;
isoal_sink_handle_t sink_hdl; 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_PARAMETER_COUNT=32
CONFIG_ZTEST_ASSERT_HOOK=y 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) 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; struct isoal_sink_config *config_ptr;
err = isoal_init(); err = isoal_init();
@ -526,7 +526,7 @@ void test_sink_create_destroy(void)
uint8_t dummy_role = BT_CONN_ROLE_CENTRAL; 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 */ /* Create a sink based on global parameters */
err = isoal_sink_create(handle, dummy_role, err = isoal_sink_create(handle, dummy_role,
burst_number, flush_timeout, burst_number, flush_timeout,
@ -545,7 +545,7 @@ void test_sink_create_destroy(void)
dummy_role = (dummy_role + 1) % (BT_ROLE_BROADCAST + 1); 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 */ /* Destroy sink */
isoal_sink_destroy(hdl[i]); isoal_sink_destroy(hdl[i]);
} }
@ -564,7 +564,7 @@ void test_sink_create_err(void)
isoal_sink_handle_t hdl; 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 */ /* Create a sink based on global parameters */
err = isoal_sink_create(handle, role, err = isoal_sink_create(handle, role,
burst_number, flush_timeout, burst_number, flush_timeout,