/* * Copyright (c) 2020 Demant * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include "hal/ccm.h" #include "util/util.h" #include "util/mem.h" #include "util/memq.h" #include "util/mayfly.h" #include "util/dbuf.h" #include "pdu_df.h" #include "lll/pdu_vendor.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_internal.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" #include #include "hal/debug.h" 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, }; void llcp_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); /* If we have a node rx it must not be marked RETAIN as * the memory referenced would leak */ LL_ASSERT(ctx->node_ref.rx == NULL || ctx->node_ref.rx->hdr.type != NODE_RX_TYPE_RETAIN); lr_dequeue(conn); llcp_proc_ctx_release(ctx); } } /* * LLCP Local Request Shared Data Locking */ static ALWAYS_INLINE uint32_t shared_data_access_lock(void) { bool enabled; if (mayfly_is_running()) { /* We are in Mayfly context, nothing to be done */ return false; } /* We are in thread context and have to disable TICKER_USER_ID_ULL_HIGH */ enabled = mayfly_is_enabled(TICKER_USER_ID_THREAD, TICKER_USER_ID_ULL_HIGH) != 0U; mayfly_enable(TICKER_USER_ID_THREAD, TICKER_USER_ID_ULL_HIGH, 0U); return enabled; } static ALWAYS_INLINE void shared_data_access_unlock(bool key) { if (key) { /* We are in thread context and have to reenable TICKER_USER_ID_ULL_HIGH */ mayfly_enable(TICKER_USER_ID_THREAD, TICKER_USER_ID_ULL_HIGH, 1U); } } /* * 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) { /* This function is called from both Thread and Mayfly (ISR), * make sure only a single context have access at a time. */ bool key = shared_data_access_lock(); sys_slist_append(&conn->llcp.local.pend_proc_list, &ctx->node); shared_data_access_unlock(key); } static struct proc_ctx *lr_dequeue(struct ll_conn *conn) { /* This function is called from both Thread and Mayfly (ISR), * make sure only a single context have access at a time. */ struct proc_ctx *ctx; bool key = shared_data_access_lock(); ctx = (struct proc_ctx *)sys_slist_get(&conn->llcp.local.pend_proc_list); shared_data_access_unlock(key); return ctx; } struct proc_ctx *llcp_lr_peek(struct ll_conn *conn) { /* This function is called from both Thread and Mayfly (ISR), * make sure only a single context have access at a time. */ struct proc_ctx *ctx; bool key = shared_data_access_lock(); ctx = (struct proc_ctx *)sys_slist_peek_head(&conn->llcp.local.pend_proc_list); shared_data_access_unlock(key); return ctx; } struct proc_ctx *llcp_lr_peek_proc(struct ll_conn *conn, uint8_t proc) { /* This function is called from both Thread and Mayfly (ISR), * make sure only a single context have access at a time. */ struct proc_ctx *ctx, *tmp; bool key = shared_data_access_lock(); SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&conn->llcp.local.pend_proc_list, ctx, tmp, node) { if (ctx->proc == proc) { break; } } shared_data_access_unlock(key); 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_flush_procedures(struct ll_conn *conn) { struct proc_ctx *ctx; /* Flush all pending procedures */ ctx = lr_dequeue(conn); while (ctx) { llcp_nodes_release(conn, ctx); llcp_proc_ctx_release(ctx); ctx = lr_dequeue(conn); } } void llcp_lr_rx(struct ll_conn *conn, struct proc_ctx *ctx, memq_link_t *link, struct node_rx_pdu *rx) { /* In the case of a specific connection update procedure collision it can occur that * an 'unexpected' REJECT_IND_PDU is received and passed as RX'ed and will then result in * discarding of the retention of the previously received CONNECTION_UPDATE_IND * and following this, an assert will be hit when attempting to use this retained * RX node for creating the notification on completion of connection param request. * (see comment in ull_llcp_conn_upd.c::lp_cu_st_wait_instant() for more details) * * The workaround/fix for this is to only store an RX node for retention if * 'we havent already' got one */ if (!ctx->node_ref.rx) { /* Store RX node and link */ ctx->node_ref.rx = rx; ctx->node_ref.link = link; } 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) */ #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) case PROC_CIS_CREATE: llcp_lp_cc_rx(conn, ctx, rx); break; #endif /* defined(CONFIG_BT_CTLR_CENTRAL_ISO) */ #if defined(CONFIG_BT_CTLR_SCA_UPDATE) case PROC_SCA_UPDATE: llcp_lp_comm_rx(conn, ctx, rx); break; #endif /* CONFIG_BT_CTLR_SCA_UPDATE */ default: /* Unknown procedure */ LL_ASSERT(0); break; } /* If rx node was not retained clear reference */ if (ctx->node_ref.rx && ctx->node_ref.rx->hdr.type != NODE_RX_TYPE_RETAIN) { ctx->node_ref.rx = NULL; } llcp_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) */ #if defined(CONFIG_BT_CTLR_SYNC_TRANSFER_SENDER) case PROC_PERIODIC_SYNC: llcp_lp_past_tx_ack(conn, ctx, tx); break; #endif /* defined(CONFIG_BT_CTLR_SYNC_TRANSFER_SENDER) */ default: break; /* Ignore tx_ack */ } /* Clear TX node reference */ ctx->node_ref.tx_ack = NULL; llcp_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; } llcp_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) case PROC_CIS_CREATE: llcp_lp_cc_run(conn, ctx, NULL); break; #endif /* CONFIG_BT_CTLR_CENTRAL_ISO */ #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) */ #if defined(CONFIG_BT_CTLR_SCA_UPDATE) case PROC_SCA_UPDATE: llcp_lp_comm_run(conn, ctx, NULL); break; #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ #if defined(CONFIG_BT_CTLR_SYNC_TRANSFER_SENDER) case PROC_PERIODIC_SYNC: llcp_lp_past_run(conn, ctx, NULL); break; #endif /* CONFIG_BT_CTLR_SYNC_TRANSFER_SENDER */ default: /* Unknown procedure */ LL_ASSERT(0); break; } llcp_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) { llcp_lr_flush_procedures(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) { /* * since the call to lr_act_run may release the context we need to remember * which procedure we are running */ const enum llcp_proc curr_proc = ctx->proc; lr_act_run(conn); if (curr_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_terminate(struct ll_conn *conn) { llcp_lr_flush_procedures(conn); llcp_lr_prt_stop(conn); llcp_rr_set_incompat(conn, 0U); lr_set_state(conn, LR_STATE_IDLE); } #ifdef ZTEST_UNITTEST bool llcp_lr_is_disconnected(struct ll_conn *conn) { return conn->llcp.local.state == LR_STATE_DISCONNECT; } bool llcp_lr_is_idle(struct ll_conn *conn) { return conn->llcp.local.state == LR_STATE_IDLE; } struct proc_ctx *llcp_lr_dequeue(struct ll_conn *conn) { return lr_dequeue(conn); } #endif