/* * Copyright (c) 2020 Demant * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include "hal/ccm.h" #include "util/util.h" #include "util/mem.h" #include "util/memq.h" #include "util/dbuf.h" #include "pdu_df.h" #include "lll/pdu_vendor.h" #include "pdu.h" #include "ll.h" #include "ll_settings.h" #include "lll.h" #include "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_internal.h" #include "ull_llcp.h" #include "ull_llcp_features.h" #include "ull_llcp_internal.h" #include "ull_conn_internal.h" #include #include "hal/debug.h" /* LLCP Local Procedure PHY Update FSM states */ enum { LP_PU_STATE_IDLE = LLCP_STATE_IDLE, LP_PU_STATE_WAIT_TX_PHY_REQ, LP_PU_STATE_WAIT_TX_ACK_PHY_REQ, LP_PU_STATE_WAIT_RX_PHY_RSP, LP_PU_STATE_WAIT_TX_PHY_UPDATE_IND, LP_PU_STATE_WAIT_TX_ACK_PHY_UPDATE_IND, LP_PU_STATE_WAIT_RX_PHY_UPDATE_IND, LP_PU_STATE_WAIT_NTF_AVAIL, LP_PU_STATE_WAIT_INSTANT, LP_PU_STATE_WAIT_INSTANT_ON_AIR, }; /* LLCP Local Procedure PHY Update FSM events */ enum { /* Procedure run */ LP_PU_EVT_RUN, /* Response received */ LP_PU_EVT_PHY_RSP, /* Indication received */ LP_PU_EVT_PHY_UPDATE_IND, /* Ack received */ LP_PU_EVT_ACK, /* Ready to notify host */ LP_PU_EVT_NTF, /* Reject response received */ LP_PU_EVT_REJECT, /* Unknown response received */ LP_PU_EVT_UNKNOWN, }; /* LLCP Remote Procedure PHY Update FSM states */ enum { RP_PU_STATE_IDLE = LLCP_STATE_IDLE, RP_PU_STATE_WAIT_RX_PHY_REQ, RP_PU_STATE_WAIT_TX_PHY_RSP, RP_PU_STATE_WAIT_TX_ACK_PHY_RSP, RP_PU_STATE_WAIT_TX_PHY_UPDATE_IND, RP_PU_STATE_WAIT_TX_ACK_PHY_UPDATE_IND, RP_PU_STATE_WAIT_RX_PHY_UPDATE_IND, RP_PU_STATE_WAIT_NTF_AVAIL, RP_PU_STATE_WAIT_INSTANT, RP_PU_STATE_WAIT_INSTANT_ON_AIR, }; /* LLCP Remote Procedure PHY Update FSM events */ enum { /* Procedure run */ RP_PU_EVT_RUN, /* Request received */ RP_PU_EVT_PHY_REQ, /* Ack received */ RP_PU_EVT_ACK, /* Indication received */ RP_PU_EVT_PHY_UPDATE_IND, /* Ready to notify host */ RP_PU_EVT_NTF, }; /* Hardcoded instant delta +6 */ #define PHY_UPDATE_INSTANT_DELTA 6 #if defined(CONFIG_BT_CENTRAL) /* PHY preference order*/ #define PHY_PREF_1 PHY_2M #define PHY_PREF_2 PHY_1M #define PHY_PREF_3 PHY_CODED static inline uint8_t pu_select_phy(uint8_t phys) { /* select only one phy, select preferred */ if (phys & PHY_PREF_1) { return PHY_PREF_1; } else if (phys & PHY_PREF_2) { return PHY_PREF_2; } else if (phys & PHY_PREF_3) { return PHY_PREF_3; } else { return 0U; } } static void pu_prep_update_ind(struct ll_conn *conn, struct proc_ctx *ctx) { ctx->data.pu.tx = pu_select_phy(ctx->data.pu.tx); ctx->data.pu.rx = pu_select_phy(ctx->data.pu.rx); if (ctx->data.pu.tx != conn->lll.phy_tx) { ctx->data.pu.c_to_p_phy = ctx->data.pu.tx; } else { ctx->data.pu.c_to_p_phy = 0U; } if (ctx->data.pu.rx != conn->lll.phy_rx) { ctx->data.pu.p_to_c_phy = ctx->data.pu.rx; } else { ctx->data.pu.p_to_c_phy = 0U; } } #endif /* CONFIG_BT_CENTRAL */ #if defined(CONFIG_BT_PERIPHERAL) static uint8_t pu_select_phy_timing_restrict(struct ll_conn *conn, uint8_t phy_tx) { /* select the probable PHY with longest Tx time, which * will be restricted to fit current * connEffectiveMaxTxTime. */ /* Note - entry 0 in table is unused, so 0 on purpose */ uint8_t phy_tx_time[8] = { 0, PHY_1M, PHY_2M, PHY_1M, PHY_CODED, PHY_CODED, PHY_CODED, PHY_CODED }; struct lll_conn *lll = &conn->lll; const uint8_t phys = phy_tx | lll->phy_tx; return phy_tx_time[phys]; } #endif /* CONFIG_BT_PERIPHERAL */ static void pu_set_timing_restrict(struct ll_conn *conn, uint8_t phy_tx) { struct lll_conn *lll = &conn->lll; lll->phy_tx_time = phy_tx; } static void pu_reset_timing_restrict(struct ll_conn *conn) { pu_set_timing_restrict(conn, conn->lll.phy_tx); } #if defined(CONFIG_BT_PERIPHERAL) static inline bool phy_validation_check_phy_ind(uint8_t phy) { /* This is equivalent to: * maximum one bit set, and no bit set is rfu's */ return (phy < 5 && phy != 3); } static uint8_t pu_check_update_ind(struct ll_conn *conn, struct proc_ctx *ctx) { uint8_t ret = 0; /* Check if either phy selected is invalid */ if (!phy_validation_check_phy_ind(ctx->data.pu.c_to_p_phy) || !phy_validation_check_phy_ind(ctx->data.pu.p_to_c_phy)) { /* more than one or any rfu bit selected in either phy */ ctx->data.pu.error = BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL; ret = 1; } /* Both tx and rx PHY unchanged */ if (!((ctx->data.pu.c_to_p_phy | ctx->data.pu.p_to_c_phy) & 0x07)) { /* if no phy changes, quit procedure, and possibly signal host */ ctx->data.pu.error = BT_HCI_ERR_SUCCESS; ret = 1; } else { /* if instant already passed, quit procedure with error */ if (is_instant_reached_or_passed(ctx->data.pu.instant, ull_conn_event_counter(conn))) { ctx->data.pu.error = BT_HCI_ERR_INSTANT_PASSED; ret = 1; } } return ret; } #endif /* CONFIG_BT_PERIPHERAL */ static uint8_t pu_apply_phy_update(struct ll_conn *conn, struct proc_ctx *ctx) { struct lll_conn *lll = &conn->lll; uint8_t phy_bitmask = PHY_1M; const uint8_t old_tx = lll->phy_tx; const uint8_t old_rx = lll->phy_rx; #if defined(CONFIG_BT_CTLR_PHY_2M) phy_bitmask |= PHY_2M; #endif #if defined(CONFIG_BT_CTLR_PHY_CODED) phy_bitmask |= PHY_CODED; #endif const uint8_t p_to_c_phy = ctx->data.pu.p_to_c_phy & phy_bitmask; const uint8_t c_to_p_phy = ctx->data.pu.c_to_p_phy & phy_bitmask; if (0) { #if defined(CONFIG_BT_PERIPHERAL) } else if (lll->role == BT_HCI_ROLE_PERIPHERAL) { if (p_to_c_phy) { lll->phy_tx = p_to_c_phy; } if (c_to_p_phy) { lll->phy_rx = c_to_p_phy; } #endif /* CONFIG_BT_PERIPHERAL */ #if defined(CONFIG_BT_CENTRAL) } else if (lll->role == BT_HCI_ROLE_CENTRAL) { if (p_to_c_phy) { lll->phy_rx = p_to_c_phy; } if (c_to_p_phy) { lll->phy_tx = c_to_p_phy; } #endif /* CONFIG_BT_CENTRAL */ } return ((old_tx != lll->phy_tx) || (old_rx != lll->phy_rx)); } #if defined(CONFIG_BT_CTLR_DATA_LENGTH) static uint16_t pu_calc_eff_time(uint8_t max_octets, uint8_t phy, uint16_t default_time) { uint16_t payload_time = PDU_DC_MAX_US(max_octets, phy); uint16_t eff_time; eff_time = MAX(PDU_DC_PAYLOAD_TIME_MIN, payload_time); eff_time = MIN(eff_time, default_time); #if defined(CONFIG_BT_CTLR_PHY_CODED) eff_time = MAX(eff_time, PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, phy)); #endif return eff_time; } static uint8_t pu_update_eff_times(struct ll_conn *conn, struct proc_ctx *ctx) { struct lll_conn *lll = &conn->lll; uint16_t eff_tx_time = lll->dle.eff.max_tx_time; uint16_t eff_rx_time = lll->dle.eff.max_rx_time; uint16_t max_rx_time, max_tx_time; ull_dle_max_time_get(conn, &max_rx_time, &max_tx_time); if ((ctx->data.pu.p_to_c_phy && (lll->role == BT_HCI_ROLE_PERIPHERAL)) || (ctx->data.pu.c_to_p_phy && (lll->role == BT_HCI_ROLE_CENTRAL))) { eff_tx_time = pu_calc_eff_time(lll->dle.eff.max_tx_octets, lll->phy_tx, max_tx_time); } if ((ctx->data.pu.p_to_c_phy && (lll->role == BT_HCI_ROLE_CENTRAL)) || (ctx->data.pu.c_to_p_phy && (lll->role == BT_HCI_ROLE_PERIPHERAL))) { eff_rx_time = pu_calc_eff_time(lll->dle.eff.max_rx_octets, lll->phy_rx, max_rx_time); } if ((eff_tx_time > lll->dle.eff.max_tx_time) || (lll->dle.eff.max_tx_time > max_tx_time) || (eff_rx_time > lll->dle.eff.max_rx_time) || (lll->dle.eff.max_rx_time > max_rx_time)) { lll->dle.eff.max_tx_time = eff_tx_time; lll->dle.eff.max_rx_time = eff_rx_time; #if defined(CONFIG_BT_CTLR_SLOT_RESERVATION_UPDATE) lll->evt_len_upd = 1U; #endif /* CONFIG_BT_CTLR_SLOT_RESERVATION_UPDATE */ return 1U; } return 0U; } #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ static inline void pu_set_preferred_phys(struct ll_conn *conn, struct proc_ctx *ctx) { conn->phy_pref_rx = ctx->data.pu.rx; conn->phy_pref_tx = ctx->data.pu.tx; /* * Note: Since 'flags' indicate local coded phy preference (S2 or S8) and * this is not negotiated with the peer, it is simply reconfigured in conn->lll when * the update is initiated, and takes effect whenever the coded phy is in use. */ conn->lll.phy_flags = ctx->data.pu.flags; } static inline void pu_combine_phys(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t tx, uint8_t rx) { /* Combine requested phys with locally preferred phys */ ctx->data.pu.rx &= rx; ctx->data.pu.tx &= tx; /* If either tx or rx is 'no change' at this point we force both to no change to * comply with the spec * Spec. BT5.2 Vol6, Part B, section 5.1.10: * The remainder of this section shall apply irrespective of which device initiated * the procedure. * * Irrespective of the above rules, the central may leave both directions * unchanged. If the periph specified a single PHY in both the TX_PHYS and * RX_PHYS fields and both fields are the same, the central shall either select * the PHY specified by the periph for both directions or shall leave both directions * unchanged. */ if (conn->lll.role == BT_HCI_ROLE_CENTRAL && (!ctx->data.pu.rx || !ctx->data.pu.tx)) { ctx->data.pu.tx = 0; ctx->data.pu.rx = 0; } } #if defined(CONFIG_BT_CENTRAL) static void pu_prepare_instant(struct ll_conn *conn, struct proc_ctx *ctx) { /* Set instance only in case there is actual PHY change. Otherwise the instant should be * set to 0. */ if (ctx->data.pu.c_to_p_phy != 0 || ctx->data.pu.p_to_c_phy != 0) { ctx->data.pu.instant = ull_conn_event_counter(conn) + conn->lll.latency + PHY_UPDATE_INSTANT_DELTA; } else { ctx->data.pu.instant = 0; } } #endif /* CONFIG_BT_CENTRAL */ /* * LLCP Local Procedure PHY Update FSM */ static void lp_pu_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { struct node_tx *tx; struct pdu_data *pdu; LL_ASSERT(ctx->node_ref.tx); #if defined(CONFIG_BT_CTLR_DATA_LENGTH) if (!((ctx->tx_opcode == PDU_DATA_LLCTRL_TYPE_PHY_REQ) && (conn->lll.role == BT_HCI_ROLE_CENTRAL))) { if (!llcp_ntf_alloc_is_available()) { /* No NTF nodes avail, so we need to hold off TX */ ctx->state = LP_PU_STATE_WAIT_NTF_AVAIL; return; } ctx->data.pu.ntf_dle_node = llcp_ntf_alloc(); LL_ASSERT(ctx->data.pu.ntf_dle_node); } #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ tx = ctx->node_ref.tx; ctx->node_ref.tx = NULL; ctx->node_ref.tx_ack = tx; pdu = (struct pdu_data *)tx->pdu; /* Encode LL Control PDU */ switch (ctx->tx_opcode) { case PDU_DATA_LLCTRL_TYPE_PHY_REQ: pu_set_preferred_phys(conn, ctx); llcp_pdu_encode_phy_req(ctx, pdu); llcp_tx_pause_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_PHY_UPDATE); ctx->state = LP_PU_STATE_WAIT_TX_ACK_PHY_REQ; break; #if defined(CONFIG_BT_CENTRAL) case PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND: pu_prep_update_ind(conn, ctx); pu_prepare_instant(conn, ctx); llcp_pdu_encode_phy_update_ind(ctx, pdu); ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; ctx->state = LP_PU_STATE_WAIT_TX_ACK_PHY_UPDATE_IND; break; #endif /* CONFIG_BT_CENTRAL */ default: LL_ASSERT(0); } /* Enqueue LL Control PDU towards LLL */ llcp_tx_enqueue(conn, tx); /* Restart procedure response timeout timer */ llcp_lr_prt_restart(conn); } static void pu_ntf(struct ll_conn *conn, struct proc_ctx *ctx) { struct node_rx_pdu *ntf; struct node_rx_pu *pdu; /* Piggy-back on stored RX node */ ntf = ctx->node_ref.rx; ctx->node_ref.rx = NULL; LL_ASSERT(ntf); if (ctx->data.pu.ntf_pu) { LL_ASSERT(ntf->hdr.type == NODE_RX_TYPE_RETAIN); ntf->hdr.type = NODE_RX_TYPE_PHY_UPDATE; ntf->hdr.handle = conn->lll.handle; pdu = (struct node_rx_pu *)ntf->pdu; pdu->status = ctx->data.pu.error; pdu->rx = conn->lll.phy_rx; pdu->tx = conn->lll.phy_tx; } else { ntf->hdr.type = NODE_RX_TYPE_RELEASE; } /* Enqueue notification towards LL */ ll_rx_put_sched(ntf->hdr.link, ntf); ctx->data.pu.ntf_pu = 0; } #if defined(CONFIG_BT_CTLR_DATA_LENGTH) static void pu_dle_ntf(struct ll_conn *conn, struct proc_ctx *ctx) { struct node_rx_pdu *ntf; struct pdu_data *pdu; /* Retrieve DLE ntf node */ ntf = ctx->data.pu.ntf_dle_node; if (!ctx->data.pu.ntf_dle) { if (!ntf) { /* If no DLE ntf was pre-allocated there is nothing more to do */ /* This will happen in case of a completion on UNKNOWN_RSP to PHY_REQ * in Central case. */ return; } /* Signal to release pre-allocated node in case there is no DLE ntf */ ntf->hdr.type = NODE_RX_TYPE_RELEASE; } else { LL_ASSERT(ntf); ntf->hdr.type = NODE_RX_TYPE_DC_PDU; ntf->hdr.handle = conn->lll.handle; pdu = (struct pdu_data *)ntf->pdu; llcp_ntf_encode_length_change(conn, pdu); } /* Enqueue notification towards LL */ ll_rx_put_sched(ntf->hdr.link, ntf); ctx->data.pu.ntf_dle = 0; ctx->data.pu.ntf_dle_node = NULL; } #endif static void lp_pu_complete_finalize(struct ll_conn *conn, struct proc_ctx *ctx) { llcp_lr_complete(conn); llcp_rr_set_paused_cmd(conn, PROC_NONE); ctx->state = LP_PU_STATE_IDLE; } static void lp_pu_tx_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { pu_ntf(conn, ctx); #if defined(CONFIG_BT_CTLR_DATA_LENGTH) pu_dle_ntf(conn, ctx); #endif lp_pu_complete_finalize(conn, ctx); } static void lp_pu_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { pu_reset_timing_restrict(conn); /* Postpone procedure completion (and possible NTF generation) to actual 'air instant' * Since LLCP STM is driven from LLL prepare this actually happens BEFORE instant * and thus NTFs are generated and propagated up prior to actual instant on air. * Instead postpone completion/NTF to the beginning of RX handling */ ctx->state = LP_PU_STATE_WAIT_INSTANT_ON_AIR; } static void lp_pu_send_phy_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { if (llcp_lr_ispaused(conn) || llcp_rr_get_collision(conn) || !llcp_tx_alloc_peek(conn, ctx) || (llcp_rr_get_paused_cmd(conn) == PROC_PHY_UPDATE)) { ctx->state = LP_PU_STATE_WAIT_TX_PHY_REQ; } else { llcp_rr_set_incompat(conn, INCOMPAT_RESOLVABLE); llcp_rr_set_paused_cmd(conn, PROC_CTE_REQ); ctx->tx_opcode = PDU_DATA_LLCTRL_TYPE_PHY_REQ; /* Allocate TX node */ ctx->node_ref.tx = llcp_tx_alloc(conn, ctx); lp_pu_tx(conn, ctx, evt, param); } } #if defined(CONFIG_BT_CENTRAL) static void lp_pu_send_phy_update_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_PU_STATE_WAIT_TX_PHY_UPDATE_IND; } else { ctx->tx_opcode = PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND; /* Allocate TX node */ ctx->node_ref.tx = llcp_tx_alloc(conn, ctx); lp_pu_tx(conn, ctx, evt, param); } } #endif /* CONFIG_BT_CENTRAL */ static void lp_pu_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_PU_EVT_RUN: lp_pu_send_phy_req(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void lp_pu_st_wait_tx_phy_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_PU_EVT_RUN: lp_pu_send_phy_req(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } #if defined(CONFIG_BT_CENTRAL) static void lp_pu_st_wait_rx_phy_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_PU_EVT_PHY_RSP: llcp_rr_set_incompat(conn, INCOMPAT_RESERVED); /* 'Prefer' the phys from the REQ */ uint8_t tx_pref = ctx->data.pu.tx; uint8_t rx_pref = ctx->data.pu.rx; llcp_pdu_decode_phy_rsp(ctx, (struct pdu_data *)param); /* Pause data tx */ llcp_tx_pause_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_PHY_UPDATE); /* Combine with the 'Preferred' phys */ pu_combine_phys(conn, ctx, tx_pref, rx_pref); /* Mark RX node to NOT release */ llcp_rx_node_retain(ctx); lp_pu_send_phy_update_ind(conn, ctx, evt, param); break; case LP_PU_EVT_UNKNOWN: llcp_rr_set_incompat(conn, INCOMPAT_NO_COLLISION); /* Unsupported in peer, so disable locally for this connection * Peer does not accept PHY UPDATE, so disable non 1M phys on current connection */ feature_unmask_features(conn, LL_FEAT_BIT_PHY_2M | LL_FEAT_BIT_PHY_CODED); /* Mark RX node to NOT release */ llcp_rx_node_retain(ctx); ctx->data.pu.error = BT_HCI_ERR_UNSUPP_REMOTE_FEATURE; ctx->data.pu.ntf_pu = 1; lp_pu_complete(conn, ctx, evt, param); llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_PHY_UPDATE); break; default: /* Ignore other evts */ break; } } #endif /* CONFIG_BT_CENTRAL */ static void lp_pu_st_wait_tx_ack_phy_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_PU_EVT_ACK: switch (conn->lll.role) { #if defined(CONFIG_BT_CENTRAL) case BT_HCI_ROLE_CENTRAL: ctx->state = LP_PU_STATE_WAIT_RX_PHY_RSP; ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_PHY_RSP; break; #endif /* CONFIG_BT_CENTRAL */ #if defined(CONFIG_BT_PERIPHERAL) case BT_HCI_ROLE_PERIPHERAL: /* If we act as peripheral apply timing restriction */ pu_set_timing_restrict( conn, pu_select_phy_timing_restrict(conn, ctx->data.pu.tx)); ctx->state = LP_PU_STATE_WAIT_RX_PHY_UPDATE_IND; ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND; llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_PHY_UPDATE); break; #endif /* CONFIG_BT_PERIPHERAL */ default: /* Unknown role */ LL_ASSERT(0); } break; default: /* Ignore other evts */ break; } } #if defined(CONFIG_BT_CENTRAL) static void lp_pu_st_wait_tx_phy_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_PU_EVT_RUN: lp_pu_send_phy_update_ind(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void lp_pu_st_wait_tx_ack_phy_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_PU_EVT_ACK: LL_ASSERT(conn->lll.role == BT_HCI_ROLE_CENTRAL); if (ctx->data.pu.p_to_c_phy || ctx->data.pu.c_to_p_phy) { /* Either phys should change */ if (ctx->data.pu.c_to_p_phy) { /* central to periph tx phy changes so, apply timing restriction */ pu_set_timing_restrict(conn, ctx->data.pu.c_to_p_phy); } /* Since at least one phy will change, * stop the procedure response timeout */ llcp_lr_prt_stop(conn); /* Now we should wait for instant */ ctx->state = LP_PU_STATE_WAIT_INSTANT; } else { llcp_rr_set_incompat(conn, INCOMPAT_NO_COLLISION); ctx->data.pu.error = BT_HCI_ERR_SUCCESS; ctx->data.pu.ntf_pu = ctx->data.pu.host_initiated; lp_pu_complete(conn, ctx, evt, param); } llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_PHY_UPDATE); break; default: /* Ignore other evts */ break; } } #endif /* CONFIG_BT_CENTRAL */ #if defined(CONFIG_BT_PERIPHERAL) static void lp_pu_st_wait_rx_phy_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_PU_EVT_PHY_UPDATE_IND: LL_ASSERT(conn->lll.role == BT_HCI_ROLE_PERIPHERAL); llcp_rr_set_incompat(conn, INCOMPAT_RESERVED); llcp_pdu_decode_phy_update_ind(ctx, (struct pdu_data *)param); const uint8_t end_procedure = pu_check_update_ind(conn, ctx); /* Mark RX node to NOT release */ llcp_rx_node_retain(ctx); if (!end_procedure) { if (ctx->data.pu.p_to_c_phy) { /* If periph to central phy changes apply tx timing restriction */ pu_set_timing_restrict(conn, ctx->data.pu.p_to_c_phy); } /* Since at least one phy will change, * stop the procedure response timeout */ llcp_lr_prt_stop(conn); ctx->state = LP_PU_STATE_WAIT_INSTANT; } else { llcp_rr_set_incompat(conn, INCOMPAT_NO_COLLISION); if (ctx->data.pu.error != BT_HCI_ERR_SUCCESS) { /* Mark the connection for termination */ conn->llcp_terminate.reason_final = ctx->data.pu.error; } ctx->data.pu.ntf_pu = ctx->data.pu.host_initiated; lp_pu_complete(conn, ctx, evt, param); } llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_PHY_UPDATE); break; case LP_PU_EVT_REJECT: llcp_pdu_decode_reject_ext_ind(ctx, (struct pdu_data *)param); ctx->data.pu.error = ctx->reject_ext_ind.error_code; /* Fallthrough */ case LP_PU_EVT_UNKNOWN: llcp_rr_set_incompat(conn, INCOMPAT_NO_COLLISION); if (evt == LP_PU_EVT_UNKNOWN) { feature_unmask_features(conn, LL_FEAT_BIT_PHY_2M | LL_FEAT_BIT_PHY_CODED); ctx->data.pu.error = BT_HCI_ERR_UNSUPP_REMOTE_FEATURE; } /* Mark RX node to NOT release */ llcp_rx_node_retain(ctx); ctx->data.pu.ntf_pu = 1; lp_pu_complete(conn, ctx, evt, param); llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_PHY_UPDATE); break; default: /* Ignore other evts */ break; } } #endif /* CONFIG_BT_PERIPHERAL */ static void lp_pu_check_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { if (is_instant_reached_or_passed(ctx->data.pu.instant, ull_conn_event_counter(conn))) { const uint8_t phy_changed = pu_apply_phy_update(conn, ctx); #if defined(CONFIG_BT_CTLR_DATA_LENGTH) if (phy_changed) { ctx->data.pu.ntf_dle = pu_update_eff_times(conn, ctx); } #endif llcp_rr_set_incompat(conn, INCOMPAT_NO_COLLISION); ctx->data.pu.error = BT_HCI_ERR_SUCCESS; ctx->data.pu.ntf_pu = (phy_changed || ctx->data.pu.host_initiated); lp_pu_complete(conn, ctx, evt, param); } } static void lp_pu_st_wait_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_PU_EVT_RUN: lp_pu_check_instant(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void lp_pu_st_wait_instant_on_air(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_PU_EVT_NTF: lp_pu_tx_ntf(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } #if defined(CONFIG_BT_CTLR_DATA_LENGTH) static void lp_pu_st_wait_ntf_avail(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case LP_PU_EVT_RUN: lp_pu_tx(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ static void lp_pu_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (ctx->state) { case LP_PU_STATE_IDLE: lp_pu_st_idle(conn, ctx, evt, param); break; case LP_PU_STATE_WAIT_TX_PHY_REQ: lp_pu_st_wait_tx_phy_req(conn, ctx, evt, param); break; case LP_PU_STATE_WAIT_TX_ACK_PHY_REQ: lp_pu_st_wait_tx_ack_phy_req(conn, ctx, evt, param); break; #if defined(CONFIG_BT_CENTRAL) case LP_PU_STATE_WAIT_RX_PHY_RSP: lp_pu_st_wait_rx_phy_rsp(conn, ctx, evt, param); break; case LP_PU_STATE_WAIT_TX_PHY_UPDATE_IND: lp_pu_st_wait_tx_phy_update_ind(conn, ctx, evt, param); break; case LP_PU_STATE_WAIT_TX_ACK_PHY_UPDATE_IND: lp_pu_st_wait_tx_ack_phy_update_ind(conn, ctx, evt, param); break; #endif /* CONFIG_BT_CENTRAL */ #if defined(CONFIG_BT_PERIPHERAL) case LP_PU_STATE_WAIT_RX_PHY_UPDATE_IND: lp_pu_st_wait_rx_phy_update_ind(conn, ctx, evt, param); break; #endif /* CONFIG_BT_PERIPHERAL */ case LP_PU_STATE_WAIT_INSTANT: lp_pu_st_wait_instant(conn, ctx, evt, param); break; case LP_PU_STATE_WAIT_INSTANT_ON_AIR: lp_pu_st_wait_instant_on_air(conn, ctx, evt, param); break; #if defined(CONFIG_BT_CTLR_DATA_LENGTH) case LP_PU_STATE_WAIT_NTF_AVAIL: lp_pu_st_wait_ntf_avail(conn, ctx, evt, param); break; #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ default: /* Unknown state */ LL_ASSERT(0); } } void llcp_lp_pu_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) { #if defined(CONFIG_BT_CENTRAL) case PDU_DATA_LLCTRL_TYPE_PHY_RSP: lp_pu_execute_fsm(conn, ctx, LP_PU_EVT_PHY_RSP, pdu); break; #endif /* CONFIG_BT_CENTRAL */ #if defined(CONFIG_BT_PERIPHERAL) case PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND: lp_pu_execute_fsm(conn, ctx, LP_PU_EVT_PHY_UPDATE_IND, pdu); break; #endif /* CONFIG_BT_PERIPHERAL */ case PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP: lp_pu_execute_fsm(conn, ctx, LP_PU_EVT_UNKNOWN, pdu); break; case PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND: lp_pu_execute_fsm(conn, ctx, LP_PU_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_PU_STATE_IDLE; break; } } void llcp_lp_pu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) { lp_pu_execute_fsm(conn, ctx, LP_PU_EVT_RUN, param); } void llcp_lp_pu_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, void *param) { lp_pu_execute_fsm(conn, ctx, LP_PU_EVT_ACK, param); } void llcp_lp_pu_tx_ntf(struct ll_conn *conn, struct proc_ctx *ctx) { lp_pu_execute_fsm(conn, ctx, LP_PU_EVT_NTF, NULL); } bool llcp_lp_pu_awaiting_instant(struct proc_ctx *ctx) { return (ctx->state == LP_PU_STATE_WAIT_INSTANT); } /* * LLCP Remote Procedure PHY Update FSM */ static void rp_pu_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { struct node_tx *tx; struct pdu_data *pdu; LL_ASSERT(ctx->node_ref.tx); #if defined(CONFIG_BT_CTLR_DATA_LENGTH) if (!llcp_ntf_alloc_is_available()) { /* No NTF nodes avail, so we need to hold off TX */ ctx->state = RP_PU_STATE_WAIT_NTF_AVAIL; return; } ctx->data.pu.ntf_dle_node = llcp_ntf_alloc(); LL_ASSERT(ctx->data.pu.ntf_dle_node); #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ tx = ctx->node_ref.tx; ctx->node_ref.tx = NULL; pdu = (struct pdu_data *)tx->pdu; ctx->node_ref.tx_ack = tx; /* Encode LL Control PDU */ switch (ctx->tx_opcode) { #if defined(CONFIG_BT_PERIPHERAL) case PDU_DATA_LLCTRL_TYPE_PHY_RSP: llcp_pdu_encode_phy_rsp(conn, pdu); ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND; ctx->state = RP_PU_STATE_WAIT_TX_ACK_PHY_RSP; break; #endif /* CONFIG_BT_PERIPHERAL */ #if defined(CONFIG_BT_CENTRAL) case PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND: pu_prep_update_ind(conn, ctx); pu_prepare_instant(conn, ctx); llcp_pdu_encode_phy_update_ind(ctx, pdu); ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; ctx->state = RP_PU_STATE_WAIT_TX_ACK_PHY_UPDATE_IND; break; #endif /* CONFIG_BT_CENTRAL */ default: LL_ASSERT(0); } /* Enqueue LL Control PDU towards LLL */ llcp_tx_enqueue(conn, tx); /* Restart procedure response timeout timer */ llcp_rr_prt_restart(conn); } static void rp_pu_complete_finalize(struct ll_conn *conn, struct proc_ctx *ctx) { llcp_rr_complete(conn); llcp_rr_set_paused_cmd(conn, PROC_NONE); ctx->state = RP_PU_STATE_IDLE; } static void rp_pu_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { pu_reset_timing_restrict(conn); /* Postpone procedure completion (and possible NTF generation) to actual 'air instant' * Since LLCP STM is driven from LLL prepare this actually happens BEFORE instant * and thus NTFs are generated and propagated up prior to actual instant on air. * Instead postpone completion/NTF to the beginning of RX handling */ ctx->state = RP_PU_STATE_WAIT_INSTANT_ON_AIR; } static void rp_pu_tx_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { pu_ntf(conn, ctx); #if defined(CONFIG_BT_CTLR_DATA_LENGTH) pu_dle_ntf(conn, ctx); #endif rp_pu_complete_finalize(conn, ctx); } #if defined(CONFIG_BT_CENTRAL) static void rp_pu_send_phy_update_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) || (llcp_rr_get_paused_cmd(conn) == PROC_PHY_UPDATE) || !ull_is_lll_tx_queue_empty(conn)) { ctx->state = RP_PU_STATE_WAIT_TX_PHY_UPDATE_IND; } else { llcp_rr_set_paused_cmd(conn, PROC_CTE_REQ); ctx->tx_opcode = PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND; ctx->node_ref.tx = llcp_tx_alloc(conn, ctx); rp_pu_tx(conn, ctx, evt, param); } } #endif /* CONFIG_BT_CENTRAL */ #if defined(CONFIG_BT_PERIPHERAL) static void rp_pu_send_phy_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) || (llcp_rr_get_paused_cmd(conn) == PROC_PHY_UPDATE)) { ctx->state = RP_PU_STATE_WAIT_TX_PHY_RSP; } else { llcp_rr_set_paused_cmd(conn, PROC_CTE_REQ); ctx->tx_opcode = PDU_DATA_LLCTRL_TYPE_PHY_RSP; ctx->node_ref.tx = llcp_tx_alloc(conn, ctx); rp_pu_tx(conn, ctx, evt, param); } } #endif /* CONFIG_BT_CENTRAL */ static void rp_pu_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_PU_EVT_RUN: ctx->state = RP_PU_STATE_WAIT_RX_PHY_REQ; break; default: /* Ignore other evts */ break; } } static void rp_pu_st_wait_rx_phy_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { llcp_pdu_decode_phy_req(ctx, (struct pdu_data *)param); /* Combine with the 'Preferred' the phys in conn->phy_pref_?x */ pu_combine_phys(conn, ctx, conn->phy_pref_tx, conn->phy_pref_rx); llcp_tx_pause_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_PHY_UPDATE); switch (evt) { case RP_PU_EVT_PHY_REQ: switch (conn->lll.role) { #if defined(CONFIG_BT_CENTRAL) case BT_HCI_ROLE_CENTRAL: /* Mark RX node to NOT release */ llcp_rx_node_retain(ctx); rp_pu_send_phy_update_ind(conn, ctx, evt, param); break; #endif /* CONFIG_BT_CENTRAL */ #if defined(CONFIG_BT_PERIPHERAL) case BT_HCI_ROLE_PERIPHERAL: rp_pu_send_phy_rsp(conn, ctx, evt, param); break; #endif /* CONFIG_BT_PERIPHERAL */ default: /* Unknown role */ LL_ASSERT(0); } break; default: /* Ignore other evts */ break; } } #if defined(CONFIG_BT_PERIPHERAL) static void rp_pu_st_wait_tx_phy_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_PU_EVT_RUN: rp_pu_send_phy_rsp(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } #endif /* CONFIG_BT_PERIPHERAL */ static void rp_pu_st_wait_tx_ack_phy(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_PU_EVT_ACK: if (0) { #if defined(CONFIG_BT_PERIPHERAL) } else if (ctx->state == RP_PU_STATE_WAIT_TX_ACK_PHY_RSP) { LL_ASSERT(conn->lll.role == BT_HCI_ROLE_PERIPHERAL); /* When we act as peripheral apply timing restriction */ pu_set_timing_restrict( conn, pu_select_phy_timing_restrict(conn, ctx->data.pu.tx)); /* RSP acked, now await update ind from central */ ctx->state = RP_PU_STATE_WAIT_RX_PHY_UPDATE_IND; #endif /* CONFIG_BT_PERIPHERAL */ #if defined(CONFIG_BT_CENTRAL) } else if (ctx->state == RP_PU_STATE_WAIT_TX_ACK_PHY_UPDATE_IND) { LL_ASSERT(conn->lll.role == BT_HCI_ROLE_CENTRAL); if (ctx->data.pu.c_to_p_phy || ctx->data.pu.p_to_c_phy) { /* UPDATE_IND acked, so lets await instant */ if (ctx->data.pu.c_to_p_phy) { /* * And if central to periph phys changes * apply timining restrictions */ pu_set_timing_restrict(conn, ctx->data.pu.c_to_p_phy); } ctx->state = RP_PU_STATE_WAIT_INSTANT; } else { rp_pu_complete(conn, ctx, evt, param); } #endif /* CONFIG_BT_CENTRAL */ } else { /* empty clause */ } llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_PHY_UPDATE); break; default: /* Ignore other evts */ break; } } #if defined(CONFIG_BT_CENTRAL) static void rp_pu_st_wait_tx_phy_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_PU_EVT_RUN: rp_pu_send_phy_update_ind(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } #endif /* CONFIG_BT_CENTRAL */ #if defined(CONFIG_BT_PERIPHERAL) static void rp_pu_st_wait_rx_phy_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_PU_EVT_PHY_UPDATE_IND: llcp_pdu_decode_phy_update_ind(ctx, (struct pdu_data *)param); const uint8_t end_procedure = pu_check_update_ind(conn, ctx); /* Mark RX node to NOT release */ llcp_rx_node_retain(ctx); if (!end_procedure) { /* Since at least one phy will change, * stop the procedure response timeout */ llcp_rr_prt_stop(conn); ctx->state = RP_PU_STATE_WAIT_INSTANT; } else { if (ctx->data.pu.error == BT_HCI_ERR_INSTANT_PASSED) { /* Mark the connection for termination */ conn->llcp_terminate.reason_final = BT_HCI_ERR_INSTANT_PASSED; } rp_pu_complete(conn, ctx, evt, param); } break; default: /* Ignore other evts */ break; } } #endif /* CONFIG_BT_PERIPHERAL */ static void rp_pu_check_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { if (is_instant_reached_or_passed(ctx->data.pu.instant, ull_conn_event_counter(conn))) { ctx->data.pu.error = BT_HCI_ERR_SUCCESS; const uint8_t phy_changed = pu_apply_phy_update(conn, ctx); #if defined(CONFIG_BT_CTLR_DATA_LENGTH) if (phy_changed) { ctx->data.pu.ntf_dle = pu_update_eff_times(conn, ctx); } #endif /* if PHY settings changed we should generate NTF */ ctx->data.pu.ntf_pu = phy_changed; rp_pu_complete(conn, ctx, evt, param); } } static void rp_pu_st_wait_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_PU_EVT_RUN: rp_pu_check_instant(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } static void rp_pu_st_wait_instant_on_air(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_PU_EVT_NTF: rp_pu_tx_ntf(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } #if defined(CONFIG_BT_CTLR_DATA_LENGTH) static void rp_pu_st_wait_ntf_avail(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (evt) { case RP_PU_EVT_RUN: rp_pu_tx(conn, ctx, evt, param); break; default: /* Ignore other evts */ break; } } #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ static void rp_pu_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) { switch (ctx->state) { case RP_PU_STATE_IDLE: rp_pu_st_idle(conn, ctx, evt, param); break; case RP_PU_STATE_WAIT_RX_PHY_REQ: rp_pu_st_wait_rx_phy_req(conn, ctx, evt, param); break; #if defined(CONFIG_BT_PERIPHERAL) case RP_PU_STATE_WAIT_TX_PHY_RSP: rp_pu_st_wait_tx_phy_rsp(conn, ctx, evt, param); break; case RP_PU_STATE_WAIT_TX_ACK_PHY_RSP: rp_pu_st_wait_tx_ack_phy(conn, ctx, evt, param); break; case RP_PU_STATE_WAIT_RX_PHY_UPDATE_IND: rp_pu_st_wait_rx_phy_update_ind(conn, ctx, evt, param); break; #endif /* CONFIG_BT_PERIPHERAL */ #if defined(CONFIG_BT_CENTRAL) case RP_PU_STATE_WAIT_TX_PHY_UPDATE_IND: rp_pu_st_wait_tx_phy_update_ind(conn, ctx, evt, param); break; case RP_PU_STATE_WAIT_TX_ACK_PHY_UPDATE_IND: rp_pu_st_wait_tx_ack_phy(conn, ctx, evt, param); break; #endif /* CONFIG_BT_CENTRAL */ case RP_PU_STATE_WAIT_INSTANT: rp_pu_st_wait_instant(conn, ctx, evt, param); break; case RP_PU_STATE_WAIT_INSTANT_ON_AIR: rp_pu_st_wait_instant_on_air(conn, ctx, evt, param); break; #if defined(CONFIG_BT_CTLR_DATA_LENGTH) case RP_PU_STATE_WAIT_NTF_AVAIL: rp_pu_st_wait_ntf_avail(conn, ctx, evt, param); break; #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ default: /* Unknown state */ LL_ASSERT(0); } } void llcp_rp_pu_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_PHY_REQ: rp_pu_execute_fsm(conn, ctx, RP_PU_EVT_PHY_REQ, pdu); break; #if defined(CONFIG_BT_PERIPHERAL) case PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND: rp_pu_execute_fsm(conn, ctx, RP_PU_EVT_PHY_UPDATE_IND, pdu); break; #endif /* CONFIG_BT_PERIPHERAL */ 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_PU_STATE_IDLE; break; } } void llcp_rp_pu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) { rp_pu_execute_fsm(conn, ctx, RP_PU_EVT_RUN, param); } void llcp_rp_pu_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, void *param) { rp_pu_execute_fsm(conn, ctx, RP_PU_EVT_ACK, param); } void llcp_rp_pu_tx_ntf(struct ll_conn *conn, struct proc_ctx *ctx) { rp_pu_execute_fsm(conn, ctx, RP_PU_EVT_NTF, NULL); } bool llcp_rp_pu_awaiting_instant(struct proc_ctx *ctx) { return (ctx->state == RP_PU_STATE_WAIT_INSTANT); }