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:
parent
958e826b69
commit
b9b1c7e19b
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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[],
|
||||
|
|
425
subsys/bluetooth/controller/ll_sw/isoal.c
Normal file
425
subsys/bluetooth/controller/ll_sw/isoal.c
Normal 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;
|
||||
}
|
242
subsys/bluetooth/controller/ll_sw/isoal.h
Normal file
242
subsys/bluetooth/controller/ll_sw/isoal.h
Normal 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);
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
13
subsys/bluetooth/controller/ll_sw/ull_iso_types.h
Normal file
13
subsys/bluetooth/controller/ll_sw/ull_iso_types.h
Normal 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;
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue