| /* |
| * 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; |
| } |