Bluetooth: controller: ISO adaptation layer, Rx unframed

First design towards ISO adaptation layer, this PR introduces
data-structures and framework for Rx unframed PDUs (BT RX ingress).
Two callbacks are defined for the SDU production (BT RX egress), one for
SDU allocation as well as a callback for emitting a reassembled SDU.

Signed-off-by: Asger Munk Nielsen <asmk@oticon.com>
This commit is contained in:
Asger Munk Nielsen 2021-02-04 16:11:06 +01:00 committed by Carles Cufí
parent 958e826b69
commit b9b1c7e19b
13 changed files with 1016 additions and 19 deletions

View file

@ -667,6 +667,12 @@ struct bt_hci_rp_read_bd_addr {
#define BT_HCI_DATAPATH_DIR_HOST_TO_CTLR 0x00
#define BT_HCI_DATAPATH_DIR_CTLR_TO_HOST 0x01
/* audio datapath IDs */
#define BT_HCI_DATAPATH_ID_HCI 0x00
#define BT_HCI_DATAPATH_ID_VS 0x01
#define BT_HCI_DATAPATH_ID_VS_END 0xfe
#define BT_HCI_DATAPATH_ID_DISABLED 0xff
/* coding format assigned numbers, used for codec IDs */
#define BT_HCI_CODING_FORMAT_ULAW_LOG 0x00
#define BT_HCI_CODING_FORMAT_ALAW_LOG 0x01

View file

@ -103,6 +103,7 @@ if(CONFIG_BT_LL_SW_SPLIT)
CONFIG_BT_CTLR_CENTRAL_ISO)
zephyr_library_sources(
ll_sw/ull_iso.c
ll_sw/isoal.c
)
endif()
if(CONFIG_BT_CONN OR

View file

@ -1926,7 +1926,7 @@ static void le_setup_iso_path(struct net_buf *buf, struct net_buf **evt)
rp = hci_cmd_complete(evt, sizeof(*rp));
rp->status = status;
rp->handle = cmd->handle;
rp->handle = sys_cpu_to_le16(handle);
}
static void le_remove_iso_path(struct net_buf *buf, struct net_buf **evt)
@ -1942,7 +1942,7 @@ static void le_remove_iso_path(struct net_buf *buf, struct net_buf **evt)
rp = hci_cmd_complete(evt, sizeof(*rp));
rp->status = status;
rp->handle = cmd->handle;
rp->handle = sys_cpu_to_le16(handle);
}
static void le_iso_receive_test(struct net_buf *buf, struct net_buf **evt)
@ -6237,6 +6237,11 @@ uint8_t hci_get_class(struct node_rx_pdu *node_rx)
#endif /* CONFIG_BT_CTLR_PHY */
return HCI_CLASS_EVT_CONNECTION;
#endif /* CONFIG_BT_CONN */
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_SYNC_ISO) || \
defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
case NODE_RX_TYPE_ISO_PDU:
return HCI_CLASS_ISO_DATA;
#endif
#if CONFIG_BT_CTLR_USER_EVT_RANGE > 0
case NODE_RX_TYPE_USER_START ... NODE_RX_TYPE_USER_END - 1:

View file

@ -45,6 +45,12 @@
#include "ll_sw/lll.h"
#include "ll.h"
#include "isoal.h"
#include "lll_conn_iso.h"
#include "ull_conn_iso_internal.h"
#include "ull_conn_iso_types.h"
#include "ull_iso_types.h"
#include "hci_internal.h"
#include "hal/debug.h"
@ -65,6 +71,93 @@ static sys_slist_t hbuf_pend;
static int32_t hbuf_count;
#endif
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_SYNC_ISO) || \
defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
#define SDU_HCI_HDR_SIZE (BT_HCI_ISO_HDR_SIZE + BT_HCI_ISO_TS_DATA_HDR_SIZE)
isoal_status_t sink_sdu_alloc_hci(const struct isoal_sink *sink_ctx,
const struct isoal_pdu_rx *valid_pdu,
struct isoal_sdu_buffer *sdu_buffer)
{
ARG_UNUSED(sink_ctx);
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);
if (buf) {
/* Reserve space for headers */
net_buf_reserve(buf, SDU_HCI_HDR_SIZE);
sdu_buffer->dbuf = buf;
sdu_buffer->size = net_buf_tailroom(buf);
} else {
LL_ASSERT(0);
}
return ISOAL_STATUS_OK;
}
isoal_status_t sink_sdu_emit_hci(const struct isoal_sink *sink_ctx,
const struct isoal_sdu_produced *valid_sdu)
{
struct ll_conn_iso_stream *cis;
uint16_t handle;
uint16_t handle_packed;
uint8_t ts, pb;
uint16_t len;
uint16_t packet_status_flag;
uint16_t slen, slen_packed;
struct bt_hci_iso_hdr *hdr;
struct bt_hci_iso_ts_data_hdr *data_hdr;
struct net_buf *buf = (struct net_buf *) valid_sdu->contents.dbuf;
if (buf) {
data_hdr = net_buf_push(buf, BT_HCI_ISO_TS_DATA_HDR_SIZE);
hdr = net_buf_push(buf, BT_HCI_ISO_HDR_SIZE);
cis = sink_ctx->session.cis;
handle = ll_conn_iso_stream_handle_get(cis);
pb = sink_ctx->sdu_production.sdu_state;
ts = 1; /*TODO: Always assume timestamp? */
handle_packed = bt_iso_handle_pack(handle, pb, ts);
len = sink_ctx->sdu_production.sdu_written + BT_HCI_ISO_TS_DATA_HDR_SIZE;
hdr->handle = sys_cpu_to_le16(handle_packed);
hdr->len = sys_cpu_to_le16(len);
packet_status_flag = 0x0000; /* TODO: For now always assume "valid data" */
slen = sink_ctx->sdu_production.sdu_written;
slen_packed = bt_iso_pkt_len_pack(slen, packet_status_flag);
data_hdr->ts = sys_cpu_to_le32((uint32_t) valid_sdu->timestamp);
data_hdr->data.sn = sys_cpu_to_le16((uint16_t) valid_sdu->seqn);
data_hdr->data.slen = sys_cpu_to_le16(slen_packed);
/* send fragment up the chain */
bt_recv(buf);
}
return ISOAL_STATUS_OK;
}
isoal_status_t sink_sdu_write_hci(void *dbuf,
const uint8_t *pdu_payload,
const size_t consume_len)
{
struct net_buf *buf = (struct net_buf *) dbuf;
LL_ASSERT(buf);
net_buf_add_mem(buf, pdu_payload, consume_len);
return ISOAL_STATUS_OK;
}
#endif
static struct net_buf *process_prio_evt(struct node_rx_pdu *node_rx,
uint8_t *evt_flags)
{
@ -203,6 +296,33 @@ static inline struct net_buf *encode_node(struct node_rx_pdu *node_rx,
hci_acl_encode(node_rx, buf);
break;
#endif
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_SYNC_ISO) || \
defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
case HCI_CLASS_ISO_DATA: {
#if defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
struct ll_conn_iso_stream *cis =
ll_conn_iso_stream_get(node_rx->hdr.handle);
struct ll_iso_datapath *dp = cis->datapath_out;
isoal_sink_handle_t sink = dp->sink_hdl;
if (dp->path_id == BT_HCI_DATAPATH_ID_HCI) {
/* If HCI datapath pass to ISO AL here */
struct isoal_pdu_rx pckt_meta = {
.meta = &node_rx->hdr.rx_iso_meta,
.pdu = (union isoal_pdu *) &node_rx->pdu[0]
};
/* Pass the ISO PDU through ISO-AL */
isoal_status_t err =
isoal_rx_pdu_recombine(sink, &pckt_meta);
LL_ASSERT(err == ISOAL_STATUS_OK); /* TODO handle err */
}
#endif
break;
}
#endif
default:
LL_ASSERT(0);
break;
@ -225,6 +345,7 @@ static inline struct net_buf *process_node(struct node_rx_pdu *node_rx)
/* controller to host flow control enabled */
switch (class) {
case HCI_CLASS_ISO_DATA:
case HCI_CLASS_EVT_DISCARDABLE:
case HCI_CLASS_EVT_REQUIRED:
break;
@ -403,7 +524,6 @@ static void recv_thread(void *p1, void *p2, void *p3)
net_buf_unref(buf);
}
}
k_yield();
}
}

View file

