Pushes all work done in the topic-ble-llcp branch into main branch This is a refactoring of the LL control procedures; the refactored control procedures are hidden behind a KConfig option and per default disabled Goal of the refactoring: close issue Link Layer Control Procedure overhaul #15256 make it easier to add/update control procedures Refactoring consists in principal of writing explicit state machines for the control procedures. To reduce the risk of regression errors unit-tests have been added Following control procedures are implemented: Connection update procedure Channel map update procedure Encryption procedure Feature exchange procedure Version exchange procedure ACL termination procedure Connection parameters request procedure LE Ping procedure Data Length Update procedure PHY update procedure Min. nr. Of channels used procedure Constant Tone extension request procedure This is a joined work by the people listed in the signed-off-by list (in alphabetical order) Signed-off-by: Andries Kruithof Andries.Kruithof@nordicsemi.no Signed-off-by: Erik Brockhoff erbr@oticon.com Signed-off-by: Piotr Pryga piotr.pryga@nordicsemi.no Signed-off-by: Szymon Janc szymon.janc@codecoup.pl Signed-off-by: Thomas Ebert Hansen thoh@oticon.com Signed-off-by: Tommie Skriver tosk@demant.com Signed-off-by: Andries Kruithof <Andries.Kruithof@nordicsemi.no>
1061 lines
26 KiB
C
1061 lines
26 KiB
C
/*
|
|
* Copyright (c) 2020 Demant
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/types.h>
|
|
#include <ztest.h>
|
|
#include "kconfig.h"
|
|
|
|
#define ULL_LLCP_UNITTEST
|
|
|
|
#include <bluetooth/hci.h>
|
|
#include <sys/byteorder.h>
|
|
#include <sys/slist.h>
|
|
#include <sys/util.h>
|
|
#include "hal/ccm.h"
|
|
|
|
#include "util/util.h"
|
|
#include "util/mem.h"
|
|
#include "util/memq.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 PREFER_S8_CODING 1
|
|
#define PREFER_S2_CODING 0
|
|
|
|
static struct ll_conn conn;
|
|
|
|
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;
|
|
}
|
|
|
|
/* +-----+ +-------+ +-----+
|
|
* | UT | | LL_A | | LT |
|
|
* +-----+ +-------+ +-----+
|
|
* | | |
|
|
*/
|
|
void test_phy_update_mas_loc(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 = 7,
|
|
.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 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);
|
|
|
|
/* 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 */
|
|
lt_tx(LL_PHY_RSP, &conn, &rsp);
|
|
|
|
/* TX Ack */
|
|
event_tx_ack(&conn, tx);
|
|
|
|
/* Done */
|
|
event_done(&conn);
|
|
|
|
/* Check that data tx was paused */
|
|
zassert_equal(conn.tx_q.pause_data, 1U, "Data tx is not paused");
|
|
|
|
/* Release Tx */
|
|
ull_cp_release_tx(&conn, tx);
|
|
|
|
/* Prepare */
|
|
event_prepare(&conn);
|
|
|
|
/* Tx Queue should have one LL Control PDU */
|
|
lt_rx(LL_PHY_UPDATE_IND, &conn, &tx, &ind);
|
|
lt_rx_q_is_empty(&conn);
|
|
|
|
/* TX Ack */
|
|
event_tx_ack(&conn, tx);
|
|
|
|
/* Check that data tx is no lonnger paused */
|
|
zassert_equal(conn.tx_q.pause_data, 0U, "Data tx is paused");
|
|
|
|
/* 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);
|
|
|
|
CHECK_CURRENT_PHY_STATE(conn, PHY_1M, PREFER_S8_CODING, PHY_1M);
|
|
|
|
/* 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 two host notifications, one pu and one dle */
|
|
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);
|
|
|
|
CHECK_CURRENT_PHY_STATE(conn, PHY_2M, PREFER_S8_CODING, PHY_2M);
|
|
CHECK_PREF_PHY_STATE(conn, PHY_2M, PHY_2M);
|
|
|
|
zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM,
|
|
"Free CTX buffers %d", ctx_buffers_free());
|
|
}
|
|
|
|
void test_phy_update_mas_loc_unsupp_feat(void)
|
|
{
|
|
uint8_t err;
|
|
struct node_tx *tx;
|
|
struct node_rx_pdu *ntf;
|
|
struct pdu_data_llctrl_phy_req req = { .rx_phys = PHY_2M, .tx_phys = PHY_2M };
|
|
|
|
struct pdu_data_llctrl_unknown_rsp unknown_rsp = { .type = PDU_DATA_LLCTRL_TYPE_PHY_REQ };
|
|
|
|
struct node_rx_pu pu = { .status = BT_HCI_ERR_UNSUPP_REMOTE_FEATURE };
|
|
|
|
/* Role */
|
|
test_set_role(&conn, BT_HCI_ROLE_CENTRAL);
|
|
|
|
/* 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 */
|
|
lt_tx(LL_UNKNOWN_RSP, &conn, &unknown_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_q_is_empty();
|
|
|
|
/* Release Ntf */
|
|
ull_cp_release_ntf(ntf);
|
|
|
|
zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM,
|
|
"Free CTX buffers %d", ctx_buffers_free());
|
|
}
|
|
|
|
void test_phy_update_mas_rem(void)
|
|
{
|
|
struct node_tx *tx;
|
|
struct node_rx_pdu *ntf;
|
|
struct pdu_data *pdu;
|
|
struct pdu_data_llctrl_phy_req req = { .rx_phys = PHY_1M, .tx_phys = PHY_2M };
|
|
struct pdu_data_llctrl_phy_upd_ind ind = { .instant = 7,
|
|
.c_to_p_phy = 0,
|
|
.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_1M)
|
|
};
|
|
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);
|
|
|
|
/* TX Ack */
|
|
event_tx_ack(&conn, tx);
|
|
|
|
/* Done */
|
|
event_done(&conn);
|
|
|
|
/* Check that data tx was 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 */
|
|
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 no longer 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);
|
|
CHECK_CURRENT_PHY_STATE(conn, PHY_1M, PREFER_S8_CODING, PHY_2M);
|
|
CHECK_PREF_PHY_STATE(conn, PHY_1M | PHY_2M | PHY_CODED, PHY_1M | PHY_2M | PHY_CODED);
|
|
|
|
zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM,
|
|
"Free CTX buffers %d", ctx_buffers_free());
|
|
}
|
|
|
|
void test_phy_update_sla_loc(void)
|
|
{
|
|
uint8_t err;
|
|
struct node_tx *tx;
|
|
struct node_rx_pdu *ntf;
|
|
struct pdu_data_llctrl_phy_req req = { .rx_phys = PHY_2M, .tx_phys = 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 node_rx_pu pu = { .status = BT_HCI_ERR_SUCCESS };
|
|
|
|
struct pdu_data_llctrl_phy_upd_ind phy_update_ind = { .c_to_p_phy = PHY_2M,
|
|
.p_to_c_phy = PHY_2M };
|
|
|
|
/* 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);
|
|
lt_rx_q_is_empty(&conn);
|
|
|
|
/* 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 NOT have a LL Control PDU */
|
|
lt_rx_q_is_empty(&conn);
|
|
|
|
/* Rx */
|
|
phy_update_ind.instant = instant = event_counter(&conn) + 6;
|
|
lt_tx(LL_PHY_UPDATE_IND, &conn, &phy_update_ind);
|
|
|
|
/* Done */
|
|
event_done(&conn);
|
|
|
|
/* */
|
|
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);
|
|
CHECK_CURRENT_PHY_STATE(conn, PHY_2M, PREFER_S8_CODING, PHY_2M);
|
|
CHECK_PREF_PHY_STATE(conn, PHY_2M, PHY_2M);
|
|
|
|
zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM,
|
|
"Free CTX buffers %d", ctx_buffers_free());
|
|
}
|
|
|
|
void test_phy_update_sla_rem(void)
|
|
{
|
|
struct node_tx *tx;
|
|
struct node_rx_pdu *ntf;
|
|
struct pdu_data_llctrl_phy_req req = { .rx_phys = PHY_1M, .tx_phys = PHY_2M };
|
|
struct pdu_data_llctrl_phy_req rsp = { .rx_phys = PHY_1M | PHY_2M | PHY_CODED,
|
|
.tx_phys = PHY_1M | PHY_2M | PHY_CODED };
|
|
struct pdu_data_llctrl_phy_upd_ind ind = { .instant = 7,
|
|
.c_to_p_phy = 0,
|
|
.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_1M),
|
|
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_PERIPHERAL);
|
|
|
|
/* Connect */
|
|
ull_cp_state_set(&conn, ULL_CP_CONNECTED);
|
|
|
|
/* Prepare */
|
|
event_prepare(&conn);
|
|
|
|
/* Tx Queue should NOT have a LL Control PDU */
|
|
lt_rx_q_is_empty(&conn);
|
|
|
|
/* Rx */
|
|
lt_tx(LL_PHY_REQ, &conn, &req);
|
|
|
|
/* Done */
|
|
event_done(&conn);
|
|
|
|
/* We received a REQ, so data tx should be 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 */
|
|
lt_rx(LL_PHY_RSP, &conn, &tx, &rsp);
|
|
lt_rx_q_is_empty(&conn);
|
|
|
|
/* Rx */
|
|
ind.instant = instant = event_counter(&conn) + 6;
|
|
lt_tx(LL_PHY_UPDATE_IND, &conn, &ind);
|
|
|
|
/* We are sending RSP, so data tx should be paused until after tx ack */
|
|
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 no longer paused */
|
|
zassert_equal(conn.tx_q.pause_data, 0U, "Data tx is paused");
|
|
|
|
/* 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 */
|
|
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);
|
|
|
|
CHECK_CURRENT_PHY_STATE(conn, PHY_2M, PREFER_S8_CODING, PHY_1M);
|
|
CHECK_PREF_PHY_STATE(conn, PHY_1M | PHY_2M | PHY_CODED, PHY_1M | PHY_2M | PHY_CODED);
|
|
|
|
zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM,
|
|
"Free CTX buffers %d", ctx_buffers_free());
|
|
}
|
|
|
|
void test_phy_update_mas_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);
|
|
|
|
/* 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(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM,
|
|
"Free CTX buffers %d", ctx_buffers_free());
|
|
}
|
|
|
|
void test_phy_update_mas_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_slave = { .rx_phys = PHY_1M, .tx_phys = PHY_2M };
|
|
struct pdu_data_llctrl_phy_req req_master = { .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_slave);
|
|
|
|
/* 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_master);
|
|
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(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM,
|
|
"Free CTX buffers %d", ctx_buffers_free());
|
|
}
|
|
|
|
void test_phy_update_sla_loc_collision(void)
|
|
{
|
|
uint8_t err;
|
|
struct node_tx *tx;
|
|
struct node_rx_pdu *ntf;
|
|
struct pdu_data_llctrl_phy_req req_master = { .rx_phys = PHY_1M, .tx_phys = PHY_2M };
|
|
struct pdu_data_llctrl_phy_req req_slave = { .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_slave);
|
|
lt_rx_q_is_empty(&conn);
|
|
|
|
/* Rx */
|
|
lt_tx(LL_PHY_REQ, &conn, &req_master);
|
|
|
|
/* 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(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM,
|
|
"Free CTX buffers %d", ctx_buffers_free());
|
|
}
|
|
|
|
void test_main(void)
|
|
{
|
|
ztest_test_suite(
|
|
phy,
|
|
ztest_unit_test_setup_teardown(test_phy_update_mas_loc, setup, unit_test_noop),
|
|
ztest_unit_test_setup_teardown(test_phy_update_mas_loc_unsupp_feat, setup,
|
|
unit_test_noop),
|
|
ztest_unit_test_setup_teardown(test_phy_update_mas_rem, setup, unit_test_noop),
|
|
ztest_unit_test_setup_teardown(test_phy_update_sla_loc, setup, unit_test_noop),
|
|
ztest_unit_test_setup_teardown(test_phy_update_sla_rem, setup, unit_test_noop),
|
|
ztest_unit_test_setup_teardown(test_phy_update_mas_loc_collision, setup,
|
|
unit_test_noop),
|
|
ztest_unit_test_setup_teardown(test_phy_update_mas_rem_collision, setup,
|
|
unit_test_noop),
|
|
ztest_unit_test_setup_teardown(test_phy_update_sla_loc_collision, setup,
|
|
unit_test_noop));
|
|
|
|
ztest_run_test_suite(phy);
|
|
}
|