blob: c917fb8b3343562e093c94868bd491c7dde9228b [file] [log] [blame]
/*
* Copyright (c) 2020 Demant
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <soc.h>
#include "hal/cpu.h"
#include "hal/ccm.h"
#include "util/memq.h"
#include "util/mem.h"
#include "util/mfifo.h"
#include "pdu.h"
#include "lll.h"
#include "lll_conn.h" /* for `struct lll_tx` */
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER)
#define LOG_MODULE_NAME bt_ctlr_ull_iso
#include "common/log.h"
#include "hal/debug.h"
#include "lll_conn_iso.h"
#include "ull_conn_iso_types.h"
#include "isoal.h"
#include "ull_iso_types.h"
#include "ull_conn_internal.h"
#include "ull_conn_iso_internal.h"
#if defined(CONFIG_BT_CTLR_CONN_ISO_STREAMS)
/* Allocate data path pools for RX/TX directions for each stream */
static struct ll_iso_datapath datapath_pool[2*CONFIG_BT_CTLR_CONN_ISO_STREAMS];
#endif
static void *datapath_free;
static int init_reset(void);
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
static MFIFO_DEFINE(iso_tx, sizeof(struct lll_tx),
CONFIG_BT_CTLR_ISO_TX_BUFFERS);
static struct {
void *free;
uint8_t pool[CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE *
CONFIG_BT_CTLR_ISO_TX_BUFFERS];
} mem_iso_tx;
#endif /* CONFIG_BT_CTLR_ADV_ISO || CONFIG_BT_CTLR_CONN_ISO */
/* 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)
{
ARG_UNUSED(data_path_dir);
ARG_UNUSED(data_path_id);
ARG_UNUSED(vs_config_len);
ARG_UNUSED(vs_config);
return BT_HCI_ERR_CMD_DISALLOWED;
}
uint8_t ll_read_iso_tx_sync(uint16_t handle, uint16_t *seq,
uint32_t *timestamp, uint32_t *offset)
{
ARG_UNUSED(handle);
ARG_UNUSED(seq);
ARG_UNUSED(timestamp);
ARG_UNUSED(offset);
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(controller_delay);
ARG_UNUSED(codec_config_len);
ARG_UNUSED(codec_config);
if (path_id == BT_HCI_DATAPATH_ID_DISABLED) {
return 0;
}
if (path_dir > BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
/* Todo: 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 defined(CONFIG_BT_CTLR_CONN_ISO)
isoal_sink_handle_t sink_hdl;
isoal_status_t err = 0;
struct ll_conn_iso_stream *cis = ll_conn_iso_stream_get(handle);
struct ll_conn_iso_group *cig;
cig = cis->group;
if ((path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR &&
cis->datapath_in) ||
(path_dir == BT_HCI_DATAPATH_DIR_CTLR_TO_HOST &&
cis->datapath_out)) {
/* Data path has been set up, can only do setup once */
return BT_HCI_ERR_CMD_DISALLOWED;
}
#endif
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;
/* TODO dp->sync_delay = controller_delay; ?*/
#if defined(CONFIG_BT_CTLR_CONN_ISO)
uint32_t sdu_interval;
uint8_t burst_number;
if (path_dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR) {
burst_number = cis->lll.tx.burst_number;
if (cig->lll.role) {
/* peripheral */
sdu_interval = cig->p_sdu_interval;
} else {
/* central */
sdu_interval = cig->c_sdu_interval;
}
cis->datapath_in = dp;
} else {
burst_number = cis->lll.rx.burst_number;
if (cig->lll.role) {
/* peripheral */
sdu_interval = cig->c_sdu_interval;
} else {
/* central */
sdu_interval = cig->p_sdu_interval;
}
cis->datapath_out = dp;
}
if (path_id == BT_HCI_DATAPATH_ID_HCI) {
/* Not vendor specific, thus alloc and emit functions known */
err = isoal_sink_create(&sink_hdl, handle, burst_number, sdu_interval,
cig->iso_interval, sink_sdu_alloc_hci,
sink_sdu_emit_hci, sink_sdu_write_hci);
} else {
/* 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;
}
#endif
return 0;
}
uint8_t ll_remove_iso_path(uint16_t handle, uint8_t 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;
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)
{
ARG_UNUSED(handle);
ARG_UNUSED(payload_type);
return BT_HCI_ERR_CMD_DISALLOWED;
}
uint8_t ll_iso_transmit_test(uint16_t handle, uint8_t payload_type)
{
ARG_UNUSED(handle);
ARG_UNUSED(payload_type);
return BT_HCI_ERR_CMD_DISALLOWED;
}
uint8_t ll_iso_test_end(uint16_t handle, uint32_t *received_cnt,
uint32_t *missed_cnt, uint32_t *failed_cnt)
{
ARG_UNUSED(handle);
ARG_UNUSED(received_cnt);
ARG_UNUSED(missed_cnt);
ARG_UNUSED(failed_cnt);
return BT_HCI_ERR_CMD_DISALLOWED;
}
uint8_t ll_iso_read_test_counters(uint16_t handle, uint32_t *received_cnt,
uint32_t *missed_cnt,
uint32_t *failed_cnt)
{
ARG_UNUSED(handle);
ARG_UNUSED(received_cnt);
ARG_UNUSED(missed_cnt);
ARG_UNUSED(failed_cnt);
return BT_HCI_ERR_CMD_DISALLOWED;
}
uint8_t ll_read_iso_link_quality(uint16_t handle,
uint32_t *tx_unacked_packets,
uint32_t *tx_flushed_packets,
uint32_t *tx_last_subevent_packets,
uint32_t *retransmitted_packets,
uint32_t *crc_error_packets,
uint32_t *rx_unreceived_packets,
uint32_t *duplicate_packets)
{
ARG_UNUSED(handle);
ARG_UNUSED(tx_unacked_packets);
ARG_UNUSED(tx_flushed_packets);
ARG_UNUSED(tx_last_subevent_packets);
ARG_UNUSED(retransmitted_packets);
ARG_UNUSED(crc_error_packets);
ARG_UNUSED(rx_unreceived_packets);
ARG_UNUSED(duplicate_packets);
return BT_HCI_ERR_CMD_DISALLOWED;
}
int ull_iso_init(void)
{
int err;
err = init_reset();
if (err) {
return err;
}
return 0;
}
int ull_iso_reset(void)
{
int err;
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
/* Re-initialize the Tx mfifo */
MFIFO_INIT(iso_tx);
#endif /* CONFIG_BT_CTLR_ADV_ISO || CONFIG_BT_CTLR_CONN_ISO */
err = init_reset();
if (err) {
return err;
}
return 0;
}
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
void *ll_iso_tx_mem_acquire(void)
{
return mem_acquire(&mem_iso_tx.free);
}
void ll_iso_tx_mem_release(void *tx)
{
mem_release(tx, &mem_iso_tx.free);
}
int ll_iso_tx_mem_enqueue(uint16_t handle, void *tx)
{
struct lll_tx *lll_tx;
uint8_t idx;
idx = MFIFO_ENQUEUE_GET(iso_tx, (void **) &lll_tx);
if (!lll_tx) {
return -ENOBUFS;
}
lll_tx->handle = handle;
lll_tx->node = tx;
MFIFO_ENQUEUE(iso_tx, idx);
return 0;
}
#endif /* CONFIG_BT_CTLR_ADV_ISO || CONFIG_BT_CTLR_CONN_ISO */
static int init_reset(void)
{
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
/* Initialize tx pool. */
mem_init(mem_iso_tx.pool, CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE,
CONFIG_BT_CTLR_ISO_TX_BUFFERS, &mem_iso_tx.free);
#endif /* CONFIG_BT_CTLR_ADV_ISO || CONFIG_BT_CTLR_CONN_ISO */
#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;
}