Skip to content

Commit 670bd3b

Browse files
erbr-otfabiobaltieri
authored andcommitted
Bluetooth: controller: fixing issue re. assert on overlapping conn upd proc
If a connection update (from central) overlaps a peripheral initiated conneection param request, then central rejects peripheral request and continues central procedure. This lead to an assert in peripheral On instant there would not be an rx_node for notification, as this would have been discarded by the reception of the REJECT. This fix ensures that once an rx_node has been 'accepted' for retention it will not be discarded/overwritten by future rx_nodes The test will trigger the assert without the fix in. Signed-off-by: Erik Brockhoff <[email protected]>
1 parent 3786b61 commit 670bd3b

File tree

4 files changed

+181
-6
lines changed

4 files changed

+181
-6
lines changed

subsys/bluetooth/controller/ll_sw/ull_llcp_conn_upd.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,42 @@ static void lp_cu_st_wait_instant(struct ll_conn *conn, struct proc_ctx *ctx, ui
676676
case LP_CU_EVT_RUN:
677677
lp_cu_check_instant(conn, ctx, evt, param);
678678
break;
679+
case LP_CU_EVT_REJECT:
680+
/* In case of a central overtaking a peripheral initiated connection
681+
* param request the following will occur:
682+
* Since CONNECTION_UPDATE_IND PDU is used both as response to the peripheral
683+
* connection param request AND as initiation of a remote connection update
684+
* the peripheral cannot tell the difference in the case when there is a
685+
* collision. Ie when the central and peripheral 'exchange' CONNECTION_PARAM_REQ
686+
* and CONNECTION_UPDATE_IND in the same connection event. In this case
687+
* the central should follow the CONNECTION_UPDATE_IND with a REJECT_IND to
688+
* signal procedure collision and then complete procedure as initiated.
689+
* The peer on the other hand, should abandon the local initiated procedure
690+
* and instead run the remote connection update to completion. What happens
691+
* instead is that the peripheral reaches WAIT_FOR_INSTANT state as it
692+
* assumes the UPDATE_IND was a response to the local procedure. As a result
693+
* the REJECT_IND will be passed into the local procedure RX path and end up
694+
* 'here'. See test case: test_conn_update_periph_loc_reject_central_overlap
695+
* in unit test: tests/bluetooth/controller/ctrl_conn_update/src/main.c
696+
*
697+
* In the current implementation of LLCP there is no way of handling
698+
* this transition from local initiated to remote initiated procedure and thus
699+
* the only way to handle this 'corner' case is to allow the local procedure to
700+
* complete and accept impact of not having it complete as a remote procedure.
701+
*
702+
* The impact being:
703+
* -> since it runs as a local procedure it will block other local procedures
704+
* from being initiated while non-complete. Since non-instant based procedures
705+
* are allowed this is a limitation
706+
* -> since procedure continues as local initiated the procedure timeout will
707+
* be off (too short) by as much as the time between CONNECTION_PARAM_REQ
708+
* and CONNECTION_UPDATE_IND pdus
709+
*
710+
* The work around for this is to ignore the REJECT_IND at this stage and
711+
* ensure proper function of RX node retention mechanism.
712+
* (see comment in ull_llcp_local.c::llcp_lr_rx() for details on this)
713+
*/
714+
/* Fall through to ignore */
679715
default:
680716
/* Ignore other evts */
681717
break;

subsys/bluetooth/controller/ll_sw/ull_llcp_local.c

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,21 @@ void llcp_lr_flush_procedures(struct ll_conn *conn)
243243
void llcp_lr_rx(struct ll_conn *conn, struct proc_ctx *ctx, memq_link_t *link,
244244
struct node_rx_pdu *rx)
245245
{
246-
/* Store RX node and link */
247-
ctx->node_ref.rx = rx;
248-
ctx->node_ref.link = link;
246+
/* In the case of a specific connection update procedure collision it can occur that
247+
* an 'unexpected' REJECT_IND_PDU is received and passed as RX'ed and will then result in
248+
* discarding of the retention of the previously received CONNECTION_UPDATE_IND
249+
* and following this, an assert will be hit when attempting to use this retained
250+
* RX node for creating the notification on completion of connection param request.
251+
* (see comment in ull_llcp_conn_upd.c::lp_cu_st_wait_instant() for more details)
252+
*
253+
* The workaround/fix for this is to only store an RX node for retention if
254+
* 'we havent already' got one
255+
*/
256+
if (!ctx->node_ref.rx) {
257+
/* Store RX node and link */
258+
ctx->node_ref.rx = rx;
259+
ctx->node_ref.link = link;
260+
}
249261

250262
switch (ctx->proc) {
251263
#if defined(CONFIG_BT_CTLR_LE_PING)

subsys/bluetooth/controller/ll_sw/ull_llcp_remote.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,12 @@ void llcp_rr_flush_procedures(struct ll_conn *conn)
236236
void llcp_rr_rx(struct ll_conn *conn, struct proc_ctx *ctx, memq_link_t *link,
237237
struct node_rx_pdu *rx)
238238
{
239-
/* Store RX node and link */
240-
ctx->node_ref.rx = rx;
241-
ctx->node_ref.link = link;
239+
/* See comment in ull_llcp_local.c::llcp_lr_rx() */
240+
if (!ctx->node_ref.rx) {
241+
/* Store RX node and link */
242+
ctx->node_ref.rx = rx;
243+
ctx->node_ref.link = link;
244+
}
242245

243246
switch (ctx->proc) {
244247
case PROC_UNKNOWN:

tests/bluetooth/controller/ctrl_conn_update/src/main.c

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,130 @@ ZTEST(periph_loc, test_conn_update_periph_loc_reject)
19911991
"Free CTX buffers %d", llcp_ctx_buffers_free());
19921992
}
19931993

1994+
/*
1995+
* Peripheral-initiated Connection Parameters Request procedure. (A)
1996+
* Peripheral requests change in LE connection parameters, central rejects due to
1997+
* Central-initiated Connection Update procedure (B) overlapping.
1998+
* Central rejects peripheral init and assumes 'own' connection update to complete
1999+
*
2000+
* +-----+ +-------+ +-----+
2001+
* | UT | | LL_P | | LT |
2002+
* +-----+ +-------+ +-----+
2003+
* | | |
2004+
* | LE Connection Update (A) | |
2005+
* |-------------------------->| |
2006+
* | | LL_CONNECTION_PARAM_REQ | (A)
2007+
* | |-------------------------------->|
2008+
* | | |
2009+
* | |<--------------------------------|
2010+
* | | LL_CONNECTION_UPDATE_IND | (B)
2011+
* | | |
2012+
* | | LL_REJECT_EXT_IND | (A)
2013+
* | |<--------------------------------|
2014+
* | | |
2015+
* | | |
2016+
* | LE Connection Update | |
2017+
* | Complete | | (A/B)
2018+
* |<--------------------------| |
2019+
* | | |
2020+
*/
2021+
ZTEST(periph_loc, test_conn_update_periph_loc_reject_central_overlap)
2022+
{
2023+
uint8_t err;
2024+
uint16_t instant;
2025+
struct node_tx *tx;
2026+
struct node_rx_pdu *ntf;
2027+
struct node_rx_pu cu2 = { .status = BT_HCI_ERR_SUCCESS };
2028+
struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = {
2029+
.reject_opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ,
2030+
.error_code = BT_HCI_ERR_LL_PROC_COLLISION
2031+
};
2032+
2033+
/* Role */
2034+
test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL);
2035+
2036+
/* Connect */
2037+
ull_cp_state_set(&conn, ULL_CP_CONNECTED);
2038+
2039+
/* Initiate a Connection Parameter Request Procedure */
2040+
err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT, NULL);
2041+
zassert_equal(err, BT_HCI_ERR_SUCCESS);
2042+
2043+
/* Prepare */
2044+
event_prepare(&conn);
2045+
conn_param_req.reference_conn_event_count = event_counter(&conn);
2046+
2047+
/* Tx Queue should have one LL Control PDU */
2048+
lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, &conn_param_req);
2049+
lt_rx_q_is_empty(&conn);
2050+
2051+
/* Done */
2052+
event_done(&conn);
2053+
2054+
/* Release Tx */
2055+
ull_cp_release_tx(&conn, tx);
2056+
2057+
/* Prepare */
2058+
event_prepare(&conn);
2059+
2060+
cu_ind_B->instant = instant = event_counter(&conn) + 6;
2061+
lt_tx(LL_CONNECTION_UPDATE_IND, &conn, cu_ind_B);
2062+
2063+
/* Done */
2064+
event_done(&conn);
2065+
2066+
/* Release Tx */
2067+
ull_cp_release_tx(&conn, tx);
2068+
2069+
/* Tx Queue should NOT have a LL Control PDU */
2070+
lt_rx_q_is_empty(&conn);
2071+
2072+
/* Prepare */
2073+
event_prepare(&conn);
2074+
2075+
/* Rx */
2076+
lt_tx(LL_REJECT_EXT_IND, &conn, &reject_ext_ind);
2077+
2078+
/* Done */
2079+
event_done(&conn);
2080+
2081+
/* There should be no host notification */
2082+
ut_rx_q_is_empty();
2083+
2084+
/* */
2085+
while (!is_instant_reached(&conn, instant)) {
2086+
/* Prepare */
2087+
event_prepare(&conn);
2088+
2089+
/* (B) Tx Queue should NOT have a LL Control PDU */
2090+
lt_rx_q_is_empty(&conn);
2091+
2092+
/* Done */
2093+
event_done(&conn);
2094+
2095+
/* (B) There should NOT be a host notification */
2096+
ut_rx_q_is_empty();
2097+
}
2098+
2099+
/* Prepare */
2100+
event_prepare(&conn);
2101+
2102+
/* (B) Tx Queue should NOT have a LL Control PDU */
2103+
lt_rx_q_is_empty(&conn);
2104+
2105+
/* Done */
2106+
event_done(&conn);
2107+
2108+
/* (B) There should be one host notification */
2109+
ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu2);
2110+
ut_rx_q_is_empty();
2111+
2112+
/* Release Ntf */
2113+
release_ntf(ntf);
2114+
zassert_equal(llcp_ctx_buffers_free(), test_ctx_buffers_cnt(),
2115+
"Free CTX buffers %d", llcp_ctx_buffers_free());
2116+
}
2117+
19942118
/*
19952119
* Peripheral-initiated Connection Parameters Request procedure.
19962120
* Peripheral requests change in LE connection parameters, central’s Controller do not

0 commit comments

Comments
 (0)