/* * Copyright (c) 2022 Demant * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include "hal/ecb.h" #include "hal/ccm.h" #include "util/util.h" #include "util/mem.h" #include "util/memq.h" #include "util/dbuf.h" #include "pdu_df.h" #include "lll/pdu_vendor.h" #include "pdu.h" #include "ll.h" #include "ll_feat.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_types.h" #include "ull_conn_iso_types.h" #include "ull_internal.h" #include "ull_llcp.h" #include "ull_llcp_internal.h" #include "ull_llcp_features.h" #include "ull_conn_internal.h" #include "ull_iso_internal.h" #include "ull_conn_iso_internal.h" #include "ull_peripheral_iso_internal.h" #include "ull_central_iso_internal.h" #include #include "hal/debug.h" static void cc_ntf_established(struct ll_conn *conn, struct proc_ctx *ctx) { struct node_rx_conn_iso_estab *pdu; struct node_rx_pdu *ntf; uint8_t piggy_back; /* Allocate ntf node */ ntf = ctx->node_ref.rx; LL_ASSERT(ntf); ctx->node_ref.rx = NULL; piggy_back = (ntf->hdr.type != NODE_RX_TYPE_RETAIN); ntf->hdr.type = NODE_RX_TYPE_CIS_ESTABLISHED; ntf->hdr.handle = conn->lll.handle; ntf->rx_ftr.param = ll_conn_iso_stream_get(ctx->data.cis_create.cis_handle); pdu = (struct node_rx_conn_iso_estab *)ntf->pdu; pdu->cis_handle = ctx->data.cis_create.cis_handle; pdu->status = ctx->data.cis_create.error; if (!piggy_back) { /* Enqueue notification towards LL */ ll_rx_put_sched(ntf->hdr.link, ntf); } } #if defined(CONFIG_BT_PERIPHERAL) /* LLCP Remote Procedure FSM states */ enum { /* Establish Procedure */ RP_CC_STATE_IDLE = LLCP_STATE_IDLE, RP_CC_STATE_WAIT_RX_CIS_REQ, RP_CC_STATE_WAIT_REPLY, RP_CC_STATE_WAIT_TX_CIS_RSP, RP_CC_STATE_WAIT_TX_REJECT_IND, RP_CC_STATE_WAIT_RX_CIS_IND, RP_CC_STATE_WAIT_INSTANT, RP_CC_STATE_WAIT_CIS_ESTABLISHED, RP_CC_STATE_WAIT_NTF_AVAIL, }; /* LLCP Remote Procedure FSM events */ enum { /* Procedure prepared */ RP_CC_EVT_RUN, /* Request received */ RP_CC_EVT_CIS_REQ, /* Response received */ RP_CC_EVT_CIS_RSP, /* Indication received */ RP_CC_EVT_CIS_IND, /* Create request accept reply */ RP_CC_EVT_CIS_REQ_ACCEPT, /* Create request decline reply */ RP_CC_EVT_CIS_REQ_REJECT, /* Reject response received */ RP_CC_EVT_REJECT, /* Established */ RP_CC_EVT_CIS_ESTABLISHED, /* Unknown response received */ RP_CC_EVT_UNKNOWN, }; static void rp_cc_check_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param); /* * LLCP Remote Procedure FSM */ static void llcp_rp_cc_tx_rsp(struct ll_conn *conn, struct proc_ctx *ctx) { uint16_t delay_conn_events; uint16_t conn_event_count; struct pdu_data *pdu; struct node_tx *tx; /* Allocate tx node */ tx = llcp_tx_alloc(conn, ctx); LL_ASSERT(tx); pdu = (struct pdu_data *)tx->pdu; conn_event_count = ctx->data.cis_create.conn_event_count; /* Postpone if instant is in this or next connection event. This would handle obsolete value * due to retransmission, as well as incorrect behavior by central. * We need at least 2 connection events to get ready. First for receiving the indication, * the second for setting up the CIS. */ ctx->data.cis_create.conn_event_count = MAX(ctx->data.cis_create.conn_event_count, ull_conn_event_counter(conn) + 2U); delay_conn_events = ctx->data.cis_create.conn_event_count - conn_event_count; /* If instant is postponed, calculate the offset to add to CIS_Offset_Min and * CIS_Offset_Max. * * BT Core v5.3, Vol 6, Part B, section 5.1.15: * Two windows are equivalent if they have the same width and the difference between their * start times is an integer multiple of ISO_Interval for the CIS. * * The offset shall compensate for the relation between ISO- and connection interval. The * offset translates to what is additionally needed to move the window up to an integer * number of ISO intervals. */ if (delay_conn_events) { uint32_t conn_interval_us = conn->lll.interval * CONN_INT_UNIT_US; uint32_t iso_interval_us = ctx->data.cis_create.iso_interval * ISO_INT_UNIT_US; uint8_t iso_intervals; uint32_t offset_us; iso_intervals = DIV_ROUND_UP(delay_conn_events * conn_interval_us, iso_interval_us); offset_us = (iso_intervals * iso_interval_us) - (delay_conn_events * conn_interval_us); ctx->data.cis_create.cis_offset_min += offset_us; ctx->data.cis_create.cis_offset_max += offset_us; } llcp_pdu_encode_cis_rsp(ctx, pdu); ctx->tx_opcode = pdu->llctrl.opcode; /* Enqueue LL Control PDU towards LLL */ llcp_tx_enqueue(conn, tx); } static void llcp_rp_cc_tx_reject(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode) { struct node_tx *tx; struct pdu_data *pdu; /* Allocate tx node */ tx = ctx->node_ref.tx; LL_ASSERT(tx); ctx->node_ref.tx = NULL; pdu = (struct pdu_data *)tx->pdu; /* Encode LL Control PDU */ llcp_pdu_encode_reject_ext_ind(pdu, opcode, ctx->data.cis_create.error); ctx->tx_opcode = pdu->llctrl.opcode; /* Enqueue LL Control PDU towards LLL */ llcp_tx_enqueue(conn, tx); } static void rp_cc_ntf_create(struct ll_conn *conn, struct proc_ctx *ctx) { struct node_rx_pdu *ntf; struct node_rx_conn_iso_req *pdu; ntf = ctx->node_ref.rx; ctx->node_ref.rx = NULL; LL_ASSERT(ntf); ntf->hdr.type = NODE_RX_TYPE_CIS_REQUEST; ntf->hdr.handle = conn->lll.handle; pdu = (struct node_rx_conn_iso_req *)ntf->pdu; pdu->cig_id = ctx->data.cis_create.cig_id; pdu->cis_id = ctx->data.cis_create.cis_id; pdu->cis_handle = ctx->data.cis_create.cis_handle; ctx->data.cis_create.host_request_to = 0U; } static void rp_cc_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { cc_ntf_established(conn, ctx); llcp_rr_complete(conn); ctx->state = RP_CC_STATE_IDLE; } static void rp_cc_send_cis_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { if (llcp_rr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { ctx->state = RP_CC_STATE_WAIT_TX_CIS_RSP; } else { llcp_rp_cc_tx_rsp(conn, ctx); /* Wait for the LL_CIS_IND */ ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_CIS_IND; ctx->state = RP_CC_STATE_WAIT_RX_CIS_IND; } } static void rp_cc_send_reject_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { if (llcp_rr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { ctx->state = RP_CC_STATE_WAIT_TX_REJECT_IND; } else { /* Allocate TX node to use, store in case we need to wait for NTF node */ ctx->node_ref.tx = llcp_tx_alloc(conn, ctx); if (ctx->data.cis_create.error == BT_HCI_ERR_CONN_ACCEPT_TIMEOUT) { /* We complete with error, so we must generate NTF, thus we must make sure * we have a node to use for NTF before TX'ing */ if (!llcp_ntf_alloc_is_available()) { ctx->state = RP_CC_STATE_WAIT_NTF_AVAIL; return; } ctx->node_ref.rx = llcp_ntf_alloc(); /* Mark node as RETAIN to trigger put/sched */ ctx->node_ref.rx->hdr.type = NODE_RX_TYPE_RETAIN; } llcp_rp_cc_tx_reject(conn, ctx, PDU_DATA_LLCTRL_TYPE_CIS_REQ); if (ctx->data.cis_create.error == BT_HCI_ERR_CONN_ACCEPT_TIMEOUT) { /* We reject due to an accept timeout, so we should generate NTF */ rp_cc_complete(conn, ctx, evt, param); } else { /* Otherwise we quietly complete the procedure */ llcp_rr_complete(conn); ctx->state = RP_CC_STATE_IDLE; } } } static void rp_cc_state_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_CC_EVT_RUN: ctx->state = RP_CC_STATE_WAIT_RX_CIS_REQ; break; default: /* Ignore other evts */ break; } } static uint8_t rp_cc_check_phy(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu) { if (!phy_valid(pdu->llctrl.cis_req.c_phy) || !phy_valid(pdu->llctrl.cis_req.p_phy)) { /* zero, more than one or any rfu bit selected in either phy */ return BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL; } #if defined(CONFIG_BT_CTLR_PHY) const uint8_t phys = pdu->llctrl.cis_req.p_phy | pdu->llctrl.cis_req.c_phy; if (((phys & PHY_2M) && !feature_phy_2m(conn)) || ((phys & PHY_CODED) && !feature_phy_coded(conn))) { /* Unsupported phy selected */ return BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL; } #endif /* CONFIG_BT_CTLR_PHY */ return BT_HCI_ERR_SUCCESS; } static void rp_cc_state_wait_rx_cis_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { struct pdu_data *pdu = (struct pdu_data *)param; switch (evt) { case RP_CC_EVT_CIS_REQ: /* Handle CIS request */ llcp_pdu_decode_cis_req(ctx, pdu); /* Check PHY */ ctx->data.cis_create.error = rp_cc_check_phy(conn, ctx, pdu); if (ctx->data.cis_create.error == BT_HCI_ERR_SUCCESS) { ctx->data.cis_create.error = ull_peripheral_iso_acquire(conn, &pdu->llctrl.cis_req, &ctx->data.cis_create.cis_handle); } if (ctx->data.cis_create.error == BT_HCI_ERR_SUCCESS) { /* Now controller accepts, so go ask the host to accept or decline */ rp_cc_ntf_create(conn, ctx); ctx->state = RP_CC_STATE_WAIT_REPLY; } else { /* Now controller rejects, right out */ rp_cc_send_reject_ind(conn, ctx, evt, param); } break; default: /* Ignore other evts */ break; } } static void rp_cc_state_wait_tx_cis_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_CC_EVT_RUN: rp_cc_send_cis_rsp(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void rp_cc_state_wait_tx_reject_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_CC_EVT_RUN: rp_cc_send_reject_ind(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void rp_cc_state_wait_rx_cis_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { struct pdu_data *pdu = (struct pdu_data *)param; switch (evt) { case RP_CC_EVT_CIS_IND: llcp_pdu_decode_cis_ind(ctx, pdu); if (!ull_peripheral_iso_setup(&pdu->llctrl.cis_ind, ctx->data.cis_create.cig_id, ctx->data.cis_create.cis_handle, &ctx->data.cis_create.conn_event_count)) { /* CIS has been setup, go wait for 'instant' before starting */ ctx->state = RP_CC_STATE_WAIT_INSTANT; /* Mark node as RETAIN to keep until we need for NTF */ llcp_rx_node_retain(ctx); /* Check if this connection event is where we need to start the CIS */ rp_cc_check_instant(conn, ctx, evt, param); break; } /* If we get to here the CIG_ID referred in req/acquire has become void/invalid */ /* This cannot happen unless the universe has started to deflate */ LL_ASSERT(0); case RP_CC_EVT_REJECT: /* Handle CIS creation rejection */ break; default: /* Ignore other evts */ break; } } static void rp_cc_state_wait_ntf_avail(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_CC_EVT_RUN: if (llcp_ntf_alloc_is_available()) { ctx->node_ref.rx = llcp_ntf_alloc(); /* Mark node as RETAIN to trigger put/sched */ ctx->node_ref.rx->hdr.type = NODE_RX_TYPE_RETAIN; /* Now we're good to TX reject and complete procedure*/ llcp_rp_cc_tx_reject(conn, ctx, PDU_DATA_LLCTRL_TYPE_CIS_REQ); rp_cc_complete(conn, ctx, evt, param); } break; default: /* Ignore other evts */ break; } } static void rp_cc_check_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { uint16_t start_event_count; uint16_t event_counter; event_counter = ull_conn_event_counter(conn); start_event_count = ctx->data.cis_create.conn_event_count; if (is_instant_reached_or_passed(start_event_count, event_counter)) { uint16_t instant_latency = (event_counter - start_event_count) & 0xffff; /* Start CIS */ ull_conn_iso_start(conn, ctx->data.cis_create.cis_handle, conn->llcp.prep.ticks_at_expire, conn->llcp.prep.remainder, instant_latency); /* Now we can wait for CIS to become established */ ctx->state = RP_CC_STATE_WAIT_CIS_ESTABLISHED; } } static void rp_cc_state_wait_reply(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_CC_EVT_CIS_REQ_ACCEPT: /* Continue procedure in next prepare run */ ctx->state = RP_CC_STATE_WAIT_TX_CIS_RSP; break; case RP_CC_EVT_RUN: /* Update 'time' and check for timeout on the Reply */ ctx->data.cis_create.host_request_to += (conn->lll.interval * CONN_INT_UNIT_US); if (ctx->data.cis_create.host_request_to < conn->connect_accept_to) { break; } /* Reject 'reason/error' */ ctx->data.cis_create.error = BT_HCI_ERR_CONN_ACCEPT_TIMEOUT; /* If timeout is hit, fall through and reject */ case RP_CC_EVT_CIS_REQ_REJECT: /* CIS Request is rejected, so clean up CIG/CIS acquisitions */ ull_peripheral_iso_release(ctx->data.cis_create.cis_handle); /* Continue procedure in next prepare run */ ctx->state = RP_CC_STATE_WAIT_TX_REJECT_IND; break; default: /* Ignore other evts */ break; } } static void rp_cc_state_wait_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_CC_EVT_RUN: rp_cc_check_instant(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void rp_cc_state_wait_cis_established(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_CC_EVT_CIS_ESTABLISHED: rp_cc_complete(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void rp_cc_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (ctx->state) { /* Create Procedure */ case RP_CC_STATE_IDLE: rp_cc_state_idle(conn, ctx, evt, param); break; case RP_CC_STATE_WAIT_RX_CIS_REQ: rp_cc_state_wait_rx_cis_req(conn, ctx, evt, param); break; case RP_CC_STATE_WAIT_TX_REJECT_IND: rp_cc_state_wait_tx_reject_ind(conn, ctx, evt, param); break; case RP_CC_STATE_WAIT_TX_CIS_RSP: rp_cc_state_wait_tx_cis_rsp(conn, ctx, evt, param); break; case RP_CC_STATE_WAIT_REPLY: rp_cc_state_wait_reply(conn, ctx, evt, param); break; case RP_CC_STATE_WAIT_RX_CIS_IND: rp_cc_state_wait_rx_cis_ind(conn, ctx, evt, param); break; case RP_CC_STATE_WAIT_INSTANT: rp_cc_state_wait_instant(conn, ctx, evt, param); break; case RP_CC_STATE_WAIT_CIS_ESTABLISHED: rp_cc_state_wait_cis_established(conn, ctx, evt, param); break; case RP_CC_STATE_WAIT_NTF_AVAIL: rp_cc_state_wait_ntf_avail(conn, ctx, evt, param); break; default: /* Unknown state */ LL_ASSERT(0); } } void llcp_rp_cc_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx) { struct pdu_data *pdu = (struct pdu_data *)rx->pdu; switch (pdu->llctrl.opcode) { case PDU_DATA_LLCTRL_TYPE_CIS_REQ: rp_cc_execute_fsm(conn, ctx, RP_CC_EVT_CIS_REQ, pdu); break; case PDU_DATA_LLCTRL_TYPE_CIS_IND: rp_cc_execute_fsm(conn, ctx, RP_CC_EVT_CIS_IND, pdu); break; case PDU_DATA_LLCTRL_TYPE_REJECT_IND: case PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND: rp_cc_execute_fsm(conn, ctx, RP_CC_EVT_REJECT, pdu); break; default: /* Invalid behaviour */ /* Invalid PDU received so terminate connection */ conn->llcp_terminate.reason_final = BT_HCI_ERR_LMP_PDU_NOT_ALLOWED; llcp_rr_complete(conn); ctx->state = RP_CC_STATE_IDLE; break; } } bool llcp_rp_cc_awaiting_reply(struct proc_ctx *ctx) { return (ctx->state == RP_CC_STATE_WAIT_REPLY); } bool llcp_rp_cc_awaiting_established(struct proc_ctx *ctx) { return (ctx->state == RP_CC_STATE_WAIT_CIS_ESTABLISHED); } void llcp_rp_cc_accept(struct ll_conn *conn, struct proc_ctx *ctx) { rp_cc_execute_fsm(conn, ctx, RP_CC_EVT_CIS_REQ_ACCEPT, NULL); } void llcp_rp_cc_reject(struct ll_conn *conn, struct proc_ctx *ctx) { rp_cc_execute_fsm(conn, ctx, RP_CC_EVT_CIS_REQ_REJECT, NULL); } void llcp_rp_cc_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) { rp_cc_execute_fsm(conn, ctx, RP_CC_EVT_RUN, param); } bool llcp_rp_cc_awaiting_instant(struct proc_ctx *ctx) { return (ctx->state == RP_CC_STATE_WAIT_INSTANT); } void llcp_rp_cc_established(struct ll_conn *conn, struct proc_ctx *ctx) { rp_cc_execute_fsm(conn, ctx, RP_CC_EVT_CIS_ESTABLISHED, NULL); } #endif /* CONFIG_BT_PERIPHERAL */ #if defined(CONFIG_BT_CTLR_CENTRAL_ISO) static void lp_cc_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param); /* LLCP Local Procedure FSM states */ enum { LP_CC_STATE_IDLE = LLCP_STATE_IDLE, LP_CC_STATE_WAIT_NTF_AVAIL, LP_CC_STATE_WAIT_OFFSET_CALC, LP_CC_STATE_WAIT_OFFSET_CALC_TX_REQ, LP_CC_STATE_WAIT_TX_CIS_REQ, LP_CC_STATE_WAIT_RX_CIS_RSP, LP_CC_STATE_WAIT_NOTIFY_CANCEL, LP_CC_STATE_WAIT_RX_CIS_RSP_CANCEL, LP_CC_STATE_WAIT_TX_CIS_IND, LP_CC_STATE_WAIT_INSTANT, LP_CC_STATE_WAIT_ESTABLISHED, }; /* LLCP Local Procedure CIS Creation FSM events */ enum { /* Procedure run */ LP_CC_EVT_RUN, /* Offset calculation reply received */ LP_CC_EVT_OFFSET_CALC_REPLY, /* Response received */ LP_CC_EVT_CIS_RSP, /* Reject response received */ LP_CC_EVT_REJECT, /* CIS established */ LP_CC_EVT_ESTABLISHED, /* Unknown response received */ LP_CC_EVT_UNKNOWN, }; static void lp_cc_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode) { 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 (opcode) { case PDU_DATA_LLCTRL_TYPE_CIS_REQ: llcp_pdu_encode_cis_req(ctx, pdu); break; case PDU_DATA_LLCTRL_TYPE_CIS_IND: llcp_pdu_encode_cis_ind(ctx, pdu); break; default: /* Unknown opcode */ LL_ASSERT(0); break; } ctx->tx_opcode = pdu->llctrl.opcode; /* Enqueue LL Control PDU towards LLL */ llcp_tx_enqueue(conn, tx); } void llcp_lp_cc_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx) { struct pdu_data *pdu = (struct pdu_data *)rx->pdu; switch (pdu->llctrl.opcode) { case PDU_DATA_LLCTRL_TYPE_CIS_RSP: lp_cc_execute_fsm(conn, ctx, LP_CC_EVT_CIS_RSP, pdu); break; case PDU_DATA_LLCTRL_TYPE_REJECT_IND: case PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND: lp_cc_execute_fsm(conn, ctx, LP_CC_EVT_REJECT, pdu); break; default: /* 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_CC_STATE_IDLE; break; } } void llcp_lp_cc_offset_calc_reply(struct ll_conn *conn, struct proc_ctx *ctx) { lp_cc_execute_fsm(conn, ctx, LP_CC_EVT_OFFSET_CALC_REPLY, NULL); } static void lp_cc_offset_calc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { if (llcp_lr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { ctx->state = LP_CC_STATE_WAIT_OFFSET_CALC_TX_REQ; } else { int err; /* Update conn_event_count */ err = ull_central_iso_cis_offset_get(ctx->data.cis_create.cis_handle, &ctx->data.cis_create.cis_offset_min, &ctx->data.cis_create.cis_offset_max, &ctx->data.cis_create.conn_event_count); if (err) { ctx->state = LP_CC_STATE_WAIT_OFFSET_CALC; return; } lp_cc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_CIS_REQ); ctx->state = LP_CC_STATE_WAIT_RX_CIS_RSP; ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_CIS_RSP; } } static void lp_cc_st_wait_offset_calc_tx_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_CC_EVT_RUN: lp_cc_offset_calc_req(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void lp_cc_st_wait_offset_calc(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_CC_EVT_RUN: /* TODO: May be have a timeout calculating the CIS offset? * otherwise, ignore */ break; case LP_CC_EVT_OFFSET_CALC_REPLY: ctx->state = LP_CC_STATE_WAIT_TX_CIS_REQ; break; default: /* Ignore other evts */ break; } } static void lp_cc_send_cis_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { if (llcp_lr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { ctx->state = LP_CC_STATE_WAIT_TX_CIS_REQ; } else { lp_cc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_CIS_REQ); ctx->state = LP_CC_STATE_WAIT_RX_CIS_RSP; ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_CIS_RSP; } } static void lp_cc_st_wait_tx_cis_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_CC_EVT_RUN: lp_cc_send_cis_req(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void lp_cc_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { cc_ntf_established(conn, ctx); llcp_lr_complete(conn); ctx->state = LP_CC_STATE_IDLE; } static void lp_cc_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_CC_EVT_RUN: switch (ctx->proc) { case PROC_CIS_CREATE: /* In case feature exchange completed after CIS create was enqueued * peer CIS peripheral support should be confirmed */ if (feature_peer_iso_peripheral(conn)) { lp_cc_offset_calc_req(conn, ctx, evt, param); } else { /* Peer doesn't support CIS Peripheral so report unsupported */ ctx->data.cis_create.error = BT_HCI_ERR_UNSUPP_REMOTE_FEATURE; ctx->state = LP_CC_STATE_WAIT_NTF_AVAIL; } break; default: /* Unknown procedure */ LL_ASSERT(0); break; } break; default: /* Ignore other evts */ break; } } static void lp_cc_state_wait_ntf_avail(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_CC_EVT_RUN: if (llcp_ntf_alloc_is_available()) { ctx->node_ref.rx = llcp_ntf_alloc(); /* Mark node as RETAIN to trigger put/sched */ ctx->node_ref.rx->hdr.type = NODE_RX_TYPE_RETAIN; /* Now we're good to complete procedure*/ lp_cc_complete(conn, ctx, evt, param); } break; default: /* Ignore other evts */ break; } } static void cc_prepare_cis_ind(struct ll_conn *conn, struct proc_ctx *ctx) { uint8_t err; /* Setup central parameters based on CIS_RSP */ err = ull_central_iso_setup(ctx->data.cis_create.cis_handle, &ctx->data.cis_create.cig_sync_delay, &ctx->data.cis_create.cis_sync_delay, &ctx->data.cis_create.cis_offset_min, &ctx->data.cis_create.cis_offset_max, &ctx->data.cis_create.conn_event_count, ctx->data.cis_create.aa); LL_ASSERT(!err); ctx->state = LP_CC_STATE_WAIT_INSTANT; ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; } static void lp_cc_send_cis_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { if (llcp_lr_ispaused(conn) || !llcp_tx_alloc_peek(conn, ctx)) { ctx->state = LP_CC_STATE_WAIT_TX_CIS_IND; } else { cc_prepare_cis_ind(conn, ctx); lp_cc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_CIS_IND); } } static void lp_cc_st_wait_rx_cis_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { struct pdu_data *pdu = (struct pdu_data *)param; switch (evt) { case LP_CC_EVT_CIS_RSP: /* TODO: Reject response if outside offset range? */ llcp_pdu_decode_cis_rsp(ctx, param); /* Mark RX node to NOT release */ llcp_rx_node_retain(ctx); lp_cc_send_cis_ind(conn, ctx, evt, param); break; case LP_CC_EVT_UNKNOWN: /* Unsupported in peer, so disable locally for this connection */ feature_unmask_peer_features(conn, LL_FEAT_BIT_CIS_PERIPHERAL); ctx->data.cis_create.error = BT_HCI_ERR_UNSUPP_REMOTE_FEATURE; lp_cc_complete(conn, ctx, evt, param); break; case LP_CC_EVT_REJECT: if (pdu->llctrl.reject_ext_ind.error_code == BT_HCI_ERR_UNSUPP_REMOTE_FEATURE) { /* Unsupported in peer, so disable locally for this connection */ feature_unmask_peer_features(conn, LL_FEAT_BIT_CIS_PERIPHERAL); } ctx->data.cis_create.error = pdu->llctrl.reject_ext_ind.error_code; lp_cc_complete(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void lp_cc_st_wait_notify_cancel(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_CC_EVT_RUN: if (llcp_ntf_alloc_is_available()) { ctx->node_ref.rx = llcp_ntf_alloc(); /* Mark node as RETAIN to trigger put/sched */ ctx->node_ref.rx->hdr.type = NODE_RX_TYPE_RETAIN; ctx->state = LP_CC_STATE_WAIT_ESTABLISHED; llcp_lp_cc_established(conn, ctx); } break; default: /* Ignore other evts */ break; } } static void lp_cc_st_wait_rx_cis_rsp_cancel(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { struct pdu_data *pdu; struct node_tx *tx; switch (evt) { case LP_CC_EVT_CIS_RSP: /* Allocate tx node */ tx = llcp_tx_alloc(conn, ctx); LL_ASSERT(tx); pdu = (struct pdu_data *)tx->pdu; /* Encode LL Control PDU */ llcp_pdu_encode_reject_ext_ind(pdu, PDU_DATA_LLCTRL_TYPE_CIS_RSP, ctx->data.cis_create.error); /* Enqueue LL Control PDU towards LLL */ llcp_tx_enqueue(conn, tx); lp_cc_complete(conn, ctx, evt, param); break; case LP_CC_EVT_UNKNOWN: case LP_CC_EVT_REJECT: lp_cc_complete(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void lp_cc_st_wait_tx_cis_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_CC_EVT_RUN: lp_cc_send_cis_ind(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void lp_cc_check_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { uint16_t start_event_count; uint16_t instant_latency; uint16_t event_counter; event_counter = ull_conn_event_counter(conn); start_event_count = ctx->data.cis_create.conn_event_count; instant_latency = (event_counter - start_event_count) & 0xffff; if (instant_latency <= 0x7fff) { /* Start CIS */ ull_conn_iso_start(conn, ctx->data.cis_create.cis_handle, conn->llcp.prep.ticks_at_expire, conn->llcp.prep.remainder, instant_latency); /* Now we can wait for CIS to become established */ ctx->state = LP_CC_STATE_WAIT_ESTABLISHED; } } static void lp_cc_st_wait_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_CC_EVT_RUN: lp_cc_check_instant(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void lp_cc_st_wait_established(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_CC_EVT_ESTABLISHED: /* CIS was established, so let's go ahead and complete procedure */ lp_cc_complete(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void lp_cc_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (ctx->state) { case LP_CC_STATE_IDLE: lp_cc_st_idle(conn, ctx, evt, param); break; case LP_CC_STATE_WAIT_NTF_AVAIL: lp_cc_state_wait_ntf_avail(conn, ctx, evt, param); break; case LP_CC_STATE_WAIT_OFFSET_CALC_TX_REQ: lp_cc_st_wait_offset_calc_tx_req(conn, ctx, evt, param); break; case LP_CC_STATE_WAIT_OFFSET_CALC: lp_cc_st_wait_offset_calc(conn, ctx, evt, param); break; case LP_CC_STATE_WAIT_TX_CIS_REQ: lp_cc_st_wait_tx_cis_req(conn, ctx, evt, param); break; case LP_CC_STATE_WAIT_RX_CIS_RSP: lp_cc_st_wait_rx_cis_rsp(conn, ctx, evt, param); break; case LP_CC_STATE_WAIT_NOTIFY_CANCEL: lp_cc_st_wait_notify_cancel(conn, ctx, evt, param); break; case LP_CC_STATE_WAIT_RX_CIS_RSP_CANCEL: lp_cc_st_wait_rx_cis_rsp_cancel(conn, ctx, evt, param); break; case LP_CC_STATE_WAIT_TX_CIS_IND: lp_cc_st_wait_tx_cis_ind(conn, ctx, evt, param); break; case LP_CC_STATE_WAIT_INSTANT: lp_cc_st_wait_instant(conn, ctx, evt, param); break; case LP_CC_STATE_WAIT_ESTABLISHED: lp_cc_st_wait_established(conn, ctx, evt, param); break; default: /* Unknown state */ LL_ASSERT(0); break; } } void llcp_lp_cc_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) { lp_cc_execute_fsm(conn, ctx, LP_CC_EVT_RUN, param); } bool llcp_lp_cc_is_active(struct proc_ctx *ctx) { return ctx->state != LP_CC_STATE_IDLE; } bool llcp_lp_cc_awaiting_established(struct proc_ctx *ctx) { return (ctx->state == LP_CC_STATE_WAIT_ESTABLISHED); } void llcp_lp_cc_established(struct ll_conn *conn, struct proc_ctx *ctx) { lp_cc_execute_fsm(conn, ctx, LP_CC_EVT_ESTABLISHED, NULL); } bool llcp_lp_cc_cancel(struct ll_conn *conn, struct proc_ctx *ctx) { ctx->data.cis_create.error = BT_HCI_ERR_OP_CANCELLED_BY_HOST; switch (ctx->state) { case LP_CC_STATE_IDLE: case LP_CC_STATE_WAIT_OFFSET_CALC: case LP_CC_STATE_WAIT_OFFSET_CALC_TX_REQ: case LP_CC_STATE_WAIT_TX_CIS_REQ: ctx->state = LP_CC_STATE_WAIT_NOTIFY_CANCEL; return true; case LP_CC_STATE_WAIT_RX_CIS_RSP: ctx->state = LP_CC_STATE_WAIT_RX_CIS_RSP_CANCEL; return true; default: break; } return false; } #endif /* CONFIG_BT_CTLR_CENTRAL_ISO */