Add the missing terminate timer for the ACL Termination procedure. Signed-off-by: Thomas Ebert Hansen <thoh@oticon.com>
530 lines
11 KiB
C
530 lines
11 KiB
C
/*
|
|
* Copyright (c) 2020 Demant
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/types.h>
|
|
|
|
#include <zephyr/bluetooth/hci.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/sys/slist.h>
|
|
#include <zephyr/sys/util.h>
|
|
|
|
#include "hal/ccm.h"
|
|
|
|
#include "util/util.h"
|
|
#include "util/mem.h"
|
|
#include "util/memq.h"
|
|
#include "util/dbuf.h"
|
|
|
|
#include "pdu.h"
|
|
#include "ll.h"
|
|
#include "ll_settings.h"
|
|
|
|
#include "lll.h"
|
|
#include "lll/lll_df_types.h"
|
|
#include "lll_conn.h"
|
|
|
|
#include "ull_tx_queue.h"
|
|
|
|
#include "ull_conn_types.h"
|
|
#include "ull_llcp.h"
|
|
#include "ull_llcp_internal.h"
|
|
#include "ull_conn_internal.h"
|
|
|
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER)
|
|
#define LOG_MODULE_NAME bt_ctlr_ull_llcp_local
|
|
#include "common/log.h"
|
|
#include <soc.h>
|
|
#include "hal/debug.h"
|
|
|
|
static void lr_check_done(struct ll_conn *conn, struct proc_ctx *ctx);
|
|
static struct proc_ctx *lr_dequeue(struct ll_conn *conn);
|
|
|
|
/* LLCP Local Request FSM State */
|
|
enum lr_state {
|
|
LR_STATE_IDLE,
|
|
LR_STATE_ACTIVE,
|
|
LR_STATE_DISCONNECT,
|
|
LR_STATE_TERMINATE,
|
|
};
|
|
|
|
/* LLCP Local Request FSM Event */
|
|
enum {
|
|
/* Procedure run */
|
|
LR_EVT_RUN,
|
|
|
|
/* Procedure completed */
|
|
LR_EVT_COMPLETE,
|
|
|
|
/* Link connected */
|
|
LR_EVT_CONNECT,
|
|
|
|
/* Link disconnected */
|
|
LR_EVT_DISCONNECT,
|
|
};
|
|
|
|
static void lr_check_done(struct ll_conn *conn, struct proc_ctx *ctx)
|
|
{
|
|
if (ctx->done) {
|
|
struct proc_ctx *ctx_header;
|
|
|
|
ctx_header = llcp_lr_peek(conn);
|
|
LL_ASSERT(ctx_header == ctx);
|
|
|
|
lr_dequeue(conn);
|
|
|
|
llcp_proc_ctx_release(ctx);
|
|
}
|
|
}
|
|
/*
|
|
* LLCP Local Request FSM
|
|
*/
|
|
|
|
static void lr_set_state(struct ll_conn *conn, enum lr_state state)
|
|
{
|
|
conn->llcp.local.state = state;
|
|
}
|
|
|
|
void llcp_lr_enqueue(struct ll_conn *conn, struct proc_ctx *ctx)
|
|
{
|
|
sys_slist_append(&conn->llcp.local.pend_proc_list, &ctx->node);
|
|
}
|
|
|
|
static struct proc_ctx *lr_dequeue(struct ll_conn *conn)
|
|
{
|
|
struct proc_ctx *ctx;
|
|
|
|
ctx = (struct proc_ctx *)sys_slist_get(&conn->llcp.local.pend_proc_list);
|
|
return ctx;
|
|
}
|
|
|
|
struct proc_ctx *llcp_lr_peek(struct ll_conn *conn)
|
|
{
|
|
struct proc_ctx *ctx;
|
|
|
|
ctx = (struct proc_ctx *)sys_slist_peek_head(&conn->llcp.local.pend_proc_list);
|
|
return ctx;
|
|
}
|
|
|
|
bool llcp_lr_ispaused(struct ll_conn *conn)
|
|
{
|
|
return conn->llcp.local.pause == 1U;
|
|
}
|
|
|
|
void llcp_lr_pause(struct ll_conn *conn)
|
|
{
|
|
conn->llcp.local.pause = 1U;
|
|
}
|
|
|
|
void llcp_lr_resume(struct ll_conn *conn)
|
|
{
|
|
conn->llcp.local.pause = 0U;
|
|
}
|
|
|
|
void llcp_lr_prt_restart(struct ll_conn *conn)
|
|
{
|
|
conn->llcp.local.prt_expire = conn->llcp.prt_reload;
|
|
}
|
|
|
|
void llcp_lr_prt_restart_with_value(struct ll_conn *conn, uint16_t value)
|
|
{
|
|
conn->llcp.local.prt_expire = value;
|
|
}
|
|
|
|
void llcp_lr_prt_stop(struct ll_conn *conn)
|
|
{
|
|
conn->llcp.local.prt_expire = 0U;
|
|
}
|
|
|
|
void llcp_lr_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx)
|
|
{
|
|
switch (ctx->proc) {
|
|
#if defined(CONFIG_BT_CTLR_LE_PING)
|
|
case PROC_LE_PING:
|
|
llcp_lp_comm_rx(conn, ctx, rx);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_LE_PING */
|
|
case PROC_FEATURE_EXCHANGE:
|
|
llcp_lp_comm_rx(conn, ctx, rx);
|
|
break;
|
|
#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN)
|
|
case PROC_MIN_USED_CHANS:
|
|
llcp_lp_comm_rx(conn, ctx, rx);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */
|
|
case PROC_VERSION_EXCHANGE:
|
|
llcp_lp_comm_rx(conn, ctx, rx);
|
|
break;
|
|
#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_CENTRAL)
|
|
case PROC_ENCRYPTION_START:
|
|
case PROC_ENCRYPTION_PAUSE:
|
|
llcp_lp_enc_rx(conn, ctx, rx);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_CENTRAL */
|
|
#ifdef CONFIG_BT_CTLR_PHY
|
|
case PROC_PHY_UPDATE:
|
|
llcp_lp_pu_rx(conn, ctx, rx);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_PHY */
|
|
case PROC_CONN_UPDATE:
|
|
case PROC_CONN_PARAM_REQ:
|
|
llcp_lp_cu_rx(conn, ctx, rx);
|
|
break;
|
|
case PROC_TERMINATE:
|
|
llcp_lp_comm_rx(conn, ctx, rx);
|
|
break;
|
|
#if defined(CONFIG_BT_CENTRAL)
|
|
case PROC_CHAN_MAP_UPDATE:
|
|
llcp_lp_chmu_rx(conn, ctx, rx);
|
|
break;
|
|
#endif /* CONFIG_BT_CENTRAL */
|
|
#if defined(CONFIG_BT_CTLR_DATA_LENGTH)
|
|
case PROC_DATA_LENGTH_UPDATE:
|
|
llcp_lp_comm_rx(conn, ctx, rx);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */
|
|
#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ)
|
|
case PROC_CTE_REQ:
|
|
llcp_lp_comm_rx(conn, ctx, rx);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */
|
|
default:
|
|
/* Unknown procedure */
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
lr_check_done(conn, ctx);
|
|
}
|
|
|
|
void llcp_lr_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx)
|
|
{
|
|
switch (ctx->proc) {
|
|
#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN)
|
|
case PROC_MIN_USED_CHANS:
|
|
llcp_lp_comm_tx_ack(conn, ctx, tx);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */
|
|
case PROC_TERMINATE:
|
|
llcp_lp_comm_tx_ack(conn, ctx, tx);
|
|
break;
|
|
#if defined(CONFIG_BT_CTLR_DATA_LENGTH)
|
|
case PROC_DATA_LENGTH_UPDATE:
|
|
llcp_lp_comm_tx_ack(conn, ctx, tx);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */
|
|
#ifdef CONFIG_BT_CTLR_PHY
|
|
case PROC_PHY_UPDATE:
|
|
llcp_lp_pu_tx_ack(conn, ctx, tx);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_PHY */
|
|
default:
|
|
break;
|
|
/* Ignore tx_ack */
|
|
}
|
|
lr_check_done(conn, ctx);
|
|
}
|
|
|
|
static void lr_act_run(struct ll_conn *conn)
|
|
{
|
|
struct proc_ctx *ctx;
|
|
|
|
ctx = llcp_lr_peek(conn);
|
|
|
|
switch (ctx->proc) {
|
|
#if defined(CONFIG_BT_CTLR_LE_PING)
|
|
case PROC_LE_PING:
|
|
llcp_lp_comm_run(conn, ctx, NULL);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_LE_PING */
|
|
case PROC_FEATURE_EXCHANGE:
|
|
llcp_lp_comm_run(conn, ctx, NULL);
|
|
break;
|
|
#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN)
|
|
case PROC_MIN_USED_CHANS:
|
|
llcp_lp_comm_run(conn, ctx, NULL);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */
|
|
case PROC_VERSION_EXCHANGE:
|
|
llcp_lp_comm_run(conn, ctx, NULL);
|
|
break;
|
|
#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_CENTRAL)
|
|
case PROC_ENCRYPTION_START:
|
|
case PROC_ENCRYPTION_PAUSE:
|
|
llcp_lp_enc_run(conn, ctx, NULL);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_CENTRAL */
|
|
#ifdef CONFIG_BT_CTLR_PHY
|
|
case PROC_PHY_UPDATE:
|
|
llcp_lp_pu_run(conn, ctx, NULL);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_PHY */
|
|
case PROC_CONN_UPDATE:
|
|
case PROC_CONN_PARAM_REQ:
|
|
llcp_lp_cu_run(conn, ctx, NULL);
|
|
break;
|
|
case PROC_TERMINATE:
|
|
llcp_lp_comm_run(conn, ctx, NULL);
|
|
break;
|
|
#if defined(CONFIG_BT_CENTRAL)
|
|
case PROC_CHAN_MAP_UPDATE:
|
|
llcp_lp_chmu_run(conn, ctx, NULL);
|
|
break;
|
|
#endif /* CONFIG_BT_CENTRAL */
|
|
#if defined(CONFIG_BT_CTLR_DATA_LENGTH)
|
|
case PROC_DATA_LENGTH_UPDATE:
|
|
llcp_lp_comm_run(conn, ctx, NULL);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */
|
|
#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ)
|
|
case PROC_CTE_REQ:
|
|
/* 3rd partam null? */
|
|
llcp_lp_comm_run(conn, ctx, NULL);
|
|
break;
|
|
#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */
|
|
default:
|
|
/* Unknown procedure */
|
|
LL_ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
lr_check_done(conn, ctx);
|
|
}
|
|
|
|
static void lr_act_complete(struct ll_conn *conn)
|
|
{
|
|
struct proc_ctx *ctx;
|
|
|
|
ctx = llcp_lr_peek(conn);
|
|
LL_ASSERT(ctx != NULL);
|
|
|
|
/* Stop procedure response timeout timer */
|
|
llcp_lr_prt_stop(conn);
|
|
|
|
/* Mark the procedure as safe to delete */
|
|
ctx->done = 1U;
|
|
}
|
|
|
|
static void lr_act_connect(struct ll_conn *conn)
|
|
{
|
|
/* Empty on purpose */
|
|
}
|
|
|
|
static void lr_act_disconnect(struct ll_conn *conn)
|
|
{
|
|
struct proc_ctx *ctx;
|
|
|
|
ctx = lr_dequeue(conn);
|
|
|
|
/*
|
|
* we may have been disconnected in the
|
|
* middle of a control procedure, in
|
|
* which case we need to release context
|
|
*/
|
|
while (ctx != NULL) {
|
|
llcp_proc_ctx_release(ctx);
|
|
ctx = lr_dequeue(conn);
|
|
}
|
|
}
|
|
|
|
static void lr_st_disconnect(struct ll_conn *conn, uint8_t evt, void *param)
|
|
{
|
|
switch (evt) {
|
|
case LR_EVT_CONNECT:
|
|
lr_act_connect(conn);
|
|
lr_set_state(conn, LR_STATE_IDLE);
|
|
break;
|
|
default:
|
|
/* Ignore other evts */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void lr_st_idle(struct ll_conn *conn, uint8_t evt, void *param)
|
|
{
|
|
struct proc_ctx *ctx;
|
|
|
|
switch (evt) {
|
|
case LR_EVT_RUN:
|
|
ctx = llcp_lr_peek(conn);
|
|
if (ctx) {
|
|
lr_act_run(conn);
|
|
if (ctx->proc != PROC_TERMINATE) {
|
|
lr_set_state(conn, LR_STATE_ACTIVE);
|
|
} else {
|
|
lr_set_state(conn, LR_STATE_TERMINATE);
|
|
}
|
|
}
|
|
break;
|
|
case LR_EVT_DISCONNECT:
|
|
lr_act_disconnect(conn);
|
|
lr_set_state(conn, LR_STATE_DISCONNECT);
|
|
break;
|
|
case LR_EVT_COMPLETE:
|
|
/* Some procedures like CTE request may be completed without actual run due to
|
|
* change in conditions while the procedure was waiting in a queue.
|
|
*/
|
|
lr_act_complete(conn);
|
|
break;
|
|
default:
|
|
/* Ignore other evts */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void lr_st_active(struct ll_conn *conn, uint8_t evt, void *param)
|
|
{
|
|
switch (evt) {
|
|
case LR_EVT_RUN:
|
|
if (llcp_lr_peek(conn)) {
|
|
lr_act_run(conn);
|
|
}
|
|
break;
|
|
case LR_EVT_COMPLETE:
|
|
lr_act_complete(conn);
|
|
lr_set_state(conn, LR_STATE_IDLE);
|
|
break;
|
|
case LR_EVT_DISCONNECT:
|
|
lr_act_disconnect(conn);
|
|
lr_set_state(conn, LR_STATE_DISCONNECT);
|
|
break;
|
|
default:
|
|
/* Ignore other evts */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void lr_st_terminate(struct ll_conn *conn, uint8_t evt, void *param)
|
|
{
|
|
switch (evt) {
|
|
case LR_EVT_RUN:
|
|
if (llcp_lr_peek(conn)) {
|
|
lr_act_run(conn);
|
|
}
|
|
break;
|
|
case LR_EVT_COMPLETE:
|
|
lr_act_complete(conn);
|
|
lr_set_state(conn, LR_STATE_IDLE);
|
|
break;
|
|
case LR_EVT_DISCONNECT:
|
|
lr_act_disconnect(conn);
|
|
lr_set_state(conn, LR_STATE_DISCONNECT);
|
|
break;
|
|
default:
|
|
/* Ignore other evts */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void lr_execute_fsm(struct ll_conn *conn, uint8_t evt, void *param)
|
|
{
|
|
switch (conn->llcp.local.state) {
|
|
case LR_STATE_DISCONNECT:
|
|
lr_st_disconnect(conn, evt, param);
|
|
break;
|
|
case LR_STATE_IDLE:
|
|
lr_st_idle(conn, evt, param);
|
|
break;
|
|
case LR_STATE_ACTIVE:
|
|
lr_st_active(conn, evt, param);
|
|
break;
|
|
case LR_STATE_TERMINATE:
|
|
lr_st_terminate(conn, evt, param);
|
|
break;
|
|
default:
|
|
/* Unknown state */
|
|
LL_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
void llcp_lr_init(struct ll_conn *conn)
|
|
{
|
|
lr_set_state(conn, LR_STATE_DISCONNECT);
|
|
conn->llcp.local.prt_expire = 0U;
|
|
}
|
|
|
|
void llcp_lr_run(struct ll_conn *conn)
|
|
{
|
|
lr_execute_fsm(conn, LR_EVT_RUN, NULL);
|
|
}
|
|
|
|
void llcp_lr_complete(struct ll_conn *conn)
|
|
{
|
|
lr_execute_fsm(conn, LR_EVT_COMPLETE, NULL);
|
|
}
|
|
|
|
void llcp_lr_connect(struct ll_conn *conn)
|
|
{
|
|
lr_execute_fsm(conn, LR_EVT_CONNECT, NULL);
|
|
}
|
|
|
|
void llcp_lr_disconnect(struct ll_conn *conn)
|
|
{
|
|
lr_execute_fsm(conn, LR_EVT_DISCONNECT, NULL);
|
|
}
|
|
|
|
void llcp_lr_abort(struct ll_conn *conn)
|
|
{
|
|
struct proc_ctx *ctx;
|
|
|
|
/* Flush all pending procedures */
|
|
ctx = lr_dequeue(conn);
|
|
while (ctx) {
|
|
llcp_proc_ctx_release(ctx);
|
|
ctx = lr_dequeue(conn);
|
|
}
|
|
|
|
llcp_lr_prt_stop(conn);
|
|
llcp_rr_set_incompat(conn, 0U);
|
|
lr_set_state(conn, LR_STATE_IDLE);
|
|
}
|
|
|
|
#ifdef ZTEST_UNITTEST
|
|
|
|
bool lr_is_disconnected(struct ll_conn *conn)
|
|
{
|
|
return conn->llcp.local.state == LR_STATE_DISCONNECT;
|
|
}
|
|
|
|
bool lr_is_idle(struct ll_conn *conn)
|
|
{
|
|
return conn->llcp.local.state == LR_STATE_IDLE;
|
|
}
|
|
|
|
void test_int_local_pending_requests(void)
|
|
{
|
|
struct ll_conn conn;
|
|
struct proc_ctx *peek_ctx;
|
|
struct proc_ctx *dequeue_ctx;
|
|
struct proc_ctx ctx;
|
|
|
|
ull_cp_init();
|
|
ull_tx_q_init(&conn.tx_q);
|
|
ull_llcp_init(&conn);
|
|
|
|
peek_ctx = llcp_lr_peek(&conn);
|
|
zassert_is_null(peek_ctx, NULL);
|
|
|
|
dequeue_ctx = lr_dequeue(&conn);
|
|
zassert_is_null(dequeue_ctx, NULL);
|
|
|
|
llcp_lr_enqueue(&conn, &ctx);
|
|
peek_ctx = (struct proc_ctx *)sys_slist_peek_head(&conn.llcp.local.pend_proc_list);
|
|
zassert_equal_ptr(peek_ctx, &ctx, NULL);
|
|
|
|
peek_ctx = llcp_lr_peek(&conn);
|
|
zassert_equal_ptr(peek_ctx, &ctx, NULL);
|
|
|
|
dequeue_ctx = lr_dequeue(&conn);
|
|
zassert_equal_ptr(dequeue_ctx, &ctx, NULL);
|
|
|
|
peek_ctx = llcp_lr_peek(&conn);
|
|
zassert_is_null(peek_ctx, NULL);
|
|
|
|
dequeue_ctx = lr_dequeue(&conn);
|
|
zassert_is_null(dequeue_ctx, NULL);
|
|
}
|
|
|
|
#endif
|