@ -28,6 +28,8 @@ extern atomic_t hci_state_mask;
#define HCI_CLASS_ACL_DATA 5 /* Asynchronous Connection Less (general
* data)
*/
#define HCI_CLASS_ISO_DATA 6 /* Isochronous data */
void hci_init(struct k_poll_signal *signal_host_buf);
struct net_buf *hci_cmd_handle(struct net_buf *cmd, void **node_rx);
@ -41,6 +43,10 @@ int hci_acl_handle(struct net_buf *acl, struct net_buf **evt);
void hci_acl_encode(struct node_rx_pdu *node_rx, struct net_buf *buf);
void hci_num_cmplt_encode(struct net_buf *buf, uint16_t handle, uint8_t num);
#endif
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_SYNC_ISO) || \
defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
void hci_iso_encode(struct net_buf *buf, uint16_t handle, uint8_t flags);
#endif
int hci_vendor_cmd_handle(uint16_t ocf, struct net_buf *cmd,
struct net_buf **evt);
uint8_t hci_vendor_read_static_addr(struct bt_hci_vs_static_addr addrs[],

View file

@ -0,0 +1,425 @@
/*
* Copyright (c) 2021 Demant
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <zephyr/types.h>
#include <sys/types.h>
#include <toolchain.h>
#include <sys/util.h>
#include "util/memq.h"
#include "pdu.h"
#include "lll.h"
#include "isoal.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
{
isoal_alloc_state_t sink_allocated[ISOAL_SINKS_MAX];
struct isoal_sink sink_state[ISOAL_SINKS_MAX];
} isoal_global;
/**
* @brief Internal reset
* Zero-init entire ISO-AL state
*/
static isoal_status_t isoal_init_reset(void)
{
memset(&isoal_global, 0, sizeof(isoal_global));
return ISOAL_STATUS_OK;
}
/**
* @brief Initialize ISO-AL
*/
isoal_status_t isoal_init(void)
{
isoal_status_t err = ISOAL_STATUS_OK;
err = isoal_init_reset();
if (err) {
return err;
}
return err;
}
/** Clean up and reinitialize */
isoal_status_t isoal_reset(void)
{
isoal_status_t err = ISOAL_STATUS_OK;
err = isoal_init_reset();
if (err) {
return err;
}
return err;
}
/**
* @brief Find free sink from statically-sized pool and allocate it
* @details Implemented as linear search since pool is very small
*
* @param hdl[out] Handle to sink
* @return ISOAL_STATUS_OK if we could allocate; otherwise ISOAL_STATUS_ERR_SINK_ALLOC
*/
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++) {
if (isoal_global.sink_allocated[i] == ISOAL_ALLOC_STATE_FREE) {
isoal_global.sink_allocated[i] = ISOAL_ALLOC_STATE_TAKEN;
*hdl = i;
return ISOAL_STATUS_OK;
}
}
return ISOAL_STATUS_ERR_SINK_ALLOC; /* All entries were taken */
}
/**
* @brief Mark a sink as being free to allocate again
* @param hdl[in] Handle to sink
*/
static void isoal_sink_deallocate(isoal_sink_handle_t hdl)
{
isoal_global.sink_allocated[hdl] = ISOAL_ALLOC_STATE_FREE;
}
/**
* @brief Create a new sink
*
* @param hdl[out] Handle to new sink
* @param cis[in] Poiner to iso stream
* @param sdu_alloc[in] Callback of SDU allocator
* @param sdu_emit[in] Callback of SDU emitter
* @return ISOAL_STATUS_OK if we could create a new sink; otherwise ISOAL_STATUS_ERR_SINK_ALLOC
*/
isoal_status_t isoal_sink_create(
isoal_sink_handle_t *hdl,
struct ll_conn_iso_stream *cis,
isoal_sink_sdu_alloc_cb sdu_alloc,
isoal_sink_sdu_emit_cb sdu_emit,
isoal_sink_sdu_write_cb sdu_write)
{
isoal_status_t err = ISOAL_STATUS_OK;
/* Allocate a new sink */
err = isoal_sink_allocate(hdl);
if (err) {
return err;
}
isoal_global.sink_state[*hdl].session.cis = cis;
/* Remember the platform-specific callbacks */
isoal_global.sink_state[*hdl].session.sdu_alloc = sdu_alloc;
isoal_global.sink_state[*hdl].session.sdu_emit = sdu_emit;
isoal_global.sink_state[*hdl].session.sdu_write = sdu_write;
/* Initialize running seq number to zero */
isoal_global.sink_state[*hdl].session.seqn = 0;
return err;
}
/**
* @brief Get reference to configuration struct
*
* @param hdl[in] Handle to new sink
* @return Reference to parameter struct, to be configured by caller
*/
struct isoal_sink_config *isoal_get_sink_param_ref(isoal_sink_handle_t hdl)
{
LL_ASSERT(isoal_global.sink_allocated[hdl] == ISOAL_ALLOC_STATE_TAKEN);
return &isoal_global.sink_state[hdl].session.param;
}
/**
* @brief Atomically enable latch-in of packets and SDU production
* @param hdl[in] Handle of existing instance
*/
void isoal_sink_enable(isoal_sink_handle_t hdl)
{
/* Reset bookkeeping state */
memset(&isoal_global.sink_state[hdl].sdu_production, 0,
sizeof(isoal_global.sink_state[hdl].sdu_production));
/* Atomically enable */
isoal_global.sink_state[hdl].sdu_production.mode = ISOAL_PRODUCTION_MODE_ENABLED;
}
/**
* @brief Atomically disable latch-in of packets and SDU production
* @param hdl[in] Handle of existing instance
*/
void isoal_sink_disable(isoal_sink_handle_t hdl)
{
/* Atomically disable */
isoal_global.sink_state[hdl].sdu_production.mode = ISOAL_PRODUCTION_MODE_DISABLED;
}
/**
* @brief Disable and deallocate existing sink
* @param hdl[in] Handle of existing instance
*/
void isoal_sink_destroy(isoal_sink_handle_t hdl)
{
/* Atomic disable */
isoal_sink_disable(hdl);
/* Permit allocation anew */
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 */
static isoal_status_t isoal_rx_allocate_sdu(struct isoal_sink *sink,
const struct isoal_pdu_rx *pdu_meta)
{
isoal_status_t err = ISOAL_STATUS_OK;
struct isoal_sdu_produced *sdu = &sink->sdu_production.sdu;
/* Allocate a SDU if the previous was filled (thus sent) */
const bool sdu_complete = (sink->sdu_production.sdu_available == 0);
if (sdu_complete) {
/* Allocate new clean SDU buffer */
err = sink->session.sdu_alloc(
sink,
pdu_meta, /* [in] PDU origin may determine buffer */
&sdu->contents /* [out] Updated with pointer and size */
);
/* Nothing has been written into buffer yet */
sink->sdu_production.sdu_written = 0;
sink->sdu_production.sdu_available = sdu->contents.size;
LL_ASSERT(sdu->contents.size > 0);
/* Remember meta data */
sdu->status = pdu_meta->meta->status;
sdu->timestamp = pdu_meta->meta->timestamp;
/* Get seq number from session counter, and increase SDU counter */
sdu->seqn = sink->session.seqn++;
}
return err;
}
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_produced *sdu = &sink->sdu_production.sdu;
/* Emit a SDU */
const bool sdu_complete = (sink->sdu_production.sdu_available == 0) || end_of_sdu;
if (end_of_sdu) {
sink->sdu_production.sdu_available = 0;
}
if (sdu_complete) {
uint8_t next_state = BT_ISO_START;
switch (sink->sdu_production.sdu_state) {
case BT_ISO_START:
if (end_of_sdu) {
sink->sdu_production.sdu_state = BT_ISO_SINGLE;
next_state = BT_ISO_START;
} else {
sink->sdu_production.sdu_state = BT_ISO_START;
next_state = BT_ISO_CONT;
}
break;
case BT_ISO_CONT:
if (end_of_sdu) {
sink->sdu_production.sdu_state = BT_ISO_END;
next_state = BT_ISO_START;
} else {
sink->sdu_production.sdu_state = BT_ISO_CONT;
next_state = BT_ISO_CONT;
}
break;
case BT_ISO_END:
case BT_ISO_SINGLE:
default:
LL_ASSERT(0);
break;
}
err = sink->session.sdu_emit(sink, sdu);
/* update next state */
sink->sdu_production.sdu_state = next_state;
}
return err;
}
static isoal_status_t isoal_rx_append_to_sdu(struct isoal_sink *sink,
const struct isoal_pdu_rx *pdu_meta,
bool is_end_fragment)
{
isoal_status_t err = ISOAL_STATUS_OK;
const uint8_t *pdu_payload = pdu_meta->pdu->cis.payload;
/* Note length field in same location for both CIS and BIS */
isoal_pdu_len_t packet_available = pdu_meta->pdu->cis.length;
/* TODO error handling and lost packet */
/* While there is something left of the packet to consume */
while (packet_available > 0) {
const isoal_status_t err_alloc = isoal_rx_allocate_sdu(sink, pdu_meta);
struct isoal_sdu_produced *sdu = &sink->sdu_production.sdu;
err |= err_alloc;
/*
* For this SDU we can only consume of packet, bounded by:
* - What can fit in the destination SDU.
* - What remains of the packet.
*/
const size_t consume_len = MIN(
packet_available,
sink->sdu_production.sdu_available
);
LL_ASSERT(sdu->contents.dbuf);
LL_ASSERT(pdu_payload);
sdu->status |= pdu_meta->meta->status;
if (pdu_meta->meta->status == ISOAL_PDU_STATUS_VALID) {
err |= sink->session.sdu_write(sdu->contents.dbuf,
pdu_payload,
consume_len);
}
pdu_payload += consume_len;
sink->sdu_production.sdu_written += consume_len;
sink->sdu_production.sdu_available -= consume_len;
packet_available -= consume_len;
bool end_of_sdu = (packet_available == 0) && is_end_fragment;
const isoal_status_t err_emit = isoal_rx_try_emit_sdu(sink, end_of_sdu);
err |= err_emit;
}
LL_ASSERT(packet_available == 0);
return err;
}
/**
* @brief Consume a 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 TODO
*/
static isoal_status_t isoal_rx_packet_consume(struct isoal_sink *sink,
const struct isoal_pdu_rx *pdu_meta)
{
isoal_pdu_cnt_t id, prev_id;
isoal_status_t err = ISOAL_STATUS_OK;
const uint8_t llid = isoal_rx_packet_classify(pdu_meta);
switch (sink->sdu_production.fsm) {
case ISOAL_START:
/* State assumes First PDU of new SDU */
sink->sdu_production.prev_pdu_id = pdu_meta->meta->payload_number;
sink->sdu_production.sdu_state = BT_ISO_START;
if (llid == PDU_BIS_LLID_START_CONTINUE) {
/* PDU contains first fragment of an SDU */
err |= isoal_rx_append_to_sdu(sink, pdu_meta, false);
sink->sdu_production.fsm = ISOAL_CONTINUE;
} else if (llid == PDU_BIS_LLID_COMPLETE_END) {
/* PDU contains complete SDU */
err |= isoal_rx_append_to_sdu(sink, pdu_meta, true);
sink->sdu_production.fsm = ISOAL_START;
} else {
/* Unsupported case */
LL_ASSERT(0);
/* TODO error handling */
break;
}
break;
case ISOAL_CONTINUE:
/* State assumes that at least one PDU has been seen of fragmented SDU */
id = pdu_meta->meta->payload_number;
prev_id = sink->sdu_production.prev_pdu_id;
if (id != (prev_id+1)) {
/* Id not as expexted, a PDU could have been lost */
LL_ASSERT(0);
/* TODO error handling? Mark SDU as incomplete? break? */
}
sink->sdu_production.prev_pdu_id = id;
if (llid == PDU_BIS_LLID_START_CONTINUE) {
/* PDU contains a continuation (neither start of end) fragment of SDU */
err |= isoal_rx_append_to_sdu(sink, pdu_meta, false);
sink->sdu_production.fsm = ISOAL_CONTINUE;
} else if (llid == PDU_BIS_LLID_COMPLETE_END) {
/* PDU contains end fragment of a fragmented SDU */
err |= isoal_rx_append_to_sdu(sink, pdu_meta, true);
sink->sdu_production.fsm = ISOAL_START;
} else {
/* Unsupported case */
LL_ASSERT(0);
/* TODO error handling */
break;
}
break;
}
return err;
}
/**
* @brief Deep copy a PDU, recombine into SDU(s)
* @details Recombination will occur individually for every enabled sink
*
* @param sink_hdl[in] Handle of destination sink
* @param pdu_meta[in] PDU along with meta information (origin, timing, status)
* @return TODO
*/
isoal_status_t isoal_rx_pdu_recombine(isoal_sink_handle_t sink_hdl,
const struct isoal_pdu_rx *pdu_meta)
{
struct isoal_sink *sink = &isoal_global.sink_state[sink_hdl];
isoal_status_t err = ISOAL_STATUS_ERR_SDU_ALLOC;
if (sink->sdu_production.mode != ISOAL_PRODUCTION_MODE_DISABLED) {
err = isoal_rx_packet_consume(sink, pdu_meta);
}
return err;
}

