zephyr/subsys/bluetooth/controller/ll_sw/ull_llcp_remote.c

888 lines
22 KiB
C
Raw Normal View History

/*
* 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 "ll_feat.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_conn_internal.h"
#include "ull_llcp.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_remote
#include "common/log.h"
#include <soc.h>
#include "hal/debug.h"
static void rr_check_done(struct ll_conn *conn, struct proc_ctx *ctx);
static struct proc_ctx *rr_dequeue(struct ll_conn *conn);
static void rr_abort(struct ll_conn *conn);
/* LLCP Remote Request FSM State */
enum rr_state {
RR_STATE_IDLE,
RR_STATE_REJECT,
RR_STATE_UNSUPPORTED,
RR_STATE_ACTIVE,
RR_STATE_DISCONNECT,
RR_STATE_TERMINATE,
};
/* LLCP Remote Request FSM Event */
enum {
/* Procedure prepare */
RR_EVT_PREPARE,
/* Procedure run */
RR_EVT_RUN,
/* Procedure completed */
RR_EVT_COMPLETE,
/* Link connected */
RR_EVT_CONNECT,
/* Link disconnected */
RR_EVT_DISCONNECT,
};
static bool proc_with_instant(struct proc_ctx *ctx)
{
switch (ctx->proc) {
case PROC_UNKNOWN:
case PROC_FEATURE_EXCHANGE:
case PROC_MIN_USED_CHANS:
case PROC_LE_PING:
case PROC_VERSION_EXCHANGE:
case PROC_ENCRYPTION_START:
case PROC_ENCRYPTION_PAUSE:
case PROC_TERMINATE:
case PROC_DATA_LENGTH_UPDATE:
case PROC_CTE_REQ:
return 0U;
case PROC_PHY_UPDATE:
case PROC_CONN_UPDATE:
case PROC_CONN_PARAM_REQ:
case PROC_CHAN_MAP_UPDATE:
return 1U;
default:
/* Unknown procedure */
LL_ASSERT(0);
break;
}
return 0U;
}
static void rr_check_done(struct ll_conn *conn, struct proc_ctx *ctx)
{
if (ctx->done) {
struct proc_ctx *ctx_header;
ctx_header = llcp_rr_peek(conn);
LL_ASSERT(ctx_header == ctx);
rr_dequeue(conn);
llcp_proc_ctx_release(ctx);
}
}
/*
* LLCP Remote Request FSM
*/
static void rr_set_state(struct ll_conn *conn, enum rr_state state)
{
conn->llcp.remote.state = state;
}
void llcp_rr_set_incompat(struct ll_conn *conn, enum proc_incompat incompat)
{
conn->llcp.remote.incompat = incompat;
}
Bluetooth: Controller: Add packet TX restrictions for CTE REQ/RSP PDUs There are packet restrictions imposed by PHY update procedure for PDU that includes CTE (BT Core 5.3 : - central/peripheral can't send PDU with CTE after receive or send LLPHY_UPDATE_IND until instant if the PHY after instant is CODED - peripheral can't send PDU including CTE after it sends LL_PHY_REQ PDU until receive LL_PHY_UPDATE_IND, LL_UNKNOWN_RSP, LL_REJECTED_EXT_- IND_PDU if there is a CODE PHY in TX_PHYS - peripheral can't send PDU including CTE after it sends LL_PHY_RSP PDU until receive LL_PHY_UPDATE_IND if there is a CODED PHY in TX_PHYS of LL_PHY_RSP PDU or RX_PHYS of LL_PHY_REQ PDU. The BT 5.3 Core spec defines only one PDU that may include CTE, that is LL_CTE_RSP PDU. To avoid a situation that there is such PDU enqueued for transmission in LLL when packet transmission restrictions should be applied, both procedures in almost all cases will not be executed in parallel. Current implementation always handles remote procedurerequest first. There are possible three scenarios: 1. Remotely requested PHY update. Locally initiated CTE REQ. In this case there is no problem with LL_CTE_RSP waiting in a TX queue in LLL. Both procedures may be executed one after another. 2. Remotely requested CTE REQ. Locally initiated PHY update. In this case the CTE REQ is handled first and it will pause the PHY update procedure until LL_CTE_RSP PDU is acknowledged by remote. Then the CTE REQ procedure will be completed and PHY update continued. 3. Locally initiated PHY update is pending. Arrives remote CTE REQ. In this case the CTE REQ will be paused until localy initiated PHY update is completed. Then the CTE REQ will be continued. Thanks to that there should be no PDU including CTE in LLL TX quueue. That releases us from a situation there is a LL_CTE_RSP PDU in the LLL TX qeueue that must be changed into LL_REJECT_EXT_IND PDU due to change of PHY to CODED PHY after PHY update procedure completes. Signed-off-by: Piotr Pryga <piotr.pryga@nordicsemi.no>
2022-01-18 11:42:30 +01:00
void llcp_rr_set_paused_cmd(struct ll_conn *conn, enum llcp_proc proc)
{
#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP) || defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ)
conn->llcp.remote.paused_cmd = proc;
#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP || CONFIG_BT_CTLR_DF_CONN_CTE_REQ */
}
enum llcp_proc llcp_rr_get_paused_cmd(struct ll_conn *conn)
{
#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP) || defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ)
return conn->llcp.remote.paused_cmd;
#else
return PROC_NONE;
#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP || CONFIG_BT_CTLR_DF_CONN_CTE_REQ */
}
static enum proc_incompat rr_get_incompat(struct ll_conn *conn)
{
return conn->llcp.remote.incompat;
}
static void rr_set_collision(struct ll_conn *conn, bool collision)
{
conn->llcp.remote.collision = collision;
}
bool llcp_rr_get_collision(struct ll_conn *conn)
{
return conn->llcp.remote.collision;
}
static void rr_enqueue(struct ll_conn *conn, struct proc_ctx *ctx)
{
sys_slist_append(&conn->llcp.remote.pend_proc_list, &ctx->node);
}
static struct proc_ctx *rr_dequeue(struct ll_conn *conn)
{
struct proc_ctx *ctx;
ctx = (struct proc_ctx *)sys_slist_get(&conn->llcp.remote.pend_proc_list);
return ctx;
}
struct proc_ctx *llcp_rr_peek(struct ll_conn *conn)
{
struct proc_ctx *ctx;
ctx = (struct proc_ctx *)sys_slist_peek_head(&conn->llcp.remote.pend_proc_list);
return ctx;
}
bool llcp_rr_ispaused(struct ll_conn *conn)
{
return conn->llcp.remote.pause == 1U;
}
void llcp_rr_pause(struct ll_conn *conn)
{
conn->llcp.remote.pause = 1U;
}
void llcp_rr_resume(struct ll_conn *conn)
{
conn->llcp.remote.pause = 0U;
}
void llcp_rr_prt_restart(struct ll_conn *conn)
{
conn->llcp.remote.prt_expire = conn->llcp.prt_reload;
}
void llcp_rr_prt_stop(struct ll_conn *conn)
{
conn->llcp.remote.prt_expire = 0U;
}
void llcp_rr_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx)
{
switch (ctx->proc) {
case PROC_UNKNOWN:
/* Do nothing */
break;
#if defined(CONFIG_BT_CTLR_LE_PING)
case PROC_LE_PING:
llcp_rp_comm_rx(conn, ctx, rx);
break;
#endif /* CONFIG_BT_CTLR_LE_PING */
case PROC_FEATURE_EXCHANGE:
llcp_rp_comm_rx(conn, ctx, rx);
break;
#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN)
case PROC_MIN_USED_CHANS:
llcp_rp_comm_rx(conn, ctx, rx);
break;
#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */
case PROC_VERSION_EXCHANGE:
llcp_rp_comm_rx(conn, ctx, rx);
break;
#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_PERIPHERAL)
case PROC_ENCRYPTION_START:
case PROC_ENCRYPTION_PAUSE:
llcp_rp_enc_rx(conn, ctx, rx);
break;
#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_PERIPHERAL */
#ifdef CONFIG_BT_CTLR_PHY
case PROC_PHY_UPDATE:
llcp_rp_pu_rx(conn, ctx, rx);
break;
#endif /* CONFIG_BT_CTLR_PHY */
case PROC_CONN_UPDATE:
case PROC_CONN_PARAM_REQ:
llcp_rp_cu_rx(conn, ctx, rx);
break;
case PROC_TERMINATE:
llcp_rp_comm_rx(conn, ctx, rx);
break;
#if defined(CONFIG_BT_PERIPHERAL)
case PROC_CHAN_MAP_UPDATE:
llcp_rp_chmu_rx(conn, ctx, rx);
break;
#endif /* CONFIG_BT_PERIPHERAL */
#if defined(CONFIG_BT_CTLR_DATA_LENGTH)
case PROC_DATA_LENGTH_UPDATE:
llcp_rp_comm_rx(conn, ctx, rx);
break;
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */
#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP)
case PROC_CTE_REQ:
llcp_rp_comm_rx(conn, ctx, rx);
break;
#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */
default:
/* Unknown procedure */
LL_ASSERT(0);
break;
}
rr_check_done(conn, ctx);
}
void llcp_rr_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx)
{
switch (ctx->proc) {
#if defined(CONFIG_BT_CTLR_DATA_LENGTH)
case PROC_DATA_LENGTH_UPDATE:
llcp_rp_comm_tx_ack(conn, ctx, tx);
break;
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */
#ifdef CONFIG_BT_CTLR_PHY
case PROC_PHY_UPDATE:
llcp_rp_pu_tx_ack(conn, ctx, tx);
break;
#endif /* CONFIG_BT_CTLR_PHY */
Bluetooth: Controller: Add packet TX restrictions for CTE REQ/RSP PDUs There are packet restrictions imposed by PHY update procedure for PDU that includes CTE (BT Core 5.3 : - central/peripheral can't send PDU with CTE after receive or send LLPHY_UPDATE_IND until instant if the PHY after instant is CODED - peripheral can't send PDU including CTE after it sends LL_PHY_REQ PDU until receive LL_PHY_UPDATE_IND, LL_UNKNOWN_RSP, LL_REJECTED_EXT_- IND_PDU if there is a CODE PHY in TX_PHYS - peripheral can't send PDU including CTE after it sends LL_PHY_RSP PDU until receive LL_PHY_UPDATE_IND if there is a CODED PHY in TX_PHYS of LL_PHY_RSP PDU or RX_PHYS of LL_PHY_REQ PDU. The BT 5.3 Core spec defines only one PDU that may include CTE, that is LL_CTE_RSP PDU. To avoid a situation that there is such PDU enqueued for transmission in LLL when packet transmission restrictions should be applied, both procedures in almost all cases will not be executed in parallel. Current implementation always handles remote procedurerequest first. There are possible three scenarios: 1. Remotely requested PHY update. Locally initiated CTE REQ. In this case there is no problem with LL_CTE_RSP waiting in a TX queue in LLL. Both procedures may be executed one after another. 2. Remotely requested CTE REQ. Locally initiated PHY update. In this case the CTE REQ is handled first and it will pause the PHY update procedure until LL_CTE_RSP PDU is acknowledged by remote. Then the CTE REQ procedure will be completed and PHY update continued. 3. Locally initiated PHY update is pending. Arrives remote CTE REQ. In this case the CTE REQ will be paused until localy initiated PHY update is completed. Then the CTE REQ will be continued. Thanks to that there should be no PDU including CTE in LLL TX quueue. That releases us from a situation there is a LL_CTE_RSP PDU in the LLL TX qeueue that must be changed into LL_REJECT_EXT_IND PDU due to change of PHY to CODED PHY after PHY update procedure completes. Signed-off-by: Piotr Pryga <piotr.pryga@nordicsemi.no>
2022-01-18 11:42:30 +01:00
#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP)
case PROC_CTE_REQ:
llcp_rp_comm_tx_ack(conn, ctx, tx);
break;
#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */
default:
/* Ignore tx_ack */
break;
}
rr_check_done(conn, ctx);
}
static void rr_act_run(struct ll_conn *conn)
{
struct proc_ctx *ctx;
ctx = llcp_rr_peek(conn);
switch (ctx->proc) {
#if defined(CONFIG_BT_CTLR_LE_PING)
case PROC_LE_PING:
llcp_rp_comm_run(conn, ctx, NULL);
break;
#endif /* CONFIG_BT_CTLR_LE_PING */
case PROC_FEATURE_EXCHANGE:
llcp_rp_comm_run(conn, ctx, NULL);
break;
#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN)
case PROC_MIN_USED_CHANS:
llcp_rp_comm_run(conn, ctx, NULL);
break;
#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */
case PROC_VERSION_EXCHANGE:
llcp_rp_comm_run(conn, ctx, NULL);
break;
#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_PERIPHERAL)
case PROC_ENCRYPTION_START:
case PROC_ENCRYPTION_PAUSE:
llcp_rp_enc_run(conn, ctx, NULL);
break;
#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_PERIPHERAL */
#ifdef CONFIG_BT_CTLR_PHY
case PROC_PHY_UPDATE:
llcp_rp_pu_run(conn, ctx, NULL);
break;
#endif /* CONFIG_BT_CTLR_PHY */
case PROC_CONN_UPDATE:
case PROC_CONN_PARAM_REQ:
llcp_rp_cu_run(conn, ctx, NULL);
break;
case PROC_TERMINATE:
llcp_rp_comm_run(conn, ctx, NULL);
break;
#if defined(CONFIG_BT_PERIPHERAL)
case PROC_CHAN_MAP_UPDATE:
llcp_rp_chmu_run(conn, ctx, NULL);
break;
#endif /* CONFIG_BT_PERIPHERAL */
#if defined(CONFIG_BT_CTLR_DATA_LENGTH)
case PROC_DATA_LENGTH_UPDATE:
llcp_rp_comm_run(conn, ctx, NULL);
break;
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */
#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP)
case PROC_CTE_REQ:
llcp_rp_comm_run(conn, ctx, NULL);
break;
#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */
default:
/* Unknown procedure */
LL_ASSERT(0);
break;
}
rr_check_done(conn, ctx);
}
static void rr_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode)
{
struct node_tx *tx;
struct pdu_data *pdu;
struct proc_ctx *ctx_local;
uint8_t reject_code;
/* Allocate tx node */
tx = llcp_tx_alloc(conn, ctx);
LL_ASSERT(tx);
pdu = (struct pdu_data *)tx->pdu;
/* Encode LL Control PDU */
switch (opcode) {
case PDU_DATA_LLCTRL_TYPE_REJECT_IND:
ctx_local = llcp_lr_peek(conn);
if (ctx_local->proc == ctx->proc ||
(ctx_local->proc == PROC_CONN_UPDATE &&
ctx->proc == PROC_CONN_PARAM_REQ)) {
reject_code = BT_HCI_ERR_LL_PROC_COLLISION;
} else {
reject_code = BT_HCI_ERR_DIFF_TRANS_COLLISION;
}
if (conn->llcp.fex.valid && feature_ext_rej_ind(conn)) {
llcp_pdu_encode_reject_ext_ind(pdu, conn->llcp.remote.reject_opcode,
reject_code);
} else {
llcp_pdu_encode_reject_ind(pdu, reject_code);
}
break;
case PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP:
llcp_pdu_encode_unknown_rsp(ctx, pdu);
break;
default:
LL_ASSERT(0);
}
ctx->tx_opcode = pdu->llctrl.opcode;
/* Enqueue LL Control PDU towards LLL */
llcp_tx_enqueue(conn, tx);
}
static void rr_act_reject(struct ll_conn *conn)
{
struct proc_ctx *ctx = llcp_rr_peek(conn);
LL_ASSERT(ctx != NULL);
if (llcp_rr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) {
rr_set_state(conn, RR_STATE_REJECT);
} else {
rr_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_REJECT_IND);
ctx->done = 1U;
rr_set_state(conn, RR_STATE_IDLE);
}
}
static void rr_act_unsupported(struct ll_conn *conn)
{
struct proc_ctx *ctx = llcp_rr_peek(conn);
LL_ASSERT(ctx != NULL);
if (llcp_rr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) {
rr_set_state(conn, RR_STATE_UNSUPPORTED);
} else {
rr_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP);
ctx->done = 1U;
rr_set_state(conn, RR_STATE_IDLE);
}
}
static void rr_act_complete(struct ll_conn *conn)
{
struct proc_ctx *ctx;
rr_set_collision(conn, 0U);
ctx = llcp_rr_peek(conn);
LL_ASSERT(ctx != NULL);
/* Stop procedure response timeout timer */
llcp_rr_prt_stop(conn);
/* Mark the procedure as safe to delete */
ctx->done = 1U;
}
static void rr_act_connect(struct ll_conn *conn)
{
/* Empty on purpose */
}
static void rr_act_disconnect(struct ll_conn *conn)
{
struct proc_ctx *ctx;
ctx = rr_dequeue(conn);
/*
* we may have been disconnected in the
* middle of a control procedure, in which
* case we need to release all contexts
*/
while (ctx != NULL) {
llcp_proc_ctx_release(ctx);
ctx = rr_dequeue(conn);
}
}
static void rr_st_disconnect(struct ll_conn *conn, uint8_t evt, void *param)
{
switch (evt) {
case RR_EVT_CONNECT:
rr_act_connect(conn);
rr_set_state(conn, RR_STATE_IDLE);
break;
default:
/* Ignore other evts */
break;
}
}
static void rr_st_idle(struct ll_conn *conn, uint8_t evt, void *param)
{
struct proc_ctx *ctx;
struct proc_ctx *ctx_local;
switch (evt) {
case RR_EVT_PREPARE:
ctx = llcp_rr_peek(conn);
if (ctx) {
const enum proc_incompat incompat = rr_get_incompat(conn);
const bool periph = !!(conn->lll.role == BT_HCI_ROLE_PERIPHERAL);
const bool central = !!(conn->lll.role == BT_HCI_ROLE_CENTRAL);
const bool with_instant = proc_with_instant(ctx);
if (ctx->proc == PROC_TERMINATE) {
/* Peer terminate overrides all */
/* Run remote procedure */
rr_act_run(conn);
rr_set_state(conn, RR_STATE_TERMINATE);
} else if (ctx->proc == PROC_UNKNOWN) {
/* Unsupported procedure */
/* Send LL_UNKNOWN_RSP */
struct node_rx_pdu *rx = (struct node_rx_pdu *)param;
struct pdu_data *pdu = (struct pdu_data *)rx->pdu;
ctx->unknown_response.type = pdu->llctrl.opcode;
rr_act_unsupported(conn);
} else if (!with_instant || incompat == INCOMPAT_NO_COLLISION) {
/* No collision
* => Run procedure
*
* Local incompatible procedure request is kept pending.
*/
/*
* Pause local incompatible procedure
* in case we run a procedure with instant
*/
rr_set_collision(conn, with_instant);
/* Run remote procedure */
rr_act_run(conn);
rr_set_state(conn, RR_STATE_ACTIVE);
} else if (periph && incompat == INCOMPAT_RESOLVABLE) {
/* Peripheral collision
* => Run procedure
*
* Local periph procedure completes with error.
*/
/* Run remote procedure */
rr_act_run(conn);
rr_set_state(conn, RR_STATE_ACTIVE);
} else if (central && incompat == INCOMPAT_RESOLVABLE) {
/* Central collision
* => Send reject
*
* Local central incompatible procedure continues unaffected.
*/
/* Send reject */
struct node_rx_pdu *rx = (struct node_rx_pdu *)param;
struct pdu_data *pdu = (struct pdu_data *)rx->pdu;
conn->llcp.remote.reject_opcode = pdu->llctrl.opcode;
rr_act_reject(conn);
} else if (incompat == INCOMPAT_RESERVED) {
/* Protocol violation.
* => Disconnect
* See Bluetooth Core Specification Version 5.3 Vol 6, Part B
* section 5.3 (page 2879) for error codes
*/
ctx_local = llcp_lr_peek(conn);
if (ctx_local->proc == ctx->proc ||
(ctx_local->proc == PROC_CONN_UPDATE &&
ctx->proc == PROC_CONN_PARAM_REQ)) {
conn->llcp_terminate.reason_final =
BT_HCI_ERR_LL_PROC_COLLISION;
} else {
conn->llcp_terminate.reason_final =
BT_HCI_ERR_DIFF_TRANS_COLLISION;
}
}
}
break;
case RR_EVT_DISCONNECT:
rr_act_disconnect(conn);
rr_set_state(conn, RR_STATE_DISCONNECT);
break;
default:
/* Ignore other evts */
break;
}
}
static void rr_st_reject(struct ll_conn *conn, uint8_t evt, void *param)
{
rr_act_reject(conn);
}
static void rr_st_unsupported(struct ll_conn *conn, uint8_t evt, void *param)
{
rr_act_unsupported(conn);
}
static void rr_st_active(struct ll_conn *conn, uint8_t evt, void *param)
{
switch (evt) {
case RR_EVT_RUN:
if (llcp_rr_peek(conn)) {
rr_act_run(conn);
}
break;
case RR_EVT_COMPLETE:
rr_act_complete(conn);
rr_set_state(conn, RR_STATE_IDLE);
break;
case RR_EVT_DISCONNECT:
rr_act_disconnect(conn);
rr_set_state(conn, RR_STATE_DISCONNECT);
break;
default:
/* Ignore other evts */
break;
}
}
static void rr_st_terminate(struct ll_conn *conn, uint8_t evt, void *param)
{
switch (evt) {
case RR_EVT_RUN:
if (llcp_rr_peek(conn)) {
rr_act_run(conn);
}
break;
case RR_EVT_COMPLETE:
rr_act_complete(conn);
rr_set_state(conn, RR_STATE_IDLE);
break;
case RR_EVT_DISCONNECT:
rr_act_disconnect(conn);
rr_set_state(conn, RR_STATE_DISCONNECT);
break;
default:
/* Ignore other evts */
break;
}
}
static void rr_execute_fsm(struct ll_conn *conn, uint8_t evt, void *param)
{
switch (conn->llcp.remote.state) {
case RR_STATE_DISCONNECT:
rr_st_disconnect(conn, evt, param);
break;
case RR_STATE_IDLE:
rr_st_idle(conn, evt, param);
break;
case RR_STATE_REJECT:
rr_st_reject(conn, evt, param);
break;
case RR_STATE_UNSUPPORTED:
rr_st_unsupported(conn, evt, param);
break;
case RR_STATE_ACTIVE:
rr_st_active(conn, evt, param);
break;
case RR_STATE_TERMINATE:
rr_st_terminate(conn, evt, param);
break;
default:
/* Unknown state */
LL_ASSERT(0);
}
}
void llcp_rr_init(struct ll_conn *conn)
{
rr_set_state(conn, RR_STATE_DISCONNECT);
conn->llcp.remote.prt_expire = 0U;
}
void llcp_rr_prepare(struct ll_conn *conn, struct node_rx_pdu *rx)
{
rr_execute_fsm(conn, RR_EVT_PREPARE, rx);
}
void llcp_rr_run(struct ll_conn *conn)
{
rr_execute_fsm(conn, RR_EVT_RUN, NULL);
}
void llcp_rr_complete(struct ll_conn *conn)
{
rr_execute_fsm(conn, RR_EVT_COMPLETE, NULL);
}
void llcp_rr_connect(struct ll_conn *conn)
{
rr_execute_fsm(conn, RR_EVT_CONNECT, NULL);
}
void llcp_rr_disconnect(struct ll_conn *conn)
{
rr_execute_fsm(conn, RR_EVT_DISCONNECT, NULL);
}
/*
* Create procedure lookup table for any given PDU opcode, these are the opcodes
* that can initiate a new remote procedure.
*
* NOTE: All array elements that are not initialized explicitly are
* zero-initialized, which matches PROC_UNKNOWN.
*
* NOTE: When initializing an array of unknown size, the largest subscript for
* which an initializer is specified determines the size of the array being
* declared.
*/
BUILD_ASSERT(0 == PROC_UNKNOWN, "PROC_UNKNOWN must have the value ZERO");
enum accept_role {
/* No role accepts this opcode */
ACCEPT_ROLE_NONE = 0,
/* Central role accepts this opcode */
ACCEPT_ROLE_CENTRAL = (1 << BT_HCI_ROLE_CENTRAL),
/* Peripheral role accepts this opcode */
ACCEPT_ROLE_PERIPHERAL = (1 << BT_HCI_ROLE_PERIPHERAL),
/* Both roles accepts this opcode */
ACCEPT_ROLE_BOTH = (ACCEPT_ROLE_CENTRAL | ACCEPT_ROLE_PERIPHERAL),
};
struct proc_role {
enum llcp_proc proc;
enum accept_role accept;
};
static const struct proc_role new_proc_lut[] = {
#if defined(CONFIG_BT_PERIPHERAL)
[PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND] = { PROC_CONN_UPDATE, ACCEPT_ROLE_PERIPHERAL },
[PDU_DATA_LLCTRL_TYPE_CHAN_MAP_IND] = { PROC_CHAN_MAP_UPDATE, ACCEPT_ROLE_PERIPHERAL },
#endif /* CONFIG_BT_PERIPHERAL */
[PDU_DATA_LLCTRL_TYPE_TERMINATE_IND] = { PROC_TERMINATE, ACCEPT_ROLE_BOTH },
#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_PERIPHERAL)
[PDU_DATA_LLCTRL_TYPE_ENC_REQ] = { PROC_ENCRYPTION_START, ACCEPT_ROLE_PERIPHERAL },
#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_PERIPHERAL */
[PDU_DATA_LLCTRL_TYPE_ENC_RSP] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
[PDU_DATA_LLCTRL_TYPE_START_ENC_REQ] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
[PDU_DATA_LLCTRL_TYPE_START_ENC_RSP] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
[PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
[PDU_DATA_LLCTRL_TYPE_FEATURE_REQ] = { PROC_FEATURE_EXCHANGE, ACCEPT_ROLE_PERIPHERAL },
[PDU_DATA_LLCTRL_TYPE_FEATURE_RSP] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_PERIPHERAL)
[PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ] = { PROC_ENCRYPTION_PAUSE, ACCEPT_ROLE_PERIPHERAL },
#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_PERIPHERAL */
[PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
[PDU_DATA_LLCTRL_TYPE_VERSION_IND] = { PROC_VERSION_EXCHANGE, ACCEPT_ROLE_BOTH },
[PDU_DATA_LLCTRL_TYPE_REJECT_IND] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
#if defined(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG) && defined(CONFIG_BT_CENTRAL)
[PDU_DATA_LLCTRL_TYPE_PER_INIT_FEAT_XCHG] = { PROC_FEATURE_EXCHANGE, ACCEPT_ROLE_CENTRAL },
#endif /* CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG && CONFIG_BT_CENTRAL */
#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ)
[PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ] = { PROC_CONN_PARAM_REQ, ACCEPT_ROLE_BOTH },
#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */
[PDU_DATA_LLCTRL_TYPE_CONN_PARAM_RSP] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
[PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
#if defined(CONFIG_BT_CTLR_LE_PING)
[PDU_DATA_LLCTRL_TYPE_PING_REQ] = { PROC_LE_PING, ACCEPT_ROLE_BOTH },
#endif /* CONFIG_BT_CTLR_LE_PING */
[PDU_DATA_LLCTRL_TYPE_PING_RSP] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
#if defined(CONFIG_BT_CTLR_DATA_LENGTH)
[PDU_DATA_LLCTRL_TYPE_LENGTH_REQ] = { PROC_DATA_LENGTH_UPDATE, ACCEPT_ROLE_BOTH },
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */
[PDU_DATA_LLCTRL_TYPE_LENGTH_RSP] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
#if defined(CONFIG_BT_CTLR_PHY)
[PDU_DATA_LLCTRL_TYPE_PHY_REQ] = { PROC_PHY_UPDATE, ACCEPT_ROLE_BOTH },
#endif /* CONFIG_BT_CTLR_PHY */
[PDU_DATA_LLCTRL_TYPE_PHY_RSP] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
[PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_CENTRAL)
[PDU_DATA_LLCTRL_TYPE_MIN_USED_CHAN_IND] = { PROC_MIN_USED_CHANS, ACCEPT_ROLE_CENTRAL },
#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_CENTRAL */
#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP)
[PDU_DATA_LLCTRL_TYPE_CTE_REQ] = { PROC_CTE_REQ, ACCEPT_ROLE_BOTH },
#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */
[PDU_DATA_LLCTRL_TYPE_CTE_RSP] = { PROC_UNKNOWN, ACCEPT_ROLE_NONE },
};
void llcp_rr_new(struct ll_conn *conn, struct node_rx_pdu *rx, bool valid_pdu)
{
struct proc_ctx *ctx;
struct pdu_data *pdu;
uint8_t proc = PROC_UNKNOWN;
pdu = (struct pdu_data *)rx->pdu;
/* Is this a valid opcode */
if (valid_pdu && pdu->llctrl.opcode < ARRAY_SIZE(new_proc_lut)) {
/* Lookup procedure */
uint8_t role_mask = (1 << conn->lll.role);
struct proc_role pr = new_proc_lut[pdu->llctrl.opcode];
if (pr.accept & role_mask) {
proc = pr.proc;
}
}
if (proc == PROC_TERMINATE) {
rr_abort(conn);
}
ctx = llcp_create_remote_procedure(proc);
if (!ctx) {
return;
}
/* Enqueue procedure */
rr_enqueue(conn, ctx);
/* Prepare procedure */
llcp_rr_prepare(conn, rx);
rr_check_done(conn, ctx);
/* Handle PDU */
ctx = llcp_rr_peek(conn);
if (ctx) {
llcp_rr_rx(conn, ctx, rx);
}
}
static void rr_abort(struct ll_conn *conn)
{
struct proc_ctx *ctx;
/* Flush all pending procedures */
ctx = rr_dequeue(conn);
while (ctx) {
llcp_proc_ctx_release(ctx);
ctx = rr_dequeue(conn);
}
llcp_rr_prt_stop(conn);
rr_set_collision(conn, 0U);
rr_set_state(conn, RR_STATE_IDLE);
}
#ifdef ZTEST_UNITTEST
bool rr_is_disconnected(struct ll_conn *conn)
{
return conn->llcp.remote.state == RR_STATE_DISCONNECT;
}
bool rr_is_idle(struct ll_conn *conn)
{
return conn->llcp.remote.state == RR_STATE_IDLE;
}
void test_int_remote_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_rr_peek(&conn);
zassert_is_null(peek_ctx, NULL);
dequeue_ctx = rr_dequeue(&conn);
zassert_is_null(dequeue_ctx, NULL);
rr_enqueue(&conn, &ctx);
peek_ctx = (struct proc_ctx *)sys_slist_peek_head(&conn.llcp.remote.pend_proc_list);
zassert_equal_ptr(peek_ctx, &ctx, NULL);
peek_ctx = llcp_rr_peek(&conn);
zassert_equal_ptr(peek_ctx, &ctx, NULL);
dequeue_ctx = rr_dequeue(&conn);
zassert_equal_ptr(dequeue_ctx, &ctx, NULL);
peek_ctx = llcp_rr_peek(&conn);
zassert_is_null(peek_ctx, NULL);
dequeue_ctx = rr_dequeue(&conn);
zassert_is_null(dequeue_ctx, NULL);
}
#endif