| /* |
| * 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 "ll_feat.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_chan_internal.h" |
| #include "ull_llcp.h" |
| #include "ull_conn_internal.h" |
| #include "ull_internal.h" |
| #include "ull_llcp_features.h" |
| #include "ull_llcp_internal.h" |
| |
| #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) |
| #define LOG_MODULE_NAME bt_ctlr_ull_llcp_common |
| #include "common/log.h" |
| #include <soc.h> |
| #include "hal/debug.h" |
| |
| /* LLCP Local Procedure FSM states */ |
| enum { |
| LP_COMMON_STATE_IDLE, |
| LP_COMMON_STATE_WAIT_TX, |
| LP_COMMON_STATE_WAIT_TX_ACK, |
| LP_COMMON_STATE_WAIT_RX, |
| LP_COMMON_STATE_WAIT_NTF, |
| }; |
| |
| /* LLCP Local Procedure Common FSM events */ |
| enum { |
| /* Procedure run */ |
| LP_COMMON_EVT_RUN, |
| |
| /* Response received */ |
| LP_COMMON_EVT_RESPONSE, |
| |
| /* Reject response received */ |
| LP_COMMON_EVT_REJECT, |
| |
| /* Unknown response received */ |
| LP_COMMON_EVT_UNKNOWN, |
| |
| /* Instant collision detected */ |
| LP_COMMON_EVT_COLLISION, |
| |
| /* Ack received */ |
| LP_COMMON_EVT_ACK, |
| }; |
| |
| /* LLCP Remote Procedure Common FSM states */ |
| enum { |
| RP_COMMON_STATE_IDLE, |
| RP_COMMON_STATE_WAIT_RX, |
| RP_COMMON_STATE_POSTPONE_TERMINATE, |
| RP_COMMON_STATE_WAIT_TX, |
| RP_COMMON_STATE_WAIT_TX_ACK, |
| RP_COMMON_STATE_WAIT_NTF, |
| }; |
| /* LLCP Remote Procedure Common FSM events */ |
| enum { |
| /* Procedure run */ |
| RP_COMMON_EVT_RUN, |
| |
| /* Ack received */ |
| RP_COMMON_EVT_ACK, |
| |
| /* Request received */ |
| RP_COMMON_EVT_REQUEST, |
| }; |
| |
| |
| static void lp_comm_ntf(struct ll_conn *conn, struct proc_ctx *ctx); |
| static void lp_comm_terminate_invalid_pdu(struct ll_conn *conn, struct proc_ctx *ctx); |
| |
| #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) |
| /** |
| * @brief Stop and tear down a connected ISO stream |
| * This function may be called to tear down a CIS. |
| * |
| * @param cig_id ID of specific ISO group |
| * @param cis_id ID of connected ISO stream to stop |
| * @param reason Termination reason |
| */ |
| static void llcp_cis_stop_by_id(uint8_t cig_id, uint8_t cis_id, uint8_t reason) |
| { |
| struct ll_conn_iso_group *cig = ll_conn_iso_group_get_by_id(cig_id); |
| |
| if (cig) { |
| struct ll_conn_iso_stream *cis; |
| uint16_t cis_handle = UINT16_MAX; |
| |
| /* Look through CIS's of specified group */ |
| cis = ll_conn_iso_stream_get_by_group(cig, &cis_handle); |
| while (cis && cis->cis_id != cis_id) { |
| /* Get next CIS */ |
| cis = ll_conn_iso_stream_get_by_group(cig, &cis_handle); |
| } |
| if (cis && cis->lll.handle == cis_handle) { |
| ull_conn_iso_cis_stop(cis, NULL, reason); |
| } |
| } |
| } |
| #endif /* CONFIG_BT_CTLR_CENTRAL_ISO || CONFIG_BT_CTLR_PERIPHERAL_ISO */ |
| |
| /* |
| * LLCP Local Procedure Common FSM |
| */ |
| |
| static void lp_comm_tx(struct ll_conn *conn, struct proc_ctx *ctx) |
| { |
| struct node_tx *tx; |
| struct pdu_data *pdu; |
| |
| /* Allocate tx node */ |
| tx = llcp_tx_alloc(conn, ctx); |
| LL_ASSERT(tx); |
| |
| pdu = (struct pdu_data *)tx->pdu; |
| |
| /* Encode LL Control PDU */ |
| switch (ctx->proc) { |
| #if defined(CONFIG_BT_CTLR_LE_PING) |
| case PROC_LE_PING: |
| llcp_pdu_encode_ping_req(pdu); |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_PING_RSP; |
| break; |
| #endif /* CONFIG_BT_CTLR_LE_PING */ |
| case PROC_FEATURE_EXCHANGE: |
| llcp_pdu_encode_feature_req(conn, pdu); |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_FEATURE_RSP; |
| break; |
| #if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_PERIPHERAL) |
| case PROC_MIN_USED_CHANS: |
| llcp_pdu_encode_min_used_chans_ind(ctx, pdu); |
| ctx->tx_ack = tx; |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; |
| break; |
| #endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_PERIPHERAL */ |
| case PROC_VERSION_EXCHANGE: |
| llcp_pdu_encode_version_ind(pdu); |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_VERSION_IND; |
| break; |
| case PROC_TERMINATE: |
| llcp_pdu_encode_terminate_ind(ctx, pdu); |
| ctx->tx_ack = tx; |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; |
| break; |
| #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) |
| case PROC_CIS_TERMINATE: |
| llcp_pdu_encode_cis_terminate_ind(ctx, pdu); |
| ctx->tx_ack = tx; |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; |
| break; |
| #endif /* CONFIG_BT_CTLR_CENTRAL_ISO || CONFIG_BT_CTLR_PERIPHERAL_ISO */ |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case PROC_DATA_LENGTH_UPDATE: |
| llcp_pdu_encode_length_req(conn, pdu); |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_LENGTH_RSP; |
| break; |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) |
| case PROC_CTE_REQ: |
| llcp_pdu_encode_cte_req(ctx, pdu); |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_CTE_RSP; |
| break; |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ |
| default: |
| /* Unknown procedure */ |
| LL_ASSERT(0); |
| } |
| |
| ctx->tx_opcode = pdu->llctrl.opcode; |
| |
| /* Enqueue LL Control PDU towards LLL */ |
| llcp_tx_enqueue(conn, tx); |
| |
| /* Restart procedure response timeout timer */ |
| if (ctx->proc != PROC_TERMINATE) { |
| /* Use normal timeout value of 40s */ |
| llcp_lr_prt_restart(conn); |
| } else { |
| /* Use supervision timeout value |
| * NOTE: As the supervision timeout is at most 32s the normal procedure response |
| * timeout of 40s will never come into play for the ACL Termination procedure. |
| */ |
| llcp_lr_prt_restart_with_value(conn, conn->supervision_reload); |
| } |
| } |
| |
| static void lp_comm_ntf_feature_exchange(struct ll_conn *conn, struct proc_ctx *ctx, |
| struct pdu_data *pdu) |
| { |
| switch (ctx->response_opcode) { |
| case PDU_DATA_LLCTRL_TYPE_FEATURE_RSP: |
| llcp_ntf_encode_feature_rsp(conn, pdu); |
| break; |
| case PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP: |
| llcp_ntf_encode_unknown_rsp(ctx, pdu); |
| break; |
| default: |
| /* Unexpected PDU, should not get through, so ASSERT */ |
| LL_ASSERT(0); |
| } |
| } |
| |
| static void lp_comm_ntf_version_ind(struct ll_conn *conn, struct proc_ctx *ctx, |
| struct pdu_data *pdu) |
| { |
| switch (ctx->response_opcode) { |
| case PDU_DATA_LLCTRL_TYPE_VERSION_IND: |
| llcp_ntf_encode_version_ind(conn, pdu); |
| break; |
| default: |
| /* Unexpected PDU, should not get through, so ASSERT */ |
| LL_ASSERT(0); |
| } |
| } |
| |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| static void lp_comm_ntf_length_change(struct ll_conn *conn, struct proc_ctx *ctx, |
| struct pdu_data *pdu) |
| { |
| llcp_ntf_encode_length_change(conn, pdu); |
| } |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) |
| |
| static void lp_comm_complete_cte_req_finalize(struct ll_conn *conn) |
| { |
| llcp_rr_set_paused_cmd(conn, PROC_NONE); |
| llcp_lr_complete(conn); |
| } |
| |
| static void lp_comm_ntf_cte_req(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu) |
| { |
| switch (ctx->response_opcode) { |
| case PDU_DATA_LLCTRL_TYPE_CTE_RSP: |
| /* Notify host that received LL_CTE_RSP does not have CTE */ |
| if (!ctx->data.cte_remote_rsp.has_cte) { |
| llcp_ntf_encode_cte_req(pdu); |
| } |
| break; |
| case PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP: |
| llcp_ntf_encode_unknown_rsp(ctx, pdu); |
| break; |
| case PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND: |
| llcp_ntf_encode_reject_ext_ind(ctx, pdu); |
| break; |
| default: |
| /* Unexpected PDU, should not get through, so ASSERT */ |
| LL_ASSERT(0); |
| } |
| } |
| |
| static void lp_comm_ntf_cte_req_tx(struct ll_conn *conn, struct proc_ctx *ctx) |
| { |
| if (llcp_ntf_alloc_is_available()) { |
| lp_comm_ntf(conn, ctx); |
| ull_cp_cte_req_set_disable(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } else { |
| ctx->state = LP_COMMON_STATE_WAIT_NTF; |
| } |
| } |
| |
| static void lp_comm_complete_cte_req(struct ll_conn *conn, struct proc_ctx *ctx) |
| { |
| if (conn->llcp.cte_req.is_enabled) { |
| if (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_CTE_RSP) { |
| if (ctx->data.cte_remote_rsp.has_cte) { |
| if (conn->llcp.cte_req.req_interval != 0U) { |
| conn->llcp.cte_req.req_expire = |
| conn->llcp.cte_req.req_interval; |
| } else { |
| /* Disable the CTE request procedure when it is completed in |
| * case it was executed as non-periodic. |
| */ |
| conn->llcp.cte_req.is_enabled = 0U; |
| } |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } else { |
| lp_comm_ntf_cte_req_tx(conn, ctx); |
| } |
| } else if (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND && |
| ctx->reject_ext_ind.reject_opcode == PDU_DATA_LLCTRL_TYPE_CTE_REQ) { |
| lp_comm_ntf_cte_req_tx(conn, ctx); |
| } else if (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP && |
| ctx->unknown_response.type == PDU_DATA_LLCTRL_TYPE_CTE_REQ) { |
| /* CTE response is unsupported in peer, so disable locally for this |
| * connection |
| */ |
| feature_unmask_features(conn, LL_FEAT_BIT_CONNECTION_CTE_REQ); |
| lp_comm_ntf_cte_req_tx(conn, ctx); |
| } else if (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_UNUSED) { |
| /* This path is related with handling disable the CTE REQ when PHY |
| * has been changed to CODED PHY. BT 5.3 Core Vol 4 Part E 7.8.85 |
| * says CTE REQ has to be automatically disabled as if it had been requested |
| * by Host. There is no notification send to Host. |
| */ |
| ull_cp_cte_req_set_disable(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } else { |
| /* Illegal response opcode, internally changes state to |
| * LP_COMMON_STATE_IDLE |
| */ |
| lp_comm_terminate_invalid_pdu(conn, ctx); |
| } |
| } else { |
| /* The CTE_REQ was disabled by Host after the request was send. |
| * It does not matter if response has arrived, it should not be handled. |
| */ |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } |
| |
| if (ctx->state == LP_COMMON_STATE_IDLE) { |
| lp_comm_complete_cte_req_finalize(conn); |
| } |
| } |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ |
| |
| static void lp_comm_ntf(struct ll_conn *conn, struct proc_ctx *ctx) |
| { |
| struct node_rx_pdu *ntf; |
| struct pdu_data *pdu; |
| |
| /* Allocate ntf node */ |
| ntf = llcp_ntf_alloc(); |
| LL_ASSERT(ntf); |
| |
| ntf->hdr.type = NODE_RX_TYPE_DC_PDU; |
| ntf->hdr.handle = conn->lll.handle; |
| pdu = (struct pdu_data *)ntf->pdu; |
| |
| switch (ctx->proc) { |
| case PROC_FEATURE_EXCHANGE: |
| lp_comm_ntf_feature_exchange(conn, ctx, pdu); |
| break; |
| case PROC_VERSION_EXCHANGE: |
| lp_comm_ntf_version_ind(conn, ctx, pdu); |
| break; |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case PROC_DATA_LENGTH_UPDATE: |
| lp_comm_ntf_length_change(conn, ctx, pdu); |
| break; |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) |
| case PROC_CTE_REQ: |
| lp_comm_ntf_cte_req(conn, ctx, pdu); |
| break; |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ |
| default: |
| LL_ASSERT(0); |
| break; |
| } |
| |
| /* Enqueue notification towards LL */ |
| ll_rx_put(ntf->hdr.link, ntf); |
| ll_rx_sched(); |
| } |
| |
| static void lp_comm_terminate_invalid_pdu(struct ll_conn *conn, struct proc_ctx *ctx) |
| { |
| /* Invalid behaviour */ |
| /* Invalid PDU received so terminate connection */ |
| conn->llcp_terminate.reason_final = BT_HCI_ERR_LMP_PDU_NOT_ALLOWED; |
| llcp_lr_complete(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } |
| |
| static void lp_comm_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) |
| { |
| switch (ctx->proc) { |
| #if defined(CONFIG_BT_CTLR_LE_PING) |
| case PROC_LE_PING: |
| if (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP || |
| ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_PING_RSP) { |
| llcp_lr_complete(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } else { |
| /* Illegal response opcode */ |
| lp_comm_terminate_invalid_pdu(conn, ctx); |
| } |
| break; |
| #endif /* CONFIG_BT_CTLR_LE_PING */ |
| case PROC_FEATURE_EXCHANGE: |
| if (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP || |
| ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_FEATURE_RSP) { |
| if (!llcp_ntf_alloc_is_available()) { |
| ctx->state = LP_COMMON_STATE_WAIT_NTF; |
| } else { |
| lp_comm_ntf(conn, ctx); |
| llcp_lr_complete(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } |
| } else { |
| /* Illegal response opcode */ |
| lp_comm_terminate_invalid_pdu(conn, ctx); |
| } |
| break; |
| #if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_PERIPHERAL) |
| case PROC_MIN_USED_CHANS: |
| llcp_lr_complete(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| break; |
| #endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_PERIPHERAL */ |
| case PROC_VERSION_EXCHANGE: |
| if (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_VERSION_IND) { |
| if (!llcp_ntf_alloc_is_available()) { |
| ctx->state = LP_COMMON_STATE_WAIT_NTF; |
| } else { |
| lp_comm_ntf(conn, ctx); |
| llcp_lr_complete(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } |
| } else { |
| /* Illegal response opcode */ |
| lp_comm_terminate_invalid_pdu(conn, ctx); |
| } |
| break; |
| case PROC_TERMINATE: |
| /* No notification */ |
| llcp_lr_complete(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| |
| /* Mark the connection for termination */ |
| conn->llcp_terminate.reason_final = BT_HCI_ERR_LOCALHOST_TERM_CONN; |
| break; |
| #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) |
| case PROC_CIS_TERMINATE: |
| /* No notification */ |
| llcp_lr_complete(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| break; |
| #endif /* CONFIG_BT_CTLR_CENTRAL_ISO || CONFIG_BT_CTLR_PERIPHERAL_ISO */ |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case PROC_DATA_LENGTH_UPDATE: |
| if (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_LENGTH_RSP) { |
| /* Apply changes in data lengths/times */ |
| uint8_t dle_changed = ull_dle_update_eff(conn); |
| |
| if (dle_changed && !llcp_ntf_alloc_is_available()) { |
| /* We need to generate NTF but no buffers avail so wait for one */ |
| ctx->state = LP_COMMON_STATE_WAIT_NTF; |
| } else { |
| if (dle_changed) { |
| lp_comm_ntf(conn, ctx); |
| } |
| llcp_lr_complete(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } |
| } else if (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP) { |
| /* Peer does not accept DLU, so disable on current connection */ |
| feature_unmask_features(conn, LL_FEAT_BIT_DLE); |
| |
| llcp_lr_complete(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } else { |
| /* Illegal response opcode */ |
| lp_comm_terminate_invalid_pdu(conn, ctx); |
| break; |
| } |
| |
| if (!ull_cp_remote_dle_pending(conn)) { |
| /* Resume data, but only if there is no remote procedure pending RSP |
| * in which case, the RSP tx-ACK will resume data |
| */ |
| llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_DATA_LENGTH); |
| } |
| break; |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) |
| case PROC_CTE_REQ: |
| lp_comm_complete_cte_req(conn, ctx); |
| break; |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ |
| default: |
| /* Unknown procedure */ |
| LL_ASSERT(0); |
| } |
| } |
| |
| #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) |
| static bool lp_cis_terminated(struct ll_conn *conn) |
| { |
| return conn->llcp.cis.terminate_ack; |
| } |
| #endif /* CONFIG_BT_CTLR_CENTRAL_ISO || CONFIG_BT_CTLR_PERIPHERAL_ISO */ |
| |
| static void lp_comm_send_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) |
| { |
| switch (ctx->proc) { |
| #if defined(CONFIG_BT_CTLR_LE_PING) |
| case PROC_LE_PING: |
| if (llcp_lr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { |
| ctx->state = LP_COMMON_STATE_WAIT_TX; |
| } else { |
| lp_comm_tx(conn, ctx); |
| ctx->state = LP_COMMON_STATE_WAIT_RX; |
| } |
| break; |
| #endif /* CONFIG_BT_CTLR_LE_PING */ |
| case PROC_FEATURE_EXCHANGE: |
| if (llcp_lr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { |
| ctx->state = LP_COMMON_STATE_WAIT_TX; |
| } else { |
| lp_comm_tx(conn, ctx); |
| conn->llcp.fex.sent = 1; |
| ctx->state = LP_COMMON_STATE_WAIT_RX; |
| } |
| break; |
| #if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_PERIPHERAL) |
| case PROC_MIN_USED_CHANS: |
| if (llcp_lr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { |
| ctx->state = LP_COMMON_STATE_WAIT_TX; |
| } else { |
| lp_comm_tx(conn, ctx); |
| ctx->state = LP_COMMON_STATE_WAIT_TX_ACK; |
| } |
| break; |
| #endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_PERIPHERAL */ |
| case PROC_VERSION_EXCHANGE: |
| /* The Link Layer shall only queue for transmission a maximum of |
| * one LL_VERSION_IND PDU during a connection. |
| */ |
| if (!conn->llcp.vex.sent) { |
| if (llcp_lr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { |
| ctx->state = LP_COMMON_STATE_WAIT_TX; |
| } else { |
| lp_comm_tx(conn, ctx); |
| conn->llcp.vex.sent = 1; |
| ctx->state = LP_COMMON_STATE_WAIT_RX; |
| } |
| } else { |
| ctx->response_opcode = PDU_DATA_LLCTRL_TYPE_VERSION_IND; |
| lp_comm_complete(conn, ctx, evt, param); |
| } |
| break; |
| case PROC_TERMINATE: |
| if (!llcp_tx_alloc_peek(conn, ctx)) { |
| ctx->state = LP_COMMON_STATE_WAIT_TX; |
| } else { |
| lp_comm_tx(conn, ctx); |
| ctx->data.term.error_code = BT_HCI_ERR_LOCALHOST_TERM_CONN; |
| ctx->state = LP_COMMON_STATE_WAIT_TX_ACK; |
| } |
| break; |
| #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) |
| case PROC_CIS_TERMINATE: |
| if (!lp_cis_terminated(conn) || llcp_lr_ispaused(conn) || |
| !llcp_tx_alloc_peek(conn, ctx)) { |
| ctx->state = LP_COMMON_STATE_WAIT_TX; |
| } else { |
| lp_comm_tx(conn, ctx); |
| ctx->state = LP_COMMON_STATE_WAIT_TX_ACK; |
| } |
| break; |
| #endif /* CONFIG_BT_CTLR_CENTRAL_ISO || CONFIG_BT_CTLR_PERIPHERAL_ISO */ |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case PROC_DATA_LENGTH_UPDATE: |
| if (!ull_cp_remote_dle_pending(conn)) { |
| if (llcp_lr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { |
| ctx->state = LP_COMMON_STATE_WAIT_TX; |
| } else { |
| /* Pause data tx, to ensure we can later (on RSP rx-ack) |
| * update DLE without conflicting with out-going LL Data PDUs |
| * See BT Core 5.2 Vol6: B-4.5.10 & B-5.1.9 |
| */ |
| llcp_tx_pause_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_DATA_LENGTH); |
| lp_comm_tx(conn, ctx); |
| ctx->state = LP_COMMON_STATE_WAIT_RX; |
| } |
| } else { |
| /* REQ was received from peer and RSP not yet sent |
| * lets piggy-back on RSP instead af sending REQ |
| * thus we can complete local req |
| */ |
| llcp_lr_complete(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } |
| break; |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) |
| case PROC_CTE_REQ: |
| if (conn->llcp.cte_req.is_enabled && |
| #if defined(CONFIG_BT_CTLR_PHY) |
| conn->lll.phy_rx != PHY_CODED) { |
| #else |
| 1) { |
| #endif /* CONFIG_BT_CTLR_PHY */ |
| if (llcp_lr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx) || |
| (llcp_rr_get_paused_cmd(conn) == PROC_CTE_REQ)) { |
| ctx->state = LP_COMMON_STATE_WAIT_TX; |
| } else { |
| lp_comm_tx(conn, ctx); |
| ctx->state = LP_COMMON_STATE_WAIT_RX; |
| } |
| } else { |
| /* The PHY was changed to CODED when the request was waiting in a local |
| * request queue. |
| * |
| * Use of pair: proc PROC_CTE_REQ and rx_opcode PDU_DATA_LLCTRL_TYPE_UNUSED |
| * to complete the procedure before sending a request to peer. |
| * This is a special complete execution path to disable the procedure |
| * due to change of RX PHY to CODED. |
| */ |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; |
| ctx->state = LP_COMMON_STATE_IDLE; |
| llcp_lr_complete(conn); |
| } |
| break; |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ |
| default: |
| /* Unknown procedure */ |
| LL_ASSERT(0); |
| } |
| } |
| |
| static void lp_comm_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) |
| { |
| switch (evt) { |
| case LP_COMMON_EVT_RUN: |
| #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) |
| if (ctx->proc == PROC_CIS_TERMINATE) { |
| /* We're getting going on a CIS Terminate */ |
| /* So we should start by requesting Terminate for the CIS in question */ |
| |
| /* Clear terminate ack flag, used to signal CIS Terminated */ |
| conn->llcp.cis.terminate_ack = 0U; |
| llcp_cis_stop_by_id(ctx->data.cis_term.cig_id, ctx->data.cis_term.cis_id, |
| ctx->data.cis_term.error_code); |
| } |
| #endif /* CONFIG_BT_CTLR_CENTRAL_ISO || CONFIG_BT_CTLR_PERIPHERAL_ISO */ |
| if (llcp_lr_ispaused(conn)) { |
| ctx->state = LP_COMMON_STATE_WAIT_TX; |
| } else { |
| lp_comm_send_req(conn, ctx, evt, param); |
| } |
| break; |
| default: |
| /* Ignore other evts */ |
| break; |
| } |
| } |
| |
| static void lp_comm_st_wait_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, |
| void *param) |
| { |
| switch (evt) { |
| case LP_COMMON_EVT_RUN: |
| lp_comm_send_req(conn, ctx, evt, param); |
| break; |
| default: |
| /* Ignore other evts */ |
| break; |
| } |
| } |
| |
| static void lp_comm_st_wait_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, |
| void *param) |
| { |
| switch (evt) { |
| case LP_COMMON_EVT_ACK: |
| switch (ctx->proc) { |
| #if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_PERIPHERAL) |
| case PROC_MIN_USED_CHANS: |
| ctx->tx_ack = NULL; |
| lp_comm_complete(conn, ctx, evt, param); |
| break; |
| #endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_PERIPHERAL */ |
| case PROC_TERMINATE: |
| ctx->tx_ack = NULL; |
| lp_comm_complete(conn, ctx, evt, param); |
| break; |
| #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) |
| case PROC_CIS_TERMINATE: |
| ctx->tx_ack = NULL; |
| lp_comm_complete(conn, ctx, evt, param); |
| break; |
| #endif /* CONFIG_BT_CTLR_CENTRAL_ISO || CONFIG_BT_CTLR_PERIPHERAL_ISO */ |
| default: |
| /* Ignore for other procedures */ |
| break; |
| } |
| break; |
| default: |
| /* Ignore other evts */ |
| break; |
| } |
| } |
| |
| static void lp_comm_rx_decode(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu) |
| { |
| ctx->response_opcode = pdu->llctrl.opcode; |
| |
| switch (pdu->llctrl.opcode) { |
| #if defined(CONFIG_BT_CTLR_LE_PING) |
| case PDU_DATA_LLCTRL_TYPE_PING_RSP: |
| /* ping_rsp has no data */ |
| break; |
| #endif /* CONFIG_BT_CTLR_LE_PING */ |
| case PDU_DATA_LLCTRL_TYPE_FEATURE_RSP: |
| llcp_pdu_decode_feature_rsp(conn, pdu); |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) && defined(CONFIG_BT_CTLR_PHY) |
| /* If Coded PHY is now supported we must update local max tx/rx times to reflect */ |
| if (feature_phy_coded(conn)) { |
| ull_dle_max_time_get(conn, &conn->lll.dle.local.max_rx_time, |
| &conn->lll.dle.local.max_tx_time); |
| } |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH && CONFIG_BT_CTLR_PHY */ |
| break; |
| #if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) |
| case PDU_DATA_LLCTRL_TYPE_MIN_USED_CHAN_IND: |
| /* No response expected */ |
| break; |
| #endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ |
| case PDU_DATA_LLCTRL_TYPE_VERSION_IND: |
| llcp_pdu_decode_version_ind(conn, pdu); |
| break; |
| case PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP: |
| llcp_pdu_decode_unknown_rsp(ctx, pdu); |
| break; |
| case PDU_DATA_LLCTRL_TYPE_TERMINATE_IND: |
| /* No response expected */ |
| LL_ASSERT(0); |
| break; |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case PDU_DATA_LLCTRL_TYPE_LENGTH_RSP: |
| llcp_pdu_decode_length_rsp(conn, pdu); |
| break; |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) |
| case PDU_DATA_LLCTRL_TYPE_CTE_RSP: |
| llcp_pdu_decode_cte_rsp(ctx, pdu); |
| break; |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ |
| case PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND: |
| llcp_pdu_decode_reject_ext_ind(ctx, pdu); |
| break; |
| case PDU_DATA_LLCTRL_TYPE_REJECT_IND: |
| /* Empty on purpose, as we don't care about the PDU content, we'll disconnect */ |
| break; |
| default: |
| /* Unknown opcode */ |
| LL_ASSERT(0); |
| } |
| } |
| |
| static void lp_comm_st_wait_rx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, |
| void *param) |
| { |
| switch (evt) { |
| case LP_COMMON_EVT_RESPONSE: |
| lp_comm_rx_decode(conn, ctx, (struct pdu_data *)param); |
| lp_comm_complete(conn, ctx, evt, param); |
| break; |
| default: |
| /* Ignore other evts */ |
| break; |
| } |
| } |
| |
| static void lp_comm_st_wait_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, |
| void *param) |
| { |
| switch (evt) { |
| case LP_COMMON_EVT_RUN: |
| switch (ctx->proc) { |
| case PROC_FEATURE_EXCHANGE: |
| case PROC_VERSION_EXCHANGE: |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case PROC_DATA_LENGTH_UPDATE: |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| if (llcp_ntf_alloc_is_available()) { |
| lp_comm_ntf(conn, ctx); |
| llcp_lr_complete(conn); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } |
| break; |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) |
| case PROC_CTE_REQ: |
| if (llcp_ntf_alloc_is_available()) { |
| lp_comm_ntf(conn, ctx); |
| ctx->state = LP_COMMON_STATE_IDLE; |
| lp_comm_complete_cte_req_finalize(conn); |
| } |
| break; |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ |
| default: |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void lp_comm_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, |
| void *param) |
| { |
| switch (ctx->state) { |
| case LP_COMMON_STATE_IDLE: |
| lp_comm_st_idle(conn, ctx, evt, param); |
| break; |
| case LP_COMMON_STATE_WAIT_TX: |
| lp_comm_st_wait_tx(conn, ctx, evt, param); |
| break; |
| case LP_COMMON_STATE_WAIT_TX_ACK: |
| lp_comm_st_wait_tx_ack(conn, ctx, evt, param); |
| break; |
| case LP_COMMON_STATE_WAIT_RX: |
| lp_comm_st_wait_rx(conn, ctx, evt, param); |
| break; |
| case LP_COMMON_STATE_WAIT_NTF: |
| lp_comm_st_wait_ntf(conn, ctx, evt, param); |
| break; |
| default: |
| /* Unknown state */ |
| LL_ASSERT(0); |
| } |
| } |
| |
| void llcp_lp_comm_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx) |
| { |
| lp_comm_execute_fsm(conn, ctx, LP_COMMON_EVT_ACK, tx->pdu); |
| } |
| |
| void llcp_lp_comm_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx) |
| { |
| lp_comm_execute_fsm(conn, ctx, LP_COMMON_EVT_RESPONSE, rx->pdu); |
| } |
| |
| void llcp_lp_comm_init_proc(struct proc_ctx *ctx) |
| { |
| ctx->state = LP_COMMON_STATE_IDLE; |
| } |
| |
| void llcp_lp_comm_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) |
| { |
| lp_comm_execute_fsm(conn, ctx, LP_COMMON_EVT_RUN, param); |
| |
| } |
| |
| static void rp_comm_terminate(struct ll_conn *conn, struct proc_ctx *ctx) |
| { |
| llcp_rr_complete(conn); |
| ctx->state = RP_COMMON_STATE_IDLE; |
| |
| /* Mark the connection for termination */ |
| conn->llcp_terminate.reason_final = ctx->data.term.error_code; |
| } |
| |
| #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) |
| static void rp_comm_stop_cis(struct proc_ctx *ctx) |
| { |
| llcp_cis_stop_by_id(ctx->data.cis_term.cig_id, ctx->data.cis_term.cis_id, |
| ctx->data.cis_term.error_code); |
| } |
| #endif /* CONFIG_BT_CTLR_CENTRAL_ISO || CONFIG_BT_CTLR_PERIPHERAL_ISO */ |
| |
| /* |
| * LLCP Remote Procedure Common FSM |
| */ |
| static void rp_comm_rx_decode(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu) |
| { |
| ctx->response_opcode = pdu->llctrl.opcode; |
| |
| switch (pdu->llctrl.opcode) { |
| #if defined(CONFIG_BT_CTLR_LE_PING) |
| case PDU_DATA_LLCTRL_TYPE_PING_REQ: |
| /* ping_req has no data */ |
| break; |
| #endif /* CONFIG_BT_CTLR_LE_PING */ |
| #if defined(CONFIG_BT_PERIPHERAL) |
| case PDU_DATA_LLCTRL_TYPE_FEATURE_REQ: |
| #endif /* CONFIG_BT_PERIPHERAL */ |
| #if defined(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG) && defined(CONFIG_BT_CENTRAL) |
| case PDU_DATA_LLCTRL_TYPE_PER_INIT_FEAT_XCHG: |
| #endif /* CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG && CONFIG_BT_CENTRAL */ |
| llcp_pdu_decode_feature_req(conn, pdu); |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) && defined(CONFIG_BT_CTLR_PHY) |
| /* If Coded PHY is now supported we must update local max tx/rx times to reflect */ |
| if (feature_phy_coded(conn)) { |
| ull_dle_max_time_get(conn, &conn->lll.dle.local.max_rx_time, |
| &conn->lll.dle.local.max_tx_time); |
| } |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH && CONFIG_BT_CTLR_PHY */ |
| break; |
| #if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_CENTRAL) |
| case PDU_DATA_LLCTRL_TYPE_MIN_USED_CHAN_IND: |
| llcp_pdu_decode_min_used_chans_ind(conn, pdu); |
| break; |
| #endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_CENTRAL */ |
| case PDU_DATA_LLCTRL_TYPE_VERSION_IND: |
| llcp_pdu_decode_version_ind(conn, pdu); |
| break; |
| case PDU_DATA_LLCTRL_TYPE_TERMINATE_IND: |
| llcp_pdu_decode_terminate_ind(ctx, pdu); |
| /* Make sure no data is tx'ed after RX of terminate ind */ |
| llcp_tx_pause_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_TERMINATE); |
| break; |
| #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) |
| case PDU_DATA_LLCTRL_TYPE_CIS_TERMINATE_IND: |
| llcp_pdu_decode_cis_terminate_ind(ctx, pdu); |
| /* Terminate CIS */ |
| rp_comm_stop_cis(ctx); |
| break; |
| #endif /* CONFIG_BT_CTLR_CENTRAL_ISO || CONFIG_BT_CTLR_PERIPHERAL_ISO */ |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case PDU_DATA_LLCTRL_TYPE_LENGTH_REQ: |
| llcp_pdu_decode_length_req(conn, pdu); |
| /* On reception of REQ mark RSP open for local piggy-back |
| * Pause data tx, to ensure we can later (on RSP tx ack) update DLE without |
| * conflicting with out-going LL Data PDUs |
| * See BT Core 5.2 Vol6: B-4.5.10 & B-5.1.9 |
| */ |
| llcp_tx_pause_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_DATA_LENGTH); |
| break; |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP) |
| case PDU_DATA_LLCTRL_TYPE_CTE_REQ: |
| llcp_pdu_decode_cte_req(ctx, pdu); |
| break; |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */ |
| default: |
| /* Unknown opcode */ |
| LL_ASSERT(0); |
| } |
| } |
| |
| static void rp_comm_tx(struct ll_conn *conn, struct proc_ctx *ctx) |
| { |
| struct node_tx *tx; |
| struct pdu_data *pdu; |
| |
| /* Allocate tx node */ |
| tx = llcp_tx_alloc(conn, ctx); |
| LL_ASSERT(tx); |
| |
| pdu = (struct pdu_data *)tx->pdu; |
| |
| /* Encode LL Control PDU */ |
| switch (ctx->proc) { |
| #if defined(CONFIG_BT_CTLR_LE_PING) |
| case PROC_LE_PING: |
| llcp_pdu_encode_ping_rsp(pdu); |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; |
| break; |
| #endif /* CONFIG_BT_CTLR_LE_PING */ |
| case PROC_FEATURE_EXCHANGE: |
| llcp_pdu_encode_feature_rsp(conn, pdu); |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; |
| break; |
| case PROC_VERSION_EXCHANGE: |
| llcp_pdu_encode_version_ind(pdu); |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; |
| break; |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case PROC_DATA_LENGTH_UPDATE: |
| llcp_pdu_encode_length_rsp(conn, pdu); |
| ctx->tx_ack = tx; |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; |
| break; |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP) |
| case PROC_CTE_REQ: { |
| uint8_t err_code = 0; |
| |
| if (conn->llcp.cte_rsp.is_enabled == 0) { |
| err_code = BT_HCI_ERR_UNSUPP_LL_PARAM_VAL; |
| } |
| |
| #if defined(CONFIG_BT_PHY_UPDATE) |
| /* If the PHY update is not possible, then PHY1M is used. |
| * CTE is supported for PHY1M. |
| */ |
| if (conn->lll.phy_tx == PHY_CODED) { |
| err_code = BT_HCI_ERR_INVALID_LL_PARAM; |
| } |
| #endif /* CONFIG_BT_PHY_UPDATE */ |
| if (!(conn->llcp.cte_rsp.cte_types & BIT(ctx->data.cte_remote_req.cte_type)) || |
| conn->llcp.cte_rsp.max_cte_len < ctx->data.cte_remote_req.min_cte_len) { |
| err_code = BT_HCI_ERR_UNSUPP_LL_PARAM_VAL; |
| } |
| |
| if (!err_code) { |
| llcp_pdu_encode_cte_rsp(ctx, pdu); |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; |
| } else { |
| llcp_pdu_encode_reject_ext_ind(pdu, PDU_DATA_LLCTRL_TYPE_CTE_REQ, err_code); |
| ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; |
| } |
| |
| ctx->tx_ack = tx; |
| |
| break; |
| } |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */ |
| default: |
| /* Unknown procedure */ |
| LL_ASSERT(0); |
| } |
| |
| ctx->tx_opcode = pdu->llctrl.opcode; |
| |
| /* Enqueue LL Control PDU towards LLL */ |
| llcp_tx_enqueue(conn, tx); |
| |
| /* Restart procedure response timeout timer */ |
| llcp_rr_prt_restart(conn); |
| } |
| |
| static void rp_comm_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) |
| { |
| switch (evt) { |
| case RP_COMMON_EVT_RUN: |
| ctx->state = RP_COMMON_STATE_WAIT_RX; |
| break; |
| default: |
| /* Ignore other evts */ |
| break; |
| } |
| } |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| static void rp_comm_ntf_length_change(struct ll_conn *conn, struct proc_ctx *ctx, |
| struct pdu_data *pdu) |
| { |
| llcp_ntf_encode_length_change(conn, pdu); |
| } |
| |
| static void rp_comm_ntf(struct ll_conn *conn, struct proc_ctx *ctx) |
| { |
| struct node_rx_pdu *ntf; |
| struct pdu_data *pdu; |
| |
| ARG_UNUSED(pdu); |
| /* Allocate ntf node */ |
| ntf = llcp_ntf_alloc(); |
| LL_ASSERT(ntf); |
| |
| ntf->hdr.type = NODE_RX_TYPE_DC_PDU; |
| ntf->hdr.handle = conn->lll.handle; |
| pdu = (struct pdu_data *)ntf->pdu; |
| switch (ctx->proc) { |
| /* Note: the 'double' ifdef in case this switch case expands |
| * in the future and the function is re-instated |
| */ |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case PROC_DATA_LENGTH_UPDATE: |
| rp_comm_ntf_length_change(conn, ctx, pdu); |
| break; |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| default: |
| LL_ASSERT(0); |
| break; |
| } |
| |
| /* Enqueue notification towards LL */ |
| ll_rx_put(ntf->hdr.link, ntf); |
| ll_rx_sched(); |
| } |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| |
| static void rp_comm_send_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) |
| { |
| switch (ctx->proc) { |
| #if defined(CONFIG_BT_CTLR_LE_PING) |
| case PROC_LE_PING: |
| /* Always respond on remote ping */ |
| if (llcp_rr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { |
| ctx->state = RP_COMMON_STATE_WAIT_TX; |
| } else { |
| rp_comm_tx(conn, ctx); |
| llcp_rr_complete(conn); |
| ctx->state = RP_COMMON_STATE_IDLE; |
| } |
| break; |
| #endif /* CONFIG_BT_CTLR_LE_PING */ |
| case PROC_FEATURE_EXCHANGE: |
| /* Always respond on remote feature exchange */ |
| if (llcp_rr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { |
| ctx->state = RP_COMMON_STATE_WAIT_TX; |
| } else { |
| rp_comm_tx(conn, ctx); |
| conn->llcp.fex.sent = 1; |
| llcp_rr_complete(conn); |
| ctx->state = RP_COMMON_STATE_IDLE; |
| } |
| break; |
| case PROC_VERSION_EXCHANGE: |
| /* The Link Layer shall only queue for transmission a maximum of one |
| * LL_VERSION_IND PDU during a connection. |
| * If the Link Layer receives an LL_VERSION_IND PDU and has already sent an |
| * LL_VERSION_IND PDU then the Link Layer shall not send another |
| * LL_VERSION_IND PDU to the peer device. |
| */ |
| if (!conn->llcp.vex.sent) { |
| if (llcp_rr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { |
| ctx->state = RP_COMMON_STATE_WAIT_TX; |
| } else { |
| rp_comm_tx(conn, ctx); |
| conn->llcp.vex.sent = 1; |
| llcp_rr_complete(conn); |
| ctx->state = RP_COMMON_STATE_IDLE; |
| } |
| } else { |
| /* Invalid behaviour |
| * A procedure already sent a LL_VERSION_IND and received a LL_VERSION_IND. |
| * Ignore and complete the procedure. |
| */ |
| llcp_rr_complete(conn); |
| ctx->state = RP_COMMON_STATE_IDLE; |
| } |
| |
| break; |
| #if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_CENTRAL) |
| case PROC_MIN_USED_CHANS: |
| /* |
| * Spec says (5.2, Vol.6, Part B, Section 5.1.11): |
| * The procedure has completed when the Link Layer acknowledgment of the |
| * LL_MIN_USED_CHANNELS_IND PDU is sent or received. |
| * In effect, for this procedure, this is equivalent to RX of PDU |
| * |
| * Also: |
| * If the Link Layer receives an LL_MIN_USED_CHANNELS_IND PDU, it should ensure |
| * that, whenever the Peripheral-to-Central PHY is one of those specified, |
| * the connection uses at least the number of channels given in the |
| * MinUsedChannels field of the PDU. |
| * |
| * The 'should' is here interpreted as 'permission' to do nothing |
| * |
| * Future improvement could implement logic to support this |
| */ |
| |
| llcp_rr_complete(conn); |
| ctx->state = RP_COMMON_STATE_IDLE; |
| break; |
| #endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_CENTRAL */ |
| case PROC_TERMINATE: |
| #if defined(CONFIG_BT_CENTRAL) |
| if (conn->lll.role == BT_HCI_ROLE_CENTRAL) { |
| /* No response, but postpone terminate until next event |
| * to ensure acking the reception of TERMINATE_IND |
| */ |
| ctx->state = RP_COMMON_STATE_POSTPONE_TERMINATE; |
| break; |
| } |
| #endif |
| #if defined(CONFIG_BT_PERIPHERAL) |
| /* Terminate right away */ |
| rp_comm_terminate(conn, ctx); |
| #endif |
| break; |
| #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) || defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) |
| case PROC_CIS_TERMINATE: |
| /* No response */ |
| llcp_rr_complete(conn); |
| ctx->state = RP_COMMON_STATE_IDLE; |
| |
| break; |
| #endif /* CONFIG_BT_CTLR_CENTRAL_ISO || CONFIG_BT_CTLR_PERIPHERAL_ISO */ |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case PROC_DATA_LENGTH_UPDATE: |
| if (llcp_rr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { |
| ctx->state = RP_COMMON_STATE_WAIT_TX; |
| } else { |
| /* On RSP tx close the window for possible local req piggy-back */ |
| rp_comm_tx(conn, ctx); |
| |
| /* Wait for the peer to have ack'ed the RSP before updating DLE */ |
| ctx->state = RP_COMMON_STATE_WAIT_TX_ACK; |
| } |
| break; |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP) |
| case PROC_CTE_REQ: |
| if (llcp_rr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx) || |
| (llcp_rr_get_paused_cmd(conn) == PROC_CTE_REQ)) { |
| ctx->state = RP_COMMON_STATE_WAIT_TX; |
| } else { |
| llcp_rr_set_paused_cmd(conn, PROC_PHY_UPDATE); |
| rp_comm_tx(conn, ctx); |
| ctx->state = RP_COMMON_STATE_WAIT_TX_ACK; |
| } |
| break; |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */ |
| default: |
| /* Unknown procedure */ |
| LL_ASSERT(0); |
| } |
| } |
| |
| static void rp_comm_st_wait_rx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) |
| { |
| switch (evt) { |
| case RP_COMMON_EVT_REQUEST: |
| rp_comm_rx_decode(conn, ctx, (struct pdu_data *)param); |
| rp_comm_send_rsp(conn, ctx, evt, param); |
| break; |
| default: |
| /* Ignore other evts */ |
| break; |
| } |
| } |
| |
| static void rp_comm_st_postpone_terminate(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, |
| void *param) |
| { |
| switch (evt) { |
| case RP_COMMON_EVT_RUN: |
| LL_ASSERT(ctx->proc == PROC_TERMINATE); |
| |
| /* Note: now we terminate, mimicking legacy LLCP behaviour |
| * A check should be added to ensure that the ack of the terminate_ind was |
| * indeed tx'ed and not scheduled out/postponed by LLL |
| */ |
| rp_comm_terminate(conn, ctx); |
| |
| break; |
| default: |
| /* Ignore other evts */ |
| break; |
| } |
| } |
| |
| static void rp_comm_st_wait_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) |
| { |
| switch (evt) { |
| case LP_COMMON_EVT_RUN: |
| rp_comm_send_rsp(conn, ctx, evt, param); |
| break; |
| default: |
| /* Ignore other evts */ |
| break; |
| } |
| } |
| |
| static void rp_comm_st_wait_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, |
| void *param) |
| { |
| switch (evt) { |
| case RP_COMMON_EVT_ACK: |
| switch (ctx->proc) { |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case PROC_DATA_LENGTH_UPDATE: { |
| /* Apply changes in data lengths/times */ |
| uint8_t dle_changed = ull_dle_update_eff(conn); |
| |
| llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_DATA_LENGTH); |
| |
| if (dle_changed && !llcp_ntf_alloc_is_available()) { |
| ctx->state = RP_COMMON_STATE_WAIT_NTF; |
| } else { |
| if (dle_changed) { |
| rp_comm_ntf(conn, ctx); |
| } |
| llcp_rr_complete(conn); |
| ctx->state = RP_COMMON_STATE_IDLE; |
| } |
| break; |
| } |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP) |
| case PROC_CTE_REQ: { |
| /* add PHY update pause = false here */ |
| ctx->tx_ack = NULL; |
| llcp_rr_set_paused_cmd(conn, PROC_NONE); |
| llcp_rr_complete(conn); |
| ctx->state = RP_COMMON_STATE_IDLE; |
| } |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */ |
| default: |
| /* Ignore other procedures */ |
| break; |
| } |
| break; |
| default: |
| /* Ignore other evts */ |
| break; |
| } |
| } |
| |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| static void rp_comm_st_wait_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, |
| void *param) |
| { |
| if (llcp_ntf_alloc_is_available()) { |
| rp_comm_ntf(conn, ctx); |
| llcp_rr_complete(conn); |
| ctx->state = RP_COMMON_STATE_IDLE; |
| } |
| } |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| |
| static void rp_comm_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, |
| void *param) |
| { |
| switch (ctx->state) { |
| case RP_COMMON_STATE_IDLE: |
| rp_comm_st_idle(conn, ctx, evt, param); |
| break; |
| case RP_COMMON_STATE_WAIT_RX: |
| rp_comm_st_wait_rx(conn, ctx, evt, param); |
| break; |
| case RP_COMMON_STATE_POSTPONE_TERMINATE: |
| rp_comm_st_postpone_terminate(conn, ctx, evt, param); |
| break; |
| case RP_COMMON_STATE_WAIT_TX: |
| rp_comm_st_wait_tx(conn, ctx, evt, param); |
| break; |
| case RP_COMMON_STATE_WAIT_TX_ACK: |
| rp_comm_st_wait_tx_ack(conn, ctx, evt, param); |
| break; |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| case RP_COMMON_STATE_WAIT_NTF: |
| rp_comm_st_wait_ntf(conn, ctx, evt, param); |
| break; |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ |
| default: |
| /* Unknown state */ |
| LL_ASSERT(0); |
| } |
| } |
| |
| void llcp_rp_comm_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx) |
| { |
| rp_comm_execute_fsm(conn, ctx, RP_COMMON_EVT_REQUEST, rx->pdu); |
| } |
| |
| void llcp_rp_comm_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx) |
| { |
| rp_comm_execute_fsm(conn, ctx, RP_COMMON_EVT_ACK, tx->pdu); |
| } |
| |
| void llcp_rp_comm_init_proc(struct proc_ctx *ctx) |
| { |
| ctx->state = RP_COMMON_STATE_IDLE; |
| } |
| |
| void llcp_rp_comm_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) |
| { |
| rp_comm_execute_fsm(conn, ctx, RP_COMMON_EVT_RUN, param); |
| } |