View file

@ -0,0 +1,242 @@
/*
* Copyright (c) 2021 Demant
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <zephyr/types.h>
#include <toolchain.h>
/** Function return error codes */
typedef uint8_t isoal_status_t;
#define ISOAL_STATUS_OK ((isoal_status_t) 0x00) /* No error */
#define ISOAL_STATUS_ERR_SINK_ALLOC ((isoal_status_t) 0x01) /* Sink 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_EMIT ((isoal_status_t) 0x08) /* SDU emission */
/** Handle to a registered ISO Sub-System sink */
typedef uint8_t isoal_sink_handle_t;
/** Byte length of an ISO SDU */
typedef uint16_t isoal_sdu_len_t;
/** Byte length of an ISO PDU */
typedef uint8_t isoal_pdu_len_t;
/** Count (ID number) of an ISO SDU */
typedef uint16_t isoal_sdu_cnt_t;
/** Count (ID number) of an ISO PDU */
typedef uint64_t isoal_pdu_cnt_t;
/** Ticks. Used for timestamp. TODO: Replace with ticks type */
typedef uint32_t isoal_time_t;
/** SDU status codes */
typedef uint8_t isoal_sdu_status_t;
#define ISOAL_SDU_STATUS_VALID ((isoal_sdu_status_t) 0x00)
#define ISOAL_SDU_STATUS_LOST_DATA ((isoal_sdu_status_t) 0x01)
#define ISOAL_SDU_STATUS_ERRORS ((isoal_sdu_status_t) 0x02)
/** PDU status codes */
typedef uint8_t isoal_pdu_status_t;
#define ISOAL_PDU_STATUS_VALID ((isoal_pdu_status_t) 0x00)
#define ISOAL_PDU_STATUS_LOST_DATA ((isoal_pdu_status_t) 0x01)
#define ISOAL_PDU_STATUS_ERRORS ((isoal_pdu_status_t) 0x02)
/** Production mode */
typedef uint8_t isoal_production_mode_t;
#define ISOAL_PRODUCTION_MODE_DISABLED ((isoal_production_mode_t) 0x00)
#define ISOAL_PRODUCTION_MODE_ENABLED ((isoal_production_mode_t) 0x01)
/**
* Origin of {PDU or SDU}.
*/
struct isoal_rx_origin {
/** Originating subsystem */
enum __packed {
ISOAL_SUBSYS_BT_LLL = 0x01, /*!< Bluetooth LLL */
ISOAL_SUBSYS_BT_HCI = 0x02, /*!< Bluetooth HCI */
ISOAL_SUBSYS_VS = 0xff /*!< Vendor specific */
} subsys;
/** Subsystem instance */
union {
struct {
uint16_t handle; /*!< BT connection handle */
} bt_lll;
} inst;
};
/**
* @brief ISO frame SDU buffer - typically an Audio frame buffer
*
* This provides the underlying vehicle of ISO SDU interchange through the
* ISO socket, the contents of which is generally an array of encoded bytes.
* Access and life time of this should be limited to ISO and the ISO socket.
* We assume no byte-fractional code words.
*
* Decoding code words to samples is the responsibility of the ISO sub system.
*/
struct isoal_sdu_buffer {
/** 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;
};
/** @brief Produced ISO SDU frame with associated meta data */
struct isoal_sdu_produced {
/** Status of contents, if valid or SDU was lost */
isoal_sdu_status_t status;
/** Regardless of status, we always have timing */
isoal_time_t timestamp;
/** Sequence number of SDU */
isoal_sdu_cnt_t seqn;
/** Regardless of status, we always know where the PDUs that produced
* this SDU, came from
*/
struct isoal_rx_origin origin;
/** Contents and length can only be trusted if status is valid */
struct isoal_sdu_buffer contents;
/** Optional context to be carried from PDU at alloc-time */
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 */
struct isoal_pdu_rx {
/** Meta */
struct node_rx_iso_meta *meta;
/** PDU contents and length can only be trusted if status is valid */
union isoal_pdu *pdu;
};
/* Forward declaration */
struct isoal_sink;
/**
* @brief Callback: Request memory for a new ISO SDU buffer
*
* Proprietary ISO sub systems may have
* specific requirements or opinions on where to locate ISO SDUs; 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_sink_sdu_alloc_cb)(
/*!< [in] Sink context */
const struct isoal_sink *sink_ctx,
/*!< [in] Received PDU */
const struct isoal_pdu_rx *valid_pdu,
/*!< [out] Struct is modified. Must not be NULL */
struct isoal_sdu_buffer *sdu_buffer
);
/**
* @brief Callback: Push an ISO SDU into an ISO sink
*
* Call also handing back buffer ownership
*/
typedef isoal_status_t (*isoal_sink_sdu_emit_cb)(
/*!< [in] Sink context */
const struct isoal_sink *sink_ctx,
/*!< [in] Filled valid SDU to be pushed */
const struct isoal_sdu_produced *valid_sdu
);
/**
* @brief Callback: Write a number of bytes to SDU buffer
*/
typedef isoal_status_t (*isoal_sink_sdu_write_cb)(
/*!< [in] Destination buffer */
void *dbuf,
/*!< [in] Source data */
const uint8_t *pdu_payload,
/*!< [in] Number of bytes to be copied */
const size_t consume_len
);
struct isoal_sink_config {
enum {
ISOAL_MODE_CIS,
ISOAL_MODE_BIS
} mode;
/* TODO add SDU and PDU max length etc. */
};
struct isoal_sink {
/* Session-constant */
struct {
struct ll_conn_iso_stream *cis;
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;
} session;
/* State for SDU production */
struct {
/* 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
} fsm;
uint8_t sdu_state;
isoal_sdu_len_t sdu_written;
isoal_sdu_len_t sdu_available;
} sdu_production;
};
isoal_status_t isoal_init(void);
isoal_status_t isoal_reset(void);
isoal_status_t isoal_sink_create(isoal_sink_handle_t *hdl,
struct ll_conn_iso_stream *cis,
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 *isoal_get_sink_param_ref(isoal_sink_handle_t hdl);
void isoal_sink_enable(isoal_sink_handle_t hdl);
void isoal_sink_disable(isoal_sink_handle_t hdl);
void isoal_sink_destroy(isoal_sink_handle_t hdl);
isoal_status_t isoal_rx_pdu_recombine(isoal_sink_handle_t sink_hdl,
const struct isoal_pdu_rx *pdu_meta);
/* SDU Call backs for HCI interface */
isoal_status_t sink_sdu_alloc_hci(const struct isoal_sink *sink_ctx,
const struct isoal_pdu_rx *valid_pdu,
struct isoal_sdu_buffer *sdu_buffer);
isoal_status_t sink_sdu_emit_hci(const struct isoal_sink *sink_ctx,
const struct isoal_sdu_produced *valid_sdu);
isoal_status_t sink_sdu_write_hci(void *dbuf,
const uint8_t *pdu_payload,
const size_t consume_len);

View file

@ -47,6 +47,7 @@
#include "ull_filter.h"
#include "ull_df.h"
#include "isoal.h"
#include "ull_internal.h"
#include "ull_iso_internal.h"
#include "ull_adv_internal.h"
@ -55,7 +56,10 @@
#include "ull_sync_iso_internal.h"
#include "ull_master_internal.h"
#include "ull_conn_internal.h"
#include "lll_conn_iso.h"
#include "ull_conn_iso_internal.h"
#include "ull_conn_iso_types.h"
#include "ull_iso_types.h"
#include "ull_central_iso_internal.h"
#include "ull_peripheral_iso_internal.h"
@ -1054,6 +1058,11 @@ void ll_rx_dequeue(void)
case NODE_RX_TYPE_CIS_ESTABLISHED:
#endif /* CONFIG_BT_CTLR_PERIPHERAL_ISO || CONFIG_BT_CTLR_CENTRAL_ISO */
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_SYNC_ISO) || \
defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
case NODE_RX_TYPE_ISO_PDU:
#endif
/* Ensure that at least one 'case' statement is present for this
* code block.
*/
@ -1223,6 +1232,11 @@ void ll_rx_mem_release(void **node_rx)
case NODE_RX_TYPE_CIS_ESTABLISHED:
#endif /* CONFIG_BT_CTLR_PERIPHERAL_ISO || CONFIG_BT_CTLR_CENTRAL_ISO */
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_SYNC_ISO) || \
defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
case NODE_RX_TYPE_ISO_PDU:
#endif
/* Ensure that at least one 'case' statement is present for this
* code block.
*/
@ -2288,6 +2302,45 @@ static inline int rx_demux_rx(memq_link_t *link, struct node_rx_hdr *rx)
* CONFIG_BT_CONN
*/
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_SYNC_ISO) || \
defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
case NODE_RX_TYPE_ISO_PDU:
{
/* Remove from receive-queue; ULL has received this now */
memq_dequeue(memq_ull_rx.tail, &memq_ull_rx.head, NULL);
#if defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
struct node_rx_pdu *rx_pdu = (struct node_rx_pdu *)rx;
struct ll_conn_iso_stream *cis =
ll_conn_iso_stream_get(rx_pdu->hdr.handle);
struct ll_iso_datapath *dp = cis->datapath_out;
isoal_sink_handle_t sink = dp->sink_hdl;
if (dp->path_id != BT_HCI_DATAPATH_ID_HCI) {
/* If vendor specific datapath pass to ISO AL here,
* in case of HCI destination it will be passed in
* HCI context.
*/
struct isoal_pdu_rx pckt_meta = {
.meta = &rx_pdu->hdr.rx_iso_meta,
.pdu = (union isoal_pdu *) &rx_pdu->pdu[0]
};
/* Pass the ISO PDU through ISO-AL */
isoal_status_t err =
isoal_rx_pdu_recombine(sink, &pckt_meta);
LL_ASSERT(err == ISOAL_STATUS_OK); /* TODO handle err */
}
#endif
/* Let ISO PDU start its long journey upwards */
ll_rx_put(link, rx);
ll_rx_sched();
}
break;
#endif
default:
{
#if defined(CONFIG_BT_CTLR_USER_EXT)

View file

@ -14,6 +14,8 @@ struct ll_conn_iso_stream {
struct lll_conn_iso_stream lll;
uint32_t sync_delay;
uint8_t cis_id;
struct ll_iso_datapath *datapath_in;
struct ll_iso_datapath *datapath_out;
uint32_t offset; /* Offset of CIS from ACL event in us */
uint8_t established; /* 0 if CIS has not yet been established.
* 1 if CIS has been established and host

View file

@ -24,6 +24,17 @@
#include "common/log.h"
#include "hal/debug.h"
#include "lll_conn_iso.h"
#include "ull_conn_iso_internal.h"
#include "ull_conn_iso_types.h"
#include "isoal.h"
#include "ull_iso_types.h"
#if defined(CONFIG_BT_CTLR_CONN_ISO_STREAMS)
static struct ll_iso_datapath datapath_pool[CONFIG_BT_CTLR_CONN_ISO_STREAMS];
#endif
static void *datapath_free;
static int init_reset(void);
static MFIFO_DEFINE(iso_tx, sizeof(struct lll_tx),
@ -35,11 +46,21 @@ static struct {
CONFIG_BT_CTLR_ISO_TX_BUFFERS];
} mem_iso_tx;
/* must be implemented by vendor */
__weak bool ll_data_path_configured(uint8_t data_path_dir,
uint8_t data_path_id)
{
ARG_UNUSED(data_path_dir);
ARG_UNUSED(data_path_id);
return false;
}
/* Contains vendor specific argument, function to be implemented by vendors */
__weak uint8_t ll_configure_data_path(uint8_t data_path_dir,
uint8_t data_path_id,
uint8_t vs_config_len,
uint8_t *vs_config)
uint8_t data_path_id,
uint8_t vs_config_len,
uint8_t *vs_config)
{
ARG_UNUSED(data_path_dir);
ARG_UNUSED(data_path_id);
@ -60,30 +81,127 @@ uint8_t ll_read_iso_tx_sync(uint16_t handle, uint16_t *seq,
return BT_HCI_ERR_CMD_DISALLOWED;
}
static inline bool path_is_vendor_specific(uint8_t path_id)
{
return (path_id >= BT_HCI_DATAPATH_ID_VS &&
path_id <= BT_HCI_DATAPATH_ID_VS_END);
}
uint8_t ll_setup_iso_path(uint16_t handle, uint8_t path_dir, uint8_t path_id,
uint8_t coding_format, uint16_t company_id,
uint16_t vs_codec_id, uint32_t controller_delay,
uint8_t codec_config_len, uint8_t *codec_config)
{
ARG_UNUSED(handle);
ARG_UNUSED(path_dir);
ARG_UNUSED(path_id);
ARG_UNUSED(coding_format);
ARG_UNUSED(company_id);
ARG_UNUSED(vs_codec_id);
ARG_UNUSED(controller_delay);
ARG_UNUSED(codec_config_len);
ARG_UNUSED(codec_config);
return BT_HCI_ERR_CMD_DISALLOWED;
isoal_sink_handle_t sink_hdl;
isoal_status_t err = 0;
if (path_id == BT_HCI_DATAPATH_ID_DISABLED) {
return 0;
}
struct ll_conn_iso_stream *cis = ll_conn_iso_stream_get(handle);
if (path_dir > BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
/* TBD 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
* shall return the error code Unknown Connection Identifier (0x02)
*/
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 */
return BT_HCI_ERR_CMD_DISALLOWED;
}
if (path_is_vendor_specific(path_id) &&
!ll_data_path_configured(path_dir, path_id)) {
/* Data path must be configured prior to setup */
return BT_HCI_ERR_CMD_DISALLOWED;
}
/* If Codec_Configuration_Length non-zero and Codec_ID set to
* transparent air mode, the Controller shall return the error code
* Invalid HCI Command Parameters (0x12).
*/
if (codec_config_len && vs_codec_id == BT_HCI_CODING_FORMAT_TRANSPARENT) {
return BT_HCI_ERR_INVALID_PARAM;
}
/* Allocate and configure datapath */
struct ll_iso_datapath *dp = mem_acquire(&datapath_free);
dp->path_dir = path_dir;
dp->path_id = path_id;
dp->coding_format = coding_format;
dp->company_id = company_id;
/* TBD dp->sync_delay = controller_delay; ?*/
if (path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR) {
cis->datapath_in = dp;
} else {
cis->datapath_out = dp;
}
if (path_id == BT_HCI_DATAPATH_ID_HCI) {
/* Not vendor specific, thus alloc and emit functions known */
err = isoal_sink_create(&sink_hdl, cis, sink_sdu_alloc_hci,
sink_sdu_emit_hci, sink_sdu_write_hci);
} else {
/* TBD call vendor specific function to set up ISO path */
}
if (!err) {
dp->sink_hdl = sink_hdl;
isoal_sink_enable(sink_hdl);
} else {
return BT_HCI_ERR_CMD_DISALLOWED;
}
return 0;
}
uint8_t ll_remove_iso_path(uint16_t handle, uint8_t path_dir)
{
ARG_UNUSED(handle);
ARG_UNUSED(path_dir);
struct ll_conn_iso_stream *cis = ll_conn_iso_stream_get(handle);
/* TBD: 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
* Connection Identifier (0x02).
*/
struct ll_iso_datapath *dp;
return BT_HCI_ERR_CMD_DISALLOWED;
if (path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR) {
dp = cis->datapath_in;
if (dp) {
cis->datapath_in = NULL;
mem_release(dp, &datapath_free);
}
} else if (path_dir == BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) {
dp = cis->datapath_out;
if (dp) {
cis->datapath_out = NULL;
mem_release(dp, &datapath_free);
}
} else {
/* Reserved for future use */
return BT_HCI_ERR_CMD_DISALLOWED;
}
if (!dp) {
/* Datapath was not previously set up */
return BT_HCI_ERR_CMD_DISALLOWED;
}
return 0;
}
uint8_t ll_iso_receive_test(uint16_t handle, uint8_t payload_type)
@ -207,5 +325,11 @@ static int init_reset(void)
mem_init(mem_iso_tx.pool, CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE,
CONFIG_BT_CTLR_ISO_TX_BUFFERS, &mem_iso_tx.free);
#if defined(CONFIG_BT_CTLR_CONN_ISO_STREAMS)
/* Initialize ISO Datapath pool */
mem_init(datapath_pool, sizeof(struct ll_iso_datapath),
sizeof(datapath_pool) / sizeof(struct ll_iso_datapath), &datapath_free);
#endif
return 0;
}

View file

@ -0,0 +1,13 @@
/*
* Copyright (c) 2021 Demant
*
* SPDX-License-Identifier: Apache-2.0
*/
struct ll_iso_datapath {
uint8_t path_dir;
uint8_t path_id;
uint8_t coding_format;
uint16_t company_id;
isoal_sink_handle_t sink_hdl;
};

View file

@ -618,7 +618,7 @@ static int hci_le_setup_iso_data_path(struct bt_conn *conn,
}
rp = (void *)rsp->data;
if (rp->status || (rp->handle != conn->handle)) {
if (rp->status || (sys_le16_to_cpu(rp->handle) != conn->handle)) {
err = -EIO;
}
@ -649,7 +649,7 @@ static int hci_le_remove_iso_data_path(struct bt_conn *conn, uint8_t dir)
}
rp = (void *)rsp->data;
if (rp->status || (rp->handle != conn->handle)) {
if (rp->status || (sys_le16_to_cpu(rp->handle) != conn->handle)) {
err = -EIO;
}

View file

@ -15,7 +15,7 @@ if NET_BUF
config NET_BUF_USER_DATA_SIZE
int "Size of user_data available in every network buffer"
default 8 if (BT || NET_TCP2) && 64BIT
default 8 if ((BT || NET_TCP2) && 64BIT) || BT_ISO
default 4
range 4 65535 if BT || NET_TCP2
range 0 65535