blob: d68c2c6c26d656af308c84c5a79fea8a7bb76ac4 [file] [log] [blame]
/*
* 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 "lll_conn_iso.h"
#include "ull_tx_queue.h"
#include "isoal.h"
#include "ull_iso_types.h"
#include "ull_conn_iso_types.h"
#include "ull_conn_iso_internal.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 */
#if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO)
case PROC_CIS_TERMINATE:
llcp_lp_comm_rx(conn, ctx, rx);
break;
#endif /* defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) */
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 */
#if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO)
case PROC_CIS_TERMINATE:
llcp_lp_comm_tx_ack(conn, ctx, tx);
break;
#endif /* defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) */
default:
break;
/* Ignore tx_ack */
}
lr_check_done(conn, ctx);
}
void llcp_lr_tx_ntf(struct ll_conn *conn, struct proc_ctx *ctx)
{
switch (ctx->proc) {
#if defined(CONFIG_BT_CTLR_PHY)
case PROC_PHY_UPDATE:
llcp_lp_pu_tx_ntf(conn, ctx);
break;
#endif /* CONFIG_BT_CTLR_PHY */
default:
/* Ignore other procedures */
break;
}
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 */
#if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO)
case PROC_CIS_TERMINATE:
llcp_lp_comm_run(conn, ctx, NULL);
break;
#endif /* defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) */
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