From be57db0c87c9aa2325499abc8f2fcc2b575926d2 Mon Sep 17 00:00:00 2001 From: Andries Kruithof Date: Wed, 11 May 2022 03:24:06 -0700 Subject: [PATCH] Bluetooth: controller: llcp: send correct collision code Upon collision either the error code for different procedure collision or same procedure collision must be transmitted, which is fixed in this PR. Previously always the error code for same procedure collision was sent Signed-off-by: Andries Kruithof --- .../bluetooth/controller/ll_sw/ull_llcp_phy.c | 3 +- .../controller/ll_sw/ull_llcp_remote.c | 15 +- .../controller/ctrl_collision/CMakeLists.txt | 17 + .../controller/ctrl_collision/src/main.c | 779 ++++++++++++++++++ .../controller/ctrl_collision/testcase.yaml | 5 + 5 files changed, 816 insertions(+), 3 deletions(-) create mode 100644 tests/bluetooth/controller/ctrl_collision/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_collision/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_collision/testcase.yaml diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_phy.c b/subsys/bluetooth/controller/ll_sw/ull_llcp_phy.c index d0f7504220190..3f92f49095589 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_llcp_phy.c +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_phy.c @@ -653,7 +653,8 @@ static void lp_pu_st_wait_rx_phy_update_ind(struct ll_conn *conn, struct proc_ct break; case LP_PU_EVT_REJECT: llcp_rr_set_incompat(conn, INCOMPAT_NO_COLLISION); - ctx->data.pu.error = BT_HCI_ERR_LL_PROC_COLLISION; + llcp_pdu_decode_reject_ext_ind(ctx, (struct pdu_data *) param); + ctx->data.pu.error = ctx->reject_ext_ind.error_code; ctx->data.pu.ntf_pu = 1; lp_pu_complete(conn, ctx, evt, param); default: diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_remote.c b/subsys/bluetooth/controller/ll_sw/ull_llcp_remote.c index 5a726916e0dc6..b42e45e8b314e 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_llcp_remote.c +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_remote.c @@ -363,6 +363,8 @@ 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); @@ -373,11 +375,20 @@ static void rr_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode) /* 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, - BT_HCI_ERR_LL_PROC_COLLISION); + reject_code); } else { - llcp_pdu_encode_reject_ind(pdu, BT_HCI_ERR_LL_PROC_COLLISION); + llcp_pdu_encode_reject_ind(pdu, reject_code); } break; case PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP: diff --git a/tests/bluetooth/controller/ctrl_collision/CMakeLists.txt b/tests/bluetooth/controller/ctrl_collision/CMakeLists.txt new file mode 100644 index 0000000000000..a6850bdf1fa88 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_collision/CMakeLists.txt @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +if (NOT BOARD STREQUAL unit_testing) + message(FATAL_ERROR "This project can only be used with '-DBOARD=unit_testing'.") +endif() + +FILE(GLOB SOURCES + src/*.c +) + +project(bluetooth_ull_llcp_collision) +find_package(ZephyrUnittest HINTS $ENV{ZEPHYR_BASE}) +include(${ZEPHYR_BASE}/tests/bluetooth/controller/common/defaults_cmake.txt) + +target_sources(testbinary PRIVATE ${ll_sw_sources} ${mock_sources} ${common_sources}) diff --git a/tests/bluetooth/controller/ctrl_collision/src/main.c b/tests/bluetooth/controller/ctrl_collision/src/main.c new file mode 100644 index 0000000000000..4339788b42184 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_collision/src/main.c @@ -0,0 +1,779 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "kconfig.h" + +#define ULL_LLCP_UNITTEST + +#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.h" +#include "ll.h" +#include "ll_settings.h" + +#include "lll.h" +#include "lll_df_types.h" +#include "lll_conn.h" + +#include "ull_tx_queue.h" + +#include "ull_conn_types.h" +#include "ull_llcp.h" +#include "ull_conn_internal.h" +#include "ull_llcp_internal.h" + +#include "helper_pdu.h" +#include "helper_util.h" + +#define INTVL_MIN 6U /* multiple of 1.25 ms (min 6, max 3200) */ +#define INTVL_MAX 6U /* multiple of 1.25 ms (min 6, max 3200) */ +#define LATENCY 1U +#define TIMEOUT 10U /* multiple of 10 ms (min 10, max 3200) */ + +#define PREFER_S8_CODING 1 +#define PREFER_S2_CODING 0 + +static struct ll_conn conn; + +struct pdu_data_llctrl_conn_update_ind conn_update_ind = { .win_size = 1U, + .win_offset = 0U, + .interval = INTVL_MAX, + .latency = LATENCY, + .timeout = TIMEOUT, + .instant = 6U }; + +struct pdu_data_llctrl_conn_param_req conn_param_req_B = { + .interval_min = INTVL_MIN, + .interval_max = INTVL_MAX, + .latency = LATENCY + 1U, /* differentiate parameter */ + .timeout = TIMEOUT + 1U, /* differentiate parameter */ + .preferred_periodicity = 0U, + .reference_conn_event_count = 0u, + .offset0 = 0x0000U, + .offset1 = 0xffffU, + .offset2 = 0xffffU, + .offset3 = 0xffffU, + .offset4 = 0xffffU, + .offset5 = 0xffffU +}; + +struct pdu_data_llctrl_conn_param_rsp conn_param_rsp = { .interval_min = INTVL_MIN, + .interval_max = INTVL_MAX, + .latency = LATENCY, + .timeout = TIMEOUT, + .preferred_periodicity = 0U, + .reference_conn_event_count = 0u, + .offset0 = 0x0000U, + .offset1 = 0xffffU, + .offset2 = 0xffffU, + .offset3 = 0xffffU, + .offset4 = 0xffffU, + .offset5 = 0xffffU }; + +struct pdu_data_llctrl_conn_param_req *req_B = &conn_param_req_B; + +static void setup(void) +{ + test_setup(&conn); + + /* Emulate initial conn state */ + conn.phy_pref_rx = PHY_1M | PHY_2M | PHY_CODED; + conn.phy_pref_tx = PHY_1M | PHY_2M | PHY_CODED; + conn.lll.phy_flags = PREFER_S2_CODING; + conn.lll.phy_tx_time = PHY_1M; + conn.lll.phy_rx = PHY_1M; + conn.lll.phy_tx = PHY_1M; + + /* Init DLE data */ + ull_conn_default_tx_octets_set(251); + ull_conn_default_tx_time_set(2120); + ull_dle_init(&conn, PHY_1M); + /* Emulate different remote numbers to trigger update of eff */ + conn.lll.dle.remote.max_tx_octets = PDU_DC_PAYLOAD_SIZE_MIN * 3; + conn.lll.dle.remote.max_rx_octets = PDU_DC_PAYLOAD_SIZE_MIN * 3; + conn.lll.dle.remote.max_tx_time = PDU_DC_MAX_US(conn.lll.dle.remote.max_tx_octets, + PHY_1M); + conn.lll.dle.remote.max_rx_time = PDU_DC_MAX_US(conn.lll.dle.remote.max_rx_octets, + PHY_1M); + ull_dle_update_eff(&conn); +} + +#define CHECK_PREF_PHY_STATE(_conn, _tx, _rx) \ + do { \ + zassert_equal(_conn.phy_pref_rx, _rx, \ + "Preferred RX PHY mismatch %d (actual) != %d (expected)", \ + _conn.phy_pref_rx, _rx); \ + zassert_equal(_conn.phy_pref_tx, _tx, \ + "Preferred TX PHY mismatch %d (actual) != %d (expected)", \ + _conn.phy_pref_tx, _tx); \ + } while (0) + +#define CHECK_CURRENT_PHY_STATE(_conn, _tx, _flags, _rx) \ + do { \ + zassert_equal(_conn.lll.phy_rx, _rx, \ + "Current RX PHY mismatch %d (actual) != %d (expected)", \ + _conn.lll.phy_rx, _rx); \ + zassert_equal(_conn.lll.phy_tx, _tx, \ + "Current TX PHY mismatch %d (actual) != %d (expected)", \ + _conn.lll.phy_tx, _tx); \ + zassert_equal(_conn.lll.phy_rx, _rx, \ + "Current Flags mismatch %d (actual) != %d (expected)", \ + _conn.lll.phy_flags, _flags); \ + } while (0) + + +static bool is_instant_reached(struct ll_conn *conn, uint16_t instant) +{ + return ((event_counter(conn) - instant) & 0xFFFF) <= 0x7FFF; +} + + +void test_phy_update_central_loc_collision(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + struct pdu_data_llctrl_phy_req req = { .rx_phys = PHY_2M, .tx_phys = PHY_2M }; + struct pdu_data_llctrl_phy_req rsp = { .rx_phys = PHY_1M | PHY_2M, + .tx_phys = PHY_1M | PHY_2M }; + struct pdu_data_llctrl_phy_upd_ind ind = { .instant = 9, + .c_to_p_phy = PHY_2M, + .p_to_c_phy = PHY_2M }; + struct pdu_data_llctrl_length_rsp length_ntf = { + 3 * PDU_DC_PAYLOAD_SIZE_MIN, PDU_DC_MAX_US(3 * PDU_DC_PAYLOAD_SIZE_MIN, PHY_2M), + 3 * PDU_DC_PAYLOAD_SIZE_MIN, PDU_DC_MAX_US(3 * PDU_DC_PAYLOAD_SIZE_MIN, PHY_2M) + }; + uint16_t instant; + + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_PHY_REQ, + .error_code = BT_HCI_ERR_LL_PROC_COLLISION + }; + + struct node_rx_pu pu = { .status = BT_HCI_ERR_SUCCESS }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Emulate valid feature exchange */ + conn.llcp.fex.valid = 1; + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate an PHY Update Procedure */ + err = ull_cp_phy_update(&conn, PHY_2M, PREFER_S8_CODING, PHY_2M, 1); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /*** ***/ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_PHY_REQ, &conn, &tx, &req); + lt_rx_q_is_empty(&conn); + + /* Rx - emulate colliding PHY_REQ from peer */ + lt_tx(LL_PHY_REQ, &conn, &req); + + /* Check that data tx is paused */ + zassert_equal(conn.tx_q.pause_data, 1U, "Data tx is not paused"); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Check that data tx is not paused */ + zassert_equal(conn.tx_q.pause_data, 0U, "Data tx is paused"); + + /* Done */ + event_done(&conn); + + /* Check that data tx is not paused */ + zassert_equal(conn.tx_q.pause_data, 0U, "Data tx is paused"); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /*** ***/ + + /* Prepare */ + event_prepare(&conn); + test_print_conn(&conn); + /* Tx Queue should have one LL Control PDU */ + printf("Tx REJECT\n"); + lt_rx(LL_REJECT_EXT_IND, &conn, &tx, &reject_ext_ind); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Done */ + printf("Done again\n"); + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /*** ***/ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should NOT have a LL Control PDU */ + printf("Empty\n"); + lt_rx_q_is_empty(&conn); + + /* Rx */ + printf("Tx again\n"); + lt_tx(LL_PHY_RSP, &conn, &rsp); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Check that data tx is paused */ + zassert_equal(conn.tx_q.pause_data, 1U, "Data tx is not paused"); + + /*** ***/ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + printf("And again\n"); + lt_rx(LL_PHY_UPDATE_IND, &conn, &tx, &ind); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Check that data tx is not paused */ + zassert_equal(conn.tx_q.pause_data, 0U, "Data tx is paused"); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.phy_upd_ind.instant); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* */ + while (!is_instant_reached(&conn, instant)) { + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should NOT be a host notification */ + ut_rx_q_is_empty(); + } + + /*** ***/ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should be one host notification */ + ut_rx_node(NODE_PHY_UPDATE, &ntf, &pu); + ut_rx_pdu(LL_LENGTH_RSP, &ntf, &length_ntf); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + zassert_equal(ctx_buffers_free(), test_ctx_buffers_cnt(), + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_phy_update_central_rem_collision(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + struct pdu_data_llctrl_phy_req req_peripheral = { .rx_phys = PHY_1M, .tx_phys = PHY_2M }; + struct pdu_data_llctrl_phy_req req_central = { .rx_phys = PHY_2M, .tx_phys = PHY_2M }; + struct pdu_data_llctrl_phy_req rsp = { .rx_phys = PHY_1M | PHY_2M, + .tx_phys = PHY_1M | PHY_2M }; + struct pdu_data_llctrl_phy_upd_ind ind_1 = { .instant = 7, + .c_to_p_phy = 0, + .p_to_c_phy = PHY_2M }; + struct pdu_data_llctrl_phy_upd_ind ind_2 = { .instant = 14, + .c_to_p_phy = PHY_2M, + .p_to_c_phy = 0 }; + struct pdu_data_llctrl_length_rsp length_ntf_1 = { + 3 * PDU_DC_PAYLOAD_SIZE_MIN, PDU_DC_MAX_US(3 * PDU_DC_PAYLOAD_SIZE_MIN, PHY_2M), + 3 * PDU_DC_PAYLOAD_SIZE_MIN, PDU_DC_MAX_US(3 * PDU_DC_PAYLOAD_SIZE_MIN, PHY_1M) + }; + struct pdu_data_llctrl_length_rsp length_ntf_2 = { + 3 * PDU_DC_PAYLOAD_SIZE_MIN, PDU_DC_MAX_US(3 * PDU_DC_PAYLOAD_SIZE_MIN, PHY_2M), + 3 * PDU_DC_PAYLOAD_SIZE_MIN, PDU_DC_MAX_US(3 * PDU_DC_PAYLOAD_SIZE_MIN, PHY_2M) + }; + uint16_t instant; + + struct node_rx_pu pu = { .status = BT_HCI_ERR_SUCCESS }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /*** ***/ + + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + lt_tx(LL_PHY_REQ, &conn, &req_peripheral); + + /* Done */ + event_done(&conn); + + /*** ***/ + + /* Initiate an PHY Update Procedure */ + err = ull_cp_phy_update(&conn, PHY_2M, PREFER_S8_CODING, PHY_2M, 1); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /*** ***/ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_PHY_UPDATE_IND, &conn, &tx, &ind_1); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.phy_upd_ind.instant); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* */ + while (!is_instant_reached(&conn, instant)) { + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should NOT be a host notification */ + ut_rx_q_is_empty(); + } + + /*** ***/ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_PHY_REQ, &conn, &tx, &req_central); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_PHY_RSP, &conn, &rsp); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* There should be one host notification */ + ut_rx_node(NODE_PHY_UPDATE, &ntf, &pu); + ut_rx_pdu(LL_LENGTH_RSP, &ntf, &length_ntf_1); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_PHY_UPDATE_IND, &conn, &tx, &ind_2); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.phy_upd_ind.instant); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* */ + while (!is_instant_reached(&conn, instant)) { + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should NOT be a host notification */ + ut_rx_q_is_empty(); + } + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should be one host notification */ + ut_rx_node(NODE_PHY_UPDATE, &ntf, &pu); + ut_rx_pdu(LL_LENGTH_RSP, &ntf, &length_ntf_2); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + zassert_equal(ctx_buffers_free(), test_ctx_buffers_cnt(), + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_phy_update_periph_loc_collision(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data_llctrl_phy_req req_central = { .rx_phys = PHY_1M, .tx_phys = PHY_2M }; + struct pdu_data_llctrl_phy_req req_peripheral = { .rx_phys = PHY_2M, .tx_phys = PHY_2M }; + struct pdu_data_llctrl_phy_req rsp = { .rx_phys = PHY_2M, .tx_phys = PHY_2M }; + struct pdu_data_llctrl_phy_upd_ind ind = { .instant = 7, + .c_to_p_phy = PHY_2M, + .p_to_c_phy = PHY_1M }; + struct pdu_data_llctrl_length_rsp length_ntf = { + 3 * PDU_DC_PAYLOAD_SIZE_MIN, PDU_DC_MAX_US(3 * PDU_DC_PAYLOAD_SIZE_MIN, PHY_2M), + 3 * PDU_DC_PAYLOAD_SIZE_MIN, PDU_DC_MAX_US(3 * PDU_DC_PAYLOAD_SIZE_MIN, PHY_1M) + }; + uint16_t instant; + + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_PHY_REQ, + .error_code = BT_HCI_ERR_LL_PROC_COLLISION + }; + + struct node_rx_pu pu = { 0 }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /*** ***/ + + /* Initiate an PHY Update Procedure */ + err = ull_cp_phy_update(&conn, PHY_2M, PREFER_S8_CODING, PHY_2M, 1); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_PHY_REQ, &conn, &tx, &req_peripheral); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_PHY_REQ, &conn, &req_central); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_PHY_RSP, &conn, &tx, &rsp); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_REJECT_EXT_IND, &conn, &reject_ext_ind); + + /* Done */ + event_done(&conn); + + /* There should be one host notification */ + pu.status = BT_HCI_ERR_LL_PROC_COLLISION; + ut_rx_node(NODE_PHY_UPDATE, &ntf, &pu); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + ind.instant = instant = event_counter(&conn) + 6; + lt_tx(LL_PHY_UPDATE_IND, &conn, &ind); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* */ + while (!is_instant_reached(&conn, instant)) { + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should NOT be a host notification */ + ut_rx_q_is_empty(); + } + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should be one host notification */ + pu.status = BT_HCI_ERR_SUCCESS; + ut_rx_node(NODE_PHY_UPDATE, &ntf, &pu); + ut_rx_pdu(LL_LENGTH_RSP, &ntf, &length_ntf); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + zassert_equal(ctx_buffers_free(), test_ctx_buffers_cnt(), + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_phy_conn_update_central_loc_collision(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + uint16_t instant; + + struct pdu_data_llctrl_length_rsp length_ntf = { + 3 * PDU_DC_PAYLOAD_SIZE_MIN, PDU_DC_MAX_US(3 * PDU_DC_PAYLOAD_SIZE_MIN, PHY_2M), + 3 * PDU_DC_PAYLOAD_SIZE_MIN, PDU_DC_MAX_US(3 * PDU_DC_PAYLOAD_SIZE_MIN, PHY_2M) + }; + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ, + .error_code = BT_HCI_ERR_DIFF_TRANS_COLLISION + }; + struct pdu_data_llctrl_phy_req req = { .rx_phys = PHY_2M, .tx_phys = PHY_2M }; + struct pdu_data_llctrl_phy_req rsp = { .rx_phys = PHY_1M | PHY_2M, + .tx_phys = PHY_1M | PHY_2M }; + struct pdu_data_llctrl_phy_upd_ind ind = { .instant = 9, + .c_to_p_phy = PHY_2M, + .p_to_c_phy = PHY_2M }; + + struct node_rx_pu pu = { .status = BT_HCI_ERR_SUCCESS }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Emulate valid feature exchange */ + conn.llcp.fex.valid = 1; + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* (A) Initiate a PHY update procedure */ + + err = ull_cp_phy_update(&conn, PHY_2M, PREFER_S8_CODING, PHY_2M, 1); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* (A) Tx Queue should have one LL Control PDU */ + lt_rx(LL_PHY_REQ, &conn, &tx, &req); + lt_rx_q_is_empty(&conn); + + /* (B) Rx */ + lt_tx(LL_CONNECTION_PARAM_REQ, &conn, req_B); + + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Prepare */ + event_prepare(&conn); + + /* (B) Rx Queue should have a REJECT_EXT_IND PDU */ + lt_rx(LL_REJECT_EXT_IND, &conn, &tx, &reject_ext_ind); + lt_rx_q_is_empty(&conn); + + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /**/ + + /* Prepare */ + event_prepare(&conn); + + /* (B) Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* (A) Rx */ + lt_tx(LL_PHY_RSP, &conn, &rsp); + + /* Done */ + event_done(&conn); + + zassert_equal(conn.tx_q.pause_data, 1U, "Data tx is not paused"); + + + /* Prepare */ + event_prepare(&conn); + + /* (A) Tx Queue should have one LL Control PDU */ + lt_rx(LL_PHY_UPDATE_IND, &conn, &tx, &ind); + lt_rx_q_is_empty(&conn); + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.phy_upd_ind.instant); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* */ + while (!is_instant_reached(&conn, instant)) { + /* Prepare */ + event_prepare(&conn); + + /* (A) Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* (A) There should NOT be a host notification */ + ut_rx_q_is_empty(); + } + + /* Prepare */ + event_prepare(&conn); + + /* (A) Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* (A) There should be one host notification */ + ut_rx_node(NODE_PHY_UPDATE, &ntf, &pu); + ut_rx_pdu(LL_LENGTH_RSP, &ntf, &length_ntf); + + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + zassert_equal(ctx_buffers_free(), test_ctx_buffers_cnt(), + "Free CTX buffers %d", ctx_buffers_free()); +} + + +void test_main(void) +{ + ztest_test_suite( + collision, + ztest_unit_test_setup_teardown(test_phy_update_central_loc_collision, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_phy_update_central_rem_collision, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_phy_update_periph_loc_collision, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_phy_conn_update_central_loc_collision, setup, + unit_test_noop)); + + ztest_run_test_suite(collision); +} diff --git a/tests/bluetooth/controller/ctrl_collision/testcase.yaml b/tests/bluetooth/controller/ctrl_collision/testcase.yaml new file mode 100644 index 0000000000000..c9ce232c1b434 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_collision/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: test_framework bluetooth bt_collision bt_ull_llcp +tests: + bluetooth.controller.ctrl_collision.test: + type: unit