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:
parent
01e7dd853f
commit
d1f71e93a0
8 changed files with 282 additions and 30 deletions
|
@ -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"
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue