Bluetooth: controller: CIS/CIG teardown at ACL disconnect

When an ACL connection with active CISes terminates, inject CIS/CIG
teardown to ensure CIS is stopped before ACL disconnection completes.
This includes stopping CIG ticker when last CIS has stopped.

Signed-off-by: Morten Priess <mtpr@oticon.com>
This commit is contained in:
Morten Priess 2021-05-05 09:14:07 +02:00 committed by Carles Cufí
commit d1f71e93a0
8 changed files with 282 additions and 30 deletions

View file

@ -43,13 +43,15 @@
#include "ll_sw/pdu.h"
#include "ll_sw/lll.h"
#include "ll_sw/lll_conn.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 "ull_conn_internal.h"
#include "ull_conn_iso_internal.h"
#include "hci_internal.h"

View file

@ -59,10 +59,11 @@
#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_conn_iso_internal.h"
#include "ull_peripheral_iso_internal.h"
#if defined(CONFIG_BT_CTLR_USER_EXT)

View file

@ -34,12 +34,14 @@
#include "ull_conn_types.h"
#include "ull_conn_iso_types.h"
#include "ull_internal.h"
#include "ull_iso_internal.h"
#include "ull_sched_internal.h"
#include "ull_chan_internal.h"
#include "ull_conn_internal.h"
#include "ull_slave_internal.h"
#include "ull_master_internal.h"
#include "ull_iso_internal.h"
#include "ull_conn_iso_internal.h"
#include "ull_peripheral_iso_internal.h"
#if defined(CONFIG_BT_CTLR_USER_EXT)
@ -80,6 +82,7 @@ static void ticker_start_conn_op_cb(uint32_t status, void *param);
static void conn_setup_adv_scan_disabled_cb(void *param);
static inline void disable(uint16_t handle);
static void conn_cleanup(struct ll_conn *conn, uint8_t reason);
static void conn_cleanup_finalize(struct ll_conn *conn);
static void tx_ull_flush(struct ll_conn *conn);
static void ticker_op_stop_cb(uint32_t status, void *param);
static void disabled_cb(void *param);
@ -1923,28 +1926,28 @@ static inline void disable(uint16_t handle)
conn->lll.link_tx_free = NULL;
}
static void conn_cleanup(struct ll_conn *conn, uint8_t reason)
#if defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
static void conn_cleanup_iso_cis_released_cb(struct ll_conn *conn)
{
struct ll_conn_iso_stream *cis;
cis = ll_conn_iso_stream_get_by_acl(conn, NULL);
if (cis) {
/* More associated CISes - stop next */
ull_conn_iso_cis_stop(cis, conn_cleanup_iso_cis_released_cb);
} else {
/* No more CISes associated with conn - finalize */
conn_cleanup_finalize(conn);
}
}
#endif /* CONFIG_BT_CTLR_PERIPHERAL_ISO || CONFIG_BT_CTLR_CENTRAL_ISO */
static void conn_cleanup_finalize(struct ll_conn *conn)
{
struct lll_conn *lll = &conn->lll;
struct node_rx_pdu *rx;
uint32_t ticker_status;
/* reset mutex */
if (conn == conn_upd_curr) {
ull_conn_upd_curr_reset();
}
/* Only termination structure is populated here in ULL context
* but the actual enqueue happens in the LLL context in
* tx_lll_flush. The reason being to avoid passing the reason
* value and handle through the mayfly scheduling of the
* tx_lll_flush.
*/
rx = (void *)&conn->llcp_terminate.node_rx;
rx->hdr.handle = conn->lll.handle;
rx->hdr.type = NODE_RX_TYPE_TERMINATE;
*((uint8_t *)rx->pdu) = reason;
/* release any llcp reserved rx node */
rx = conn->llcp_rx;
while (rx) {
@ -1979,6 +1982,42 @@ static void conn_cleanup(struct ll_conn *conn, uint8_t reason)
ull_conn_tx_demux(UINT8_MAX);
}
static void conn_cleanup(struct ll_conn *conn, uint8_t reason)
{
struct node_rx_pdu *rx;
#if defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
struct ll_conn_iso_stream *cis;
#endif /* CONFIG_BT_CTLR_PERIPHERAL_ISO || CONFIG_BT_CTLR_CENTRAL_ISO */
/* Reset mutex */
if (conn == conn_upd_curr) {
ull_conn_upd_curr_reset();
}
/* Only termination structure is populated here in ULL context
* but the actual enqueue happens in the LLL context in
* tx_lll_flush. The reason being to avoid passing the reason
* value and handle through the mayfly scheduling of the
* tx_lll_flush.
*/
rx = (void *)&conn->llcp_terminate.node_rx;
rx->hdr.handle = conn->lll.handle;
rx->hdr.type = NODE_RX_TYPE_TERMINATE;
*((uint8_t *)rx->pdu) = reason;
#if defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) || defined(CONFIG_BT_CTLR_CENTRAL_ISO)
cis = ll_conn_iso_stream_get_by_acl(conn, NULL);
if (cis) {
/* Stop CIS and defer cleanup to after teardown. */
ull_conn_iso_cis_stop(cis, conn_cleanup_iso_cis_released_cb);
return;
}
#endif /* CONFIG_BT_CTLR_PERIPHERAL_ISO || CONFIG_BT_CTLR_CENTRAL_ISO */
conn_cleanup_finalize(conn);
}
static void tx_ull_flush(struct ll_conn *conn)
{
while (conn->tx_head) {

View file

@ -19,9 +19,9 @@
#include "lll_conn.h"
#include "ull_conn_types.h"
#include "lll_conn_iso.h"
#include "ull_conn_iso_types.h"
#include "ull_conn_internal.h"
#include "ull_conn_iso_internal.h"
#include "ull_internal.h"
#include "lll/lll_vendor.h"
@ -114,6 +114,51 @@ struct ll_conn_iso_stream *ll_iso_stream_connected_get(uint16_t handle)
return cis;
}
struct ll_conn_iso_stream *ll_conn_iso_stream_get_by_acl(struct ll_conn *conn, uint16_t *cis_iter)
{
uint8_t cis_iter_start = (cis_iter == NULL) || (*cis_iter) == UINT16_MAX;
uint8_t cig_handle;
/* Find CIS associated with ACL conn */
for (cig_handle = 0; cig_handle < CONFIG_BT_CTLR_CONN_ISO_GROUPS; cig_handle++) {
struct ll_conn_iso_stream *cis;
struct ll_conn_iso_group *cig;
uint16_t handle_iter;
int8_t cis_idx;
cig = ll_conn_iso_group_get(cig_handle);
if (!cig) {
continue;
}
handle_iter = UINT16_MAX;
for (cis_idx = 0; cis_idx < cig->lll.num_cis; cis_idx++) {
cis = ll_conn_iso_stream_get_by_group(cig, &handle_iter);
LL_ASSERT(cis);
uint16_t cis_handle = cis->lll.handle;
cis = ll_iso_stream_connected_get(cis_handle);
if (!cis) {
continue;
}
if (!cis_iter_start) {
/* Look for iterator start handle */
cis_iter_start = cis_handle == (*cis_iter);
} else if (cis->lll.acl_handle == conn->lll.handle) {
if (cis_iter) {
(*cis_iter) = cis_handle;
}
return cis;
}
}
}
return NULL;
}
struct ll_conn_iso_stream *ll_conn_iso_stream_get_by_group(struct ll_conn_iso_group *cig,
uint16_t *handle_iter)
{
@ -127,7 +172,7 @@ struct ll_conn_iso_stream *ll_conn_iso_stream_get_by_group(struct ll_conn_iso_gr
for (handle = handle_start; handle <= LAST_VALID_CIS_HANDLE; handle++) {
cis = ll_conn_iso_stream_get(handle);
if (cis->group == cig) {
if (*handle_iter) {
if (handle_iter) {
(*handle_iter) = handle;
}
return cis;
@ -349,3 +394,155 @@ void ull_conn_iso_resume_ticker_start(struct lll_event *resume_event,
LL_ASSERT((ret == TICKER_STATUS_SUCCESS) ||
(ret == TICKER_STATUS_BUSY));
}
static void disabled_cig_cb(void *param)
{
struct ll_conn_iso_group *cig;
cig = param;
ll_conn_iso_group_release(cig);
/* TODO: Flush pending TX in LLL */
}
static void ticker_stop_op_cb(uint32_t status, void *param)
{
static memq_link_t link;
static struct mayfly mfy = {0, 0, &link, NULL, NULL};
struct ll_conn_iso_group *cig;
struct ull_hdr *hdr;
uint32_t ret;
/* Assert if race between thread and ULL */
LL_ASSERT(status == TICKER_STATUS_SUCCESS);
cig = param;
hdr = &cig->ull;
mfy.param = cig;
if (ull_ref_get(hdr)) {
/* Event active (prepare/done ongoing) - wait for done and
* disable there. Abort the ongoing event in LLL.
*/
LL_ASSERT(!hdr->disabled_cb);
hdr->disabled_param = mfy.param;
hdr->disabled_cb = disabled_cig_cb;
mfy.fp = lll_disable;
ret = mayfly_enqueue(TICKER_USER_ID_ULL_LOW,
TICKER_USER_ID_LLL, 0, &mfy);
LL_ASSERT(!ret);
} else {
/* Disable now */
mfy.fp = disabled_cig_cb;
ret = mayfly_enqueue(TICKER_USER_ID_ULL_LOW,
TICKER_USER_ID_ULL_HIGH, 0, &mfy);
LL_ASSERT(!ret);
}
}
static void disabled_cis_cb(void *param)
{
struct ll_conn_iso_group *cig;
struct ll_conn_iso_stream *cis;
uint32_t ticker_status;
uint16_t handle_iter;
uint8_t cis_idx;
uint8_t num_cis;
cig = param;
num_cis = cig->lll.num_cis;
handle_iter = UINT16_MAX;
/* Remove all CISes marked for teardown */
for (cis_idx = 0; cis_idx < cig->lll.num_cis; cis_idx++) {
cis = ll_conn_iso_stream_get_by_group(cig, &handle_iter);
LL_ASSERT(cis);
if (cis->teardown) {
struct ll_conn *conn;
ll_iso_stream_released_cb_t cis_released_cb;
conn = ll_conn_get(cis->lll.acl_handle);
cis_released_cb = cis->released_cb;
ll_conn_iso_stream_release(cis);
cig->lll.num_cis--;
/* Check if removed CIS had an ACL disassociation callback. Invoke
* the callback to allow cleanup.
*/
if (cis_released_cb) {
/* CIS removed - notify caller */
cis_released_cb(conn);
}
}
}
if (num_cis && cig->lll.num_cis == 0) {
/* This was the last CIS of the CIG. Initiate CIG teardown by
* stopping ticker.
*/
ticker_status = ticker_stop(TICKER_INSTANCE_ID_CTLR,
TICKER_USER_ID_ULL_HIGH,
TICKER_ID_CONN_ISO_BASE +
ll_conn_iso_group_handle_get(cig),
ticker_stop_op_cb,
cig);
LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) ||
(ticker_status == TICKER_STATUS_BUSY));
}
}
/**
* @brief Stop and tear down a connected ISO stream
* This function may be called to tear down a CIS. When the CIS teardown
* has completed and the stream is released and callback is provided, the
* cis_released_cb callback is invoked.
*
* @param cis Pointer to connected ISO stream to stop
* @param cis_relased_cb Callback to invoke when the CIS has been released.
* NULL to ignore.
*/
void ull_conn_iso_cis_stop(struct ll_conn_iso_stream *cis,
ll_iso_stream_released_cb_t cis_relased_cb)
{
struct ll_conn_iso_group *cig;
struct ull_hdr *hdr;
cig = cis->group;
hdr = &cig->ull;
if (cis->teardown) {
/* Teardown already started */
return;
}
cis->teardown = 1;
cis->released_cb = cis_relased_cb;
if (ull_ref_get(hdr)) {
/* Event is active (prepare/done ongoing) - wait for done and
* continue CIS teardown from there. The disabled_cb cannot be
* reserved for other use.
*/
LL_ASSERT(!hdr->disabled_cb || hdr->disabled_cb == disabled_cis_cb);
hdr->disabled_param = cig;
hdr->disabled_cb = disabled_cis_cb;
} else {
static memq_link_t link;
static struct mayfly mfy = {0, 0, &link, NULL, NULL};
/* Tear down CIS now in ULL_HIGH context. Ignore enqueue
* error (already enqueued) as all CISes marked for teardown
* will be handled in disabled_cis_cb. Use mayfly chaining to
* prevent recursive stop calls.
*/
mfy.fp = disabled_cis_cb;
mfy.param = cig;
mayfly_enqueue(TICKER_USER_ID_ULL_LOW,
TICKER_USER_ID_ULL_HIGH, 1, &mfy);
}
}

View file

@ -19,11 +19,15 @@ void ll_conn_iso_stream_release(struct ll_conn_iso_stream *cis);
uint16_t ll_conn_iso_stream_handle_get(struct ll_conn_iso_stream *cis);
struct ll_conn_iso_stream *ll_conn_iso_stream_get(uint16_t handle);
struct ll_conn_iso_stream *ll_iso_stream_connected_get(uint16_t handle);
struct ll_conn_iso_stream *ll_conn_iso_stream_get_by_acl(struct ll_conn *conn,
uint16_t *cis_iter);
struct ll_conn_iso_stream *ll_conn_iso_stream_get_by_group(struct ll_conn_iso_group *cig,
uint16_t *handle_iter);
void ull_conn_iso_done(struct node_rx_event_done *done);
void ull_conn_iso_cis_established(struct ll_conn_iso_stream *cis);
void ull_conn_iso_cis_stop(struct ll_conn_iso_stream *cis,
ll_iso_stream_released_cb_t cis_released_cb);
void ull_conn_iso_resume_ticker_start(struct lll_event *resume_event,
uint16_t cis_handle,

View file

@ -4,6 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
struct ll_conn;
typedef void (*ll_iso_stream_released_cb_t)(struct ll_conn *conn);
#define LL_CIS_HANDLE_BASE CONFIG_BT_MAX_CONN
#define LL_CIS_IDX_FROM_HANDLE(_handle) \
@ -16,11 +20,13 @@ struct ll_conn_iso_stream {
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
* notified.
*/
uint32_t offset; /* Offset of CIS from ACL event in us */
ll_iso_stream_released_cb_t released_cb; /* CIS release callback */
uint8_t established : 1; /* 0 if CIS has not yet been established.
* 1 if CIS has been established and host
* notified.
*/
uint8_t teardown : 1; /* 1 if CIS teardown has been initiated */
};
struct ll_conn_iso_group {

View file

@ -25,10 +25,11 @@
#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"
#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 */

View file

@ -24,11 +24,11 @@
#include "lll_conn_iso.h"
#include "ull_conn_types.h"
#include "ull_conn_internal.h"
#include "ull_conn_iso_types.h"
#include "ull_conn_iso_internal.h"
#include "ull_internal.h"
#include "ull_conn_internal.h"
#include "ull_conn_iso_internal.h"
#include "lll_peripheral_iso.h"
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER)
@ -129,6 +129,8 @@ uint8_t ull_peripheral_iso_acquire(struct ll_conn *acl,
cis->cis_id = req->cis_id;
cis->established = 0;
cis->group = cig;
cis->teardown = 0;
cis->released_cb = NULL;
cis->lll.handle = 0xFFFF;
cis->lll.acl_handle = acl->lll.handle;