zephyr/subsys/bluetooth/controller/ll_sw/ull_conn_iso.c
Morten Priess 96a13712ad Bluetooth: controller: Initial CIS peripheral establishment
LL_CIS_IND starts a ticker for the created CIG, using the event_counter
and offset provided. Ticker generates callbacks to
lll_peripheral_iso_prepare. Event done with ISO (extra) type is demuxed
and done handled for CIG including ticker update with drift
compensation.

TODO: Handle multiple CISes as well as pause/resume and scheduling
latency.

Signed-off-by: Morten Priess <mtpr@oticon.com>
2021-03-27 08:57:03 -04:00

225 lines
5.1 KiB
C

/*
* Copyright (c) 2021 Demant
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <sys/byteorder.h>
#include "util/mem.h"
#include "util/memq.h"
#include "util/mayfly.h"
#include "ticker/ticker.h"
#include "lll.h"
#include "lll_conn_iso.h"
#include "ull_conn_iso_types.h"
#include "ull_internal.h"
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER)
#define LOG_MODULE_NAME bt_ctlr_ull_conn_iso
#include "common/log.h"
#include "hal/debug.h"
static struct ll_conn_iso_stream cis_pool[CONFIG_BT_CTLR_CONN_ISO_STREAMS];
static void *cis_free;
static struct ll_conn_iso_group cig_pool[CONFIG_BT_CTLR_CONN_ISO_GROUPS];
static void *cig_free;
static int init_reset(void);
struct ll_conn_iso_group *ll_conn_iso_group_acquire(void)
{
return mem_acquire(&cig_free);
}
void ll_conn_iso_group_release(struct ll_conn_iso_group *cig)
{
mem_release(cig, &cig_free);
}
uint16_t ll_conn_iso_group_handle_get(struct ll_conn_iso_group *cig)
{
return mem_index_get(cig, cig_pool, sizeof(struct ll_conn_iso_group));
}
struct ll_conn_iso_group *ll_conn_iso_group_get(uint16_t handle)
{
return mem_get(cig_pool, sizeof(struct ll_conn_iso_group), handle);
}
struct ll_conn_iso_group *ll_conn_iso_group_get_by_id(uint8_t id)
{
struct ll_conn_iso_group *cig;
for (int h = 0; h < CONFIG_BT_CTLR_CONN_ISO_GROUPS; h++) {
cig = ll_conn_iso_group_get(h);
if (id == cig->cig_id) {
return cig;
}
}
return NULL;
}
struct ll_conn_iso_stream *ll_conn_iso_stream_acquire(void)
{
return mem_acquire(&cis_free);
}
void ll_conn_iso_stream_release(struct ll_conn_iso_stream *cis)
{
mem_release(cis, &cig_free);
}
uint16_t ll_conn_iso_stream_handle_get(struct ll_conn_iso_stream *cis)
{
return mem_index_get(cis, cis_pool,
sizeof(struct ll_conn_iso_stream)) +
LL_CIS_HANDLE_BASE;
}
struct ll_conn_iso_stream *ll_conn_iso_stream_get(uint16_t handle)
{
return mem_get(cis_pool, sizeof(struct ll_conn_iso_stream), handle -
LL_CIS_HANDLE_BASE);
}
struct ll_conn_iso_stream *ll_iso_stream_connected_get(uint16_t handle)
{
struct ll_conn_iso_stream *cis;
if (handle >= CONFIG_BT_CTLR_CONN_ISO_STREAMS +
LL_CIS_HANDLE_BASE) {
return NULL;
}
cis = ll_conn_iso_stream_get(handle);
if (cis->lll.handle != handle) {
return NULL;
}
return cis;
}
int ull_conn_iso_init(void)
{
return init_reset();
}
int ull_conn_iso_reset(void)
{
return init_reset();
}
static int init_reset(void)
{
int err;
/* Initialize CIS pool */
mem_init(cis_pool, sizeof(struct ll_conn_iso_stream),
sizeof(cis_pool) / sizeof(struct ll_conn_iso_stream),
&cis_free);
/* Initialize CIG pool */
mem_init(cig_pool, sizeof(struct ll_conn_iso_group),
sizeof(cig_pool) / sizeof(struct ll_conn_iso_group),
&cig_free);
for (int h = 0; h < CONFIG_BT_CTLR_CONN_ISO_GROUPS; h++) {
ll_conn_iso_group_get(h)->cig_id = 0xFF;
}
/* Initialize LLL */
err = lll_conn_iso_init();
if (err) {
return err;
}
return 0;
}
static void ticker_update_cig_op_cb(uint32_t status, void *param)
{
/* CIG drift compensation succeeds, or it fails in a race condition
* when disconnecting (race between ticker_update and ticker_stop
* calls). TODO: Are the race-checks needed?
*/
LL_ASSERT(status == TICKER_STATUS_SUCCESS ||
param == ull_update_mark_get() ||
param == ull_disable_mark_get());
}
void ull_conn_iso_cis_established(struct ll_conn_iso_stream *cis)
{
struct node_rx_conn_iso_estab *est;
struct node_rx_pdu *node_rx;
node_rx = ull_pdu_rx_alloc();
if (!node_rx) {
/* No node available - try again later */
return;
}
/* TODO: Send CIS_ESTABLISHED with status != 0 in error scenarios */
node_rx->hdr.type = NODE_RX_TYPE_CIS_ESTABLISHED;
node_rx->hdr.handle = 0xFFFF;
node_rx->hdr.rx_ftr.param = cis;
est = (void *)node_rx->pdu;
est->status = 0;
est->cis_handle = sys_le16_to_cpu(cis->lll.handle);
ll_rx_put(node_rx->hdr.link, node_rx);
ll_rx_sched();
cis->established = 1;
}
void ull_conn_iso_done(struct node_rx_event_done *done)
{
struct lll_conn_iso_group *lll = (void *)HDR_ULL2LLL(done->param);
struct ll_conn_iso_group *cig = (void *)HDR_LLL2EVT(lll);
uint32_t ticks_drift_minus;
uint32_t ticks_drift_plus;
/* Skip if CIG terminated by local host */
if (unlikely(lll->handle == 0xFFFF)) {
return;
}
ticks_drift_plus = 0;
ticks_drift_minus = 0;
if (done->extra.trx_cnt) {
if (IS_ENABLED(CONFIG_BT_CTLR_PERIPHERAL_ISO) && lll->role) {
ull_drift_ticks_get(done, &ticks_drift_plus,
&ticks_drift_minus);
}
}
/* Update CIG ticker to compensate for drift */
if (ticks_drift_plus || ticks_drift_minus) {
uint8_t ticker_id = TICKER_ID_CONN_ISO_BASE +
ll_conn_iso_group_handle_get(cig);
struct ll_conn *conn = lll->hdr.parent;
uint32_t ticker_status;
ticker_status = ticker_update(TICKER_INSTANCE_ID_CTLR,
TICKER_USER_ID_ULL_HIGH,
ticker_id,
ticks_drift_plus,
ticks_drift_minus, 0, 0,
TICKER_NULL_LAZY, 0,
ticker_update_cig_op_cb,
cig);
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
(ticker_status == TICKER_STATUS_BUSY) ||
((void *)conn == ull_disable_mark_get()));
}
}