diff --git a/include/bluetooth/hci_vs.h b/include/bluetooth/hci_vs.h index e4f94b6a833..01fe781f500 100644 --- a/include/bluetooth/hci_vs.h +++ b/include/bluetooth/hci_vs.h @@ -201,6 +201,14 @@ struct bt_hci_cp_vs_set_usb_transport_mode { uint8_t mode; } __packed; +#define BT_HCI_OP_VS_SET_MIN_NUM_USED_CHANS BT_OP(BT_OGF_VS, 0x0012) + +struct bt_hci_cp_vs_set_min_num_used_chans { + uint16_t handle; + uint8_t phys; + uint8_t min_used_chans; +} __packed; + /* Events */ struct bt_hci_evt_vs { diff --git a/subsys/bluetooth/controller/CMakeLists.txt b/subsys/bluetooth/controller/CMakeLists.txt index e870c82708d..9b54ca97e19 100644 --- a/subsys/bluetooth/controller/CMakeLists.txt +++ b/subsys/bluetooth/controller/CMakeLists.txt @@ -71,6 +71,27 @@ if(CONFIG_BT_LL_SW_SPLIT) zephyr_library_sources( ll_sw/ull_conn.c ) + if(CONFIG_BT_LL_SW_LLCP_LEGACY) + else() + zephyr_library_sources_ifdef( + CONFIG_BT_PHY_UPDATE + ll_sw/ull_llcp_phy.c + ) + zephyr_library_sources_ifdef( + CONFIG_BT_CTLR_LE_ENC + ll_sw/ull_llcp_enc.c + ) + zephyr_library_sources( + ll_sw/ull_tx_queue.c + ll_sw/ull_llcp.c + ll_sw/ull_llcp_common.c + ll_sw/ull_llcp_local.c + ll_sw/ull_llcp_pdu.c + ll_sw/ull_llcp_conn_upd.c + ll_sw/ull_llcp_chmu.c + ll_sw/ull_llcp_remote.c + ) + endif() if(CONFIG_BT_PERIPHERAL) zephyr_library_sources( ll_sw/ull_peripheral.c diff --git a/subsys/bluetooth/controller/Kconfig.ll_sw_split b/subsys/bluetooth/controller/Kconfig.ll_sw_split index 64a3f323ca9..ad849cb5cbc 100644 --- a/subsys/bluetooth/controller/Kconfig.ll_sw_split +++ b/subsys/bluetooth/controller/Kconfig.ll_sw_split @@ -74,6 +74,27 @@ config BT_CTLR_TIFS_HW_SUPPORT config BT_CTLR_ULL_LLL_PRIO_SUPPORT bool +choice BT_LL_SW_LLCP_IMPL + prompt "Bluetooth Low Energy Software Link Layer Control Procedure Implementation" + default BT_LL_SW_LLCP_LEGACY + help + Select the Bluetooth Low Energy Software Link Layer Control Procedure implementation. + +config BT_LL_SW_LLCP_LEGACY + bool "Legacy implementation" + help + Use the Bluetooth Low Energy Software Link Layer Legacy Control Procedure implementation. + +config BT_LL_SW_LLCP + bool "New implementation, replacing the legacy one [EXPERIMENTAL]" + select EXPERIMENTAL + help + Use the new Bluetooth Low Energy Software Link Layer Control Procedure implementation. + It is considered experimental because it is still under development and is not qualifiable yet. + +endchoice + + config BT_CTLR_RX_PRIO_STACK_SIZE # Controller's Co-Operative high priority Rx thread stack size. int "High priority Rx thread stack size" @@ -461,6 +482,49 @@ config BT_CTLR_LLCP_CONN more than this number of connections simultaneously may cause instabilities. +if !BT_LL_SW_LLCP_LEGACY +config BT_CTLR_LLCP_TX_PER_CONN_TX_CTRL_BUF_NUM_MAX + int + default 4 + help + The theoretical maximum number of tx ctrl buffers needed for any connection, is 4. + two for active encryption procedure plus one for rejecting a remote request + and one for a local terminate + +config BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM + int "Number of tx control buffers to be reserved per connection" + default BT_CTLR_LLCP_TX_PER_CONN_TX_CTRL_BUF_NUM_MAX + range 0 BT_CTLR_LLCP_TX_PER_CONN_TX_CTRL_BUF_NUM_MAX + help + Set the number control buffers that is to be pre allocated per connection + This defines the minimum number of buffers available for any connection + Setting this to non zero will ensure a connection will always have access + to buffer(s) for control procedure TX + +config BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM + int "Number of tx control buffers to be available across all connections" + default 0 + range 0 255 + help + Set the number control buffers that is to be available for tx. + This defines the size of the pool of tx buffers available + for control procedure tx. This pool is shared across all + procedures/connections with allocation through a fifo queue. + Configure between 0 and (4 - BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM) * BT_CTLR_LLCP_CONN + +config BT_CTLR_LLCP_PROC_CTX_BUF_NUM + int "Number of control procedure contexts to be available across all connections" + default BT_CTLR_LLCP_CONN + range 1 255 + help + Set the number control procedure contexts that is to be available. + This defines the size of the pool of control procedure contexts available + for handlign control procedures. This pool is shared across all + connections (local vs remote initiate), with allocation through a queue + +endif #!BT_LL_SW_LLCP_LEGACY + + config BT_CTLR_LLID_DATA_START_EMPTY bool "Handle zero length L2CAP start frame" default y if BT_HCI_RAW diff --git a/subsys/bluetooth/controller/hci/hci.c b/subsys/bluetooth/controller/hci/hci.c index 1a6f925c905..6c08e33989a 100644 --- a/subsys/bluetooth/controller/hci/hci.c +++ b/subsys/bluetooth/controller/hci/hci.c @@ -47,6 +47,9 @@ #include "ll_sw/ull_adv_types.h" #include "ll_sw/ull_scan_types.h" #include "ll_sw/ull_sync_types.h" +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#include "ull_tx_queue.h" +#endif #include "ll_sw/ull_sync_internal.h" #include "ll_sw/ull_conn_types.h" #include "ll_sw/ull_conn_internal.h" @@ -2325,6 +2328,7 @@ static void le_reject_cis(struct net_buf *buf, struct net_buf **evt) #endif /* CONFIG_BT_PERIPHERAL */ +#if defined(CONFIG_BT_CENTRAL) || defined(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG) static void le_read_remote_features(struct net_buf *buf, struct net_buf **evt) { struct bt_hci_cp_le_read_remote_features *cmd = (void *)buf->data; @@ -2336,6 +2340,7 @@ static void le_read_remote_features(struct net_buf *buf, struct net_buf **evt) *evt = cmd_status(status); } +#endif /* CONFIG_BT_CENTRAL || CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG */ static void le_read_chan_map(struct net_buf *buf, struct net_buf **evt) { @@ -3995,9 +4000,11 @@ static int controller_cmd_handle(uint16_t ocf, struct net_buf *cmd, le_read_chan_map(cmd, evt); break; +#if defined(CONFIG_BT_CENTRAL) || defined(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG) case BT_OCF(BT_HCI_OP_LE_READ_REMOTE_FEATURES): le_read_remote_features(cmd, evt); break; +#endif /* CONFIG_BT_CENTRAL || CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG */ case BT_OCF(BT_HCI_OP_LE_CONN_UPDATE): le_conn_update(cmd, evt); @@ -4380,7 +4387,20 @@ static void vs_read_key_hierarchy_roots(struct net_buf *buf, rp->status = 0x00; hci_vendor_read_key_hierarchy_roots(rp->ir, rp->er); } +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_PERIPHERAL) +static void vs_set_min_used_chans(struct net_buf *buf, struct net_buf **evt) +{ + struct bt_hci_cp_vs_set_min_num_used_chans *cmd = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(cmd->handle); + uint8_t status; + status = ll_set_min_used_chans(handle, cmd->phys, cmd->min_used_chans); + + *evt = cmd_complete_status(status); +} +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_PERIPHERAL */ +#endif /* !CONFIG_BT_LL_SW_LLCP_LEGACY */ #if defined(CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL) static void vs_write_tx_power_level(struct net_buf *buf, struct net_buf **evt) { @@ -4635,6 +4655,14 @@ int hci_vendor_cmd_handle_common(uint16_t ocf, struct net_buf *cmd, break; #endif /* CONFIG_BT_HCI_MESH_EXT */ +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_PERIPHERAL) + case BT_OCF(BT_HCI_OP_VS_SET_MIN_NUM_USED_CHANS): + vs_set_min_used_chans(cmd, evt); + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_PERIPHERAL */ +#endif /* !CONFIG_BT_LL_SW_LLCP_LEGACY */ + default: return -EINVAL; } diff --git a/subsys/bluetooth/controller/include/ll.h b/subsys/bluetooth/controller/include/ll.h index 33ec3838cbf..24677f66c7a 100644 --- a/subsys/bluetooth/controller/include/ll.h +++ b/subsys/bluetooth/controller/include/ll.h @@ -255,8 +255,8 @@ uint8_t ll_conn_update(uint16_t handle, uint8_t cmd, uint8_t status, uint16_t in uint16_t interval_max, uint16_t latency, uint16_t timeout); uint8_t ll_chm_update(uint8_t const *const chm); uint8_t ll_chm_get(uint16_t handle, uint8_t *const chm); -uint8_t ll_enc_req_send(uint16_t handle, uint8_t const *const rand, - uint8_t const *const ediv, uint8_t const *const ltk); +uint8_t ll_enc_req_send(uint16_t handle, uint8_t const *const rand_num, uint8_t const *const ediv, + uint8_t const *const ltk); uint8_t ll_start_enc_req_send(uint16_t handle, uint8_t err_code, uint8_t const *const ltk); uint8_t ll_feature_req_send(uint16_t handle); @@ -285,6 +285,9 @@ uint8_t ll_phy_get(uint16_t handle, uint8_t *const tx, uint8_t *const rx); uint8_t ll_phy_default_set(uint8_t tx, uint8_t rx); uint8_t ll_phy_req_send(uint16_t handle, uint8_t tx, uint8_t flags, uint8_t rx); +uint8_t ll_set_min_used_chans(uint16_t handle, uint8_t const phys, + uint8_t const min_used_chans); + /* Direction Finding */ /* Sets CTE transmission parameters for periodic advertising */ uint8_t ll_df_set_cl_cte_tx_params(uint8_t adv_handle, uint8_t cte_len, diff --git a/subsys/bluetooth/controller/include/ll_feat.h b/subsys/bluetooth/controller/include/ll_feat.h index 70ede821be7..c665b9eeb6f 100644 --- a/subsys/bluetooth/controller/include/ll_feat.h +++ b/subsys/bluetooth/controller/include/ll_feat.h @@ -192,7 +192,10 @@ /* All defined feature bits */ #define LL_FEAT_BIT_MASK 0xFFFFFFFFFULL -/* Feature bits that are valid from controller to controller */ +/* + * LL_FEAT_BIT_MASK_VALID is defined as per + * Core Spec V5.2 Volume 6, Part B, chapter 4.6 + */ #define LL_FEAT_BIT_MASK_VALID 0xFF787CF2FULL /* Mask to filter away octet 0 for feature exchange */ diff --git a/subsys/bluetooth/controller/ll_sw/ll_addr.c b/subsys/bluetooth/controller/ll_sw/ll_addr.c index 207339485e2..37a682dd8b6 100644 --- a/subsys/bluetooth/controller/ll_sw/ll_addr.c +++ b/subsys/bluetooth/controller/ll_sw/ll_addr.c @@ -87,5 +87,5 @@ uint8_t *ll_addr_read(uint8_t addr_type, uint8_t *const bdaddr) void bt_ctlr_set_public_addr(const uint8_t *addr) { - (void)memcpy(pub_addr, addr, sizeof(pub_addr)); + (void)memcpy(pub_addr, addr, sizeof(pub_addr)); } diff --git a/subsys/bluetooth/controller/ll_sw/ll_tx_pwr.c b/subsys/bluetooth/controller/ll_sw/ll_tx_pwr.c index 71301a683fd..0c167520e89 100644 --- a/subsys/bluetooth/controller/ll_sw/ll_tx_pwr.c +++ b/subsys/bluetooth/controller/ll_sw/ll_tx_pwr.c @@ -28,6 +28,10 @@ #include "lll/lll_df_types.h" #include "lll_conn.h" +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#include "ull_tx_queue.h" +#endif + #include "ull_adv_types.h" #include "ull_scan_types.h" #include "ull_conn_types.h" diff --git a/subsys/bluetooth/controller/ll_sw/lll.h b/subsys/bluetooth/controller/ll_sw/lll.h index 165af5ccb68..c34cb8b1173 100644 --- a/subsys/bluetooth/controller/ll_sw/lll.h +++ b/subsys/bluetooth/controller/ll_sw/lll.h @@ -143,6 +143,9 @@ enum done_result { DONE_LATE }; +/* Forward declaration data type to store CTE IQ samples report related data */ +struct cte_conn_iq_report; + struct ull_hdr { uint8_t volatile ref; /* Number of ongoing (between Prepare and Done) * events @@ -279,6 +282,8 @@ struct node_rx_ftr { */ void *aux_ptr; uint8_t aux_phy; + uint8_t aux_sched; + struct cte_conn_iq_report *iq_report; }; uint32_t ticks_anchor; uint32_t radio_end_us; diff --git a/subsys/bluetooth/controller/ll_sw/lll_conn.h b/subsys/bluetooth/controller/ll_sw/lll_conn.h index 2b4f29440a2..7221a07b107 100644 --- a/subsys/bluetooth/controller/ll_sw/lll_conn.h +++ b/subsys/bluetooth/controller/ll_sw/lll_conn.h @@ -28,6 +28,15 @@ struct node_tx { uint8_t pdu[]; }; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +struct data_pdu_length { + uint16_t max_tx_octets; + uint16_t max_rx_octets; + uint16_t max_tx_time; + uint16_t max_rx_time; +}; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + struct lll_conn { struct lll_hdr hdr; @@ -78,6 +87,8 @@ struct lll_conn { }; #if defined(CONFIG_BT_CTLR_DATA_LENGTH) + +#ifdef CONFIG_BT_LL_SW_LLCP_LEGACY uint16_t max_tx_octets; uint16_t max_rx_octets; @@ -85,7 +96,16 @@ struct lll_conn { uint16_t max_tx_time; uint16_t max_rx_time; #endif /* CONFIG_BT_CTLR_PHY */ + +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + struct { + struct data_pdu_length local; + struct data_pdu_length remote; + struct data_pdu_length eff; + uint8_t update; + } dle; #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#endif/* CONFIG_BT_LL_SW_LLCP_LEGACY */ #if defined(CONFIG_BT_CTLR_PHY) uint8_t phy_tx:3; diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c index 304e554825f..bd0eebfc708 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c @@ -34,10 +34,10 @@ #include "lll_adv_pdu.h" #include "lll_adv_aux.h" #include "lll_adv_sync.h" +#include "lll_df_types.h" #include "lll_conn.h" #include "lll_chan.h" #include "lll_filter.h" -#include "lll_df_types.h" #include "lll_internal.h" #include "lll_tim_internal.h" diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_aux.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_aux.c index e002bb6b8c3..890490c4355 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_aux.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_aux.c @@ -26,6 +26,7 @@ #include "lll_vendor.h" #include "lll_clock.h" #include "lll_chan.h" +#include "lll/lll_df_types.h" #include "lll_conn.h" #include "lll_adv_types.h" #include "lll_adv.h" diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_conn.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_conn.c index c66c8d5b9f3..4651f864d8c 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_conn.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_conn.c @@ -480,7 +480,11 @@ void lll_conn_rx_pkt_set(struct lll_conn *lll) LL_ASSERT(node_rx); #if defined(CONFIG_BT_CTLR_DATA_LENGTH) +#ifdef CONFIG_BT_LL_SW_LLCP_LEGACY max_rx_octets = lll->max_rx_octets; +#else + max_rx_octets = lll->dle.eff.max_rx_octets; +#endif #else /* !CONFIG_BT_CTLR_DATA_LENGTH */ max_rx_octets = PDU_DC_PAYLOAD_SIZE_MIN; #endif /* !CONFIG_BT_CTLR_DATA_LENGTH */ @@ -524,7 +528,11 @@ void lll_conn_tx_pkt_set(struct lll_conn *lll, struct pdu_data *pdu_data_tx) uint8_t phy, flags; #if defined(CONFIG_BT_CTLR_DATA_LENGTH) +#ifdef CONFIG_BT_LL_SW_LLCP_LEGACY max_tx_octets = lll->max_tx_octets; +#else + max_tx_octets = lll->dle.eff.max_tx_octets; +#endif #else /* !CONFIG_BT_CTLR_DATA_LENGTH */ max_tx_octets = PDU_DC_PAYLOAD_SIZE_MIN; #endif /* !CONFIG_BT_CTLR_DATA_LENGTH */ diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_types.h b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_types.h index 71c54de9892..17291007548 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_types.h +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_types.h @@ -57,7 +57,7 @@ struct lll_df_adv_cfg { #endif #define IQ_SAMPLE_TOTAL_CNT ((IQ_SAMPLE_REF_CNT) + (IQ_SAMPLE_SWITCH_CNT)) -#define IQ_SAMPLE_CNT (PDU_DC_LL_HEADER_SIZE + LL_LENGTH_OCTETS_RX_MAX) +#define IQ_SAMPLE_CNT (PDU_DC_LL_HEADER_SIZE + LL_LENGTH_OCTETS_RX_MAX) #define RSSI_DBM_TO_DECI_DBM(x) (-(x) * 10) #define IQ_SHIFT_12_TO_8_BIT(x) ((x) >> 4) @@ -116,3 +116,21 @@ struct lll_df_conn_rx_params { uint8_t ant_sw_len : 7; uint8_t ant_ids[BT_CTLR_DF_MAX_ANT_SW_PATTERN_LEN]; }; + +/* @brief Structure to store data required to prepare LE Connection IQ Report event or LE + * Connectionless IQ Report event. + * + * TODO (ppryga): use struct cte_conn_iq_report in connected mode. Members are exactly the same as + * members of node_rx_iq_report except hdr. + */ +struct cte_conn_iq_report { + struct pdu_cte_info cte_info; + uint8_t local_slot_durations; + uint8_t packet_status; + uint8_t sample_count; + uint8_t rssi_ant_id; + union { + uint8_t pdu[0] __aligned(4); + struct iq_sample sample[0]; + }; +}; diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_peripheral.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_peripheral.c index 373c8e2fb85..6c0024f8744 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_peripheral.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_peripheral.c @@ -15,6 +15,7 @@ #include "hal/radio.h" #include "hal/ticker.h" +#include "util/util.h" #include "util/memq.h" #include "pdu.h" @@ -22,6 +23,7 @@ #include "lll.h" #include "lll_vendor.h" #include "lll_clock.h" +#include "lll_df_types.h" #include "lll_conn.h" #include "lll_peripheral.h" #include "lll_chan.h" diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan_aux.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan_aux.c index 9375dc4f8a7..8974631b868 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan_aux.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan_aux.c @@ -47,6 +47,7 @@ #define LOG_MODULE_NAME bt_ctlr_lll_scan_aux #include "common/log.h" #include +#include #include "hal/debug.h" static int init_reset(void); @@ -453,6 +454,12 @@ static int prepare_cb(struct lll_prepare_param *p) } #endif /* CONFIG_BT_CENTRAL */ + /* Initialize scanning state */ + lll->state = 0U; + + /* Reset Tx/rx count */ + trx_cnt = 0U; + /* Start setting up Radio h/w */ radio_reset(); @@ -693,7 +700,6 @@ static void isr_rx(struct lll_scan *lll, struct lll_scan_aux *lll_aux, irkmatch_ok = radio_ar_has_match(); irkmatch_id = radio_ar_match_get(); rssi_ready = radio_rssi_is_ready(); - phy_aux_flags_rx = radio_phy_flags_rx_get(); } else { crc_ok = devmatch_ok = irkmatch_ok = rssi_ready = phy_aux_flags_rx = 0U; diff --git a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c index 808025d5242..4a32ffe1f65 100644 --- a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c +++ b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_adv.c @@ -31,6 +31,7 @@ #include "lll_adv_types.h" #include "lll_adv.h" #include "lll_adv_pdu.h" +#include "lll_df_types.h" #include "lll_conn.h" #include "lll_chan.h" #include "lll_filter.h" diff --git a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_central.c b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_central.c index 63cb4c3260f..e9159d59a44 100644 --- a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_central.c +++ b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_central.c @@ -14,12 +14,14 @@ #include "hal/radio.h" #include "hal/ticker.h" +#include "util/util.h" #include "util/memq.h" #include "pdu.h" #include "lll.h" #include "lll_vendor.h" +#include "lll_df_types.h" #include "lll_conn.h" #include "lll_central.h" #include "lll_chan.h" diff --git a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_conn.c b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_conn.c index 62e5c4e043d..ab631a6f8a3 100644 --- a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_conn.c +++ b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_conn.c @@ -15,6 +15,7 @@ #include "hal/ccm.h" #include "hal/radio.h" +#include "util/util.h" #include "util/mem.h" #include "util/memq.h" #include "util/mfifo.h" @@ -22,6 +23,7 @@ #include "pdu.h" #include "lll.h" +#include "lll_df_types.h" #include "lll_conn.h" #include "lll_internal.h" @@ -427,7 +429,11 @@ void lll_conn_rx_pkt_set(struct lll_conn *lll) LL_ASSERT(node_rx); #if defined(CONFIG_BT_CTLR_DATA_LENGTH) +#ifdef CONFIG_BT_LL_SW_LLCP_LEGACY max_rx_octets = lll->max_rx_octets; +#else + max_rx_octets = lll->dle.eff.max_rx_octets; +#endif #else /* !CONFIG_BT_CTLR_DATA_LENGTH */ max_rx_octets = PDU_DC_PAYLOAD_SIZE_MIN; #endif /* !CONFIG_BT_CTLR_DATA_LENGTH */ @@ -461,7 +467,11 @@ void lll_conn_tx_pkt_set(struct lll_conn *lll, struct pdu_data *pdu_data_tx) uint8_t phy, flags; #if defined(CONFIG_BT_CTLR_DATA_LENGTH) +#ifdef CONFIG_BT_LL_SW_LLCP_LEGACY max_tx_octets = lll->max_tx_octets; +#else + max_tx_octets = lll->dle.eff.max_tx_octets; +#endif #else /* !CONFIG_BT_CTLR_DATA_LENGTH */ max_tx_octets = PDU_DC_PAYLOAD_SIZE_MIN; #endif /* !CONFIG_BT_CTLR_DATA_LENGTH */ diff --git a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_peripheral.c b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_peripheral.c index b95b1e684a8..c3d046600a6 100644 --- a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_peripheral.c +++ b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_peripheral.c @@ -14,12 +14,14 @@ #include "hal/radio.h" #include "hal/ticker.h" +#include "util/util.h" #include "util/memq.h" #include "pdu.h" #include "lll.h" #include "lll_vendor.h" +#include "lll_df_types.h" #include "lll_conn.h" #include "lll_peripheral.h" #include "lll_chan.h" diff --git a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c index 0f491db44b3..ced2e43a442 100644 --- a/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c +++ b/subsys/bluetooth/controller/ll_sw/openisa/lll/lll_scan.c @@ -26,6 +26,7 @@ #include "lll_vendor.h" #include "lll_clock.h" #include "lll_scan.h" +#include "lll_df_types.h" #include "lll_conn.h" #include "lll_chan.h" #include "lll_filter.h" diff --git a/subsys/bluetooth/controller/ll_sw/pdu.h b/subsys/bluetooth/controller/ll_sw/pdu.h index 031b649aa1d..2f4bcfb9760 100644 --- a/subsys/bluetooth/controller/ll_sw/pdu.h +++ b/subsys/bluetooth/controller/ll_sw/pdu.h @@ -214,6 +214,33 @@ #define PKT_BIS_US(octets, mic, phy) PDU_MAX_US((octets), (mic), (phy)) +/* TODO: verify if the following lines are correct */ +/* Extra bytes for enqueued node_rx metadata: rssi (always), resolving + * index, directed adv report, and mesh channel and instant. + */ +#define PDU_AC_SIZE_RSSI 1 +#if defined(CONFIG_BT_CTLR_PRIVACY) +#define PDU_AC_SIZE_PRIV 1 +#else +#define PDU_AC_SIZE_PRIV 0 +#endif /* CONFIG_BT_CTLR_PRIVACY */ +#if defined(CONFIG_BT_CTLR_EXT_SCAN_FP) +#define PDU_AC_SIZE_SCFP 1 +#else +#define PDU_AC_SIZE_SCFP 0 +#endif /* CONFIG_BT_CTLR_EXT_SCAN_FP */ +#if defined(CONFIG_BT_HCI_MESH_EXT) +#define PDU_AC_SIZE_MESH 5 +#else +#define PDU_AC_SIZE_MESH 0 +#endif /* CONFIG_BT_HCI_MESH_EXT */ + +#define PDU_AC_LL_SIZE_EXTRA (PDU_AC_SIZE_RSSI + \ + PDU_AC_SIZE_PRIV + \ + PDU_AC_SIZE_SCFP + \ + PDU_AC_SIZE_MESH) + + struct pdu_adv_adv_ind { uint8_t addr[BDADDR_SIZE]; uint8_t data[PDU_AC_DATA_SIZE_MAX]; @@ -485,10 +512,13 @@ enum pdu_data_llctrl_type { PDU_DATA_LLCTRL_TYPE_PHY_RSP = 0x17, PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND = 0x18, PDU_DATA_LLCTRL_TYPE_MIN_USED_CHAN_IND = 0x19, + PDU_DATA_LLCTRL_TYPE_CTE_REQ = 0x1A, + PDU_DATA_LLCTRL_TYPE_CTE_RSP = 0x1B, PDU_DATA_LLCTRL_TYPE_CIS_REQ = 0x1F, PDU_DATA_LLCTRL_TYPE_CIS_RSP = 0x20, PDU_DATA_LLCTRL_TYPE_CIS_IND = 0x21, PDU_DATA_LLCTRL_TYPE_CIS_TERMINATE_IND = 0x22, + PDU_DATA_LLCTRL_TYPE_UNUSED = 0xFF }; struct pdu_data_llctrl_conn_update_ind { @@ -641,6 +671,24 @@ struct pdu_data_llctrl_min_used_chans_ind { uint8_t min_used_chans; } __packed; +struct pdu_data_llctrl_cte_req { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint8_t min_cte_len_req : 5; + uint8_t rfu : 1; + uint8_t cte_type_req : 2; +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + uint8_t cte_type_req : 2; + uint8_t rfu : 1; + uint8_t min_cte_len_req : 5; +#else +#error "Unsupported endianness" +#endif +} __packed; + +struct pdu_data_llctrl_cte_rsp { + /* no members */ +} __packed; + struct pdu_data_llctrl_cis_req { uint8_t cig_id; uint8_t cis_id; @@ -733,6 +781,8 @@ struct pdu_data_llctrl { struct pdu_data_llctrl_phy_rsp phy_rsp; struct pdu_data_llctrl_phy_upd_ind phy_upd_ind; struct pdu_data_llctrl_min_used_chans_ind min_used_chans_ind; + struct pdu_data_llctrl_cte_req cte_req; + struct pdu_data_llctrl_cte_rsp cte_rsp; struct pdu_data_llctrl_cis_req cis_req; struct pdu_data_llctrl_cis_rsp cis_rsp; struct pdu_data_llctrl_cis_ind cis_ind; diff --git a/subsys/bluetooth/controller/ll_sw/ull.c b/subsys/bluetooth/controller/ll_sw/ull.c index f52ca8d2915..b955c6483a6 100644 --- a/subsys/bluetooth/controller/ll_sw/ull.c +++ b/subsys/bluetooth/controller/ll_sw/ull.c @@ -45,6 +45,9 @@ #include "ull_adv_types.h" #include "ull_scan_types.h" #include "ull_sync_types.h" +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#include "ull_tx_queue.h" +#endif #include "ull_conn_types.h" #include "ull_filter.h" #include "ull_df_types.h" diff --git a/subsys/bluetooth/controller/ll_sw/ull_adv.c b/subsys/bluetooth/controller/ll_sw/ull_adv.c index 47bee192a65..2b5abbb6209 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_adv.c +++ b/subsys/bluetooth/controller/ll_sw/ull_adv.c @@ -38,6 +38,10 @@ #include "lll_filter.h" #include "lll/lll_df_types.h" +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#include "ll_sw/ull_tx_queue.h" +#endif /* !CONFIG_BT_LL_SW_LLCP_LEGACY */ + #include "ull_adv_types.h" #include "ull_scan_types.h" #include "ull_conn_types.h" @@ -52,6 +56,10 @@ #include "ll_feat.h" #include "ll_settings.h" +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#include "ll_sw/ull_llcp.h" +#endif /* !CONFIG_BT_LL_SW_LLCP_LEGACY */ + #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) #define LOG_MODULE_NAME bt_ctlr_ull_adv #include "common/log.h" @@ -938,6 +946,7 @@ uint8_t ll_adv_enable(uint8_t enable) conn_lll->nesn = 0; conn_lll->empty = 0; +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) #if defined(CONFIG_BT_CTLR_DATA_LENGTH) conn_lll->max_tx_octets = PDU_DC_PAYLOAD_SIZE_MIN; conn_lll->max_rx_octets = PDU_DC_PAYLOAD_SIZE_MIN; @@ -957,7 +966,17 @@ uint8_t ll_adv_enable(uint8_t enable) lll->phy_s)); #endif /* CONFIG_BT_CTLR_ADV_EXT */ #endif /* CONFIG_BT_CTLR_PHY */ +#endif +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +#if defined(CONFIG_BT_CTLR_PHY) && defined(CONFIG_BT_CTLR_ADV_EXT) + const uint8_t phy = lll->phy_s; +#else + const uint8_t phy = PHY_1M; +#endif + ull_dle_init(conn, phy); #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ #if defined(CONFIG_BT_CTLR_PHY) conn_lll->phy_flags = 0; @@ -1024,6 +1043,7 @@ uint8_t ll_adv_enable(uint8_t enable) sizeof(conn->peer_id_addr)); #endif /* CONFIG_BT_CTLR_CHECK_SAME_PEER_CONN */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) conn->common.fex_valid = 0; conn->common.txn_lock = 0; conn->periph.latency_cancel = 0; @@ -1063,7 +1083,6 @@ uint8_t ll_adv_enable(uint8_t enable) conn->llcp_length.disabled = 0U; conn->llcp_length.cache.tx_octets = 0U; conn->default_tx_octets = ull_conn_default_tx_octets_get(); - #if defined(CONFIG_BT_CTLR_PHY) conn->default_tx_time = ull_conn_default_tx_time_get(); #endif /* CONFIG_BT_CTLR_PHY */ @@ -1079,6 +1098,24 @@ uint8_t ll_adv_enable(uint8_t enable) conn->tx_head = conn->tx_ctrl = conn->tx_ctrl_last = conn->tx_data = conn->tx_data_last = 0; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + /* Re-initialize the control procedure data structures */ + ull_llcp_init(conn); + + conn->llcp_terminate.reason_final = 0; + /* NOTE: use allocated link for generating dedicated + * terminate ind rx node + */ + conn->llcp_terminate.node_rx.hdr.link = link; + +#if defined(CONFIG_BT_CTLR_PHY) + conn->phy_pref_tx = ull_conn_default_phy_tx_get(); + conn->phy_pref_rx = ull_conn_default_phy_rx_get(); +#endif /* CONFIG_BT_CTLR_PHY */ + + /* Re-initialize the Tx Q */ + ull_tx_q_init(&conn->tx_q); +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ /* NOTE: using same link as supplied for terminate ind */ adv->link_cc_free = link; @@ -2455,7 +2492,8 @@ static void ext_disabled_cb(void *param) struct node_rx_hdr *rx_hdr = (void *)lll->node_rx_adv_term; /* Under race condition, if a connection has been established then - * node_rx is already utilized to send terminate event on connection */ + * node_rx is already utilized to send terminate event on connection + */ if (!rx_hdr) { return; } diff --git a/subsys/bluetooth/controller/ll_sw/ull_adv_aux.c b/subsys/bluetooth/controller/ll_sw/ull_adv_aux.c index c761ba4c7ac..6b2fdbc39d0 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_adv_aux.c +++ b/subsys/bluetooth/controller/ll_sw/ull_adv_aux.c @@ -10,6 +10,7 @@ #include #include "hal/cpu.h" +#include "hal/ccm.h" #include "hal/ticker.h" #include "util/util.h" @@ -30,6 +31,7 @@ #include "lll/lll_adv_pdu.h" #include "lll_adv_aux.h" #include "lll/lll_df_types.h" +#include "lll_conn.h" #include "ull_adv_types.h" diff --git a/subsys/bluetooth/controller/ll_sw/ull_adv_sync.c b/subsys/bluetooth/controller/ll_sw/ull_adv_sync.c index 307ae9a9c07..4c28b6c5079 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_adv_sync.c +++ b/subsys/bluetooth/controller/ll_sw/ull_adv_sync.c @@ -10,6 +10,7 @@ #include #include "hal/cpu.h" +#include "hal/ccm.h" #include "hal/ticker.h" #include "util/util.h" @@ -29,6 +30,7 @@ #include "lll/lll_adv_pdu.h" #include "lll_adv_sync.h" #include "lll/lll_df_types.h" +#include "lll_conn.h" #include "lll_chan.h" #include "ull_adv_types.h" diff --git a/subsys/bluetooth/controller/ll_sw/ull_central.c b/subsys/bluetooth/controller/ll_sw/ull_central.c index fb2a24bdcf9..15d0e81c510 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_central.c +++ b/subsys/bluetooth/controller/ll_sw/ull_central.c @@ -36,6 +36,10 @@ #include "lll_central.h" #include "lll_filter.h" +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#include "ull_tx_queue.h" +#endif /* !CONFIG_BT_LL_SW_LLCP_LEGACY */ + #include "ull_adv_types.h" #include "ull_scan_types.h" #include "ull_conn_types.h" @@ -51,6 +55,10 @@ #include "ll_feat.h" #include "ll_settings.h" +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#include "ll_sw/ull_llcp.h" +#endif /* !CONFIG_BT_LL_SW_LLCP_LEGACY */ + #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) #define LOG_MODULE_NAME bt_ctlr_ull_central #include "common/log.h" @@ -202,6 +210,7 @@ uint8_t ll_create_connection(uint16_t scan_interval, uint16_t scan_window, conn_lll->nesn = 0; conn_lll->empty = 0; +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) #if defined(CONFIG_BT_CTLR_DATA_LENGTH) conn_lll->max_tx_octets = PDU_DC_PAYLOAD_SIZE_MIN; conn_lll->max_rx_octets = PDU_DC_PAYLOAD_SIZE_MIN; @@ -214,6 +223,11 @@ uint8_t ll_create_connection(uint16_t scan_interval, uint16_t scan_window, conn_lll->max_rx_time = PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, PHY_1M); #endif /* CONFIG_BT_CTLR_PHY */ #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + ull_dle_init(conn, PHY_1M); +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ #if defined(CONFIG_BT_CTLR_PHY) /* Use the default 1M PHY, extended connection initiation in LLL will @@ -279,6 +293,7 @@ uint8_t ll_create_connection(uint16_t scan_interval, uint16_t scan_window, conn->apto_reload; #endif /* CONFIG_BT_CTLR_LE_PING */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) conn->common.fex_valid = 0U; conn->common.txn_lock = 0U; conn->central.terminate_ack = 0U; @@ -332,6 +347,26 @@ uint8_t ll_create_connection(uint16_t scan_interval, uint16_t scan_window, conn->tx_head = conn->tx_ctrl = conn->tx_ctrl_last = conn->tx_data = conn->tx_data_last = 0; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + /* Re-initialize the control procedure data structures */ + ull_llcp_init(conn); + + conn->central.terminate_ack = 0U; + + conn->llcp_terminate.reason_final = 0U; + /* NOTE: use allocated link for generating dedicated + * terminate ind rx node + */ + conn->llcp_terminate.node_rx.hdr.link = link; + +#if defined(CONFIG_BT_CTLR_PHY) + conn->phy_pref_tx = ull_conn_default_phy_tx_get(); + conn->phy_pref_rx = ull_conn_default_phy_rx_get(); +#endif /* CONFIG_BT_CTLR_PHY */ + + /* Re-initialize the Tx Q */ + ull_tx_q_init(&conn->tx_q); +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ /* TODO: active_to_start feature port */ conn->ull.ticks_active_to_start = 0U; @@ -361,6 +396,7 @@ conn_is_valid: ready_delay_us = lll_radio_tx_ready_delay_get(0, 0); #endif +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) #if defined(CONFIG_BT_CTLR_DATA_LENGTH) #if defined(CONFIG_BT_CTLR_PHY) #if defined(CONFIG_BT_CTLR_ADV_EXT) @@ -387,6 +423,15 @@ conn_is_valid: PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, lll->phy)); #endif /* CONFIG_BT_CTLR_ADV_EXT */ #endif /* !CONFIG_BT_CTLR_DATA_LENGTH */ +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + /* TODO(thoh-ot): Not entirely sure this is correct */ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + ull_dle_max_time_get(conn, &max_rx_time, &max_tx_time); +#else /* CONFIG_BT_CTLR_DATA_LENGTH */ + max_tx_time = PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, PHY_1M); + max_rx_time = PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, PHY_1M); +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ conn->ull.ticks_slot = HAL_TICKER_US_TO_TICKS(EVENT_OVERHEAD_START_US + @@ -572,7 +617,7 @@ uint8_t ll_connect_disable(void **rx) } #if defined(CONFIG_BT_CTLR_LE_ENC) -uint8_t ll_enc_req_send(uint16_t handle, uint8_t const *const rand, +uint8_t ll_enc_req_send(uint16_t handle, uint8_t const *const rand_num, uint8_t const *const ediv, uint8_t const *const ltk) { struct ll_conn *conn; @@ -583,6 +628,7 @@ uint8_t ll_enc_req_send(uint16_t handle, uint8_t const *const rand, return BT_HCI_ERR_UNKNOWN_CONN_ID; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if ((conn->llcp_enc.req != conn->llcp_enc.ack) || ((conn->llcp_req != conn->llcp_ack) && (conn->llcp_type == LLCP_ENCRYPTION))) { @@ -608,13 +654,13 @@ uint8_t ll_enc_req_send(uint16_t handle, uint8_t const *const rand, PDU_DATA_LLCTRL_TYPE_ENC_REQ; enc_req = (void *) &pdu_data_tx->llctrl.enc_req; - memcpy(enc_req->rand, rand, sizeof(enc_req->rand)); + memcpy(enc_req->rand, rand_num, sizeof(enc_req->rand)); enc_req->ediv[0] = ediv[0]; enc_req->ediv[1] = ediv[1]; lll_csrand_get(enc_req->skdm, sizeof(enc_req->skdm)); lll_csrand_get(enc_req->ivm, sizeof(enc_req->ivm)); } else if (conn->lll.enc_rx && conn->lll.enc_tx) { - memcpy(&conn->llcp_enc.rand[0], rand, + memcpy(&conn->llcp_enc.rand[0], rand_num, sizeof(conn->llcp_enc.rand)); conn->llcp_enc.ediv[0] = ediv[0]; @@ -641,6 +687,17 @@ uint8_t ll_enc_req_send(uint16_t handle, uint8_t const *const rand, return 0; } +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + ARG_UNUSED(tx); + + if (!conn->lll.enc_tx && !conn->lll.enc_rx) { + /* Encryption is fully disabled */ + return ull_cp_encryption_start(conn, rand_num, ediv, ltk); + } else if (conn->lll.enc_tx && conn->lll.enc_rx) { + /* Encryption is fully enabled */ + return ull_cp_encryption_pause(conn, rand_num, ediv, ltk); + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ return BT_HCI_ERR_CMD_DISALLOWED; } @@ -807,6 +864,11 @@ void ull_central_setup(struct node_rx_hdr *rx, struct node_rx_ftr *ftr, lll->handle = ll_conn_handle_get(conn); rx->handle = lll->handle; +#if (!defined(CONFIG_BT_LL_SW_LLCP_LEGACY)) + /* Set LLCP as connection-wise connected */ + ull_cp_state_set(conn, ULL_CP_CONNECTED); +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + #if defined(CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL) lll->tx_pwr_lvl = RADIO_TXP_DEFAULT; #endif /* CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL */ @@ -1042,6 +1104,7 @@ uint8_t ull_central_chm_update(void) continue; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) ret = ull_conn_llcp_req(conn); if (ret) { return ret; @@ -1053,6 +1116,15 @@ uint8_t ull_central_chm_update(void) conn->llcp_type = LLCP_CHAN_MAP; conn->llcp_req++; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + uint8_t chm[5]; + + ull_chan_map_get(chm); + ret = ull_cp_chan_map_update(conn, chm); + if (ret) { + return ret; + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ } return 0; diff --git a/subsys/bluetooth/controller/ll_sw/ull_chan.c b/subsys/bluetooth/controller/ll_sw/ull_chan.c index f3ac1c905f0..5519bdbc075 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_chan.c +++ b/subsys/bluetooth/controller/ll_sw/ull_chan.c @@ -23,6 +23,7 @@ #include "lll/lll_adv_types.h" #include "lll_adv.h" #include "lll/lll_adv_pdu.h" +#include "lll/lll_df_types.h" #include "lll_conn.h" #include "ull_adv_types.h" diff --git a/subsys/bluetooth/controller/ll_sw/ull_chan_internal.h b/subsys/bluetooth/controller/ll_sw/ull_chan_internal.h index 869af26ac1a..cfc6dd0ead4 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_chan_internal.h +++ b/subsys/bluetooth/controller/ll_sw/ull_chan_internal.h @@ -6,3 +6,6 @@ int ull_chan_reset(void); uint8_t ull_chan_map_get(uint8_t *const chan_map); +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +void ull_chan_map_set(uint8_t const *const chan_map); +#endif diff --git a/subsys/bluetooth/controller/ll_sw/ull_conn.c b/subsys/bluetooth/controller/ll_sw/ull_conn.c index d76cf8229e7..91cca1c3bb3 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_conn.c +++ b/subsys/bluetooth/controller/ll_sw/ull_conn.c @@ -7,8 +7,8 @@ #include #include #include -#include #include +#include #include #include "hal/cpu.h" @@ -32,6 +32,10 @@ #include "lll_conn.h" #include "lll_conn_iso.h" +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#include "ull_tx_queue.h" +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + #include "ull_conn_types.h" #include "ull_conn_iso_types.h" #include "ull_internal.h" @@ -53,6 +57,11 @@ #include "ll_feat.h" #include "ll_settings.h" +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#include "ull_llcp.h" +#include "ull_llcp_features.h" +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) #define LOG_MODULE_NAME bt_ctlr_ull_conn #include "common/log.h" @@ -92,6 +101,7 @@ static void tx_lll_flush(void *param); static int empty_data_start_release(struct ll_conn *conn, struct node_tx *tx); #endif /* CONFIG_BT_CTLR_LLID_DATA_START_EMPTY */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) static inline void ctrl_tx_enqueue(struct ll_conn *conn, struct node_tx *tx); static inline void event_fex_prep(struct ll_conn *conn); static inline void event_vex_prep(struct ll_conn *conn); @@ -153,6 +163,7 @@ static inline void ctrl_tx_ack(struct ll_conn *conn, struct node_tx **tx, struct pdu_data *pdu_tx); static inline int ctrl_rx(memq_link_t *link, struct node_rx_pdu **rx, struct pdu_data *pdu_rx, struct ll_conn *conn); +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ #if defined(CONFIG_BT_CTLR_FORCE_MD_AUTO) static uint8_t force_md_cnt_calc(struct lll_conn *lll_conn, uint32_t tx_rate); @@ -360,6 +371,7 @@ uint8_t ll_conn_update(uint16_t handle, uint8_t cmd, uint8_t status, uint16_t in return BT_HCI_ERR_UNKNOWN_CONN_ID; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (!cmd) { #if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) if (!conn->llcp_conn_param.disabled && @@ -432,6 +444,35 @@ uint8_t ll_conn_update(uint16_t handle, uint8_t cmd, uint8_t status, uint16_t in return BT_HCI_ERR_CMD_DISALLOWED; #endif /* !CONFIG_BT_CTLR_CONN_PARAM_REQ */ } +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + if (cmd == 0U) { + uint8_t err; + + err = ull_cp_conn_update(conn, interval_min, interval_max, latency, timeout); + if (err) { + return err; + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && + conn->lll.role) { + ull_periph_latency_cancel(conn, handle); + } + } else if (cmd == 2U) { +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + if (status == 0U) { + ull_cp_conn_param_req_reply(conn); + } else { + ull_cp_conn_param_req_neg_reply(conn, status); + } + return BT_HCI_ERR_SUCCESS; +#else /* !CONFIG_BT_CTLR_CONN_PARAM_REQ */ + /* CPR feature not supported */ + return BT_HCI_ERR_CMD_DISALLOWED; +#endif /* !CONFIG_BT_CTLR_CONN_PARAM_REQ */ + } else { + return BT_HCI_ERR_UNKNOWN_CMD; + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ return 0; } @@ -445,6 +486,7 @@ uint8_t ll_chm_get(uint16_t handle, uint8_t *chm) return BT_HCI_ERR_UNKNOWN_CONN_ID; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) /* Iterate until we are sure the ISR did not modify the value while * we were reading it from memory. */ @@ -453,6 +495,25 @@ uint8_t ll_chm_get(uint16_t handle, uint8_t *chm) memcpy(chm, conn->lll.data_chan_map, sizeof(conn->lll.data_chan_map)); } while (conn->chm_updated); +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + /* + * Core Spec 5.2 Vol4: 7.8.20: + * The HCI_LE_Read_Channel_Map command returns the current Channel_Map + * for the specified Connection_Handle. The returned value indicates the state of + * the Channel_Map specified by the last transmitted or received Channel_Map + * (in a CONNECT_IND or LL_CHANNEL_MAP_IND message) for the specified + * Connection_Handle, regardless of whether the Master has received an + * acknowledgment + */ + const uint8_t *pending_chm; + + pending_chm = ull_cp_chan_map_update_pending(conn); + if (pending_chm) { + memcpy(chm, pending_chm, sizeof(conn->lll.data_chan_map)); + } else { + memcpy(chm, conn->lll.data_chan_map, sizeof(conn->lll.data_chan_map)); + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ return 0; } @@ -482,16 +543,27 @@ uint8_t ll_terminate_ind_send(uint16_t handle, uint8_t reason) return BT_HCI_ERR_UNKNOWN_CONN_ID; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (conn->llcp_terminate.req != conn->llcp_terminate.ack) { return BT_HCI_ERR_CMD_DISALLOWED; } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ if (!is_valid_disconnect_reason(reason)) { return BT_HCI_ERR_INVALID_PARAM; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) conn->llcp_terminate.reason_own = reason; conn->llcp_terminate.req++; /* (req - ack) == 1, TERM_REQ */ +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + uint8_t err; + + err = ull_cp_terminate(conn, reason); + if (err) { + return err; + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn->lll.role) { ull_periph_latency_cancel(conn, handle); @@ -500,6 +572,7 @@ uint8_t ll_terminate_ind_send(uint16_t handle, uint8_t reason) return 0; } +#if defined(CONFIG_BT_CENTRAL) || defined(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG) uint8_t ll_feature_req_send(uint16_t handle) { struct ll_conn *conn; @@ -509,11 +582,20 @@ uint8_t ll_feature_req_send(uint16_t handle) return BT_HCI_ERR_UNKNOWN_CONN_ID; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (conn->llcp_feature.req != conn->llcp_feature.ack) { return BT_HCI_ERR_CMD_DISALLOWED; } conn->llcp_feature.req++; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + uint8_t err; + + err = ull_cp_feature_exchange(conn); + if (err) { + return err; + } +#endif if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && IS_ENABLED(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG) && @@ -523,6 +605,7 @@ uint8_t ll_feature_req_send(uint16_t handle) return 0; } +#endif /* CONFIG_BT_CENTRAL || CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG */ uint8_t ll_version_ind_send(uint16_t handle) { @@ -533,11 +616,20 @@ uint8_t ll_version_ind_send(uint16_t handle) return BT_HCI_ERR_UNKNOWN_CONN_ID; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (conn->llcp_version.req != conn->llcp_version.ack) { return BT_HCI_ERR_CMD_DISALLOWED; } conn->llcp_version.req++; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + uint8_t err; + + err = ull_cp_version_exchange(conn); + if (err) { + return err; + } +#endif if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn->lll.role) { ull_periph_latency_cancel(conn, handle); @@ -572,6 +664,7 @@ uint32_t ll_length_req_send(uint16_t handle, uint16_t tx_octets, return BT_HCI_ERR_UNKNOWN_CONN_ID; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (conn->llcp_length.disabled || (conn->common.fex_valid && !(conn->llcp_feature.features_conn & BIT64(BT_LE_FEAT_BIT_DLE)))) { @@ -607,6 +700,18 @@ uint32_t ll_length_req_send(uint16_t handle, uint16_t tx_octets, #endif /* CONFIG_BT_CTLR_PHY */ conn->llcp_length.req++; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + if (!feature_dle(conn)) { + return BT_HCI_ERR_UNSUPP_REMOTE_FEATURE; + } + + uint8_t err; + + err = ull_cp_data_length_update(conn, tx_octets, tx_time); + if (err) { + return err; + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn->lll.role) { ull_periph_latency_cancel(conn, handle); @@ -634,16 +739,16 @@ uint32_t ll_length_default_set(uint16_t max_tx_octets, uint16_t max_tx_time) void ll_length_max_get(uint16_t *max_tx_octets, uint16_t *max_tx_time, uint16_t *max_rx_octets, uint16_t *max_rx_time) { +#if defined(CONFIG_BT_CTLR_PHY) && defined(CONFIG_BT_CTLR_PHY_CODED) +#define PHY (PHY_CODED) +#else /* CONFIG_BT_CTLR_PHY && CONFIG_BT_CTLR_PHY_CODED */ +#define PHY (PHY_1M) +#endif /* CONFIG_BT_CTLR_PHY && CONFIG_BT_CTLR_PHY_CODED */ *max_tx_octets = LL_LENGTH_OCTETS_RX_MAX; *max_rx_octets = LL_LENGTH_OCTETS_RX_MAX; -#if defined(CONFIG_BT_CTLR_PHY) - *max_tx_time = PDU_DC_MAX_US(LL_LENGTH_OCTETS_RX_MAX, PHY_CODED); - *max_rx_time = PDU_DC_MAX_US(LL_LENGTH_OCTETS_RX_MAX, PHY_CODED); -#else /* !CONFIG_BT_CTLR_PHY */ - /* Default is 1M packet timing */ - *max_tx_time = PDU_DC_MAX_US(LL_LENGTH_OCTETS_RX_MAX, PHY_1M); - *max_rx_time = PDU_DC_MAX_US(LL_LENGTH_OCTETS_RX_MAX, PHY_1M); -#endif /* !CONFIG_BT_CTLR_PHY */ + *max_tx_time = PDU_DC_MAX_US(LL_LENGTH_OCTETS_RX_MAX, PHY); + *max_rx_time = PDU_DC_MAX_US(LL_LENGTH_OCTETS_RX_MAX, PHY); +#undef PHY } #endif /* CONFIG_BT_CTLR_DATA_LENGTH */ @@ -683,6 +788,7 @@ uint8_t ll_phy_req_send(uint16_t handle, uint8_t tx, uint8_t flags, uint8_t rx) return BT_HCI_ERR_UNKNOWN_CONN_ID; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (conn->llcp_phy.disabled || (conn->common.fex_valid && !(conn->llcp_feature.features_conn & BIT64(BT_LE_FEAT_BIT_PHY_2M)) && @@ -701,6 +807,18 @@ uint8_t ll_phy_req_send(uint16_t handle, uint8_t tx, uint8_t flags, uint8_t rx) conn->llcp_phy.flags = flags; conn->llcp_phy.rx = rx; conn->llcp_phy.req++; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + if (!feature_phy_2m(conn) && !feature_phy_coded(conn)) { + return BT_HCI_ERR_UNSUPP_REMOTE_FEATURE; + } + + uint8_t err; + + err = ull_cp_phy_update(conn, tx, flags, rx, 1U); + if (err) { + return err; + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn->lll.role) { ull_periph_latency_cancel(conn, handle); @@ -790,10 +908,12 @@ int ull_conn_reset(void) /* Re-initialize the Tx Ack mfifo */ MFIFO_INIT(conn_ack); +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) #if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) /* Reset CPR mutex */ cpr_active_reset(); #endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ err = init_reset(); if (err) { @@ -856,7 +976,6 @@ bool ull_conn_peer_connected(uint8_t const own_id_addr_type, void ull_conn_setup(memq_link_t *rx_link, struct node_rx_hdr *rx) { struct node_rx_ftr *ftr; - struct lll_conn *lll; struct ull_hdr *hdr; /* Store the link in the node rx so that when done event is @@ -868,8 +987,6 @@ void ull_conn_setup(memq_link_t *rx_link, struct node_rx_hdr *rx) * struct lll_adv and struct lll_scan. */ ftr = &(rx->rx_ftr); - lll = *((struct lll_conn **)((uint8_t *)ftr->param + - sizeof(struct lll_hdr))); /* Check for reference count and decide to setup connection * here or when done event arrives. @@ -905,16 +1022,31 @@ int ull_conn_rx(memq_link_t *link, struct node_rx_pdu **rx) switch (pdu_rx->ll_id) { case PDU_DATA_LLID_CTRL: { +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) int nack; nack = ctrl_rx(link, rx, pdu_rx, conn); return nack; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + ARG_UNUSED(link); + ARG_UNUSED(pdu_rx); + + ull_cp_rx(conn, *rx); + + /* Mark buffer for release */ + (*rx)->hdr.type = NODE_RX_TYPE_RELEASE; + return 0; +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ } case PDU_DATA_LLID_DATA_CONTINUE: case PDU_DATA_LLID_DATA_START: #if defined(CONFIG_BT_CTLR_LE_ENC) +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (conn->llcp_enc.pause_rx) { +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + if (conn->pause_rx_data) { +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ conn->llcp_terminate.reason_final = BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL; @@ -927,7 +1059,11 @@ int ull_conn_rx(memq_link_t *link, struct node_rx_pdu **rx) case PDU_DATA_LLID_RESV: default: #if defined(CONFIG_BT_CTLR_LE_ENC) +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (conn->llcp_enc.pause_rx) { +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + if (conn->pause_rx_data) { +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ conn->llcp_terminate.reason_final = BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL; } @@ -947,6 +1083,7 @@ int ull_conn_rx(memq_link_t *link, struct node_rx_pdu **rx) int ull_conn_llcp(struct ll_conn *conn, uint32_t ticks_at_expire, uint16_t lazy) { +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) /* Check if no other procedure with instant is requested and not in * Encryption setup. */ @@ -1201,6 +1338,29 @@ int ull_conn_llcp(struct ll_conn *conn, uint32_t ticks_at_expire, uint16_t lazy) } return 0; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + LL_ASSERT(conn->lll.handle != LLL_HANDLE_INVALID); + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + conn->llcp.prep.ticks_at_expire = ticks_at_expire; +#else /* !CONFIG_BT_CTLR_CONN_PARAM_REQ */ + ARG_UNUSED(ticks_at_expire); +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + conn->llcp.prep.lazy = lazy; + + ull_cp_run(conn); + + if (conn->cancel_prepare) { + /* Reset signal */ + conn->cancel_prepare = 0U; + + /* Cancel prepare */ + return -ECANCELED; + } + + /* Continue prepare */ + return 0; +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ } void ull_conn_done(struct node_rx_event_done *done) @@ -1229,7 +1389,11 @@ void ull_conn_done(struct node_rx_event_done *done) switch (done->extra.mic_state) { case LLL_CONN_MIC_NONE: #if defined(CONFIG_BT_CTLR_LE_PING) +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (lll->enc_rx || conn->llcp_enc.pause_rx) { +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + if (lll->enc_rx || ull_cp_encryption_paused(conn)) { +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ uint16_t appto_reload_new; /* check for change in apto */ @@ -1278,9 +1442,11 @@ void ull_conn_done(struct node_rx_event_done *done) 0 || #endif /* CONFIG_BT_PERIPHERAL */ #if defined(CONFIG_BT_CENTRAL) +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) (((conn->llcp_terminate.req - conn->llcp_terminate.ack) & 0xFF) == TERM_ACKED) || +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ conn->central.terminate_ack || (reason_final == BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL) #else /* CONFIG_BT_CENTRAL */ @@ -1315,6 +1481,7 @@ void ull_conn_done(struct node_rx_event_done *done) ull_drift_ticks_get(done, &ticks_drift_plus, &ticks_drift_minus); +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (!conn->tx_head) { ull_conn_tx_demux(UINT8_MAX); } @@ -1326,6 +1493,19 @@ void ull_conn_done(struct node_rx_event_done *done) } else if (lll->periph.latency_enabled) { lll->latency_event = lll->latency; } +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + if (!ull_tx_q_peek(&conn->tx_q)) { + ull_conn_tx_demux(UINT8_MAX); + } + + if (ull_tx_q_peek(&conn->tx_q) || + memq_peek(lll->memq_tx.head, + lll->memq_tx.tail, NULL)) { + lll->latency_event = 0; + } else if (lll->periph.latency_enabled) { + lll->latency_event = lll->latency; + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ #endif /* CONFIG_BT_PERIPHERAL */ #if defined(CONFIG_BT_CENTRAL) @@ -1447,11 +1627,16 @@ void ull_conn_done(struct node_rx_event_done *done) } else { conn->appto_expire = 0U; +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if ((conn->procedure_expire == 0U) && (conn->llcp_req == conn->llcp_ack)) { conn->llcp_type = LLCP_PING; conn->llcp_ack -= 2U; } +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + /* Initiate LE_PING procedure */ + ull_cp_le_ping(conn); +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ } } #endif /* CONFIG_BT_CTLR_LE_PING */ @@ -1482,6 +1667,7 @@ void ull_conn_done(struct node_rx_event_done *done) } #endif /* CONFIG_BT_CTLR_CONN_RSSI_EVENT */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) /* break latency based on ctrl procedure pending */ if (((((conn->llcp_req - conn->llcp_ack) & 0x03) == 0x02) && ((conn->llcp_type == LLCP_CONN_UPD) || @@ -1489,6 +1675,7 @@ void ull_conn_done(struct node_rx_event_done *done) (conn->llcp_cu.req != conn->llcp_cu.ack)) { lll->latency_event = 0U; } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ /* check if latency needs update */ lazy = 0U; @@ -1543,6 +1730,7 @@ void ull_conn_tx_demux(uint8_t count) } #endif /* CONFIG_BT_CTLR_LLID_DATA_START_EMPTY */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) tx->next = NULL; if (!conn->tx_data) { conn->tx_data = tx; @@ -1557,6 +1745,9 @@ void ull_conn_tx_demux(uint8_t count) } conn->tx_data_last = tx; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + ull_tx_q_enqueue_data(&conn->tx_q, tx); +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ } else { struct node_tx *tx = lll_tx->node; struct pdu_data *p = (void *)tx->pdu; @@ -1575,6 +1766,7 @@ ull_conn_tx_demux_release: void ull_conn_tx_lll_enqueue(struct ll_conn *conn, uint8_t count) { +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) bool pause_tx = false; while (conn->tx_head && @@ -1604,6 +1796,24 @@ void ull_conn_tx_lll_enqueue(struct ll_conn *conn, uint8_t count) memq_enqueue(link, tx, &conn->lll.memq_tx.tail); } +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + while (count--) { + struct node_tx *tx; + memq_link_t *link; + + tx = tx_ull_dequeue(conn, NULL); + if (!tx) { + /* No more tx nodes available */ + break; + } + + link = mem_acquire(&mem_link_tx.free); + LL_ASSERT(link); + + /* Enqueue towards LLL */ + memq_enqueue(link, tx, &conn->lll.memq_tx.tail); + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ } void ull_conn_link_tx_release(void *link) @@ -1681,21 +1891,30 @@ void ull_conn_tx_ack(uint16_t handle, memq_link_t *link, struct node_tx *tx) if (handle != LLL_HANDLE_INVALID) { struct ll_conn *conn = ll_conn_get(handle); +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) ctrl_tx_ack(conn, &tx, pdu_tx); +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + ull_cp_tx_ack(conn, tx); +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ } /* release ctrl mem if points to itself */ if (link->next == (void *)tx) { LL_ASSERT(link->next); +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) mem_release(tx, &mem_conn_tx_ctrl.free); +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + struct ll_conn *conn = ll_conn_get(handle); + + ull_cp_release_tx(conn, tx); +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ return; } else if (!tx) { /* Tx Node re-used to enqueue new ctrl PDU */ return; - } else { - LL_ASSERT(!link->next); } + LL_ASSERT(!link->next); } else if (handle == LLL_HANDLE_INVALID) { pdu_tx->ll_id = PDU_DATA_LLID_RESV; } else { @@ -1703,13 +1922,13 @@ void ull_conn_tx_ack(uint16_t handle, memq_link_t *link, struct node_tx *tx) } ll_tx_ack_put(handle, tx); - - return; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) uint8_t ull_conn_llcp_req(void *conn) { struct ll_conn * const conn_hdr = conn; + if (conn_hdr->llcp_req != conn_hdr->llcp_ack) { return BT_HCI_ERR_CMD_DISALLOWED; } @@ -1722,6 +1941,7 @@ uint8_t ull_conn_llcp_req(void *conn) return 0; } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ uint16_t ull_conn_lll_max_tx_octets_get(struct lll_conn *lll) { @@ -1736,7 +1956,11 @@ uint16_t ull_conn_lll_max_tx_octets_get(struct lll_conn *lll) * Deduct 10 bytes for preamble (1), access address (4), * header (2), and CRC (3). */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) max_tx_octets = (lll->max_tx_time >> 3) - 10; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + max_tx_octets = (lll->dle.eff.max_tx_time >> 3) - 10; +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ break; case PHY_2M: @@ -1744,7 +1968,11 @@ uint16_t ull_conn_lll_max_tx_octets_get(struct lll_conn *lll) * Deduct 11 bytes for preamble (2), access address (4), * header (2), and CRC (3). */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) max_tx_octets = (lll->max_tx_time >> 2) - 11; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + max_tx_octets = (lll->dle.eff.max_tx_time >> 2) - 11; +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ break; #if defined(CONFIG_BT_CTLR_PHY_CODED) @@ -1757,8 +1985,13 @@ uint16_t ull_conn_lll_max_tx_octets_get(struct lll_conn *lll) * TERM2 (24), total 592 us. * Subtract 2 bytes for header. */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) max_tx_octets = ((lll->max_tx_time - 592) >> 6) - 2; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + max_tx_octets = ((lll->dle.eff.max_tx_time - 592) >> + 6) - 2; +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ } else { /* S2 Coded PHY, 2us = 1 bit, hence divide by * 16. @@ -1767,8 +2000,13 @@ uint16_t ull_conn_lll_max_tx_octets_get(struct lll_conn *lll) * TERM2 (6), total 430 us. * Subtract 2 bytes for header. */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) max_tx_octets = ((lll->max_tx_time - 430) >> 4) - 2; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + max_tx_octets = ((lll->dle.eff.max_tx_time - 430) >> + 4) - 2; +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ } break; #endif /* CONFIG_BT_CTLR_PHY_CODED */ @@ -1781,11 +2019,22 @@ uint16_t ull_conn_lll_max_tx_octets_get(struct lll_conn *lll) } #endif /* CONFIG_BT_CTLR_LE_ENC */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (max_tx_octets > lll->max_tx_octets) { max_tx_octets = lll->max_tx_octets; } +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + if (max_tx_octets > lll->dle.eff.max_tx_octets) { + max_tx_octets = lll->dle.eff.max_tx_octets; + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + #else /* !CONFIG_BT_CTLR_PHY */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) max_tx_octets = lll->max_tx_octets; +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + max_tx_octets = lll->dle.eff.max_tx_octets; +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ #endif /* !CONFIG_BT_CTLR_PHY */ #else /* !CONFIG_BT_CTLR_DATA_LENGTH */ max_tx_octets = PDU_DC_PAYLOAD_SIZE_MIN; @@ -1812,6 +2061,11 @@ static int init_reset(void) CONFIG_BT_BUF_ACL_TX_COUNT + CONN_TX_CTRL_BUFFERS, &mem_link_tx.free); +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) + /* Initialize control procedure system. */ + ull_cp_init(); +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + #if defined(CONFIG_BT_CTLR_DATA_LENGTH) /* Initialize the DLE defaults */ default_tx_octets = PDU_DC_PAYLOAD_SIZE_MIN; @@ -1844,6 +2098,7 @@ static void tx_demux(void *param) ull_conn_tx_lll_enqueue(param, 1); } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) static struct node_tx *tx_ull_dequeue(struct ll_conn *conn, struct node_tx *tx) { #if defined(CONFIG_BT_CTLR_LE_ENC) @@ -1875,6 +2130,27 @@ static struct node_tx *tx_ull_dequeue(struct ll_conn *conn, struct node_tx *tx) return tx; } +#else +static struct node_tx *tx_ull_dequeue(struct ll_conn *conn, struct node_tx *unused) +{ + struct node_tx *tx = NULL; + + tx = ull_tx_q_dequeue(&conn->tx_q); + if (tx) { + struct pdu_data *pdu_tx; + + pdu_tx = (void *)tx->pdu; + if (pdu_tx->ll_id == PDU_DATA_LLID_CTRL) { + /* Mark the tx node as belonging to the ctrl pool */ + tx->next = tx; + } else { + /* Mark the tx node as belonging to the data pool */ + tx->next = NULL; + } + } + return tx; +} +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ static void ticker_update_conn_op_cb(uint32_t status, void *param) { @@ -1983,6 +2259,7 @@ static void conn_cleanup_finalize(struct ll_conn *conn) struct node_rx_pdu *rx; uint32_t ticker_status; +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) /* release any llcp reserved rx node */ rx = conn->llcp_rx; while (rx) { @@ -1998,6 +2275,9 @@ static void conn_cleanup_finalize(struct ll_conn *conn) /* enqueue rx node towards Thread */ ll_rx_put(hdr->link, hdr); } +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + ARG_UNUSED(rx); +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ /* flush demux-ed Tx buffer still in ULL context */ tx_ull_flush(conn); @@ -2025,10 +2305,12 @@ static void conn_cleanup(struct ll_conn *conn, uint8_t reason) struct ll_conn_iso_stream *cis; #endif /* CONFIG_BT_CTLR_PERIPHERAL_ISO || CONFIG_BT_CTLR_CENTRAL_ISO */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) #if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) /* Reset CPR mutex */ cpr_active_check_and_reset(conn); #endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ /* Only termination structure is populated here in ULL context * but the actual enqueue happens in the LLL context in @@ -2056,6 +2338,7 @@ static void conn_cleanup(struct ll_conn *conn, uint8_t reason) static void tx_ull_flush(struct ll_conn *conn) { +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) while (conn->tx_head) { struct node_tx *tx; memq_link_t *link; @@ -2067,6 +2350,22 @@ static void tx_ull_flush(struct ll_conn *conn) memq_enqueue(link, tx, &conn->lll.memq_tx.tail); } +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + struct node_tx *tx; + + tx = tx_ull_dequeue(conn, NULL); + while (tx) { + memq_link_t *link; + + link = mem_acquire(&mem_link_tx.free); + LL_ASSERT(link); + + /* Enqueue towards LLL */ + memq_enqueue(link, tx, &conn->lll.memq_tx.tail); + + tx = tx_ull_dequeue(conn, NULL); + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ } static void ticker_stop_op_cb(uint32_t status, void *param) @@ -2203,6 +2502,7 @@ static int empty_data_start_release(struct ll_conn *conn, struct node_tx *tx) } #endif /* CONFIG_BT_CTLR_LLID_DATA_START_EMPTY */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) /* Check transaction violation and get free ctrl tx PDU */ static struct node_tx *ctrl_tx_rsp_mem_acquire(struct ll_conn *conn, struct node_rx_pdu *rx, @@ -7070,6 +7370,7 @@ ull_conn_rx_unknown_rsp_send: return nack; } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ #if defined(CONFIG_BT_CTLR_FORCE_MD_AUTO) static uint8_t force_md_cnt_calc(struct lll_conn *lll_conn, uint32_t tx_rate) @@ -7120,3 +7421,416 @@ static uint8_t force_md_cnt_calc(struct lll_conn *lll_conn, uint32_t tx_rate) return force_md_cnt; } #endif /* CONFIG_BT_CTLR_FORCE_MD_AUTO */ + +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) + +#if defined(CONFIG_BT_CTLR_LE_ENC) +/** + * @brief Pause the data path of a rx queue. + */ +void ull_conn_pause_rx_data(struct ll_conn *conn) +{ + conn->pause_rx_data = 1U; +} + +/** + * @brief Resume the data path of a rx queue. + */ +void ull_conn_resume_rx_data(struct ll_conn *conn) +{ + conn->pause_rx_data = 0U; +} +#endif /* CONFIG_BT_CTLR_LE_ENC */ + +/** + * @brief Restart procedure timeout 'timer' + */ +void ull_conn_prt_reload(struct ll_conn *conn, uint16_t procedure_reload) +{ + conn->procedure_expire = procedure_reload; +} + +/** + * @brief Clear procedure timeout 'timer' + */ +void ull_conn_prt_clear(struct ll_conn *conn) +{ + conn->procedure_expire = 0U; +} +uint16_t ull_conn_event_counter(struct ll_conn *conn) +{ + struct lll_conn *lll; + uint16_t event_counter; + + uint16_t lazy = conn->llcp.prep.lazy; + + lll = &conn->lll; + + /* Calculate current event counter */ + event_counter = lll->event_counter + lll->latency_prepare + lazy; + + return event_counter; +} + +void ull_conn_update_parameters(struct ll_conn *conn, uint8_t is_cu_proc, uint8_t win_size, + uint16_t win_offset_us, uint16_t interval, uint16_t latency, + uint16_t timeout, uint16_t instant) +{ + struct lll_conn *lll; + uint32_t ticks_win_offset = 0U; + uint32_t ticks_slot_overhead; + uint16_t conn_interval_old; + uint16_t conn_interval_new; + uint32_t conn_interval_us; + uint8_t ticker_id_conn; + uint32_t ticker_status; + uint32_t periodic_us; + uint16_t latency_upd; + uint16_t instant_latency; + uint16_t event_counter; + uint32_t ticks_at_expire; + + lll = &conn->lll; + + /* Calculate current event counter */ + event_counter = ull_conn_event_counter(conn); + + instant_latency = (event_counter - instant) & 0xFFFF; + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + if (!is_cu_proc) { + /* Stop procedure timeout */ + conn->procedure_expire = 0U; + } +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + + ticks_at_expire = conn->llcp.prep.ticks_at_expire; + +#if defined(CONFIG_BT_CTLR_XTAL_ADVANCED) + /* restore to normal prepare */ + if (conn->ull.ticks_prepare_to_start & XON_BITMASK) { + uint32_t ticks_prepare_to_start = + MAX(conn->ull.ticks_active_to_start, conn->ull.ticks_preempt_to_start); + + conn->ull.ticks_prepare_to_start &= ~XON_BITMASK; + + ticks_at_expire -= (conn->ull.ticks_prepare_to_start - ticks_prepare_to_start); + } +#endif /* CONFIG_BT_CTLR_XTAL_ADVANCED */ + + /* compensate for instant_latency due to laziness */ + conn_interval_old = instant_latency * lll->interval; + latency_upd = conn_interval_old / interval; + conn_interval_new = latency_upd * interval; + if (conn_interval_new > conn_interval_old) { + ticks_at_expire += HAL_TICKER_US_TO_TICKS((conn_interval_new - conn_interval_old) * + CONN_INT_UNIT_US); + } else { + ticks_at_expire -= HAL_TICKER_US_TO_TICKS((conn_interval_old - conn_interval_new) * + CONN_INT_UNIT_US); + } + + lll->latency_prepare += conn->llcp.prep.lazy; + lll->latency_prepare -= (instant_latency - latency_upd); + + /* calculate the offset */ + if (IS_ENABLED(CONFIG_BT_CTLR_LOW_LAT)) { + ticks_slot_overhead = + MAX(conn->ull.ticks_active_to_start, conn->ull.ticks_prepare_to_start); + } else { + ticks_slot_overhead = 0U; + } + + /* calculate the window widening and interval */ + conn_interval_us = interval * CONN_INT_UNIT_US; + periodic_us = conn_interval_us; + + switch (lll->role) { +#if defined(CONFIG_BT_PERIPHERAL) + case BT_HCI_ROLE_PERIPHERAL: + lll->periph.window_widening_prepare_us -= + lll->periph.window_widening_periodic_us * instant_latency; + + lll->periph.window_widening_periodic_us = + (((lll_clock_ppm_local_get() + lll_clock_ppm_get(conn->periph.sca)) * + conn_interval_us) + + (1000000U - 1U)) / + 1000000U; + lll->periph.window_widening_max_us = (conn_interval_us >> 1U) - EVENT_IFS_US; + lll->periph.window_size_prepare_us = win_size * CONN_INT_UNIT_US; + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + conn->periph.ticks_to_offset = 0U; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + + lll->periph.window_widening_prepare_us += + lll->periph.window_widening_periodic_us * latency_upd; + if (lll->periph.window_widening_prepare_us > lll->periph.window_widening_max_us) { + lll->periph.window_widening_prepare_us = lll->periph.window_widening_max_us; + } + + ticks_at_expire -= HAL_TICKER_US_TO_TICKS(lll->periph.window_widening_periodic_us * + latency_upd); + ticks_win_offset = HAL_TICKER_US_TO_TICKS((win_offset_us / CONN_INT_UNIT_US) * + CONN_INT_UNIT_US); + periodic_us -= lll->periph.window_widening_periodic_us; + break; +#endif /* CONFIG_BT_PERIPHERAL */ +#if defined(CONFIG_BT_CENTRAL) + case BT_HCI_ROLE_CENTRAL: + ticks_win_offset = HAL_TICKER_US_TO_TICKS(win_offset_us); + + /* Workaround: Due to the missing remainder param in + * ticker_start function for first interval; add a + * tick so as to use the ceiled value. + */ + ticks_win_offset += 1U; + break; +#endif /*CONFIG_BT_CENTRAL */ + default: + LL_ASSERT(0); + break; + } + + lll->interval = interval; + lll->latency = latency; + + conn->supervision_reload = RADIO_CONN_EVENTS((timeout * 10U * 1000U), conn_interval_us); + conn->procedure_reload = RADIO_CONN_EVENTS((40U * 1000U * 1000U), conn_interval_us); + +#if defined(CONFIG_BT_CTLR_LE_PING) + /* APTO in no. of connection events */ + conn->apto_reload = RADIO_CONN_EVENTS((30U * 1000U * 1000U), conn_interval_us); + /* Dispatch LE Ping PDU 6 connection events (that peer would + * listen to) before 30s timeout + * TODO: "peer listens to" is greater than 30s due to latency + */ + conn->appto_reload = (conn->apto_reload > (lll->latency + 6U)) ? + (conn->apto_reload - (lll->latency + 6U)) : + conn->apto_reload; +#endif /* CONFIG_BT_CTLR_LE_PING */ + + if (is_cu_proc) { + conn->supervision_expire = 0U; + } + +#if (CONFIG_BT_CTLR_ULL_HIGH_PRIO == CONFIG_BT_CTLR_ULL_LOW_PRIO) + /* disable ticker job, in order to chain stop and start + * to avoid RTC being stopped if no tickers active. + */ + uint32_t mayfly_was_enabled = + mayfly_is_enabled(TICKER_USER_ID_ULL_HIGH, TICKER_USER_ID_ULL_LOW); + mayfly_enable(TICKER_USER_ID_ULL_HIGH, TICKER_USER_ID_ULL_LOW, 0U); +#endif /* CONFIG_BT_CTLR_ULL_HIGH_PRIO == CONFIG_BT_CTLR_ULL_LOW_PRIO */ + + /* start periph/central with new timings */ + ticker_id_conn = TICKER_ID_CONN_BASE + ll_conn_handle_get(conn); + ticker_status = ticker_stop(TICKER_INSTANCE_ID_CTLR, TICKER_USER_ID_ULL_HIGH, + ticker_id_conn, ticker_stop_conn_op_cb, (void *)conn); + LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) || + (ticker_status == TICKER_STATUS_BUSY)); + ticker_status = ticker_start( + TICKER_INSTANCE_ID_CTLR, TICKER_USER_ID_ULL_HIGH, ticker_id_conn, ticks_at_expire, + ticks_win_offset, HAL_TICKER_US_TO_TICKS(periodic_us), + HAL_TICKER_REMAINDER(periodic_us), +#if defined(CONFIG_BT_TICKER_LOW_LAT) + TICKER_NULL_LAZY, +#else /* !CONFIG_BT_TICKER_LOW_LAT */ + TICKER_LAZY_MUST_EXPIRE_KEEP, +#endif /* CONFIG_BT_TICKER_LOW_LAT */ + (ticks_slot_overhead + conn->ull.ticks_slot), +#if defined(CONFIG_BT_PERIPHERAL) && defined(CONFIG_BT_CENTRAL) + lll->role == BT_HCI_ROLE_PERIPHERAL ? ull_periph_ticker_cb : ull_central_ticker_cb, +#elif defined(CONFIG_BT_PERIPHERAL) + ull_periph_ticker_cb, +#else + ull_central_ticker_cb, +#endif /* CONFIG_BT_PERIPHERAL && CONFIG_BT_CENTRAL */ + conn, ticker_start_conn_op_cb, (void *)conn); + LL_ASSERT((ticker_status == TICKER_STATUS_SUCCESS) || + (ticker_status == TICKER_STATUS_BUSY)); + +#if (CONFIG_BT_CTLR_ULL_HIGH_PRIO == CONFIG_BT_CTLR_ULL_LOW_PRIO) + /* enable ticker job, if disabled in this function */ + if (mayfly_was_enabled) { + mayfly_enable(TICKER_USER_ID_ULL_HIGH, TICKER_USER_ID_ULL_LOW, 1U); + } +#endif /* CONFIG_BT_CTLR_ULL_HIGH_PRIO == CONFIG_BT_CTLR_ULL_LOW_PRIO */ + + /* Signal that the prepare needs to be canceled */ + conn->cancel_prepare = 1U; +} + +void ull_conn_chan_map_set(struct ll_conn *conn, const uint8_t chm[5]) +{ + struct lll_conn *lll = &conn->lll; + + memcpy(lll->data_chan_map, chm, sizeof(lll->data_chan_map)); + lll->data_chan_count = util_ones_count_get(lll->data_chan_map, sizeof(lll->data_chan_map)); +} + +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +static inline void dle_max_time_get(struct ll_conn *conn, uint16_t *max_rx_time, + uint16_t *max_tx_time) +{ + uint16_t rx_time = 0; + uint16_t tx_time = 0; + uint8_t phy_select = PHY_1M; + +#if defined(CONFIG_BT_CTLR_PHY) + if (conn->llcp.fex.valid && feature_phy_coded(conn)) { + /* If coded PHY is supported on the connection + * this will define the max times + */ + phy_select = PHY_CODED; + /* If not, max times should be defined by 1M timing */ + } +#endif + + rx_time = PDU_DC_MAX_US(LL_LENGTH_OCTETS_RX_MAX, phy_select); + +#if defined(CONFIG_BT_CTLR_PHY) + tx_time = MIN(conn->lll.dle.local.max_tx_time, + PDU_DC_MAX_US(LL_LENGTH_OCTETS_RX_MAX, phy_select)); +#else /* !CONFIG_BT_CTLR_PHY */ + tx_time = PDU_DC_MAX_US(conn->lll.dle.local.max_tx_octets, phy_select); +#endif /* !CONFIG_BT_CTLR_PHY */ + + /* + * see Vol. 6 Part B chapter 4.5.10 + * minimum value for time is 328 us + */ + rx_time = MAX(PDU_DC_PAYLOAD_TIME_MIN, rx_time); + tx_time = MAX(PDU_DC_PAYLOAD_TIME_MIN, tx_time); + + *max_rx_time = rx_time; + *max_tx_time = tx_time; +} + +void ull_dle_max_time_get(struct ll_conn *conn, uint16_t *max_rx_time, + uint16_t *max_tx_time) +{ + return dle_max_time_get(conn, max_rx_time, max_tx_time); +} + +uint8_t ull_dle_update_eff(struct ll_conn *conn) +{ + uint8_t dle_changed = 0; + + const uint16_t eff_tx_octets = + MIN(conn->lll.dle.local.max_tx_octets, conn->lll.dle.remote.max_rx_octets); + const uint16_t eff_rx_octets = + MIN(conn->lll.dle.local.max_rx_octets, conn->lll.dle.remote.max_tx_octets); +#if defined(CONFIG_BT_CTLR_PHY) + const uint16_t eff_tx_time = + MIN(conn->lll.dle.local.max_tx_time, conn->lll.dle.remote.max_rx_time); + const uint16_t eff_rx_time = + MIN(conn->lll.dle.local.max_rx_time, conn->lll.dle.remote.max_tx_time); + + if (eff_tx_time != conn->lll.dle.eff.max_tx_time) { + conn->lll.dle.eff.max_tx_time = eff_tx_time; + dle_changed = 1; + } + if (eff_rx_time != conn->lll.dle.eff.max_rx_time) { + conn->lll.dle.eff.max_rx_time = eff_rx_time; + dle_changed = 1; + } +#else + conn->lll.dle.eff.max_rx_time = PDU_DC_MAX_US(eff_rx_octets, PHY_1M); + conn->lll.dle.eff.max_tx_time = PDU_DC_MAX_US(eff_tx_octets, PHY_1M); +#endif + + if (eff_tx_octets != conn->lll.dle.eff.max_tx_octets) { + conn->lll.dle.eff.max_tx_octets = eff_tx_octets; + dle_changed = 1; + } + if (eff_rx_octets != conn->lll.dle.eff.max_rx_octets) { + conn->lll.dle.eff.max_rx_octets = eff_rx_octets; + dle_changed = 1; + } + + return dle_changed; +} + +void ull_dle_local_tx_update(struct ll_conn *conn, uint16_t tx_octets, uint16_t tx_time) +{ + conn->lll.dle.local.max_tx_octets = tx_octets; + +#if defined(CONFIG_BT_CTLR_PHY) + conn->lll.dle.local.max_tx_time = tx_time; +#endif /* CONFIG_BT_CTLR_PHY */ + + dle_max_time_get(conn, &conn->lll.dle.local.max_rx_time, &conn->lll.dle.local.max_tx_time); +} + +void ull_dle_init(struct ll_conn *conn, uint8_t phy) +{ +#if defined(CONFIG_BT_CTLR_PHY) + const uint16_t max_time_min = PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, phy); + const uint16_t max_time_max = PDU_DC_MAX_US(LL_LENGTH_OCTETS_RX_MAX, phy); +#endif + + /* Clear DLE data set */ + memset(&conn->lll.dle, 0, sizeof(conn->lll.dle)); + /* See BT. 5.2 Spec - Vol 6, Part B, Sect 4.5.10 + * Default to locally max supported rx/tx length/time + */ + ull_dle_local_tx_update(conn, default_tx_octets, default_tx_time); + + conn->lll.dle.local.max_rx_octets = LL_LENGTH_OCTETS_RX_MAX; +#if defined(CONFIG_BT_CTLR_PHY) + conn->lll.dle.local.max_rx_time = max_time_max; +#endif /* CONFIG_BT_CTLR_PHY */ + + /* Default to minimum rx/tx data length/time */ + conn->lll.dle.remote.max_tx_octets = PDU_DC_PAYLOAD_SIZE_MIN; + conn->lll.dle.remote.max_rx_octets = PDU_DC_PAYLOAD_SIZE_MIN; + +#if defined(CONFIG_BT_CTLR_PHY) + conn->lll.dle.remote.max_tx_time = max_time_min; + conn->lll.dle.remote.max_rx_time = max_time_min; +#endif /* CONFIG_BT_CTLR_PHY */ + + ull_dle_update_eff(conn); + + /* Check whether the controller should perform a data length update after + * connection is established + */ +#if defined(CONFIG_BT_CTLR_PHY) + if ((conn->lll.dle.local.max_rx_time != max_time_min || + conn->lll.dle.local.max_tx_time != max_time_min)) { + conn->lll.dle.update = 1; + } else +#endif + { + if (conn->lll.dle.local.max_tx_octets != PDU_DC_PAYLOAD_SIZE_MIN || + conn->lll.dle.local.max_rx_octets != PDU_DC_PAYLOAD_SIZE_MIN) { + conn->lll.dle.update = 1; + } + } +} + +void ull_conn_default_tx_octets_set(uint16_t tx_octets) +{ + default_tx_octets = tx_octets; +} + +void ull_conn_default_tx_time_set(uint16_t tx_time) +{ + default_tx_time = tx_time; +} +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + +uint8_t ull_conn_lll_phy_active(struct ll_conn *conn, uint8_t phys) +{ +#if defined(CONFIG_BT_CTLR_PHY) + if (!(phys & (conn->lll.phy_tx | conn->lll.phy_rx))) { +#else /* !CONFIG_BT_CTLR_PHY */ + if (!(phys & 0x01)) { +#endif /* !CONFIG_BT_CTLR_PHY */ + return 0; + } + return 1; +} + +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ diff --git a/subsys/bluetooth/controller/ll_sw/ull_conn_internal.h b/subsys/bluetooth/controller/ll_sw/ull_conn_internal.h index 15c7ca2eab8..900543feb0d 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_conn_internal.h +++ b/subsys/bluetooth/controller/ll_sw/ull_conn_internal.h @@ -13,7 +13,6 @@ uint16_t ll_conn_free_count_get(void); void ll_tx_ack_put(uint16_t handle, struct node_tx *node_tx); int ull_conn_init(void); int ull_conn_reset(void); -void ull_conn_chan_map_set(uint8_t *chan_map); uint16_t ull_conn_default_tx_octets_get(void); uint16_t ull_conn_default_tx_time_get(void); uint8_t ull_conn_default_phy_tx_get(void); @@ -37,3 +36,63 @@ memq_link_t *ull_conn_ack_by_last_peek(uint8_t last, uint16_t *handle, void *ull_conn_ack_dequeue(void); void ull_conn_tx_ack(uint16_t handle, memq_link_t *link, struct node_tx *tx); uint8_t ull_conn_llcp_req(void *conn); + +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) + +uint16_t ull_conn_event_counter(struct ll_conn *conn); + +void ull_conn_update_parameters(struct ll_conn *conn, uint8_t is_cu_proc, + uint8_t win_size, uint16_t win_offset_us, + uint16_t interval, uint16_t latency, + uint16_t timeout, uint16_t instant); + +void ull_conn_default_tx_octets_set(uint16_t tx_octets); + +void ull_conn_default_tx_time_set(uint16_t tx_time); + +uint8_t ull_conn_lll_phy_active(struct ll_conn *conn, uint8_t phy); + +void ull_dle_init(struct ll_conn *conn, uint8_t phy); + +void ull_dle_max_time_get(struct ll_conn *conn, uint16_t *max_rx_time, + uint16_t *max_tx_time); + +uint8_t ull_dle_update_eff(struct ll_conn *conn); + +void ull_dle_local_tx_update(struct ll_conn *conn, uint16_t tx_octets, uint16_t tx_time); + +void ull_conn_default_phy_tx_set(uint8_t tx); + +void ull_conn_default_phy_rx_set(uint8_t rx); + +void ull_conn_chan_map_set(struct ll_conn *conn, const uint8_t chm[5]); + +void *ull_conn_tx_mem_acquire(void); + +void ull_conn_tx_mem_release(void *tx); + +uint8_t ull_conn_mfifo_get_tx(void **lll_tx); + +void ull_conn_mfifo_enqueue_tx(uint8_t idx); + +/** + * @brief Pause the data path of a rx queue. + */ +void ull_conn_pause_rx_data(struct ll_conn *conn); + +/** + * @brief Resume the data path of a rx queue. + */ +void ull_conn_resume_rx_data(struct ll_conn *conn); + +/** + * @brief Restart procedure timeout timer + */ +void ull_conn_prt_reload(struct ll_conn *conn, uint16_t procedure_reload); + +/** + * @brief Clear procedure timeout timer + */ +void ull_conn_prt_clear(struct ll_conn *conn); + +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ diff --git a/subsys/bluetooth/controller/ll_sw/ull_conn_types.h b/subsys/bluetooth/controller/ll_sw/ull_conn_types.h index 0eb60f6afd0..62f7da3c625 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_conn_types.h +++ b/subsys/bluetooth/controller/ll_sw/ull_conn_types.h @@ -32,6 +32,7 @@ enum llcp { #endif /* CONFIG_BT_CTLR_PHY */ }; +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) struct ll_conn { struct ull_hdr ull; struct lll_conn lll; @@ -345,6 +346,194 @@ struct ll_conn { struct lll_df_conn_rx_params df_rx_params; #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ }; +#else +/* + * This is for the refactored LLCP + * + * to reduce length and unreadability of the ll_conn struct the + * structures inside it have been defined first + */ +struct llcp_struct { + /* Local Request */ + struct { + sys_slist_t pend_proc_list; + uint8_t state; + } local; + + /* Remote Request */ + struct { + sys_slist_t pend_proc_list; + uint8_t state; + uint8_t collision; + uint8_t incompat; + uint8_t reject_opcode; + } remote; + + /* Prepare parameters */ + struct { + uint32_t ticks_at_expire; + uint16_t lazy; + } prep; + + /* Version Exchange Procedure State */ + struct { + uint8_t sent; + uint8_t valid; + struct pdu_data_llctrl_version_ind cached; + } vex; + + /* + * As of today only 36 feature bits are in use, + * so some optimisation is possible + * we also need to keep track of the features of the + * other node, so that we can send a proper + * reply over HCI to the host + * see BT Core spec 5.2 Vol 6, Part B, sec. 5.1.4 + */ + struct { + uint8_t sent; + uint8_t valid; + uint64_t features_peer; + uint64_t features_used; + } fex; + + /* Minimum used channels procedure state */ + struct { + uint8_t phys; + uint8_t min_used_chans; + } muc; + + /* TODO: we'll need the next few structs eventually, + * Thomas and Szymon please comment on names etc. + */ + struct { + uint16_t *pdu_win_offset; + uint32_t ticks_anchor; + } conn_upd; + +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + /* @brief Constant Tone Extension configuration for CTE request control procedure. */ + struct llcp_df_req_cfg { + /* Procedure may be active periodically, active state must be stored. + * If procedure is active, request parameters update may not be issued. + */ + uint8_t is_enabled; + uint8_t cte_type; + /* Minimum requested CTE length in 8us units */ + uint8_t min_cte_len; + uint16_t req_interval; + } cte_req; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP) + /* TODO (ppryga): Consided move of the type of structure to ull_df_types.h or + * lll_df_types.h. To have single definition of the type and share it wish LLL. + */ + struct llcp_df_rsp_cfg { + uint8_t is_enabled; + uint8_t cte_types; + uint8_t max_cte_len; + uint8_t ant_sw_len; + /* TODO (ppryga): Update to use the same macro as in lll_df_types.h */ + uint8_t ant_ids[CONFIG_BT_CTLR_DF_MAX_ANT_SW_PATTERN_LEN]; + } cte_rsp; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */ +#if (CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0) &&\ + (CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM <\ + CONFIG_BT_CTLR_LLCP_TX_PER_CONN_TX_CTRL_BUF_NUM_MAX) + + uint8_t tx_buffer_alloc; +#endif /* (CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0) */ + +}; /* struct llcp_struct */ + +struct ll_conn { + struct ull_hdr ull; + struct lll_conn lll; + + struct ull_tx_q tx_q; + struct llcp_struct llcp; + + struct { + uint8_t reason_final; + /* node rx type with memory aligned storage for terminate + * reason. + * HCI will reference the value using the pdu member of + * struct node_rx_pdu. + */ + struct { + struct node_rx_hdr hdr; + + uint8_t reason __aligned(4); + } node_rx; + } llcp_terminate; + +/* + * TODO: all the following comes from the legacy LL llcp structure + * and/or needs to be properly integrated in the control procedures + */ + union { +#if defined(CONFIG_BT_PERIPHERAL) + struct { + uint8_t latency_cancel:1; + uint8_t sca:3; + uint32_t force; + uint32_t ticks_to_offset; + } periph; +#endif /* CONFIG_BT_PERIPHERAL */ + +#if defined(CONFIG_BT_CENTRAL) + struct { + uint8_t terminate_ack:1; + } central; +#endif /* CONFIG_BT_CENTRAL */ + }; + + /* Cancel the prepare in the instant a Connection Update takes place */ + uint8_t cancel_prepare:1; + +#if defined(CONFIG_BT_CTLR_LE_ENC) + /* Pause Rx data PDU's */ + uint8_t pause_rx_data:1; +#endif /* CONFIG_BT_CTLR_LE_ENC */ + +#if defined(CONFIG_BT_CTLR_LE_PING) + uint16_t appto_reload; + uint16_t appto_expire; + uint16_t apto_reload; + uint16_t apto_expire; +#endif /* CONFIG_BT_CTLR_LE_PING */ + + uint16_t connect_expire; + uint16_t supervision_reload; + uint16_t supervision_expire; + uint16_t procedure_reload; + uint16_t procedure_expire; + +#if defined(CONFIG_BT_CTLR_PHY) + uint8_t phy_pref_tx:3; + uint8_t phy_pref_rx:3; +#endif /* CONFIG_BT_CTLR_PHY */ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + uint16_t default_tx_octets; + +#if defined(CONFIG_BT_CTLR_PHY) + uint16_t default_tx_time; +#endif /* CONFIG_BT_CTLR_PHY */ +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + +#if defined(CONFIG_BT_CTLR_CHECK_SAME_PEER_CONN) + uint8_t own_id_addr_type:1; + uint8_t peer_id_addr_type:1; + uint8_t own_id_addr[BDADDR_SIZE]; + uint8_t peer_id_addr[BDADDR_SIZE]; +#endif /* CONFIG_BT_CTLR_CHECK_SAME_PEER_CONN */ + +#if defined(CONFIG_BT_CTLR_LLID_DATA_START_EMPTY) + /* Detect empty L2CAP start frame */ + uint8_t start_empty:1; +#endif /* CONFIG_BT_CTLR_LLID_DATA_START_EMPTY */ +}; /* struct ll_conn */ +#endif /* BT_LL_SW_SPLIT_LEGACY */ struct node_rx_cc { uint8_t status; diff --git a/subsys/bluetooth/controller/ll_sw/ull_filter.c b/subsys/bluetooth/controller/ll_sw/ull_filter.c index efc8ab7a317..1908927ae34 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_filter.c +++ b/subsys/bluetooth/controller/ll_sw/ull_filter.c @@ -30,6 +30,10 @@ #include "lll_conn.h" #include "lll_filter.h" +#if (!defined(CONFIG_BT_LL_SW_LLCP_LEGACY)) +#include "ll_sw/ull_tx_queue.h" +#endif + #include "ull_adv_types.h" #include "ull_scan_types.h" #include "ull_conn_types.h" diff --git a/subsys/bluetooth/controller/ll_sw/ull_internal.h b/subsys/bluetooth/controller/ll_sw/ull_internal.h index 8e374e33da3..22b9820eb16 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_internal.h +++ b/subsys/bluetooth/controller/ll_sw/ull_internal.h @@ -31,6 +31,7 @@ static inline void ull_hdr_init(struct ull_hdr *hdr) hdr->disabled_cb = hdr->disabled_param = NULL; } +void ll_tx_ack_put(uint16_t handle, struct node_tx *node_tx); void *ll_rx_link_alloc(void); void ll_rx_link_release(void *link); void *ll_rx_alloc(void); diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp.c b/subsys/bluetooth/controller/ll_sw/ull_llcp.c new file mode 100644 index 00000000000..28f32ee7a4c --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp.c @@ -0,0 +1,1109 @@ +/* + * 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 "pdu.h" +#include "ll.h" +#include "ll_feat.h" +#include "ll_settings.h" + +#include "lll.h" +#include "lll/lll_df_types.h" +#include "lll_conn.h" + +#include "ull_tx_queue.h" + +#include "ull_internal.h" +#include "ull_conn_types.h" +#include "ull_conn_internal.h" +#include "ull_llcp.h" +#include "ull_llcp_features.h" +#include "ull_llcp_internal.h" +#include "ull_periph_internal.h" + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_ctlr_ull_llcp +#include "common/log.h" +#include +#include "hal/debug.h" + +/* LLCP Memory Pool Descriptor */ +struct mem_pool { + void *free; + uint8_t *pool; +}; + +#define LLCTRL_PDU_SIZE (offsetof(struct pdu_data, llctrl) + sizeof(struct pdu_data_llctrl)) +#define PROC_CTX_BUF_SIZE WB_UP(sizeof(struct proc_ctx)) +#define TX_CTRL_BUF_SIZE WB_UP(offsetof(struct node_tx, pdu) + LLCTRL_PDU_SIZE) +#define NTF_BUF_SIZE WB_UP(offsetof(struct node_rx_pdu, pdu) + LLCTRL_PDU_SIZE) + +/* LLCP Allocations */ +#if defined(LLCP_TX_CTRL_BUF_QUEUE_ENABLE) +sys_slist_t tx_buffer_wait_list; +static uint8_t common_tx_buffer_alloc; +#endif /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ + +/* TODO: Determine 'correct' number of tx nodes */ +static uint8_t buffer_mem_tx[TX_CTRL_BUF_SIZE * LLCP_TX_CTRL_BUF_COUNT]; +static struct mem_pool mem_tx = { .pool = buffer_mem_tx }; + +/* TODO: Determine 'correct' number of ctx */ +static uint8_t buffer_mem_ctx[PROC_CTX_BUF_SIZE * CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM]; +static struct mem_pool mem_ctx = { .pool = buffer_mem_ctx }; + +/* + * LLCP Resource Management + */ +static struct proc_ctx *proc_ctx_acquire(void) +{ + struct proc_ctx *ctx; + + ctx = (struct proc_ctx *)mem_acquire(&mem_ctx.free); + return ctx; +} + +void llcp_proc_ctx_release(struct proc_ctx *ctx) +{ + mem_release(ctx, &mem_ctx.free); +} + +#if defined(LLCP_TX_CTRL_BUF_QUEUE_ENABLE) +/* + * @brief Check for per conn pre-allocated tx buffer allowance + * @return true if buffer is available + */ +static inline bool static_tx_buffer_available(struct ll_conn *conn, struct proc_ctx *ctx) +{ +#if (CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0) + /* Check if per connection pre-aloted tx buffer is available */ + if (conn->llcp.tx_buffer_alloc < CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM) { + /* This connection has not yet used up all the pre-aloted tx buffers */ + return true; + } +#endif /* CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0 */ + return false; +} + +/* + * @brief pre-alloc/peek of a tx buffer, leave requester on the wait list (@head if first up) + * + * @return true if alloc is allowed, false if not + * + */ +bool llcp_tx_alloc_peek(struct ll_conn *conn, struct proc_ctx *ctx) +{ + if (!static_tx_buffer_available(conn, ctx)) { + /* The conn already has spent its pre-aloted tx buffer(s), + * so we should consider the common tx buffer pool + */ + if (ctx->wait_reason == WAITING_FOR_NOTHING) { + /* The current procedure is not in line for a tx buffer + * so sign up on the wait list + */ + sys_slist_append(&tx_buffer_wait_list, &ctx->wait_node); + ctx->wait_reason = WAITING_FOR_TX_BUFFER; + } + + /* Now check to see if this procedure context is @ head of the wait list */ + if (ctx->wait_reason == WAITING_FOR_TX_BUFFER && + sys_slist_peek_head(&tx_buffer_wait_list) == &ctx->wait_node) { + return (common_tx_buffer_alloc < + CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM); + } + + return false; + } + return true; +} + +/* + * @brief un-peek of a tx buffer, in case ongoing alloc is aborted + * + */ +void llcp_tx_alloc_unpeek(struct proc_ctx *ctx) +{ + sys_slist_find_and_remove(&tx_buffer_wait_list, &ctx->wait_node); + ctx->wait_reason = WAITING_FOR_NOTHING; +} + +/* + * @brief complete alloc of a tx buffer, must preceded by successful call to + * llcp_tx_alloc_peek() + * + * @return node_tx* that was peek'ed by llcp_tx_alloc_peek() + * + */ +struct node_tx *llcp_tx_alloc(struct ll_conn *conn, struct proc_ctx *ctx) +{ +#if (CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0) + conn->llcp.tx_buffer_alloc++; + if (conn->llcp.tx_buffer_alloc > CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM) { + common_tx_buffer_alloc++; + /* global buffer allocated, so we're at the head and should just pop head */ + sys_slist_get(&tx_buffer_wait_list); + } else { + /* we're allocating conn_tx_buffer, so remove from wait list if waiting */ + if (ctx->wait_reason == WAITING_FOR_TX_BUFFER) { + sys_slist_find_and_remove(&tx_buffer_wait_list, &ctx->wait_node); + } + } +#else /* CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0 */ + /* global buffer allocated, so remove head of wait list */ + common_tx_buffer_alloc++; + sys_slist_get(&tx_buffer_wait_list); +#endif /* CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0 */ + ctx->wait_reason = WAITING_FOR_NOTHING; + + return (struct node_tx *)mem_acquire(&mem_tx.free); +} +#else /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ +bool llcp_tx_alloc_peek(struct ll_conn *conn, struct proc_ctx *ctx) +{ + ARG_UNUSED(conn); + return mem_tx.free != NULL; +} + +void llcp_tx_alloc_unpeek(struct proc_ctx *ctx) +{ + /* Empty on purpose, as unpeek is not needed when no buffer queueing is used */ + ARG_UNUSED(ctx); +} + +struct node_tx *llcp_tx_alloc(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_tx *tx; + + ARG_UNUSED(conn); + tx = (struct node_tx *)mem_acquire(&mem_tx.free); + return tx; +} +#endif /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ + +static void tx_release(struct node_tx *tx) +{ + mem_release(tx, &mem_tx.free); +} + +bool llcp_ntf_alloc_is_available(void) +{ + return ll_pdu_rx_alloc_peek(1) != NULL; +} + +bool llcp_ntf_alloc_num_available(uint8_t count) +{ + return ll_pdu_rx_alloc_peek(count) != NULL; +} + +struct node_rx_pdu *llcp_ntf_alloc(void) +{ + return ll_pdu_rx_alloc(); +} + +/* + * ULL -> LLL Interface + */ + +void llcp_tx_enqueue(struct ll_conn *conn, struct node_tx *tx) +{ + ull_tx_q_enqueue_ctrl(&conn->tx_q, tx); +} + +void llcp_tx_pause_data(struct ll_conn *conn) +{ + ull_tx_q_pause_data(&conn->tx_q); +} + +void llcp_tx_resume_data(struct ll_conn *conn) +{ + ull_tx_q_resume_data(&conn->tx_q); +} + +void llcp_tx_flush(struct ll_conn *conn) +{ + /* TODO(thoh): do something here to flush the TX Q */ +} + +/* + * LLCP Procedure Creation + */ + +static struct proc_ctx *create_procedure(enum llcp_proc proc) +{ + struct proc_ctx *ctx; + + ctx = proc_ctx_acquire(); + if (!ctx) { + return NULL; + } + + ctx->proc = proc; + ctx->collision = 0U; + ctx->pause = 0U; + ctx->done = 0U; + + /* Clear procedure data */ + memset((void *)&ctx->data, 0, sizeof(ctx->data)); + + /* Initialize opcodes fields to known values */ + ctx->rx_opcode = ULL_LLCP_INVALID_OPCODE; + ctx->tx_opcode = ULL_LLCP_INVALID_OPCODE; + ctx->response_opcode = ULL_LLCP_INVALID_OPCODE; + + return ctx; +} + +struct proc_ctx *llcp_create_local_procedure(enum llcp_proc proc) +{ + struct proc_ctx *ctx; + + ctx = create_procedure(proc); + if (!ctx) { + return NULL; + } + + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PROC_LE_PING: + llcp_lp_comm_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PROC_FEATURE_EXCHANGE: + llcp_lp_comm_init_proc(ctx); + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) + case PROC_MIN_USED_CHANS: + llcp_lp_comm_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + case PROC_VERSION_EXCHANGE: + llcp_lp_comm_init_proc(ctx); + break; +#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_CENTRAL) + case PROC_ENCRYPTION_START: + case PROC_ENCRYPTION_PAUSE: + llcp_lp_enc_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_CENTRAL */ +#ifdef CONFIG_BT_CTLR_PHY + case PROC_PHY_UPDATE: + llcp_lp_pu_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_PHY */ + case PROC_CONN_UPDATE: + case PROC_CONN_PARAM_REQ: + llcp_lp_cu_init_proc(ctx); + break; + case PROC_TERMINATE: + llcp_lp_comm_init_proc(ctx); + break; +#if defined(CONFIG_BT_CENTRAL) + case PROC_CHAN_MAP_UPDATE: + llcp_lp_chmu_init_proc(ctx); + break; +#endif /* CONFIG_BT_CENTRAL */ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + llcp_lp_comm_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: + llcp_lp_comm_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + break; + } + + return ctx; +} + +struct proc_ctx *llcp_create_remote_procedure(enum llcp_proc proc) +{ + struct proc_ctx *ctx; + + ctx = create_procedure(proc); + if (!ctx) { + return NULL; + } + + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PROC_LE_PING: + llcp_rp_comm_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PROC_FEATURE_EXCHANGE: + llcp_rp_comm_init_proc(ctx); + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) + case PROC_MIN_USED_CHANS: + llcp_rp_comm_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + case PROC_VERSION_EXCHANGE: + llcp_rp_comm_init_proc(ctx); + break; +#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_PERIPHERAL) + case PROC_ENCRYPTION_START: + case PROC_ENCRYPTION_PAUSE: + llcp_rp_enc_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_PERIPHERAL */ +#ifdef CONFIG_BT_CTLR_PHY + case PROC_PHY_UPDATE: + llcp_rp_pu_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_PHY */ + case PROC_CONN_UPDATE: + case PROC_CONN_PARAM_REQ: + llcp_rp_cu_init_proc(ctx); + break; + case PROC_TERMINATE: + llcp_rp_comm_init_proc(ctx); + break; +#if defined(CONFIG_BT_PERIPHERAL) + case PROC_CHAN_MAP_UPDATE: + llcp_rp_chmu_init_proc(ctx); + break; +#endif /* CONFIG_BT_PERIPHERAL */ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + llcp_rp_comm_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: + llcp_rp_comm_init_proc(ctx); + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + break; + } + + return ctx; +} + +/* + * LLCP Public API + */ + +void ull_cp_init(void) +{ + mem_init(mem_ctx.pool, PROC_CTX_BUF_SIZE, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + &mem_ctx.free); + mem_init(mem_tx.pool, TX_CTRL_BUF_SIZE, LLCP_TX_CTRL_BUF_COUNT, &mem_tx.free); + +#if defined(LLCP_TX_CTRL_BUF_QUEUE_ENABLE) + /* Reset buffer alloc management */ + sys_slist_init(&tx_buffer_wait_list); + common_tx_buffer_alloc = 0; +#endif /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ +} + +void ull_llcp_init(struct ll_conn *conn) +{ + /* Reset local request fsm */ + llcp_lr_init(conn); + sys_slist_init(&conn->llcp.local.pend_proc_list); + + /* Reset remote request fsm */ + llcp_rr_init(conn); + sys_slist_init(&conn->llcp.remote.pend_proc_list); + conn->llcp.remote.incompat = INCOMPAT_NO_COLLISION; + conn->llcp.remote.collision = 0U; + + /* Reset the cached version Information (PROC_VERSION_EXCHANGE) */ + memset(&conn->llcp.vex, 0, sizeof(conn->llcp.vex)); + +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) + /* Reset the cached min used channels information (PROC_MIN_USED_CHANS) */ + memset(&conn->llcp.muc, 0, sizeof(conn->llcp.muc)); +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + + /* Reset the feature exchange fields */ + memset(&conn->llcp.fex, 0, sizeof(conn->llcp.fex)); + conn->llcp.fex.features_used = LL_FEAT; + +#if defined(CONFIG_BT_CTLR_LE_ENC) + /* Reset encryption related state */ + conn->lll.enc_tx = 0U; + conn->lll.enc_rx = 0U; +#endif /* CONFIG_BT_CTLR_LE_ENC */ + +#if defined(LLCP_TX_CTRL_BUF_QUEUE_ENABLE) +#if (CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0) + conn->llcp.tx_buffer_alloc = 0; +#endif /* (CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0) */ +#endif /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ + + conn->lll.event_counter = 0; + + llcp_lr_init(conn); + llcp_rr_init(conn); +} + +void ull_cp_release_tx(struct ll_conn *conn, struct node_tx *tx) +{ +#if defined(LLCP_TX_CTRL_BUF_QUEUE_ENABLE) +#if (CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0) + if (conn->llcp.tx_buffer_alloc > CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM) { + common_tx_buffer_alloc--; + } + conn->llcp.tx_buffer_alloc--; +#else /* CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0 */ + ARG_UNUSED(conn); + common_tx_buffer_alloc--; +#endif /* CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0 */ +#else /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ + ARG_UNUSED(conn); +#endif /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ + tx_release(tx); +} + +void ull_cp_release_ntf(struct node_rx_pdu *ntf) +{ + ntf->hdr.next = NULL; + ll_rx_mem_release((void **)&ntf); +} + +void ull_cp_run(struct ll_conn *conn) +{ + llcp_rr_run(conn); + llcp_lr_run(conn); +} + +void ull_cp_state_set(struct ll_conn *conn, uint8_t state) +{ + switch (state) { + case ULL_CP_CONNECTED: + llcp_rr_connect(conn); + llcp_lr_connect(conn); + break; + case ULL_CP_DISCONNECTED: + llcp_rr_disconnect(conn); + llcp_lr_disconnect(conn); + break; + default: + break; + } +} + +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) +uint8_t ull_cp_min_used_chans(struct ll_conn *conn, uint8_t phys, uint8_t min_used_chans) +{ + struct proc_ctx *ctx; + + if (conn->lll.role != BT_HCI_ROLE_PERIPHERAL) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + ctx = llcp_create_local_procedure(PROC_MIN_USED_CHANS); + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + ctx->data.muc.phys = phys; + ctx->data.muc.min_used_chans = min_used_chans; + + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + +#if defined(CONFIG_BT_CTLR_LE_PING) +uint8_t ull_cp_le_ping(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = llcp_create_local_procedure(PROC_LE_PING); + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} +#endif /* CONFIG_BT_CTLR_LE_PING */ + +#if defined(CONFIG_BT_CENTRAL) || defined(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG) +uint8_t ull_cp_feature_exchange(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = llcp_create_local_procedure(PROC_FEATURE_EXCHANGE); + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} +#endif /* CONFIG_BT_CENTRAL || CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG */ + +uint8_t ull_cp_version_exchange(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = llcp_create_local_procedure(PROC_VERSION_EXCHANGE); + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} + +#if defined(CONFIG_BT_CTLR_LE_ENC) +#if defined(CONFIG_BT_CENTRAL) +uint8_t ull_cp_encryption_start(struct ll_conn *conn, const uint8_t rand[8], const uint8_t ediv[2], + const uint8_t ltk[16]) +{ + struct proc_ctx *ctx; + + /* TODO(thoh): Proper checks for role, parameters etc. */ + + ctx = llcp_create_local_procedure(PROC_ENCRYPTION_START); + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + /* Copy input parameters */ + memcpy(ctx->data.enc.rand, rand, sizeof(ctx->data.enc.rand)); + ctx->data.enc.ediv[0] = ediv[0]; + ctx->data.enc.ediv[1] = ediv[1]; + memcpy(ctx->data.enc.ltk, ltk, sizeof(ctx->data.enc.ltk)); + + /* Enqueue request */ + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} + +uint8_t ull_cp_encryption_pause(struct ll_conn *conn, const uint8_t rand[8], const uint8_t ediv[2], + const uint8_t ltk[16]) +{ + struct proc_ctx *ctx; + + /* TODO(thoh): Proper checks for role, parameters etc. */ + + ctx = llcp_create_local_procedure(PROC_ENCRYPTION_PAUSE); + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + /* Copy input parameters */ + memcpy(ctx->data.enc.rand, rand, sizeof(ctx->data.enc.rand)); + ctx->data.enc.ediv[0] = ediv[0]; + ctx->data.enc.ediv[1] = ediv[1]; + memcpy(ctx->data.enc.ltk, ltk, sizeof(ctx->data.enc.ltk)); + + /* Enqueue request */ + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} +#endif /* CONFIG_BT_CENTRAL */ + +uint8_t ull_cp_encryption_paused(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = llcp_rr_peek(conn); + if (ctx && ctx->proc == PROC_ENCRYPTION_PAUSE) { + return 1; + } + + ctx = llcp_lr_peek(conn); + if (ctx && ctx->proc == PROC_ENCRYPTION_PAUSE) { + return 1; + } + + return 0; +} +#endif /* CONFIG_BT_CTLR_LE_ENC */ + +#if defined(CONFIG_BT_CTLR_PHY) +uint8_t ull_cp_phy_update(struct ll_conn *conn, uint8_t tx, uint8_t flags, uint8_t rx, + uint8_t host_initiated) +{ + struct proc_ctx *ctx; + + /* TODO(thoh): Proper checks for role, parameters etc. */ + + ctx = llcp_create_local_procedure(PROC_PHY_UPDATE); + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + ctx->data.pu.tx = tx; + ctx->data.pu.flags = flags; + ctx->data.pu.rx = rx; + ctx->data.pu.host_initiated = host_initiated; + + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} +#endif /* CONFIG_BT_CTLR_PHY */ + +uint8_t ull_cp_terminate(struct ll_conn *conn, uint8_t error_code) +{ + struct proc_ctx *ctx; + + llcp_lr_abort(conn); + + ctx = llcp_create_local_procedure(PROC_TERMINATE); + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + ctx->data.term.error_code = error_code; + + /* TODO + * Termination procedure may be initiated at any time, even if other + * LLCP is active. + */ + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} + +#if defined(CONFIG_BT_CENTRAL) +uint8_t ull_cp_chan_map_update(struct ll_conn *conn, const uint8_t chm[5]) +{ + struct proc_ctx *ctx; + + if (conn->lll.role != BT_HCI_ROLE_CENTRAL) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + ctx = llcp_create_local_procedure(PROC_CHAN_MAP_UPDATE); + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + memcpy(ctx->data.chmu.chm, chm, sizeof(ctx->data.chmu.chm)); + + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} +#endif /* CONFIG_BT_CENTRAL */ + +const uint8_t *ull_cp_chan_map_update_pending(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + if (conn->lll.role == BT_HCI_ROLE_CENTRAL) { + ctx = llcp_lr_peek(conn); + } else { + ctx = llcp_rr_peek(conn); + } + + if (ctx && ctx->proc == PROC_CHAN_MAP_UPDATE) { + return ctx->data.chmu.chm; + } + + return NULL; +} + +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +uint8_t ull_cp_data_length_update(struct ll_conn *conn, uint16_t max_tx_octets, + uint16_t max_tx_time) +{ + struct proc_ctx *ctx; + + ctx = llcp_create_local_procedure(PROC_DATA_LENGTH_UPDATE); + + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + /* Apply update to local */ + ull_dle_local_tx_update(conn, max_tx_octets, max_tx_time); + + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + +#if defined(CONFIG_BT_CTLR_LE_ENC) +void ull_cp_ltk_req_reply(struct ll_conn *conn, const uint8_t ltk[16]) +{ + /* TODO(thoh): Call rp_enc to query if LTK request reply is allowed */ + struct proc_ctx *ctx; + + ctx = llcp_rr_peek(conn); + if (ctx && (ctx->proc == PROC_ENCRYPTION_START || ctx->proc == PROC_ENCRYPTION_PAUSE)) { + memcpy(ctx->data.enc.ltk, ltk, sizeof(ctx->data.enc.ltk)); + llcp_rp_enc_ltk_req_reply(conn, ctx); + } +} + +void ull_cp_ltk_req_neq_reply(struct ll_conn *conn) +{ + /* TODO(thoh): Call rp_enc to query if LTK negative request reply is allowed */ + struct proc_ctx *ctx; + + ctx = llcp_rr_peek(conn); + if (ctx && (ctx->proc == PROC_ENCRYPTION_START || ctx->proc == PROC_ENCRYPTION_PAUSE)) { + llcp_rp_enc_ltk_req_neg_reply(conn, ctx); + } +} +#endif /* CONFIG_BT_CTLR_LE_ENC */ + +uint8_t ull_cp_conn_update(struct ll_conn *conn, uint16_t interval_min, uint16_t interval_max, + uint16_t latency, uint16_t timeout) +{ + struct proc_ctx *ctx; + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + if (feature_conn_param_req(conn)) { + ctx = llcp_create_local_procedure(PROC_CONN_PARAM_REQ); + } else if (conn->lll.role == BT_HCI_ROLE_CENTRAL) { + ctx = llcp_create_local_procedure(PROC_CONN_UPDATE); + } else { + return BT_HCI_ERR_UNSUPP_REMOTE_FEATURE; + } +#else /* !CONFIG_BT_CTLR_CONN_PARAM_REQ */ + if (conn->lll.role == BT_HCI_ROLE_PERIPHERAL) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + ctx = llcp_create_local_procedure(PROC_CONN_UPDATE); +#endif /* !CONFIG_BT_CTLR_CONN_PARAM_REQ */ + + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + /* Store arguments in corresponding procedure context */ + if (ctx->proc == PROC_CONN_UPDATE) { + ctx->data.cu.interval_max = interval_max; + ctx->data.cu.latency = latency; + ctx->data.cu.timeout = timeout; +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + } else if (ctx->proc == PROC_CONN_PARAM_REQ) { + ctx->data.cu.interval_min = interval_min; + ctx->data.cu.interval_max = interval_max; + ctx->data.cu.latency = latency; + ctx->data.cu.timeout = timeout; + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && + (conn->lll.role == BT_HCI_ROLE_PERIPHERAL)) { + uint16_t handle = ll_conn_handle_get(conn); + + ull_periph_latency_cancel(conn, handle); + } +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + } else { + LL_ASSERT(0); /* Unknown procedure */ + } + + /* TODO(tosk): Check what to handle (ADV_SCHED) from this legacy fct. */ + /* event_conn_upd_prep() (event_conn_upd_init()) */ + + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} + +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +uint8_t ull_cp_remote_dle_pending(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = llcp_rr_peek(conn); + + return (ctx && ctx->proc == PROC_DATA_LENGTH_UPDATE); +} +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +void ull_cp_conn_param_req_reply(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = llcp_rr_peek(conn); + if (ctx && ctx->proc == PROC_CONN_PARAM_REQ) { + llcp_rp_conn_param_req_reply(conn, ctx); + } +} + +void ull_cp_conn_param_req_neg_reply(struct ll_conn *conn, uint8_t error_code) +{ + struct proc_ctx *ctx; + + ctx = llcp_rr_peek(conn); + if (ctx && ctx->proc == PROC_CONN_PARAM_REQ) { + ctx->data.cu.error = error_code; + llcp_rp_conn_param_req_neg_reply(conn, ctx); + } +} +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP) +void ull_cp_cte_rsp_enable(struct ll_conn *conn, bool enable, uint8_t max_cte_len, + uint8_t cte_types) +{ + conn->llcp.cte_rsp.is_enabled = enable; + + if (enable) { + conn->llcp.cte_rsp.max_cte_len = max_cte_len; + conn->llcp.cte_rsp.cte_types = cte_types; + } +} +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RSP */ + +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) +uint8_t ull_cp_cte_req(struct ll_conn *conn, uint8_t min_cte_len, uint8_t cte_type) +{ + struct proc_ctx *ctx; + + ctx = llcp_create_local_procedure(PROC_CTE_REQ); + if (!ctx) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + ctx->data.cte_req.min_len = min_cte_len; + ctx->data.cte_req.type = cte_type; + llcp_lr_enqueue(conn, ctx); + + return BT_HCI_ERR_SUCCESS; +} +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + +static bool pdu_is_expected(struct pdu_data *pdu, struct proc_ctx *ctx) +{ + return ctx->rx_opcode == pdu->llctrl.opcode; +} + +static bool pdu_is_unknown(struct pdu_data *pdu, struct proc_ctx *ctx) +{ + return ((pdu->llctrl.opcode == PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP) && + (ctx->tx_opcode == pdu->llctrl.unknown_rsp.type)); +} + +static bool pdu_is_reject(struct pdu_data *pdu, struct proc_ctx *ctx) +{ + /* TODO(thoh): For LL_REJECT_IND check if the active procedure is supporting the PDU */ + return (((pdu->llctrl.opcode == PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND) && + (ctx->tx_opcode == pdu->llctrl.reject_ext_ind.reject_opcode)) || + (pdu->llctrl.opcode == PDU_DATA_LLCTRL_TYPE_REJECT_IND)); +} + +static bool pdu_is_terminate(struct pdu_data *pdu) +{ + return pdu->llctrl.opcode == PDU_DATA_LLCTRL_TYPE_TERMINATE_IND; +} + +void ull_cp_tx_ack(struct ll_conn *conn, struct node_tx *tx) +{ + struct proc_ctx *ctx; + + ctx = llcp_lr_peek(conn); + if (ctx && ctx->tx_ack == tx) { + /* TX ack re. local request */ + llcp_lr_tx_ack(conn, ctx, tx); + } + + ctx = llcp_rr_peek(conn); + if (ctx && ctx->tx_ack == tx) { + /* TX ack re. remote response */ + llcp_rr_tx_ack(conn, ctx, tx); + } +} + +void ull_cp_rx(struct ll_conn *conn, struct node_rx_pdu *rx) +{ + struct pdu_data *pdu; + struct proc_ctx *ctx; + + pdu = (struct pdu_data *)rx->pdu; + + if (!pdu_is_terminate(pdu)) { + /* + * Process non LL_TERMINATE_IND PDU's as responses to active + * procedures + */ + + ctx = llcp_lr_peek(conn); + if (ctx && (pdu_is_expected(pdu, ctx) || pdu_is_unknown(pdu, ctx) || + pdu_is_reject(pdu, ctx))) { + /* Response on local procedure */ + llcp_lr_rx(conn, ctx, rx); + return; + } + + ctx = llcp_rr_peek(conn); + if (ctx && (pdu_is_expected(pdu, ctx) || pdu_is_unknown(pdu, ctx) || + pdu_is_reject(pdu, ctx))) { + /* Response on remote procedure */ + llcp_rr_rx(conn, ctx, rx); + return; + } + } + + /* New remote request */ + llcp_rr_new(conn, rx); +} + +#ifdef ZTEST_UNITTEST + +int ctx_buffers_free(void) +{ + int nr_of_free_ctx; + + nr_of_free_ctx = mem_free_count_get(mem_ctx.free); + + return nr_of_free_ctx; +} + +void test_int_mem_proc_ctx(void) +{ + struct proc_ctx *ctx1; + struct proc_ctx *ctx2; + int nr_of_free_ctx; + + ull_cp_init(); + + nr_of_free_ctx = ctx_buffers_free(); + zassert_equal(nr_of_free_ctx, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, NULL); + + for (int i = 0U; i < CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM; i++) { + ctx1 = proc_ctx_acquire(); + + /* The previous acquire should be valid */ + zassert_not_null(ctx1, NULL); + } + + nr_of_free_ctx = ctx_buffers_free(); + zassert_equal(nr_of_free_ctx, 0, NULL); + + ctx2 = proc_ctx_acquire(); + + /* The last acquire should fail */ + zassert_is_null(ctx2, NULL); + + llcp_proc_ctx_release(ctx1); + nr_of_free_ctx = ctx_buffers_free(); + zassert_equal(nr_of_free_ctx, 1, NULL); + + ctx1 = proc_ctx_acquire(); + + /* Releasing returns the context to the avilable pool */ + zassert_not_null(ctx1, NULL); +} + +void test_int_mem_tx(void) +{ + bool peek; +#if defined(LLCP_TX_CTRL_BUF_QUEUE_ENABLE) + #define TX_BUFFER_POOL_SIZE (CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM + \ + CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM) +#else /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ + #define TX_BUFFER_POOL_SIZE (CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM + \ + CONFIG_BT_CTLR_LLCP_CONN * \ + CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM) +#endif /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ + struct ll_conn conn; + struct node_tx *txl[TX_BUFFER_POOL_SIZE]; + struct proc_ctx *ctx; + + ull_cp_init(); + ull_llcp_init(&conn); + + ctx = llcp_create_local_procedure(PROC_CONN_UPDATE); + + for (int i = 0U; i < TX_BUFFER_POOL_SIZE; i++) { + peek = llcp_tx_alloc_peek(&conn, ctx); + + /* The previous tx alloc peek should be valid */ + zassert_true(peek, NULL); + + txl[i] = llcp_tx_alloc(&conn, ctx); + + /* The previous alloc should be valid */ + zassert_not_null(txl[i], NULL); + } + + peek = llcp_tx_alloc_peek(&conn, ctx); + + /* The last tx alloc peek should fail */ + zassert_false(peek, NULL); + + /* Release all */ + for (int i = 0U; i < TX_BUFFER_POOL_SIZE; i++) { + ull_cp_release_tx(&conn, txl[i]); + } + + for (int i = 0U; i < TX_BUFFER_POOL_SIZE; i++) { + peek = llcp_tx_alloc_peek(&conn, ctx); + + /* The previous tx alloc peek should be valid */ + zassert_true(peek, NULL); + + txl[i] = llcp_tx_alloc(&conn, ctx); + + /* The previous alloc should be valid */ + zassert_not_null(txl[i], NULL); + } + + peek = llcp_tx_alloc_peek(&conn, ctx); + + /* The last tx alloc peek should fail */ + zassert_false(peek, NULL); + + /* Release all */ + for (int i = 0U; i < TX_BUFFER_POOL_SIZE; i++) { + ull_cp_release_tx(&conn, txl[i]); + } +} + +void test_int_create_proc(void) +{ + struct proc_ctx *ctx; + + ull_cp_init(); + + ctx = create_procedure(PROC_VERSION_EXCHANGE); + zassert_not_null(ctx, NULL); + + zassert_equal(ctx->proc, PROC_VERSION_EXCHANGE, NULL); + zassert_equal(ctx->collision, 0, NULL); + zassert_equal(ctx->pause, 0, NULL); + + for (int i = 0U; i < CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM; i++) { + zassert_not_null(ctx, NULL); + ctx = create_procedure(PROC_VERSION_EXCHANGE); + } + + zassert_is_null(ctx, NULL); +} + +#endif diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp.h b/subsys/bluetooth/controller/ll_sw/ull_llcp.h new file mode 100644 index 00000000000..c7cf75ee514 --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp.h @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Temporary data structure to avoid change ll_conn/lll_conn to much + * and having to update all the dependencies + */ + +enum { ULL_CP_CONNECTED, ULL_CP_DISCONNECTED }; + +/** + * @brief Initialize the LL Control Procedure system. + */ +void ull_cp_init(void); + +/** + * @brief Initialize the LL Control Procedure connection data. + */ +void ull_llcp_init(struct ll_conn *conn); + +/** + * @brief XXX + */ +void ull_cp_state_set(struct ll_conn *conn, uint8_t state); + +/** + * + */ +void ull_cp_release_tx(struct ll_conn *conn, struct node_tx *tx); + +/** + * + */ +void ull_cp_release_ntf(struct node_rx_pdu *ntf); + +/** + * @brief Run pending LL Control Procedures. + */ +void ull_cp_run(struct ll_conn *conn); + +/** + * @brief Handle TX ack PDU. + */ +void ull_cp_tx_ack(struct ll_conn *conn, struct node_tx *tx); + +/** + * @brief Handle received LL Control PDU. + */ +void ull_cp_rx(struct ll_conn *conn, struct node_rx_pdu *rx); + +#if defined(CONFIG_BT_CTLR_LE_PING) +/** + * @brief Initiate a LE Ping Procedure. + */ +uint8_t ull_cp_le_ping(struct ll_conn *conn); +#endif /* CONFIG_BT_CTLR_LE_PING */ + +/** + * @brief Initiate a Version Exchange Procedure. + */ +uint8_t ull_cp_version_exchange(struct ll_conn *conn); + +/** + * @brief Initiate a Feature Exchange Procedure. + */ +uint8_t ull_cp_feature_exchange(struct ll_conn *conn); + +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) +/** + * @brief Initiate a Minimum used channels Procedure. + */ +uint8_t ull_cp_min_used_chans(struct ll_conn *conn, uint8_t phys, uint8_t min_used_chans); +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + +/** + * @brief Initiate a Encryption Start Procedure. + */ +uint8_t ull_cp_encryption_start(struct ll_conn *conn, const uint8_t rand[8], const uint8_t ediv[2], + const uint8_t ltk[16]); + +/** + * @brief Initiate a Encryption Pause Procedure. + */ +uint8_t ull_cp_encryption_pause(struct ll_conn *conn, const uint8_t rand[8], const uint8_t ediv[2], + const uint8_t ltk[16]); + +/** + * @brief Check if an encryption pause procedure is active. + */ +uint8_t ull_cp_encryption_paused(struct ll_conn *conn); + +/** + */ +void ull_cp_ltk_req_reply(struct ll_conn *conn, const uint8_t ltk[16]); + +/** + */ +void ull_cp_ltk_req_neq_reply(struct ll_conn *conn); + +/** + * @brief Initiate a PHY Update Procedure. + */ +uint8_t ull_cp_phy_update(struct ll_conn *conn, uint8_t tx, uint8_t flags, uint8_t rx, + uint8_t host_initiated); + +/** + * @brief Initiate a Connection Parameter Request Procedure or Connection Update Procedure + */ +uint8_t ull_cp_conn_update(struct ll_conn *conn, uint16_t interval_min, uint16_t interval_max, + uint16_t latency, uint16_t timeout); + +/** + * @brief Accept the remote device’s request to change connection parameters. + */ +void ull_cp_conn_param_req_reply(struct ll_conn *conn); + +/** + * @brief Reject the remote device’s request to change connection parameters. + */ +void ull_cp_conn_param_req_neg_reply(struct ll_conn *conn, uint8_t error_code); + +/** + * @brief Check if a remote data length update is in the works. + */ +uint8_t ull_cp_remote_dle_pending(struct ll_conn *conn); + +/** + * @brief Initiate a Termination Procedure. + */ +uint8_t ull_cp_terminate(struct ll_conn *conn, uint8_t error_code); + +/** + * @brief Initiate a Channel Map Update Procedure. + */ +uint8_t ull_cp_chan_map_update(struct ll_conn *conn, const uint8_t chm[5]); + +/** + * @brief Check if Channel Map Update Procedure is pending + */ +const uint8_t *ull_cp_chan_map_update_pending(struct ll_conn *conn); + +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +/** + * @brief Initiate a Data Length Update Procedure. + */ +uint8_t ull_cp_data_length_update(struct ll_conn *conn, uint16_t max_tx_octets, + uint16_t max_tx_time); +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +/** + * @brief Initiate a CTE Request Procedure. + */ +uint8_t ull_cp_cte_req(struct ll_conn *conn, uint8_t min_cte_len, uint8_t cte_type); + +/** + * @brief Enable or disable response to CTE Request Procedure. + */ +void ull_cp_cte_rsp_enable(struct ll_conn *conn, bool enable, uint8_t max_cte_len, + uint8_t cte_types); diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_chmu.c b/subsys/bluetooth/controller/ll_sw/ull_llcp_chmu.c new file mode 100644 index 00000000000..860525d97fd --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_chmu.c @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * 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 "pdu.h" +#include "ll.h" +#include "ll_settings.h" + +#include "lll.h" +#include "lll/lll_df_types.h" +#include "lll_conn.h" + +#include "ull_tx_queue.h" +#include "ull_conn_types.h" +#include "ull_llcp.h" +#include "ull_llcp_internal.h" +#include "ull_conn_internal.h" + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_ctlr_ull_llcp_chmu +#include "common/log.h" +#include +#include "hal/debug.h" + +/* Hardcoded instant delta +6 */ +#define CHMU_INSTANT_DELTA 6U + +/* LLCP Local Procedure Channel Map Update FSM states */ +enum { + LP_CHMU_STATE_IDLE, + LP_CHMU_STATE_WAIT_TX_CHAN_MAP_IND, + LP_CHMU_STATE_WAIT_INSTANT, +}; + +/* LLCP Local Procedure Channel Map Update FSM events */ +enum { + /* Procedure run */ + LP_CHMU_EVT_RUN, +}; + +/* LLCP Remote Procedure Channel Map Update FSM states */ +enum { + RP_CHMU_STATE_IDLE, + RP_CHMU_STATE_WAIT_RX_CHAN_MAP_IND, + RP_CHMU_STATE_WAIT_INSTANT, +}; + +/* LLCP Remote Procedure Channel Map Update FSM events */ +enum { + /* Procedure run */ + RP_CHMU_EVT_RUN, + + /* Indication received */ + RP_CHMU_EVT_RX_CHAN_MAP_IND, +}; + +#if defined(CONFIG_BT_CENTRAL) +/* + * LLCP Local Procedure Channel Map Update FSM + */ + +/* TODO should go into some utils file */ +static uint16_t lp_event_counter(struct ll_conn *conn) +{ + struct lll_conn *lll = &conn->lll; + + /* Calculate current event counter */ + return lll->event_counter + lll->latency_prepare; +} + +static void lp_chmu_tx(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_tx *tx; + struct pdu_data *pdu; + + /* Allocate tx node */ + tx = llcp_tx_alloc(conn, ctx); + LL_ASSERT(tx); + + pdu = (struct pdu_data *)tx->pdu; + + /* Encode LL Control PDU */ + llcp_pdu_encode_chan_map_update_ind(ctx, pdu); + + ctx->tx_opcode = pdu->llctrl.opcode; + + /* Enqueue LL Control PDU towards LLL */ + llcp_tx_enqueue(conn, tx); +} + +static void lp_chmu_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + ull_conn_chan_map_set(conn, ctx->data.chmu.chm); + llcp_lr_complete(conn); + ctx->state = LP_CHMU_STATE_IDLE; +} + +static void lp_chmu_send_channel_map_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + if (llcp_rr_get_collision(conn) || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_CHMU_STATE_WAIT_TX_CHAN_MAP_IND; + } else { + llcp_rr_set_incompat(conn, INCOMPAT_RESOLVABLE); + + /* TODO Hardcoded instant delta */ + ctx->data.chmu.instant = lp_event_counter(conn) + CHMU_INSTANT_DELTA; + + lp_chmu_tx(conn, ctx); + + ctx->state = LP_CHMU_STATE_WAIT_INSTANT; + } +} + +static void lp_chmu_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case LP_CHMU_EVT_RUN: + lp_chmu_send_channel_map_update_ind(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_chmu_st_wait_tx_chan_map_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case LP_CHMU_EVT_RUN: + lp_chmu_send_channel_map_update_ind(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_chmu_check_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + uint16_t event_counter = lp_event_counter(conn); + + if (is_instant_reached_or_passed(ctx->data.chmu.instant, event_counter)) { + llcp_rr_set_incompat(conn, INCOMPAT_NO_COLLISION); + lp_chmu_complete(conn, ctx, evt, param); + } +} + +static void lp_chmu_st_wait_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case LP_CHMU_EVT_RUN: + lp_chmu_check_instant(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_chmu_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (ctx->state) { + case LP_CHMU_STATE_IDLE: + lp_chmu_st_idle(conn, ctx, evt, param); + break; + case LP_CHMU_STATE_WAIT_TX_CHAN_MAP_IND: + lp_chmu_st_wait_tx_chan_map_ind(conn, ctx, evt, param); + break; + case LP_CHMU_STATE_WAIT_INSTANT: + lp_chmu_st_wait_instant(conn, ctx, evt, param); + break; + default: + /* Unknown state */ + LL_ASSERT(0); + } +} + +void llcp_lp_chmu_init_proc(struct proc_ctx *ctx) +{ + ctx->state = LP_CHMU_STATE_IDLE; +} + +void llcp_lp_chmu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) +{ + lp_chmu_execute_fsm(conn, ctx, LP_CHMU_EVT_RUN, param); +} +#endif /* CONFIG_BT_CENTRAL */ + +#if defined(CONFIG_BT_PERIPHERAL) +/* + * LLCP Remote Procedure Channel Map Update FSM + */ + +/* TODO should go into some utils file */ +static uint16_t rp_event_counter(struct ll_conn *conn) +{ + struct lll_conn *lll = &conn->lll; + + /* Calculate current event counter */ + return lll->event_counter + lll->latency_prepare; +} + +static void rp_chmu_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + ull_conn_chan_map_set(conn, ctx->data.chmu.chm); + llcp_rr_complete(conn); + ctx->state = RP_CHMU_STATE_IDLE; +} + +static void rp_chmu_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + /* TODO */ + switch (evt) { + case RP_CHMU_EVT_RUN: + ctx->state = RP_CHMU_STATE_WAIT_RX_CHAN_MAP_IND; + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_chmu_st_wait_rx_channel_map_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + switch (evt) { + case RP_CHMU_EVT_RX_CHAN_MAP_IND: + llcp_pdu_decode_chan_map_update_ind(ctx, param); + ctx->state = RP_CHMU_STATE_WAIT_INSTANT; + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_chmu_check_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + uint16_t event_counter = rp_event_counter(conn); + + if (((event_counter - ctx->data.chmu.instant) & 0xFFFF) <= 0x7FFF) { + rp_chmu_complete(conn, ctx, evt, param); + } +} + +static void rp_chmu_st_wait_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case RP_CHMU_EVT_RUN: + rp_chmu_check_instant(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_chmu_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (ctx->state) { + case RP_CHMU_STATE_IDLE: + rp_chmu_st_idle(conn, ctx, evt, param); + break; + case RP_CHMU_STATE_WAIT_RX_CHAN_MAP_IND: + rp_chmu_st_wait_rx_channel_map_update_ind(conn, ctx, evt, param); + break; + case RP_CHMU_STATE_WAIT_INSTANT: + rp_chmu_st_wait_instant(conn, ctx, evt, param); + break; + default: + /* Unknown state */ + LL_ASSERT(0); + } +} + +void llcp_rp_chmu_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_CHAN_MAP_IND: + rp_chmu_execute_fsm(conn, ctx, RP_CHMU_EVT_RX_CHAN_MAP_IND, pdu); + break; + default: + /* Unknown opcode */ + LL_ASSERT(0); + } +} + +void llcp_rp_chmu_init_proc(struct proc_ctx *ctx) +{ + ctx->state = RP_CHMU_STATE_IDLE; +} + +void llcp_rp_chmu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) +{ + rp_chmu_execute_fsm(conn, ctx, RP_CHMU_EVT_RUN, param); +} +#endif /* CONFIG_BT_PERIPHERAL */ diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_common.c b/subsys/bluetooth/controller/ll_sw/ull_llcp_common.c new file mode 100644 index 00000000000..7b6df24280f --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_common.c @@ -0,0 +1,1055 @@ +/* + * 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 "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 "ull_tx_queue.h" + +#include "ull_conn_types.h" +#include "ull_chan_internal.h" +#include "ull_llcp.h" +#include "ull_conn_internal.h" +#include "ull_internal.h" +#include "ull_llcp_features.h" +#include "ull_llcp_internal.h" + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_ctlr_ull_llcp_common +#include "common/log.h" +#include +#include "hal/debug.h" + +/* LLCP Local Procedure FSM states */ +enum { + LP_COMMON_STATE_IDLE, + LP_COMMON_STATE_WAIT_TX, + LP_COMMON_STATE_WAIT_TX_ACK, + LP_COMMON_STATE_WAIT_RX, + LP_COMMON_STATE_WAIT_NTF, +}; + +/* LLCP Local Procedure Common FSM events */ +enum { + /* Procedure run */ + LP_COMMON_EVT_RUN, + + /* Response received */ + LP_COMMON_EVT_RESPONSE, + + /* Reject response received */ + LP_COMMON_EVT_REJECT, + + /* Unknown response received */ + LP_COMMON_EVT_UNKNOWN, + + /* Instant collision detected */ + LP_COMMON_EVT_COLLISION, + + /* Ack received */ + LP_COMMON_EVT_ACK, +}; + +/* LLCP Remote Procedure Common FSM states */ +enum { + RP_COMMON_STATE_IDLE, + RP_COMMON_STATE_WAIT_RX, + RP_COMMON_STATE_WAIT_TX, + RP_COMMON_STATE_WAIT_TX_ACK, + RP_COMMON_STATE_WAIT_NTF, +}; +/* LLCP Remote Procedure Common FSM events */ +enum { + /* Procedure run */ + RP_COMMON_EVT_RUN, + + /* Ack received */ + RP_COMMON_EVT_ACK, + + /* Request received */ + RP_COMMON_EVT_REQUEST, +}; + +/* + * LLCP Local Procedure Common FSM + */ + +static void lp_comm_tx(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_tx *tx; + struct pdu_data *pdu; + + /* Allocate tx node */ + tx = llcp_tx_alloc(conn, ctx); + LL_ASSERT(tx); + + pdu = (struct pdu_data *)tx->pdu; + + /* Encode LL Control PDU */ + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PROC_LE_PING: + llcp_pdu_encode_ping_req(pdu); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_PING_RSP; + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PROC_FEATURE_EXCHANGE: + llcp_pdu_encode_feature_req(conn, pdu); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_FEATURE_RSP; + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_PERIPHERAL) + case PROC_MIN_USED_CHANS: + llcp_pdu_encode_min_used_chans_ind(ctx, pdu); + ctx->tx_ack = tx; + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_PERIPHERAL */ + case PROC_VERSION_EXCHANGE: + llcp_pdu_encode_version_ind(pdu); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_VERSION_IND; + break; + case PROC_TERMINATE: + llcp_pdu_encode_terminate_ind(ctx, pdu); + ctx->tx_ack = tx; + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; + break; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + llcp_pdu_encode_length_req(conn, pdu); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_LENGTH_RSP; + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: + llcp_pdu_encode_cte_req(ctx, pdu); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_CTE_RSP; + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + } + + ctx->tx_opcode = pdu->llctrl.opcode; + + /* Enqueue LL Control PDU towards LLL */ + llcp_tx_enqueue(conn, tx); + + /* Update procedure timeout. For TERMINATE supervision_timeout is used */ + ull_conn_prt_reload(conn, (ctx->proc != PROC_TERMINATE) ? conn->procedure_reload : + conn->supervision_reload); +} + +static void lp_comm_ntf_feature_exchange(struct ll_conn *conn, struct proc_ctx *ctx, + struct pdu_data *pdu) +{ + switch (ctx->response_opcode) { + case PDU_DATA_LLCTRL_TYPE_FEATURE_RSP: + llcp_ntf_encode_feature_rsp(conn, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_PER_INIT_FEAT_XCHG: + case PDU_DATA_LLCTRL_TYPE_FEATURE_REQ: + /* + * No notification on feature-request or periph-feature request + * TODO: probably handle as an unexpected call + */ + break; + case PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP: + llcp_ntf_encode_unknown_rsp(ctx, pdu); + break; + default: + /* TODO: define behaviour for unexpected PDU */ + LL_ASSERT(0); + } +} + +static void lp_comm_ntf_version_ind(struct ll_conn *conn, struct proc_ctx *ctx, + struct pdu_data *pdu) +{ + switch (ctx->response_opcode) { + case PDU_DATA_LLCTRL_TYPE_VERSION_IND: + llcp_ntf_encode_version_ind(conn, pdu); + break; + default: + /* TODO: define behaviour for unexpected PDU */ + LL_ASSERT(0); + } +} + +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +static void lp_comm_ntf_length_change(struct ll_conn *conn, struct proc_ctx *ctx, + struct pdu_data *pdu) +{ + llcp_ntf_encode_length_change(conn, pdu); +} +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) +static void lp_comm_ntf_cte_req(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu) +{ + /* TODO (ppryga): pack IQ samples and send them to host */ + /* TODO (ppryga): procedure may be re-triggered periodically by controller itself. + * Add periodicy handling code. It should be executed after receive + * notification about end of current procedure run. + */ + /* TODO (ppryga): Add handling of rejections in HCI: HCI_LE_CTE_Request_Failed. */ + switch (ctx->response_opcode) { + case PDU_DATA_LLCTRL_TYPE_CTE_RSP: + llcp_ntf_encode_cte_req(conn, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND: + llcp_ntf_encode_reject_ext_ind(ctx, pdu); + break; + default: + /* TODO (ppryga): Update when behavior for unexpected PDU is defined */ + LL_ASSERT(0); + } +} +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + +static void lp_comm_ntf(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + + /* Allocate ntf node */ + ntf = llcp_ntf_alloc(); + LL_ASSERT(ntf); + + ntf->hdr.type = NODE_RX_TYPE_DC_PDU; + ntf->hdr.handle = conn->lll.handle; + pdu = (struct pdu_data *)ntf->pdu; + + switch (ctx->proc) { + case PROC_FEATURE_EXCHANGE: + lp_comm_ntf_feature_exchange(conn, ctx, pdu); + break; + case PROC_VERSION_EXCHANGE: + lp_comm_ntf_version_ind(conn, ctx, pdu); + break; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + lp_comm_ntf_length_change(conn, ctx, pdu); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: + lp_comm_ntf_cte_req(conn, ctx, pdu); + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + LL_ASSERT(0); + break; + } + + /* Enqueue notification towards LL */ + ll_rx_put(ntf->hdr.link, ntf); + ll_rx_sched(); +} + +static void lp_comm_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PROC_LE_PING: + if (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP || + ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_PING_RSP) { + llcp_lr_complete(conn); + ctx->state = LP_COMMON_STATE_IDLE; + } else { + /* Illegal response opcode */ + LL_ASSERT(0); + } + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PROC_FEATURE_EXCHANGE: + if (!llcp_ntf_alloc_is_available()) { + ctx->state = LP_COMMON_STATE_WAIT_NTF; + } else { + lp_comm_ntf(conn, ctx); + llcp_lr_complete(conn); + ctx->state = LP_COMMON_STATE_IDLE; + } + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_PERIPHERAL) + case PROC_MIN_USED_CHANS: + llcp_lr_complete(conn); + ctx->state = LP_COMMON_STATE_IDLE; + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_PERIPHERAL */ + case PROC_VERSION_EXCHANGE: + if (!llcp_ntf_alloc_is_available()) { + ctx->state = LP_COMMON_STATE_WAIT_NTF; + } else { + lp_comm_ntf(conn, ctx); + llcp_lr_complete(conn); + ctx->state = LP_COMMON_STATE_IDLE; + } + break; + case PROC_TERMINATE: + /* No notification */ + llcp_lr_complete(conn); + ctx->state = LP_COMMON_STATE_IDLE; + + /* Mark the connection for termination */ + conn->llcp_terminate.reason_final = BT_HCI_ERR_LOCALHOST_TERM_CONN; + break; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + if (ctx->response_opcode != PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP) { + /* Apply changes in data lengths/times */ + uint8_t dle_changed = ull_dle_update_eff(conn); + + if (dle_changed && !llcp_ntf_alloc_is_available()) { + /* We need to generate NTF but no buffers avail so wait for one */ + ctx->state = LP_COMMON_STATE_WAIT_NTF; + } else { + if (dle_changed) { + lp_comm_ntf(conn, ctx); + } + llcp_lr_complete(conn); + ctx->state = LP_COMMON_STATE_IDLE; + } + } else { + /* Peer does not accept DLU, so disable on current connection */ + feature_unmask_features(conn, LL_FEAT_BIT_DLE); + + llcp_lr_complete(conn); + ctx->state = LP_COMMON_STATE_IDLE; + } + + if (!ull_cp_remote_dle_pending(conn)) { + /* Resume data, but only if there is no remote procedure pending RSP + * in which case, the RSP tx-ACK will resume data + */ + llcp_tx_resume_data(conn); + } + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: + if (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_CTE_RSP || + (ctx->response_opcode == PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND && + ctx->reject_ext_ind.reject_opcode == PDU_DATA_LLCTRL_TYPE_CTE_REQ)) { + lp_comm_ntf(conn, ctx); + llcp_lr_complete(conn); + ctx->state = LP_COMMON_STATE_IDLE; + } + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + } +} + +static void lp_comm_send_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PROC_LE_PING: + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_COMMON_STATE_WAIT_TX; + } else { + lp_comm_tx(conn, ctx); + ctx->state = LP_COMMON_STATE_WAIT_RX; + } + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PROC_FEATURE_EXCHANGE: + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_COMMON_STATE_WAIT_TX; + } else { + lp_comm_tx(conn, ctx); + conn->llcp.fex.sent = 1; + ctx->state = LP_COMMON_STATE_WAIT_RX; + } + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_PERIPHERAL) + case PROC_MIN_USED_CHANS: + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_COMMON_STATE_WAIT_TX; + } else { + lp_comm_tx(conn, ctx); + ctx->state = LP_COMMON_STATE_WAIT_TX_ACK; + } + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_PERIPHERAL */ + case PROC_VERSION_EXCHANGE: + /* The Link Layer shall only queue for transmission a maximum of + * one LL_VERSION_IND PDU during a connection. + */ + if (!conn->llcp.vex.sent) { + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_COMMON_STATE_WAIT_TX; + } else { + lp_comm_tx(conn, ctx); + conn->llcp.vex.sent = 1; + ctx->state = LP_COMMON_STATE_WAIT_RX; + } + } else { + ctx->response_opcode = PDU_DATA_LLCTRL_TYPE_VERSION_IND; + lp_comm_complete(conn, ctx, evt, param); + } + break; + case PROC_TERMINATE: + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_COMMON_STATE_WAIT_TX; + } else { + lp_comm_tx(conn, ctx); + ctx->state = LP_COMMON_STATE_WAIT_TX_ACK; + } + break; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + if (!ull_cp_remote_dle_pending(conn)) { + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_COMMON_STATE_WAIT_TX; + } else { + /* Pause data tx, to ensure we can later (on RSP rx-ack) + * update DLE without conflicting with out-going LL Data PDUs + * See BT Core 5.2 Vol6: B-4.5.10 & B-5.1.9 + */ + llcp_tx_pause_data(conn); + lp_comm_tx(conn, ctx); + ctx->state = LP_COMMON_STATE_WAIT_RX; + } + } else { + /* REQ was received from peer and RSP not yet sent + * lets piggy-back on RSP instead af sending REQ + * thus we can complete local req + */ + llcp_lr_complete(conn); + ctx->state = LP_COMMON_STATE_IDLE; + } + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_COMMON_STATE_WAIT_TX; + } else { + lp_comm_tx(conn, ctx); + ctx->state = LP_COMMON_STATE_WAIT_RX; + } + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + } +} + +static void lp_comm_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case LP_COMMON_EVT_RUN: + if (ctx->pause) { + ctx->state = LP_COMMON_STATE_WAIT_TX; + } else { + lp_comm_send_req(conn, ctx, evt, param); + } + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_comm_st_wait_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case LP_COMMON_EVT_RUN: + lp_comm_send_req(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_comm_st_wait_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case LP_COMMON_EVT_ACK: + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_PERIPHERAL) + case PROC_MIN_USED_CHANS: + ctx->tx_ack = NULL; + lp_comm_complete(conn, ctx, evt, param); + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_PERIPHERAL */ + case PROC_TERMINATE: + ctx->tx_ack = NULL; + lp_comm_complete(conn, ctx, evt, param); + break; + default: + /* Ignore for other procedures */ + break; + } + break; + default: + /* Ignore other evts */ + break; + } + /* TODO */ +} + +static void lp_comm_rx_decode(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu) +{ + ctx->response_opcode = pdu->llctrl.opcode; + + switch (pdu->llctrl.opcode) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PDU_DATA_LLCTRL_TYPE_PING_RSP: + /* ping_rsp has no data */ + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PDU_DATA_LLCTRL_TYPE_FEATURE_RSP: + llcp_pdu_decode_feature_rsp(conn, pdu); + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) + case PDU_DATA_LLCTRL_TYPE_MIN_USED_CHAN_IND: + /* No response expected */ + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + case PDU_DATA_LLCTRL_TYPE_VERSION_IND: + llcp_pdu_decode_version_ind(conn, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP: + llcp_pdu_decode_unknown_rsp(ctx, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_TERMINATE_IND: + /* No response expected */ + LL_ASSERT(0); + break; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PDU_DATA_LLCTRL_TYPE_LENGTH_RSP: + llcp_pdu_decode_length_rsp(conn, pdu); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + case PDU_DATA_LLCTRL_TYPE_CTE_RSP: + /* CTE Response PDU had no data */ + break; + case PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND: + llcp_pdu_decode_reject_ext_ind(ctx, pdu); + break; + default: + /* Unknown opcode */ + LL_ASSERT(0); + } +} + +static void lp_comm_st_wait_rx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case LP_COMMON_EVT_RESPONSE: + lp_comm_rx_decode(conn, ctx, (struct pdu_data *)param); + lp_comm_complete(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_comm_st_wait_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + /* TODO */ + switch (evt) { + case LP_COMMON_EVT_RUN: + switch (ctx->proc) { + case PROC_FEATURE_EXCHANGE: + case PROC_VERSION_EXCHANGE: +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + if (llcp_ntf_alloc_is_available()) { + lp_comm_ntf(conn, ctx); + llcp_lr_complete(conn); + ctx->state = LP_COMMON_STATE_IDLE; + } + break; + default: + break; + } + break; + default: + break; + } +} + +static void lp_comm_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (ctx->state) { + case LP_COMMON_STATE_IDLE: + lp_comm_st_idle(conn, ctx, evt, param); + break; + case LP_COMMON_STATE_WAIT_TX: + lp_comm_st_wait_tx(conn, ctx, evt, param); + break; + case LP_COMMON_STATE_WAIT_TX_ACK: + lp_comm_st_wait_tx_ack(conn, ctx, evt, param); + break; + case LP_COMMON_STATE_WAIT_RX: + lp_comm_st_wait_rx(conn, ctx, evt, param); + break; + case LP_COMMON_STATE_WAIT_NTF: + lp_comm_st_wait_ntf(conn, ctx, evt, param); + break; + default: + /* Unknown state */ + LL_ASSERT(0); + } +} + +void llcp_lp_comm_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx) +{ + lp_comm_execute_fsm(conn, ctx, LP_COMMON_EVT_ACK, tx->pdu); +} + +void llcp_lp_comm_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx) +{ + lp_comm_execute_fsm(conn, ctx, LP_COMMON_EVT_RESPONSE, rx->pdu); +} + +void llcp_lp_comm_init_proc(struct proc_ctx *ctx) +{ + ctx->state = LP_COMMON_STATE_IDLE; +} + +void llcp_lp_comm_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) +{ + lp_comm_execute_fsm(conn, ctx, LP_COMMON_EVT_RUN, param); +} + +/* + * LLCP Remote Procedure Common FSM + */ +static void rp_comm_rx_decode(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu) +{ + ctx->response_opcode = pdu->llctrl.opcode; + + switch (pdu->llctrl.opcode) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PDU_DATA_LLCTRL_TYPE_PING_REQ: + /* ping_req has no data */ + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ +#if defined(CONFIG_BT_PERIPHERAL) + case PDU_DATA_LLCTRL_TYPE_FEATURE_REQ: +#endif /* CONFIG_BT_PERIPHERAL */ +#if defined(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG) && defined(CONFIG_BT_CENTRAL) + case PDU_DATA_LLCTRL_TYPE_PER_INIT_FEAT_XCHG: +#endif /* CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG && CONFIG_BT_CENTRAL */ + llcp_pdu_decode_feature_req(conn, pdu); + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_CENTRAL) + case PDU_DATA_LLCTRL_TYPE_MIN_USED_CHAN_IND: + llcp_pdu_decode_min_used_chans_ind(conn, pdu); + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_CENTRAL */ + case PDU_DATA_LLCTRL_TYPE_VERSION_IND: + llcp_pdu_decode_version_ind(conn, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_TERMINATE_IND: + llcp_pdu_decode_terminate_ind(ctx, pdu); + break; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PDU_DATA_LLCTRL_TYPE_LENGTH_REQ: + llcp_pdu_decode_length_req(conn, pdu); + /* On reception of REQ mark RSP open for local piggy-back + * Pause data tx, to ensure we can later (on RSP tx ack) update DLE without + * conflicting with out-going LL Data PDUs + * See BT Core 5.2 Vol6: B-4.5.10 & B-5.1.9 + */ + llcp_tx_pause_data(conn); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PDU_DATA_LLCTRL_TYPE_CTE_REQ: + llcp_pdu_decode_cte_req(conn, pdu); + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown opcode */ + LL_ASSERT(0); + } +} + +static void rp_comm_tx(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_tx *tx; + struct pdu_data *pdu; + + /* Allocate tx node */ + tx = llcp_tx_alloc(conn, ctx); + LL_ASSERT(tx); + + pdu = (struct pdu_data *)tx->pdu; + + /* Encode LL Control PDU */ + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PROC_LE_PING: + llcp_pdu_encode_ping_rsp(pdu); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_PING_RSP; + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PROC_FEATURE_EXCHANGE: + llcp_pdu_encode_feature_rsp(conn, pdu); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_FEATURE_RSP; + break; + case PROC_VERSION_EXCHANGE: + llcp_pdu_encode_version_ind(pdu); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_VERSION_IND; + break; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + llcp_pdu_encode_length_rsp(conn, pdu); + ctx->tx_ack = tx; + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_LENGTH_RSP; + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: { + uint8_t err_code = 0; + + if (conn->llcp.cte_rsp.is_enabled == 0) { + err_code = BT_HCI_ERR_UNSUPP_LL_PARAM_VAL; + } +#if defined(CONFIG_BT_PHY_UPDATE) + /* If the PHY update is not possible, then PHY1M is used. + * CTE is supported for PHY1M. + */ + if (conn->lll.phy_tx != PHY_CODED) { + err_code = BT_HCI_ERR_INVALID_LL_PARAM; + } +#endif /* CONFIG_BT_PHY_UPDATE */ + if (!(conn->llcp.cte_rsp.cte_types & BIT(conn->llcp.cte_req.cte_type)) && + conn->llcp.cte_rsp.max_cte_len >= conn->llcp.cte_req.min_cte_len) { + err_code = BT_HCI_ERR_UNSUPP_LL_PARAM_VAL; + } + + if (!err_code) { + llcp_pdu_encode_cte_rsp(pdu); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_CTE_RSP; + } else { + llcp_pdu_encode_reject_ext_ind(pdu, PDU_DATA_LLCTRL_TYPE_CTE_REQ, err_code); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND; + } + break; + } +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + } + + ctx->tx_opcode = pdu->llctrl.opcode; + + /* Enqueue LL Control PDU towards LLL */ + llcp_tx_enqueue(conn, tx); +} + +static void rp_comm_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case RP_COMMON_EVT_RUN: + ctx->state = RP_COMMON_STATE_WAIT_RX; + break; + default: + /* Ignore other evts */ + break; + } +} +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +static void rp_comm_ntf_length_change(struct ll_conn *conn, struct proc_ctx *ctx, + struct pdu_data *pdu) +{ + llcp_ntf_encode_length_change(conn, pdu); +} + +static void rp_comm_ntf(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + + ARG_UNUSED(pdu); + /* Allocate ntf node */ + ntf = llcp_ntf_alloc(); + LL_ASSERT(ntf); + + ntf->hdr.type = NODE_RX_TYPE_DC_PDU; + ntf->hdr.handle = conn->lll.handle; + pdu = (struct pdu_data *)ntf->pdu; + switch (ctx->proc) { +/* Note: the 'double' ifdef in case this switch case expands + * in the future and the function is re-instated + */ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + rp_comm_ntf_length_change(conn, ctx, pdu); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + default: + LL_ASSERT(0); + break; + } + + /* Enqueue notification towards LL */ + ll_rx_put(ntf->hdr.link, ntf); + ll_rx_sched(); +} +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + +static void rp_comm_send_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PROC_LE_PING: + /* Always respond on remote ping */ + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_COMMON_STATE_WAIT_TX; + } else { + rp_comm_tx(conn, ctx); + llcp_rr_complete(conn); + ctx->state = RP_COMMON_STATE_IDLE; + } + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PROC_FEATURE_EXCHANGE: + /* Always respond on remote feature exchange */ + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_COMMON_STATE_WAIT_TX; + } else { + rp_comm_tx(conn, ctx); + conn->llcp.fex.sent = 1; + llcp_rr_complete(conn); + ctx->state = RP_COMMON_STATE_IDLE; + } + break; + case PROC_VERSION_EXCHANGE: + /* The Link Layer shall only queue for transmission a maximum of one + * LL_VERSION_IND PDU during a connection. + */ + if (!conn->llcp.vex.sent) { + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_COMMON_STATE_WAIT_TX; + } else { + rp_comm_tx(conn, ctx); + conn->llcp.vex.sent = 1; + llcp_rr_complete(conn); + ctx->state = RP_COMMON_STATE_IDLE; + } + } else { + /* Protocol Error. + * + * A procedure already sent a LL_VERSION_IND and received a LL_VERSION_IND. + */ + /* TODO */ + LL_ASSERT(0); + } + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) && defined(CONFIG_BT_CENTRAL) + case PROC_MIN_USED_CHANS: + /* + * Spec says (5.2, Vol.6, Part B, Section 5.1.11): + * The procedure has completed when the Link Layer acknowledgment of the + * LL_MIN_USED_CHANNELS_IND PDU is sent or received. + * In effect, for this procedure, this is equivalent to RX of PDU + */ + /* Inititate a chmap update, but only if acting as central, just in case ... */ + if (conn->lll.role == BT_HCI_ROLE_CENTRAL && + ull_conn_lll_phy_active(conn, conn->llcp.muc.phys)) { + uint8_t chmap[5]; + + ull_chan_map_get((uint8_t *const)chmap); + ull_cp_chan_map_update(conn, chmap); + /* TODO - what to do on failure of ull_cp_chan_map_update() */ + } + /* No response */ + llcp_rr_complete(conn); + ctx->state = RP_COMMON_STATE_IDLE; + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN && CONFIG_BT_CENTRAL */ + case PROC_TERMINATE: + /* No response */ + llcp_rr_complete(conn); + ctx->state = RP_COMMON_STATE_IDLE; + + /* Mark the connection for termination */ + conn->llcp_terminate.reason_final = ctx->data.term.error_code; + break; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_COMMON_STATE_WAIT_TX; + } else { + /* On RSP tx close the window for possible local req piggy-back */ + rp_comm_tx(conn, ctx); + + /* Wait for the peer to have ack'ed the RSP before updating DLE */ + ctx->state = RP_COMMON_STATE_WAIT_TX_ACK; + } + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: + if (ctx->pause || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_COMMON_STATE_WAIT_TX; + } else { + rp_comm_tx(conn, ctx); + llcp_rr_complete(conn); + ctx->state = RP_COMMON_STATE_IDLE; + } + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + } +} + +static void rp_comm_st_wait_rx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case RP_COMMON_EVT_REQUEST: + rp_comm_rx_decode(conn, ctx, (struct pdu_data *)param); + rp_comm_send_rsp(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_comm_st_wait_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case LP_COMMON_EVT_RUN: + rp_comm_send_rsp(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +static void rp_comm_st_wait_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case RP_COMMON_EVT_ACK: + switch (ctx->proc) { + case PROC_DATA_LENGTH_UPDATE: { + /* Apply changes in data lengths/times */ + uint8_t dle_changed = ull_dle_update_eff(conn); + + llcp_tx_resume_data(conn); + + if (dle_changed && !llcp_ntf_alloc_is_available()) { + ctx->state = RP_COMMON_STATE_WAIT_NTF; + } else { + if (dle_changed) { + rp_comm_ntf(conn, ctx); + } + llcp_rr_complete(conn); + ctx->state = RP_COMMON_STATE_IDLE; + } + break; + } + default: + /* Ignore other procedures */ + break; + } + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_comm_st_wait_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (llcp_ntf_alloc_is_available()) { + rp_comm_ntf(conn, ctx); + llcp_rr_complete(conn); + ctx->state = RP_COMMON_STATE_IDLE; + } +} +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + +static void rp_comm_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (ctx->state) { + case RP_COMMON_STATE_IDLE: + rp_comm_st_idle(conn, ctx, evt, param); + break; + case RP_COMMON_STATE_WAIT_RX: + rp_comm_st_wait_rx(conn, ctx, evt, param); + break; + case RP_COMMON_STATE_WAIT_TX: + rp_comm_st_wait_tx(conn, ctx, evt, param); + break; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case RP_COMMON_STATE_WAIT_TX_ACK: + rp_comm_st_wait_tx_ack(conn, ctx, evt, param); + break; + case RP_COMMON_STATE_WAIT_NTF: + rp_comm_st_wait_ntf(conn, ctx, evt, param); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + default: + /* Unknown state */ + LL_ASSERT(0); + } +} + +void llcp_rp_comm_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx) +{ + rp_comm_execute_fsm(conn, ctx, RP_COMMON_EVT_REQUEST, rx->pdu); +} + +void llcp_rp_comm_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx) +{ + rp_comm_execute_fsm(conn, ctx, RP_COMMON_EVT_ACK, tx->pdu); +} + +void llcp_rp_comm_init_proc(struct proc_ctx *ctx) +{ + ctx->state = RP_COMMON_STATE_IDLE; +} + +void llcp_rp_comm_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) +{ + rp_comm_execute_fsm(conn, ctx, RP_COMMON_EVT_RUN, param); +} diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_conn_upd.c b/subsys/bluetooth/controller/ll_sw/ull_llcp_conn_upd.c new file mode 100644 index 00000000000..a5343427a7b --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_conn_upd.c @@ -0,0 +1,994 @@ +/* + * 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 "pdu.h" +#include "ll.h" +#include "ll_feat.h" +#include "ll_settings.h" + +#include "lll.h" +#include "lll/lll_df_types.h" +#include "lll_conn.h" + +#include "ull_tx_queue.h" +#include "ull_conn_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" + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_ctlr_ull_llcp_conn_upd +#include "common/log.h" +#include +#include "hal/debug.h" + +/* Hardcoded instant delta +6 */ +#define CONN_UPDATE_INSTANT_DELTA 6U + +/* TODO: Known, missing items (missing implementation): + * LL/CON/MAS/BV-34-C [Accepting Connection Parameter Request – event masked] + */ + +/* LLCP Local Procedure Connection Update FSM states */ +enum { + LP_CU_STATE_IDLE, + LP_CU_STATE_WAIT_TX_CONN_PARAM_REQ, + LP_CU_STATE_WAIT_RX_CONN_PARAM_RSP, + LP_CU_STATE_WAIT_TX_CONN_UPDATE_IND, + LP_CU_STATE_WAIT_RX_CONN_UPDATE_IND, + LP_CU_STATE_WAIT_INSTANT, + LP_CU_STATE_WAIT_NTF, +}; + +/* LLCP Local Procedure Connection Update FSM events */ +enum { + /* Procedure run */ + LP_CU_EVT_RUN, + + /* Response received */ + LP_CU_EVT_CONN_PARAM_RSP, + + /* Indication received */ + LP_CU_EVT_CONN_UPDATE_IND, + + /* Reject response received */ + LP_CU_EVT_REJECT, + + /* Unknown response received */ + LP_CU_EVT_UNKNOWN, +}; + +/* LLCP Remote Procedure Connection Update FSM states */ +enum { + RP_CU_STATE_IDLE, + RP_CU_STATE_WAIT_RX_CONN_PARAM_REQ, + RP_CU_STATE_WAIT_NTF_CONN_PARAM_REQ, + RP_CU_STATE_WAIT_CONN_PARAM_REQ_REPLY, + RP_CU_STATE_WAIT_CONN_PARAM_REQ_REPLY_CONTINUE, + RP_CU_STATE_WAIT_TX_REJECT_EXT_IND, + RP_CU_STATE_WAIT_TX_CONN_PARAM_RSP, + RP_CU_STATE_WAIT_TX_CONN_UPDATE_IND, + RP_CU_STATE_WAIT_RX_CONN_UPDATE_IND, + RP_CU_STATE_WAIT_INSTANT, + RP_CU_STATE_WAIT_NTF, + RP_CU_STATE_WAIT_TX_UNKNOWN_RSP +}; + +/* LLCP Remote Procedure Connection Update FSM events */ +enum { + /* Procedure run */ + RP_CU_EVT_RUN, + + /* Request received */ + RP_CU_EVT_CONN_PARAM_REQ, + + /* Indication received */ + RP_CU_EVT_CONN_UPDATE_IND, + + /* CONN_PARAM_REQ reply */ + RP_CU_EVT_CONN_PARAM_REQ_REPLY, + + /* CONN_PARAM_REQ negative reply */ + RP_CU_EVT_CONN_PARAM_REQ_NEG_REPLY, +}; + +/* + * LLCP Local Procedure Connection Update FSM + */ + +static bool cu_have_params_changed(struct ll_conn *conn, uint16_t interval, uint16_t latency, + uint16_t timeout) +{ + struct lll_conn *lll = &conn->lll; + + if ((interval != lll->interval) || (latency != lll->latency) || + (RADIO_CONN_EVENTS(timeout * 10000U, lll->interval * CONN_INT_UNIT_US) != + conn->supervision_reload)) { + return true; + } + return false; +} + +static void cu_update_conn_parameters(struct ll_conn *conn, struct proc_ctx *ctx) +{ + ctx->data.cu.params_changed = cu_have_params_changed( + conn, ctx->data.cu.interval_max, ctx->data.cu.latency, ctx->data.cu.timeout); + + ull_conn_update_parameters(conn, (ctx->proc == PROC_CONN_UPDATE), ctx->data.cu.win_size, + ctx->data.cu.win_offset_us, ctx->data.cu.interval_max, + ctx->data.cu.latency, ctx->data.cu.timeout, + ctx->data.cu.instant); +} + +static bool cu_should_notify_host(struct proc_ctx *ctx) +{ + return (((ctx->proc == PROC_CONN_PARAM_REQ) && (ctx->data.cu.error != 0U)) || + (ctx->data.cu.params_changed != 0U)); +} + +static void lp_cu_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode) +{ + struct node_tx *tx; + struct pdu_data *pdu; + + /* Allocate tx node */ + tx = llcp_tx_alloc(conn, ctx); + LL_ASSERT(tx); + + pdu = (struct pdu_data *)tx->pdu; + + /* Encode LL Control PDU */ + switch (opcode) { +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + case PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ: + llcp_pdu_encode_conn_param_req(ctx, pdu); + break; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ +#if defined(CONFIG_BT_CENTRAL) + case PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND: + llcp_pdu_encode_conn_update_ind(ctx, pdu); + break; +#endif /* CONFIG_BT_CENTRAL */ + default: + LL_ASSERT(0); + break; + } + + ctx->tx_opcode = pdu->llctrl.opcode; + + /* Enqueue LL Control PDU towards LLL */ + llcp_tx_enqueue(conn, tx); +} + +static void lp_cu_ntf(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_rx_pdu *ntf; + struct node_rx_cu *pdu; + + /* Allocate ntf node */ + ntf = llcp_ntf_alloc(); + LL_ASSERT(ntf); + + ntf->hdr.type = NODE_RX_TYPE_CONN_UPDATE; + ntf->hdr.handle = conn->lll.handle; + pdu = (struct node_rx_cu *)ntf->pdu; + + pdu->status = ctx->data.cu.error; + pdu->interval = ctx->data.cu.interval_max; + pdu->latency = ctx->data.cu.latency; + pdu->timeout = ctx->data.cu.timeout; + + /* Enqueue notification towards LL */ + ll_rx_put(ntf->hdr.link, ntf); + ll_rx_sched(); +} + +static void lp_cu_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + if (!llcp_ntf_alloc_is_available()) { + ctx->state = LP_CU_STATE_WAIT_NTF; + } else { + lp_cu_ntf(conn, ctx); + llcp_lr_complete(conn); + ctx->state = LP_CU_STATE_IDLE; + } +} + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +static void lp_cu_send_conn_param_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (llcp_rr_get_collision(conn) || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_CU_STATE_WAIT_TX_CONN_PARAM_REQ; + } else { + uint16_t event_counter = ull_conn_event_counter(conn); + + llcp_rr_set_incompat(conn, INCOMPAT_RESOLVABLE); + + ctx->data.cu.reference_conn_event_count = event_counter; + ctx->data.cu.preferred_periodicity = 0U; + ctx->data.cu.offset0 = 0x0000U; + ctx->data.cu.offset1 = 0xffffU; + ctx->data.cu.offset2 = 0xffffU; + ctx->data.cu.offset3 = 0xffffU; + ctx->data.cu.offset4 = 0xffffU; + ctx->data.cu.offset5 = 0xffffU; + + lp_cu_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ); + + switch (conn->lll.role) { +#if defined(CONFIG_BT_CENTRAL) + case BT_HCI_ROLE_CENTRAL: + ctx->state = LP_CU_STATE_WAIT_RX_CONN_PARAM_RSP; + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_RSP; + break; +#endif /* CONFIG_BT_CENTRAL */ +#if defined(CONFIG_BT_PERIPHERAL) + case BT_HCI_ROLE_PERIPHERAL: + ctx->state = LP_CU_STATE_WAIT_RX_CONN_UPDATE_IND; + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND; + break; +#endif /* CONFIG_BT_PERIPHERAL */ + default: + /* Unknown role */ + LL_ASSERT(0); + break; + } + } +} +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + +#if defined(CONFIG_BT_CENTRAL) +static void lp_cu_send_conn_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_CU_STATE_WAIT_TX_CONN_UPDATE_IND; + } else { + ctx->data.cu.win_size = 1U; + ctx->data.cu.win_offset_us = 0U; + ctx->data.cu.instant = ull_conn_event_counter(conn) + conn->lll.latency + + CONN_UPDATE_INSTANT_DELTA; + lp_cu_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; + ctx->state = LP_CU_STATE_WAIT_INSTANT; + } +} +#endif /* CONFIG_BT_CENTRAL */ + +static void lp_cu_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case LP_CU_EVT_RUN: + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + case PROC_CONN_PARAM_REQ: + lp_cu_send_conn_param_req(conn, ctx, evt, param); + break; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ +#if defined(CONFIG_BT_CENTRAL) + case PROC_CONN_UPDATE: + lp_cu_send_conn_update_ind(conn, ctx, evt, param); + break; +#endif /* CONFIG_BT_CENTRAL */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + break; + } + break; + default: + /* Ignore other evts */ + break; + } +} + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +static void lp_cu_st_wait_tx_conn_param_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case LP_CU_EVT_RUN: + lp_cu_send_conn_param_req(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + +#if defined(CONFIG_BT_CENTRAL) +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +static void lp_cu_st_wait_rx_conn_param_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + struct pdu_data *pdu = (struct pdu_data *)param; + + switch (evt) { + case LP_CU_EVT_CONN_PARAM_RSP: + llcp_rr_set_incompat(conn, INCOMPAT_RESERVED); + lp_cu_send_conn_update_ind(conn, ctx, evt, param); + break; + case LP_CU_EVT_UNKNOWN: + llcp_rr_set_incompat(conn, INCOMPAT_RESERVED); + /* Unsupported in peer, so disable locally for this connection */ + feature_unmask_features(conn, LL_FEAT_BIT_CONN_PARAM_REQ); + lp_cu_send_conn_update_ind(conn, ctx, evt, param); + break; + case LP_CU_EVT_REJECT: + /* TODO(tosk): Select between LL_REJECT_IND and LL_REJECT_EXT_IND */ + if (pdu->llctrl.reject_ext_ind.error_code == BT_HCI_ERR_UNSUPP_REMOTE_FEATURE) { + /* Remote legacy Host */ + llcp_rr_set_incompat(conn, INCOMPAT_RESERVED); + /* Unsupported in peer, so disable locally for this connection */ + feature_unmask_features(conn, LL_FEAT_BIT_CONN_PARAM_REQ); + lp_cu_send_conn_update_ind(conn, ctx, evt, param); + } else { + llcp_rr_set_incompat(conn, INCOMPAT_NO_COLLISION); + ctx->data.cu.error = pdu->llctrl.reject_ext_ind.error_code; + lp_cu_complete(conn, ctx, evt, param); + } + break; + default: + /* Ignore other evts */ + break; + } +} +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + +static void lp_cu_st_wait_tx_conn_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + switch (evt) { + case LP_CU_EVT_RUN: + lp_cu_send_conn_update_ind(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} +#endif /* CONFIG_BT_CENTRAL */ + +#if defined(CONFIG_BT_PERIPHERAL) +static void lp_cu_st_wait_rx_conn_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + struct pdu_data *pdu = (struct pdu_data *)param; + + switch (evt) { + case LP_CU_EVT_CONN_UPDATE_IND: + llcp_pdu_decode_conn_update_ind(ctx, param); + ctx->state = LP_CU_STATE_WAIT_INSTANT; + break; + case LP_CU_EVT_UNKNOWN: + ctx->data.cu.error = BT_HCI_ERR_UNSUPP_REMOTE_FEATURE; + lp_cu_complete(conn, ctx, evt, param); + break; + case LP_CU_EVT_REJECT: + ctx->data.cu.error = pdu->llctrl.reject_ext_ind.error_code; + lp_cu_complete(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} +#endif /* CONFIG_BT_PERIPHERAL */ + +static void lp_cu_check_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + uint16_t event_counter = ull_conn_event_counter(conn); + + if (is_instant_reached_or_passed(ctx->data.cu.instant, event_counter)) { + bool notify; + + /* Procedure is complete when the instant has passed, and the + * new connection event parameters have been applied. + */ + cu_update_conn_parameters(conn, ctx); + notify = cu_should_notify_host(ctx); + if (notify) { + llcp_rr_set_incompat(conn, INCOMPAT_NO_COLLISION); + ctx->data.cu.error = BT_HCI_ERR_SUCCESS; + lp_cu_complete(conn, ctx, evt, param); + } else { + llcp_lr_complete(conn); + ctx->state = LP_CU_STATE_IDLE; + } + } +} + +static void lp_cu_st_wait_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + /* TODO */ + switch (evt) { + case LP_CU_EVT_RUN: + lp_cu_check_instant(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_cu_st_wait_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case LP_CU_EVT_RUN: + lp_cu_complete(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_cu_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (ctx->state) { + case LP_CU_STATE_IDLE: + lp_cu_st_idle(conn, ctx, evt, param); + break; +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + case LP_CU_STATE_WAIT_TX_CONN_PARAM_REQ: + lp_cu_st_wait_tx_conn_param_req(conn, ctx, evt, param); + break; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ +#if defined(CONFIG_BT_CENTRAL) +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + case LP_CU_STATE_WAIT_RX_CONN_PARAM_RSP: + lp_cu_st_wait_rx_conn_param_rsp(conn, ctx, evt, param); + break; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + case LP_CU_STATE_WAIT_TX_CONN_UPDATE_IND: + lp_cu_st_wait_tx_conn_update_ind(conn, ctx, evt, param); + break; +#endif /* CONFIG_BT_CENTRAL */ +#if defined(CONFIG_BT_PERIPHERAL) + case LP_CU_STATE_WAIT_RX_CONN_UPDATE_IND: + lp_cu_st_wait_rx_conn_update_ind(conn, ctx, evt, param); + break; +#endif /* CONFIG_BT_PERIPHERAL */ + case LP_CU_STATE_WAIT_INSTANT: + lp_cu_st_wait_instant(conn, ctx, evt, param); + break; + case LP_CU_STATE_WAIT_NTF: + lp_cu_st_wait_ntf(conn, ctx, evt, param); + break; + default: + /* Unknown state */ + LL_ASSERT(0); + break; + } +} + +void llcp_lp_cu_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_CTLR_CONN_PARAM_REQ) + case PDU_DATA_LLCTRL_TYPE_CONN_PARAM_RSP: + lp_cu_execute_fsm(conn, ctx, LP_CU_EVT_CONN_PARAM_RSP, pdu); + break; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + case PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND: + lp_cu_execute_fsm(conn, ctx, LP_CU_EVT_CONN_UPDATE_IND, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP: + lp_cu_execute_fsm(conn, ctx, LP_CU_EVT_UNKNOWN, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND: + lp_cu_execute_fsm(conn, ctx, LP_CU_EVT_REJECT, pdu); + break; + default: + /* Unknown opcode */ + LL_ASSERT(0); + break; + } +} + +void llcp_lp_cu_init_proc(struct proc_ctx *ctx) +{ + ctx->state = LP_CU_STATE_IDLE; +} + +void llcp_lp_cu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) +{ + lp_cu_execute_fsm(conn, ctx, LP_CU_EVT_RUN, param); +} + +/* + * LLCP Remote Procedure Connection Update FSM + */ + +static void rp_cu_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode) +{ + struct node_tx *tx; + struct pdu_data *pdu; + + /* Allocate tx node */ + tx = llcp_tx_alloc(conn, ctx); + LL_ASSERT(tx); + + pdu = (struct pdu_data *)tx->pdu; + + /* Encode LL Control PDU */ + switch (opcode) { +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + case PDU_DATA_LLCTRL_TYPE_CONN_PARAM_RSP: + llcp_pdu_encode_conn_param_rsp(ctx, pdu); + break; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + case PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND: + llcp_pdu_encode_conn_update_ind(ctx, pdu); + break; +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + case PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND: + /* TODO(thoh): Select between LL_REJECT_IND and LL_REJECT_EXT_IND */ + llcp_pdu_encode_reject_ext_ind(pdu, PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ, + ctx->data.cu.error); + break; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + case PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP: + llcp_pdu_encode_unknown_rsp(ctx, pdu); + break; + default: + LL_ASSERT(0); + break; + } + + ctx->tx_opcode = pdu->llctrl.opcode; + + /* Enqueue LL Control PDU towards LLL */ + llcp_tx_enqueue(conn, tx); +} + +static void rp_cu_ntf(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_rx_pdu *ntf; + struct node_rx_cu *pdu; + + /* Allocate ntf node */ + ntf = llcp_ntf_alloc(); + LL_ASSERT(ntf); + + ntf->hdr.type = NODE_RX_TYPE_CONN_UPDATE; + ntf->hdr.handle = conn->lll.handle; + pdu = (struct node_rx_cu *)ntf->pdu; + + pdu->status = ctx->data.cu.error; + pdu->interval = ctx->data.cu.interval_max; + pdu->latency = ctx->data.cu.latency; + pdu->timeout = ctx->data.cu.timeout; + + /* Enqueue notification towards LL */ + ll_rx_put(ntf->hdr.link, ntf); + ll_rx_sched(); +} + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +static void rp_cu_conn_param_req_ntf(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + + /* Allocate ntf node */ + ntf = llcp_ntf_alloc(); + LL_ASSERT(ntf); + + ntf->hdr.type = NODE_RX_TYPE_DC_PDU; + ntf->hdr.handle = conn->lll.handle; + pdu = (struct pdu_data *)ntf->pdu; + + llcp_pdu_encode_conn_param_req(ctx, pdu); + + /* Enqueue notification towards LL */ + ll_rx_put(ntf->hdr.link, ntf); + ll_rx_sched(); +} +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + +static void rp_cu_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + if (!llcp_ntf_alloc_is_available()) { + ctx->state = RP_CU_STATE_WAIT_NTF; + } else { + rp_cu_ntf(conn, ctx); + llcp_rr_complete(conn); + ctx->state = RP_CU_STATE_IDLE; + } +} + +static void rp_cu_send_conn_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_CU_STATE_WAIT_TX_CONN_UPDATE_IND; + } else { + ctx->data.cu.win_size = 1U; + ctx->data.cu.win_offset_us = 0U; + ctx->data.cu.instant = ull_conn_event_counter(conn) + conn->lll.latency + + CONN_UPDATE_INSTANT_DELTA; + rp_cu_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; + ctx->state = RP_CU_STATE_WAIT_INSTANT; + } +} + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +static void rp_cu_send_reject_ext_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_CU_STATE_WAIT_TX_REJECT_EXT_IND; + } else { + rp_cu_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND); + llcp_rr_complete(conn); + ctx->state = RP_CU_STATE_IDLE; + } +} + +static void rp_cu_send_conn_param_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_CU_STATE_WAIT_TX_CONN_PARAM_RSP; + } else { + rp_cu_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_CONN_PARAM_RSP); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND; + ctx->state = RP_CU_STATE_WAIT_RX_CONN_UPDATE_IND; + } +} + +static void rp_cu_send_conn_param_req_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_ntf_alloc_is_available()) { + ctx->state = RP_CU_STATE_WAIT_NTF_CONN_PARAM_REQ; + } else { + rp_cu_conn_param_req_ntf(conn, ctx); + ctx->state = RP_CU_STATE_WAIT_CONN_PARAM_REQ_REPLY; + } +} +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + +static void rp_cu_send_unknown_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_CU_STATE_WAIT_TX_UNKNOWN_RSP; + } else { + rp_cu_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP); + llcp_rr_complete(conn); + ctx->state = RP_CU_STATE_IDLE; + } +} + +static void rp_cu_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case RP_CU_EVT_RUN: + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + case PROC_CONN_PARAM_REQ: + ctx->state = RP_CU_STATE_WAIT_RX_CONN_PARAM_REQ; + break; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + case PROC_CONN_UPDATE: + ctx->state = RP_CU_STATE_WAIT_RX_CONN_UPDATE_IND; + break; + default: + /* Unknown proceduce */ + LL_ASSERT(0); + break; + } + break; + default: + /* Ignore other evts */ + break; + } +} + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +static void rp_cu_st_wait_rx_conn_param_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case RP_CU_EVT_CONN_PARAM_REQ: + llcp_pdu_decode_conn_param_req(ctx, param); + + bool params_changed = + cu_have_params_changed(conn, ctx->data.cu.interval_max, + ctx->data.cu.latency, ctx->data.cu.timeout); + + /* notify Host if conn parameters changed, else respond */ + if (params_changed) { + rp_cu_send_conn_param_req_ntf(conn, ctx, evt, param); + } else { + ctx->state = RP_CU_STATE_WAIT_CONN_PARAM_REQ_REPLY; + } + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_cu_state_wait_ntf_conn_param_req(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + switch (evt) { + case RP_CU_EVT_RUN: + rp_cu_send_conn_param_req_ntf(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_cu_state_wait_conn_param_req_reply(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + switch (evt) { + case RP_CU_EVT_CONN_PARAM_REQ_REPLY: + /* Continue procedure in next prepare run */ + ctx->state = RP_CU_STATE_WAIT_CONN_PARAM_REQ_REPLY_CONTINUE; + break; + case RP_CU_EVT_CONN_PARAM_REQ_NEG_REPLY: + /* Send reject in next prepare run */ + ctx->state = RP_CU_STATE_WAIT_TX_REJECT_EXT_IND; + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_cu_state_wait_conn_param_req_reply_continue(struct ll_conn *conn, + struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case RP_CU_EVT_RUN: + if (conn->lll.role == BT_HCI_ROLE_CENTRAL) { + rp_cu_send_conn_update_ind(conn, ctx, evt, param); + } else if (conn->lll.role == BT_HCI_ROLE_PERIPHERAL) { + rp_cu_send_conn_param_rsp(conn, ctx, evt, param); + } else { + /* Unknown role */ + LL_ASSERT(0); + } + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_cu_state_wait_tx_reject_ext_ind(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + switch (evt) { + case RP_CU_EVT_RUN: + rp_cu_send_reject_ext_ind(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_cu_st_wait_tx_conn_param_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case RP_CU_EVT_RUN: + rp_cu_send_conn_param_rsp(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + +static void rp_cu_st_wait_tx_conn_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + switch (evt) { + case RP_CU_EVT_RUN: + rp_cu_send_conn_update_ind(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_cu_st_wait_rx_conn_update_ind(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + switch (evt) { + case RP_CU_EVT_CONN_UPDATE_IND: + switch (conn->lll.role) { + case BT_HCI_ROLE_CENTRAL: + ctx->unknown_response.type = PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND; + rp_cu_send_unknown_rsp(conn, ctx, evt, param); + break; + case BT_HCI_ROLE_PERIPHERAL: + llcp_pdu_decode_conn_update_ind(ctx, param); + /* TODO(tosk): skip/terminate if instant passed? */ +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + /* conn param req procedure, if any, is complete */ + ull_conn_prt_clear(conn); +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + ctx->state = RP_CU_STATE_WAIT_INSTANT; + break; + default: + /* Unknown role */ + LL_ASSERT(0); + } + default: + /* Ignore other evts */ + break; + } +} + +static void rp_cu_check_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + uint16_t event_counter = ull_conn_event_counter(conn); + + if (is_instant_reached_or_passed(ctx->data.cu.instant, event_counter)) { + bool notify; + + /* Procedure is complete when the instant has passed, and the + * new connection event parameters have been applied. + */ + cu_update_conn_parameters(conn, ctx); + notify = cu_should_notify_host(ctx); + if (notify) { + ctx->data.cu.error = BT_HCI_ERR_SUCCESS; + rp_cu_complete(conn, ctx, evt, param); + } else { + llcp_rr_complete(conn); + ctx->state = RP_CU_STATE_IDLE; + } + } +} + +static void rp_cu_st_wait_instant(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case RP_CU_EVT_RUN: + rp_cu_check_instant(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_cu_st_wait_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case RP_CU_EVT_RUN: + rp_cu_complete(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_cu_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (ctx->state) { + case RP_CU_STATE_IDLE: + rp_cu_st_idle(conn, ctx, evt, param); + break; +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + case RP_CU_STATE_WAIT_RX_CONN_PARAM_REQ: + rp_cu_st_wait_rx_conn_param_req(conn, ctx, evt, param); + break; + case RP_CU_STATE_WAIT_NTF_CONN_PARAM_REQ: + rp_cu_state_wait_ntf_conn_param_req(conn, ctx, evt, param); + break; + case RP_CU_STATE_WAIT_CONN_PARAM_REQ_REPLY: + rp_cu_state_wait_conn_param_req_reply(conn, ctx, evt, param); + break; + case RP_CU_STATE_WAIT_CONN_PARAM_REQ_REPLY_CONTINUE: + rp_cu_state_wait_conn_param_req_reply_continue(conn, ctx, evt, param); + break; + case RP_CU_STATE_WAIT_TX_REJECT_EXT_IND: + rp_cu_state_wait_tx_reject_ext_ind(conn, ctx, evt, param); + break; + case RP_CU_STATE_WAIT_TX_CONN_PARAM_RSP: + rp_cu_st_wait_tx_conn_param_rsp(conn, ctx, evt, param); + break; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + case RP_CU_STATE_WAIT_TX_CONN_UPDATE_IND: + rp_cu_st_wait_tx_conn_update_ind(conn, ctx, evt, param); + break; + case RP_CU_STATE_WAIT_RX_CONN_UPDATE_IND: + rp_cu_st_wait_rx_conn_update_ind(conn, ctx, evt, param); + break; + case RP_CU_STATE_WAIT_INSTANT: + rp_cu_st_wait_instant(conn, ctx, evt, param); + break; + case RP_CU_STATE_WAIT_NTF: + rp_cu_st_wait_ntf(conn, ctx, evt, param); + break; + default: + /* Unknown state */ + LL_ASSERT(0); + break; + } +} + +void llcp_rp_cu_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_CTLR_CONN_PARAM_REQ) + case PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ: + rp_cu_execute_fsm(conn, ctx, RP_CU_EVT_CONN_PARAM_REQ, pdu); + break; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + case PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND: + rp_cu_execute_fsm(conn, ctx, RP_CU_EVT_CONN_UPDATE_IND, pdu); + break; + default: + /* Unknown opcode */ + LL_ASSERT(0); + break; + } +} + +void llcp_rp_cu_init_proc(struct proc_ctx *ctx) +{ + ctx->state = RP_CU_STATE_IDLE; +} + +void llcp_rp_cu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) +{ + rp_cu_execute_fsm(conn, ctx, RP_CU_EVT_RUN, param); +} + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +void llcp_rp_conn_param_req_reply(struct ll_conn *conn, struct proc_ctx *ctx) +{ + rp_cu_execute_fsm(conn, ctx, RP_CU_EVT_CONN_PARAM_REQ_REPLY, NULL); +} + +void llcp_rp_conn_param_req_neg_reply(struct ll_conn *conn, struct proc_ctx *ctx) +{ + rp_cu_execute_fsm(conn, ctx, RP_CU_EVT_CONN_PARAM_REQ_NEG_REPLY, NULL); +} +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_enc.c b/subsys/bluetooth/controller/ll_sw/ull_llcp_enc.c new file mode 100644 index 00000000000..8cb7b53dae7 --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_enc.c @@ -0,0 +1,1197 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +#include "hal/ecb.h" +#include "hal/ccm.h" + +#include "util/util.h" +#include "util/mem.h" +#include "util/memq.h" + +#include "pdu.h" +#include "ll.h" +#include "ll_settings.h" + +#include "lll.h" +#include "lll/lll_df_types.h" +#include "lll_conn.h" + +#include "ull_tx_queue.h" + +#include "ull_conn_types.h" +#include "ull_internal.h" +#include "ull_llcp.h" +#include "ull_llcp_internal.h" +#include "ull_conn_internal.h" + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_ctlr_ull_llcp_enc +#include "common/log.h" +#include +#include "hal/debug.h" + +#if defined(CONFIG_BT_CENTRAL) +/* LLCP Local Procedure Encryption FSM states */ +enum { + /* Start Procedure */ + LP_ENC_STATE_UNENCRYPTED, + LP_ENC_STATE_WAIT_TX_ENC_REQ, + LP_ENC_STATE_WAIT_RX_ENC_RSP, + LP_ENC_STATE_WAIT_RX_START_ENC_REQ, + LP_ENC_STATE_WAIT_TX_START_ENC_RSP, + LP_ENC_STATE_WAIT_RX_START_ENC_RSP, + LP_ENC_STATE_WAIT_NTF, + /* Pause Procedure */ + LP_ENC_STATE_ENCRYPTED, + LP_ENC_STATE_WAIT_TX_PAUSE_ENC_REQ, + LP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP, + LP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP, +}; + +/* LLCP Local Procedure Encryption FSM events */ +enum { + /* Procedure prepared */ + LP_ENC_EVT_RUN, + + /* Response received */ + LP_ENC_EVT_ENC_RSP, + + /* Request received */ + LP_ENC_EVT_START_ENC_REQ, + + /* Response received */ + LP_ENC_EVT_START_ENC_RSP, + + /* Reject response received */ + LP_ENC_EVT_REJECT, + + /* Unknown response received */ + LP_ENC_EVT_UNKNOWN, + + /* Response received */ + LP_ENC_EVT_PAUSE_ENC_RSP, +}; +#endif /* CONFIG_BT_CENTRAL */ + +#if defined(CONFIG_BT_PERIPHERAL) +/* LLCP Remote Procedure Encryption FSM states */ +enum { + /* Start Procedure */ + RP_ENC_STATE_UNENCRYPTED, + RP_ENC_STATE_WAIT_RX_ENC_REQ, + RP_ENC_STATE_WAIT_TX_ENC_RSP, + RP_ENC_STATE_WAIT_NTF_LTK_REQ, + RP_ENC_STATE_WAIT_LTK_REPLY, + RP_ENC_STATE_WAIT_TX_START_ENC_REQ, + RP_ENC_STATE_WAIT_TX_REJECT_IND, + RP_ENC_STATE_WAIT_RX_START_ENC_RSP, + RP_ENC_STATE_WAIT_NTF, + RP_ENC_STATE_WAIT_TX_START_ENC_RSP, + /* Pause Procedure */ + RP_ENC_STATE_ENCRYPTED, + RP_ENC_STATE_WAIT_RX_PAUSE_ENC_REQ, + RP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP, + RP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP, +}; + +/* LLCP Remote Procedure Encryption FSM events */ +enum { + /* Procedure prepared */ + RP_ENC_EVT_RUN, + + /* Request received */ + RP_ENC_EVT_ENC_REQ, + + /* Response received */ + RP_ENC_EVT_START_ENC_RSP, + + /* LTK request reply */ + RP_ENC_EVT_LTK_REQ_REPLY, + + /* LTK request negative reply */ + RP_ENC_EVT_LTK_REQ_NEG_REPLY, + + /* Reject response received */ + RP_ENC_EVT_REJECT, + + /* Unknown response received */ + RP_ENC_EVT_UNKNOWN, + + /* Request received */ + RP_ENC_EVT_PAUSE_ENC_REQ, + + /* Response received */ + RP_ENC_EVT_PAUSE_ENC_RSP, +}; +#endif /* CONFIG_BT_PERIPHERAL */ + +#if defined(CONFIG_BT_CENTRAL) +/* + * LLCP Local Procedure Encryption FSM + */ + +static struct node_tx *llcp_lp_enc_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode) +{ + struct node_tx *tx; + struct pdu_data *pdu; + + /* Allocate tx node */ + tx = llcp_tx_alloc(conn, ctx); + LL_ASSERT(tx); + + pdu = (struct pdu_data *)tx->pdu; + + /* Encode LL Control PDU */ + switch (opcode) { + case PDU_DATA_LLCTRL_TYPE_ENC_REQ: + llcp_pdu_encode_enc_req(ctx, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_START_ENC_RSP: + llcp_pdu_encode_start_enc_rsp(pdu); + break; + case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ: + llcp_pdu_encode_pause_enc_req(pdu); + break; + case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP: + llcp_pdu_encode_pause_enc_rsp(pdu); + break; + default: + LL_ASSERT(0); + } + + ctx->tx_opcode = pdu->llctrl.opcode; + + /* Enqueue LL Control PDU towards LLL */ + llcp_tx_enqueue(conn, tx); + + /* Update procedure timeout */ + ull_conn_prt_reload(conn, conn->procedure_reload); + + return tx; +} + +static void lp_enc_ntf(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + + /* Allocate ntf node */ + ntf = llcp_ntf_alloc(); + LL_ASSERT(ntf); + + ntf->hdr.type = NODE_RX_TYPE_DC_PDU; + ntf->hdr.handle = conn->lll.handle; + pdu = (struct pdu_data *)ntf->pdu; + + if (ctx->data.enc.error == BT_HCI_ERR_SUCCESS) { + if (ctx->proc == PROC_ENCRYPTION_START) { + /* Encryption Change Event */ + /* TODO(thoh): is this correct? */ + llcp_pdu_encode_start_enc_rsp(pdu); + } else if (ctx->proc == PROC_ENCRYPTION_PAUSE) { + /* Encryption Key Refresh Complete Event */ + ntf->hdr.type = NODE_RX_TYPE_ENC_REFRESH; + } else { + /* Should never happen */ + LL_ASSERT(0); + } + } else { + llcp_pdu_encode_reject_ind(pdu, ctx->data.enc.error); + } + + /* Enqueue notification towards LL */ + ll_rx_put(ntf->hdr.link, ntf); + ll_rx_sched(); +} + +static void lp_enc_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + if (!llcp_ntf_alloc_is_available()) { + ctx->state = LP_ENC_STATE_WAIT_NTF; + } else { + lp_enc_ntf(conn, ctx); + llcp_lr_complete(conn); + ctx->state = LP_ENC_STATE_UNENCRYPTED; + } +} + +static void lp_enc_store_m(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu) +{ + /* Store SKDm */ + memcpy(&ctx->data.enc.skd[0], pdu->llctrl.enc_req.skdm, sizeof(pdu->llctrl.enc_req.skdm)); + /* Store IVm in the LLL CCM RX + * TODO(thoh): Should this be made into a ULL function, as it + * interacts with data outside of LLCP? + */ + memcpy(&conn->lll.ccm_rx.iv[0], pdu->llctrl.enc_req.ivm, sizeof(pdu->llctrl.enc_req.ivm)); +} + +static void lp_enc_send_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + struct node_tx *tx; + + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_ENC_STATE_WAIT_TX_ENC_REQ; + } else { + tx = llcp_lp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_ENC_REQ); + lp_enc_store_m(conn, ctx, (struct pdu_data *)tx->pdu); + /* Wait for the LL_ENC_RSP */ + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_ENC_RSP; + ctx->state = LP_ENC_STATE_WAIT_RX_ENC_RSP; + } +} + +static void lp_enc_send_pause_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_ENC_STATE_WAIT_TX_PAUSE_ENC_REQ; + } else { + llcp_lp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ); + /* Wait for the LL_PAUSE_ENC_RSP */ + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP; + ctx->state = LP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP; + } +} + +static void lp_enc_send_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP; + } else { + llcp_lp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP); + + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; + /* Continue with an encapsulated Start Procedure */ + ctx->state = LP_ENC_STATE_UNENCRYPTED; + + /* Tx Encryption disabled */ + conn->lll.enc_tx = 0U; + + /* Rx Decryption disabled */ + conn->lll.enc_rx = 0U; + } +} + +static void lp_enc_setup_lll(struct ll_conn *conn, struct proc_ctx *ctx) +{ + /* TODO(thoh): Move LLL/CCM manipulation to ULL? */ + + /* Calculate the Session Key */ + ecb_encrypt(&ctx->data.enc.ltk[0], &ctx->data.enc.skd[0], NULL, &conn->lll.ccm_rx.key[0]); + + /* Copy the Session Key */ + memcpy(&conn->lll.ccm_tx.key[0], &conn->lll.ccm_rx.key[0], sizeof(conn->lll.ccm_tx.key)); + + /* Copy the IV */ + memcpy(&conn->lll.ccm_tx.iv[0], &conn->lll.ccm_rx.iv[0], sizeof(conn->lll.ccm_tx.iv)); + + /* Reset CCM counter */ + conn->lll.ccm_tx.counter = 0U; + conn->lll.ccm_rx.counter = 0U; + + /* Set CCM direction: + * periph to central = 0, + * central to periph = 1 + */ + conn->lll.ccm_tx.direction = 1U; + conn->lll.ccm_rx.direction = 0U; +} + +static void lp_enc_send_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_ENC_STATE_WAIT_TX_START_ENC_RSP; + } else { + lp_enc_setup_lll(conn, ctx); + llcp_lp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_START_ENC_RSP); + + /* Wait for LL_START_ENC_RSP */ + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_START_ENC_RSP; + ctx->state = LP_ENC_STATE_WAIT_RX_START_ENC_RSP; + + /* Tx Encryption enabled */ + conn->lll.enc_tx = 1U; + + /* Rx Decryption enabled */ + conn->lll.enc_rx = 1U; + } +} + +static void lp_enc_st_unencrypted(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + /* TODO */ + switch (evt) { + case LP_ENC_EVT_RUN: + /* Pause Tx data */ + llcp_tx_pause_data(conn); + llcp_tx_flush(conn); + lp_enc_send_enc_req(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_enc_st_wait_tx_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case LP_ENC_EVT_RUN: + lp_enc_send_enc_req(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_enc_store_s(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu) +{ + /* Store SKDs */ + memcpy(&ctx->data.enc.skd[8], pdu->llctrl.enc_rsp.skds, sizeof(pdu->llctrl.enc_rsp.skds)); + /* Store IVs in the LLL CCM RX + * TODO(thoh): Should this be made into a ULL function, as it + * interacts with data outside of LLCP? + */ + memcpy(&conn->lll.ccm_rx.iv[4], pdu->llctrl.enc_rsp.ivs, sizeof(pdu->llctrl.enc_rsp.ivs)); +} + +static void lp_enc_st_wait_rx_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + struct pdu_data *pdu = (struct pdu_data *)param; + + switch (evt) { + case LP_ENC_EVT_ENC_RSP: + /* Pause Rx data */ + ull_conn_pause_rx_data(conn); + lp_enc_store_s(conn, ctx, pdu); + /* Wait for LL_START_ENC_REQ */ + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_START_ENC_REQ; + ctx->state = LP_ENC_STATE_WAIT_RX_START_ENC_REQ; + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_enc_st_wait_rx_start_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + struct pdu_data *pdu = (struct pdu_data *)param; + + switch (evt) { + case LP_ENC_EVT_START_ENC_REQ: + lp_enc_send_start_enc_rsp(conn, ctx, evt, param); + break; + case LP_ENC_EVT_REJECT: + /* Resume Tx data */ + llcp_tx_resume_data(conn); + /* Resume Rx data */ + ull_conn_resume_rx_data(conn); + ctx->data.enc.error = (pdu->llctrl.opcode == PDU_DATA_LLCTRL_TYPE_REJECT_IND) ? + pdu->llctrl.reject_ind.error_code : + pdu->llctrl.reject_ext_ind.error_code; + lp_enc_complete(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_enc_st_wait_tx_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case LP_ENC_EVT_RUN: + lp_enc_send_start_enc_rsp(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_enc_st_wait_rx_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case LP_ENC_EVT_START_ENC_RSP: + /* Resume Tx data */ + llcp_tx_resume_data(conn); + /* Resume Rx data */ + ull_conn_resume_rx_data(conn); + ctx->data.enc.error = BT_HCI_ERR_SUCCESS; + lp_enc_complete(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_enc_st_wait_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + /* TODO */ + switch (evt) { + case LP_ENC_EVT_RUN: + lp_enc_complete(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_enc_state_encrypted(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + /* TODO */ + switch (evt) { + case LP_ENC_EVT_RUN: + /* Pause Tx data */ + llcp_tx_pause_data(conn); + llcp_tx_flush(conn); + lp_enc_send_pause_enc_req(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_enc_state_wait_tx_pause_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + switch (evt) { + case LP_ENC_EVT_RUN: + lp_enc_send_pause_enc_req(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_enc_state_wait_rx_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + switch (evt) { + case LP_ENC_EVT_PAUSE_ENC_RSP: + /* + * Pause Rx data; will be resumed when the encapsulated + * Start Procedure is done. + */ + ull_conn_pause_rx_data(conn); + lp_enc_send_pause_enc_rsp(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_enc_state_wait_tx_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + switch (evt) { + case LP_ENC_EVT_RUN: + lp_enc_send_pause_enc_rsp(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lp_enc_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (ctx->state) { + /* Start Procedure */ + case LP_ENC_STATE_UNENCRYPTED: + lp_enc_st_unencrypted(conn, ctx, evt, param); + break; + case LP_ENC_STATE_WAIT_TX_ENC_REQ: + lp_enc_st_wait_tx_enc_req(conn, ctx, evt, param); + break; + case LP_ENC_STATE_WAIT_RX_ENC_RSP: + lp_enc_st_wait_rx_enc_rsp(conn, ctx, evt, param); + break; + case LP_ENC_STATE_WAIT_RX_START_ENC_REQ: + lp_enc_st_wait_rx_start_enc_req(conn, ctx, evt, param); + break; + case LP_ENC_STATE_WAIT_TX_START_ENC_RSP: + lp_enc_st_wait_tx_start_enc_rsp(conn, ctx, evt, param); + break; + case LP_ENC_STATE_WAIT_RX_START_ENC_RSP: + lp_enc_st_wait_rx_start_enc_rsp(conn, ctx, evt, param); + break; + case LP_ENC_STATE_WAIT_NTF: + lp_enc_st_wait_ntf(conn, ctx, evt, param); + break; + /* Pause Procedure */ + case LP_ENC_STATE_ENCRYPTED: + lp_enc_state_encrypted(conn, ctx, evt, param); + break; + case LP_ENC_STATE_WAIT_TX_PAUSE_ENC_REQ: + lp_enc_state_wait_tx_pause_enc_req(conn, ctx, evt, param); + break; + case LP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP: + lp_enc_state_wait_rx_pause_enc_rsp(conn, ctx, evt, param); + break; + case LP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP: + lp_enc_state_wait_tx_pause_enc_rsp(conn, ctx, evt, param); + break; + default: + /* Unknown state */ + LL_ASSERT(0); + } +} + +void llcp_lp_enc_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_ENC_RSP: + lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_ENC_RSP, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_START_ENC_REQ: + lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_START_ENC_REQ, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_START_ENC_RSP: + lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_START_ENC_RSP, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_REJECT_IND: + case PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND: + lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_REJECT, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP: + lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_PAUSE_ENC_RSP, pdu); + break; + default: + /* Unknown opcode */ + LL_ASSERT(0); + } +} + +void llcp_lp_enc_init_proc(struct proc_ctx *ctx) +{ + switch (ctx->proc) { + case PROC_ENCRYPTION_START: + ctx->state = LP_ENC_STATE_UNENCRYPTED; + break; + case PROC_ENCRYPTION_PAUSE: + ctx->state = LP_ENC_STATE_ENCRYPTED; + break; + default: + LL_ASSERT(0); + } +} + +void llcp_lp_enc_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) +{ + lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_RUN, param); +} + +#endif /* CONFIG_BT_CENTRAL */ + +#if defined(CONFIG_BT_PERIPHERAL) +/* + * LLCP Remote Procedure Encryption FSM + */ + +static struct node_tx *llcp_rp_enc_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode) +{ + struct node_tx *tx; + struct pdu_data *pdu; + + /* Allocate tx node */ + tx = llcp_tx_alloc(conn, ctx); + LL_ASSERT(tx); + + pdu = (struct pdu_data *)tx->pdu; + + /* Encode LL Control PDU */ + switch (opcode) { + case PDU_DATA_LLCTRL_TYPE_ENC_RSP: + llcp_pdu_encode_enc_rsp(pdu); + break; + case PDU_DATA_LLCTRL_TYPE_START_ENC_REQ: + llcp_pdu_encode_start_enc_req(pdu); + break; + case PDU_DATA_LLCTRL_TYPE_START_ENC_RSP: + llcp_pdu_encode_start_enc_rsp(pdu); + break; + case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP: + llcp_pdu_encode_pause_enc_rsp(pdu); + break; + case PDU_DATA_LLCTRL_TYPE_REJECT_IND: + /* TODO(thoh): Select between LL_REJECT_IND and LL_REJECT_EXT_IND */ + llcp_pdu_encode_reject_ext_ind(pdu, PDU_DATA_LLCTRL_TYPE_ENC_REQ, + BT_HCI_ERR_PIN_OR_KEY_MISSING); + break; + default: + LL_ASSERT(0); + } + + ctx->tx_opcode = pdu->llctrl.opcode; + + /* Enqueue LL Control PDU towards LLL */ + llcp_tx_enqueue(conn, tx); + + return tx; +} + +static void rp_enc_ntf_ltk(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + + /* Allocate ntf node */ + ntf = llcp_ntf_alloc(); + 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_enc_req(ctx, pdu); + + /* Enqueue notification towards LL */ + ll_rx_put(ntf->hdr.link, ntf); + ll_rx_sched(); +} + +static void rp_enc_ntf(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + + /* Allocate ntf node */ + ntf = llcp_ntf_alloc(); + LL_ASSERT(ntf); + + ntf->hdr.type = NODE_RX_TYPE_DC_PDU; + ntf->hdr.handle = conn->lll.handle; + pdu = (struct pdu_data *)ntf->pdu; + + if (ctx->proc == PROC_ENCRYPTION_START) { + /* Encryption Change Event */ + /* TODO(thoh): is this correct? */ + llcp_pdu_encode_start_enc_rsp(pdu); + } else if (ctx->proc == PROC_ENCRYPTION_PAUSE) { + /* Encryption Key Refresh Complete Event */ + ntf->hdr.type = NODE_RX_TYPE_ENC_REFRESH; + } else { + /* Should never happen */ + LL_ASSERT(0); + } + + /* Enqueue notification towards LL */ + ll_rx_put(ntf->hdr.link, ntf); + ll_rx_sched(); +} + +static void rp_enc_send_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param); + +static void rp_enc_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + if (!llcp_ntf_alloc_is_available()) { + ctx->state = RP_ENC_STATE_WAIT_NTF; + } else { + rp_enc_ntf(conn, ctx); + rp_enc_send_start_enc_rsp(conn, ctx, evt, param); + } +} + +static void rp_enc_send_ltk_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_ntf_alloc_is_available()) { + ctx->state = RP_ENC_STATE_WAIT_NTF_LTK_REQ; + } else { + rp_enc_ntf_ltk(conn, ctx); + ctx->state = RP_ENC_STATE_WAIT_LTK_REPLY; + } +} + +static void rp_enc_store_s(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu) +{ + /* Store SKDs */ + memcpy(&ctx->data.enc.skds, pdu->llctrl.enc_rsp.skds, sizeof(pdu->llctrl.enc_rsp.skds)); + /* Store IVs in the LLL CCM RX + * TODO(thoh): Should this be made into a ULL function, as it + * interacts with data outside of LLCP? + */ + memcpy(&conn->lll.ccm_rx.iv[4], pdu->llctrl.enc_rsp.ivs, sizeof(pdu->llctrl.enc_rsp.ivs)); +} + +static void rp_enc_send_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + struct node_tx *tx; + + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_ENC_STATE_WAIT_TX_ENC_RSP; + } else { + tx = llcp_rp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_ENC_RSP); + rp_enc_store_s(conn, ctx, (struct pdu_data *)tx->pdu); + rp_enc_send_ltk_ntf(conn, ctx, evt, param); + } +} + +static void rp_enc_setup_lll(struct ll_conn *conn, struct proc_ctx *ctx) +{ + /* TODO(thoh): Move LLL/CCM manipulation to ULL? */ + + /* Calculate the Session Key */ + ecb_encrypt(&ctx->data.enc.ltk[0], &ctx->data.enc.skd[0], NULL, &conn->lll.ccm_rx.key[0]); + + /* Copy the Session Key */ + memcpy(&conn->lll.ccm_tx.key[0], &conn->lll.ccm_rx.key[0], sizeof(conn->lll.ccm_tx.key)); + + /* Copy the IV */ + memcpy(&conn->lll.ccm_tx.iv[0], &conn->lll.ccm_rx.iv[0], sizeof(conn->lll.ccm_tx.iv)); + + /* Reset CCM counter */ + conn->lll.ccm_tx.counter = 0U; + conn->lll.ccm_rx.counter = 0U; + + /* Set CCM direction: + * periph to central = 0, + * central to periph = 1 + */ + conn->lll.ccm_tx.direction = 0U; + conn->lll.ccm_rx.direction = 1U; +} + +static void rp_enc_send_start_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_ENC_STATE_WAIT_TX_START_ENC_REQ; + } else { + rp_enc_setup_lll(conn, ctx); + llcp_rp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_START_ENC_REQ); + /* Wait for the LL_START_ENC_RSP */ + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_START_ENC_RSP; + ctx->state = RP_ENC_STATE_WAIT_RX_START_ENC_RSP; + + /* Rx Decryption enabled */ + conn->lll.enc_rx = 1U; + } +} + +static void rp_enc_send_reject_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_ENC_STATE_WAIT_TX_REJECT_IND; + } else { + llcp_rp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_REJECT_IND); + llcp_rr_complete(conn); + ctx->state = RP_ENC_STATE_UNENCRYPTED; + + /* Resume Tx data */ + llcp_tx_resume_data(conn); + /* Resume Rx data */ + ull_conn_resume_rx_data(conn); + } +} + +static void rp_enc_send_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_ENC_STATE_WAIT_TX_START_ENC_RSP; + } else { + llcp_rp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_START_ENC_RSP); + llcp_rr_complete(conn); + ctx->state = RP_ENC_STATE_UNENCRYPTED; + + /* Resume Tx data */ + llcp_tx_resume_data(conn); + /* Resume Rx data */ + ull_conn_resume_rx_data(conn); + + /* Tx Encryption enabled */ + conn->lll.enc_tx = 1U; + } +} + +static void rp_enc_send_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + if (!llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP; + } else { + llcp_rp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP); + /* Wait for the LL_PAUSE_ENC_RSP */ + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP; + ctx->state = RP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP; + + /* Rx Decryption disabled */ + conn->lll.enc_rx = 0U; + } +} + +static void rp_enc_state_unencrypted(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case RP_ENC_EVT_RUN: + ctx->state = RP_ENC_STATE_WAIT_RX_ENC_REQ; + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_store_m(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu) +{ + /* Store Rand */ + memcpy(ctx->data.enc.rand, pdu->llctrl.enc_req.rand, sizeof(ctx->data.enc.rand)); + + /* Store EDIV */ + ctx->data.enc.ediv[0] = pdu->llctrl.enc_req.ediv[0]; + ctx->data.enc.ediv[1] = pdu->llctrl.enc_req.ediv[1]; + + /* Store SKDm */ + memcpy(&ctx->data.enc.skdm, pdu->llctrl.enc_req.skdm, sizeof(ctx->data.enc.skdm)); + + /* Store IVm in the LLL CCM RX + * TODO(thoh): Should this be made into a ULL function, as it + * interacts with data outside of LLCP? + */ + memcpy(&conn->lll.ccm_rx.iv[0], pdu->llctrl.enc_req.ivm, sizeof(pdu->llctrl.enc_req.ivm)); +} + +static void rp_enc_state_wait_rx_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case RP_ENC_EVT_ENC_REQ: + /* Pause Tx data */ + llcp_tx_pause_data(conn); + llcp_tx_flush(conn); + /* Pause Rx data */ + ull_conn_pause_rx_data(conn); + rp_enc_store_m(conn, ctx, param); + rp_enc_send_enc_rsp(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_wait_tx_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + /* TODO */ + switch (evt) { + case RP_ENC_EVT_RUN: + rp_enc_send_enc_rsp(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_wait_ntf_ltk_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + /* TODO */ + switch (evt) { + case RP_ENC_EVT_RUN: + rp_enc_send_ltk_ntf(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_wait_ltk_reply(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + /* TODO */ + switch (evt) { + case RP_ENC_EVT_LTK_REQ_REPLY: + rp_enc_send_start_enc_req(conn, ctx, evt, param); + break; + case RP_ENC_EVT_LTK_REQ_NEG_REPLY: + rp_enc_send_reject_ind(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_wait_tx_start_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + /* TODO */ + switch (evt) { + case RP_ENC_EVT_RUN: + rp_enc_send_start_enc_req(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_wait_tx_reject_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + /* TODO */ + switch (evt) { + case RP_ENC_EVT_RUN: + rp_enc_send_reject_ind(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_wait_rx_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + /* TODO */ + switch (evt) { + case RP_ENC_EVT_START_ENC_RSP: + rp_enc_complete(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_wait_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + /* TODO */ + switch (evt) { + case RP_ENC_EVT_RUN: + rp_enc_complete(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_wait_tx_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + /* TODO */ + switch (evt) { + case RP_ENC_EVT_RUN: + rp_enc_send_start_enc_rsp(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_encrypted(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, + void *param) +{ + switch (evt) { + case RP_ENC_EVT_RUN: + ctx->state = RP_ENC_STATE_WAIT_RX_PAUSE_ENC_REQ; + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_wait_rx_pause_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + /* TODO */ + switch (evt) { + case RP_ENC_EVT_PAUSE_ENC_REQ: + /* Pause Tx data */ + llcp_tx_pause_data(conn); + llcp_tx_flush(conn); + /* + * Pause Rx data; will be resumed when the encapsulated + * Start Procedure is done. + */ + ull_conn_pause_rx_data(conn); + rp_enc_send_pause_enc_rsp(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_wait_tx_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + /* TODO */ + switch (evt) { + case RP_ENC_EVT_RUN: + rp_enc_send_pause_enc_rsp(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_state_wait_rx_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, + uint8_t evt, void *param) +{ + /* TODO */ + switch (evt) { + case RP_ENC_EVT_PAUSE_ENC_RSP: + /* Continue with an encapsulated Start Procedure */ + /* Wait for the LL_ENC_REQ */ + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_ENC_REQ; + ctx->state = RP_ENC_STATE_WAIT_RX_ENC_REQ; + + /* Tx Encryption disabled */ + conn->lll.enc_tx = 0U; + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rp_enc_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (ctx->state) { + /* Start Procedure */ + case RP_ENC_STATE_UNENCRYPTED: + rp_enc_state_unencrypted(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_RX_ENC_REQ: + rp_enc_state_wait_rx_enc_req(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_TX_ENC_RSP: + rp_enc_state_wait_tx_enc_rsp(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_NTF_LTK_REQ: + rp_enc_state_wait_ntf_ltk_req(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_LTK_REPLY: + rp_enc_state_wait_ltk_reply(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_TX_START_ENC_REQ: + rp_enc_state_wait_tx_start_enc_req(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_TX_REJECT_IND: + rp_enc_state_wait_tx_reject_ind(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_RX_START_ENC_RSP: + rp_enc_state_wait_rx_start_enc_rsp(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_NTF: + rp_enc_state_wait_ntf(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_TX_START_ENC_RSP: + rp_enc_state_wait_tx_start_enc_rsp(conn, ctx, evt, param); + break; + /* Pause Procedure */ + case RP_ENC_STATE_ENCRYPTED: + rp_enc_state_encrypted(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_RX_PAUSE_ENC_REQ: + rp_enc_state_wait_rx_pause_enc_req(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP: + rp_enc_state_wait_tx_pause_enc_rsp(conn, ctx, evt, param); + break; + case RP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP: + rp_enc_state_wait_rx_pause_enc_rsp(conn, ctx, evt, param); + break; + default: + /* Unknown state */ + LL_ASSERT(0); + } +} + +void llcp_rp_enc_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_ENC_REQ: + rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_ENC_REQ, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_START_ENC_RSP: + rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_START_ENC_RSP, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ: + rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_PAUSE_ENC_REQ, pdu); + break; + case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP: + rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_PAUSE_ENC_RSP, pdu); + break; + default: + /* Unknown opcode */ + LL_ASSERT(0); + } +} + +void llcp_rp_enc_init_proc(struct proc_ctx *ctx) +{ + switch (ctx->proc) { + case PROC_ENCRYPTION_START: + ctx->state = RP_ENC_STATE_UNENCRYPTED; + break; + case PROC_ENCRYPTION_PAUSE: + ctx->state = RP_ENC_STATE_ENCRYPTED; + break; + default: + LL_ASSERT(0); + } +} + +void llcp_rp_enc_ltk_req_reply(struct ll_conn *conn, struct proc_ctx *ctx) +{ + rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_LTK_REQ_REPLY, NULL); +} + +void llcp_rp_enc_ltk_req_neg_reply(struct ll_conn *conn, struct proc_ctx *ctx) +{ + rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_LTK_REQ_NEG_REPLY, NULL); +} + +void llcp_rp_enc_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param) +{ + rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_RUN, param); +} +#endif /* CONFIG_BT_PERIPHERAL */ diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_features.h b/subsys/bluetooth/controller/ll_sw/ull_llcp_features.h new file mode 100644 index 00000000000..1786f93875e --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_features.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2018-2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +static inline void feature_unmask_features(struct ll_conn *conn, uint64_t ll_feat_mask) +{ + conn->llcp.fex.features_used &= ~ll_feat_mask; +} + +static inline bool feature_le_encryption(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_LE_ENC) + return conn->llcp.fex.features_used & LL_FEAT_BIT_ENC; +#else + return 0; +#endif +} + +static inline bool feature_conn_param_req(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + return conn->llcp.fex.features_used & LL_FEAT_BIT_CONN_PARAM_REQ; +#else + return 0; +#endif +} + +static inline bool feature_ext_rej_ind(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_EXT_REJ_IND) + return conn->llcp.fex.features_used & LL_FEAT_BIT_EXT_REJ_IND; +#else + return 0; +#endif +} + +static inline bool feature_periph_feat_req(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG) + return conn->llcp.fex.features_used & LL_FEAT_BIT_PER_INIT_FEAT_XCHG; +#else + return 0; +#endif +} + +static inline bool feature_le_ping(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_LE_PING) + return conn->llcp.fex.features_used & LL_FEAT_BIT_PING; +#else + return 0; +#endif +} + +static inline bool feature_dle(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + return conn->llcp.fex.features_used & LL_FEAT_BIT_DLE; +#else + return 0; +#endif +} + +static inline bool feature_privacy(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_PRIVACY) + return conn->llcp.fex.features_used & LL_FEAT_BIT_PRIVACY; +#else + return 0; +#endif +} + +static inline bool feature_ext_scan(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_EXT_SCAN_FP) + return conn->llcp.fex.features_used & LL_FEAT_BIT_EXT_SCAN; +#else + return 0; +#endif +} + +static inline bool feature_chan_sel_2(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_CHAN_SEL_2) + return conn->llcp.fex.features_used & LL_FEAT_BIT_CHAN_SEL_2; +#else + return 0; +#endif +} + +static inline bool feature_min_used_chan(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) + return conn->llcp.fex.features_used & LL_FEAT_BIT_MIN_USED_CHAN; +#else + return 0; +#endif +} + +static inline bool feature_phy_2m(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_PHY_2M) + return conn->llcp.fex.features_used & LL_FEAT_BIT_PHY_2M; +#else + return 0; +#endif +} + +static inline bool feature_phy_coded(struct ll_conn *conn) +{ +#if defined(CONFIG_BT_CTLR_PHY_CODED) + return conn->llcp.fex.features_used & LL_FEAT_BIT_PHY_CODED; +#else + return 0; +#endif +} + +/* + * for assymetric features we can check either if we support it + * or if the peer supports it + */ +static inline bool feature_smi_rx(struct ll_conn *conn) +{ + return LL_FEAT_BIT_SMI_RX; +} + +static inline bool feature_peer_smi_rx(struct ll_conn *conn) +{ + return conn->llcp.fex.features_peer & BIT64(BT_LE_FEAT_BIT_SMI_RX); +} + +static inline bool feature_smi_tx(struct ll_conn *conn) +{ + return LL_FEAT_BIT_SMI_TX; +} + +static inline bool feature_peer_smi_tx(struct ll_conn *conn) +{ + return conn->llcp.fex.features_peer & BIT64(BT_LE_FEAT_BIT_SMI_TX); +} + +/* + * The following features are not yet defined in KConfig and do + * not have a bitfield defined in ll_feat.h + * ext_adv + * per_adv + * pwr_class1 + * min_chann + * CTE_req + * CTE_rsp + * CTE_tx + * CTE_rx + * ant_sw_CTE_tx + * ant_sw_CTE_rx + * tone_ext + * per_adv_sync_tx + * per_adv_sync_rx + * sleep_upd + * rpk_valid + * iso_central + * iso_periph + * iso_broadcast + * iso_receiver + * iso_channels + * le_pwr_req + * le_pwr_ind + * le_path_loss + */ diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_internal.h b/subsys/bluetooth/controller/ll_sw/ull_llcp_internal.h new file mode 100644 index 00000000000..10cf74c3b34 --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_internal.h @@ -0,0 +1,592 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* LLCP Procedure */ +enum llcp_proc { + PROC_UNKNOWN, + PROC_LE_PING, + PROC_FEATURE_EXCHANGE, + PROC_MIN_USED_CHANS, + PROC_VERSION_EXCHANGE, + PROC_ENCRYPTION_START, + PROC_ENCRYPTION_PAUSE, + PROC_PHY_UPDATE, + PROC_CONN_UPDATE, + PROC_CONN_PARAM_REQ, + PROC_TERMINATE, + PROC_CHAN_MAP_UPDATE, + PROC_DATA_LENGTH_UPDATE, + PROC_CTE_REQ, +}; +#if ((CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM <\ + (CONFIG_BT_CTLR_LLCP_TX_PER_CONN_TX_CTRL_BUF_NUM_MAX *\ + CONFIG_BT_CTLR_LLCP_CONN)) &&\ + (CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM <\ + CONFIG_BT_CTLR_LLCP_TX_PER_CONN_TX_CTRL_BUF_NUM_MAX)) +#define LLCP_TX_CTRL_BUF_QUEUE_ENABLE +#define LLCP_TX_CTRL_BUF_COUNT ((CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM +\ + (CONFIG_BT_CTLR_LLCP_CONN * CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM))) +#else +#define LLCP_TX_CTRL_BUF_COUNT (CONFIG_BT_CTLR_LLCP_TX_PER_CONN_TX_CTRL_BUF_NUM_MAX *\ + CONFIG_BT_CTLR_LLCP_CONN) +#endif + +#if defined(LLCP_TX_CTRL_BUF_QUEUE_ENABLE) +enum llcp_wait_reason { + WAITING_FOR_NOTHING, + WAITING_FOR_TX_BUFFER, +}; +#endif /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ + +#if defined(CONFIG_BT_CTLR_LE_ENC) +struct llcp_enc { + uint8_t error; + + /* NOTE: To save memory, SKD(m|s) and IV(m|s) are + * generated just-in-time for PDU enqueuing and are + * therefore not present in this structure. + */ + + /* TODO(thoh): Do we want a version without JIT vector + * generation? + */ + + /* TODO(thoh): Optimize memory layout. + * * Overlay memory? + * * Repurpose memory used by lll.ccm_tx/rx? + */ + + /* Master: Rand and EDIV are input copies from + * HCI that only live until the LL_ENC_REQ has + * been enqueued. + * + * Slave: Rand and EDIV are input copies from + * the LL_ENC_REQ that only live until host + * notification has been enqueued. + */ + + /* 64 bit random number. */ + uint8_t rand[8]; + + /* 16 bit encrypted diversifier.*/ + uint8_t ediv[2]; + + /* 128 bit long term key. */ + uint8_t ltk[16]; + + /* SKD is the concatenation of SKDm and SKDs and + * is used to calculate the session key, SK. + * + * Lifetime: + * M : Generate SKDm and IVm + * M->S : LL_ENC_REQ(Rand, EDIV, SKDm, IVm) + * S : Notify host (Rand, EDIV) + * S : Generate SKDs and IVs + * S : Calculate SK = e(LTK, SKD) + * M<-S : LL_ENC_RSP(SKDs, IVs) + * M : Calculate SK = e(LTK, SKD) + * + * where security function e generates 128-bit + * encryptedData from a 128-bit key and 128-bit + * plaintextData using the AES-128-bit block + * cypher as defined in FIPS-1971: + * encryptedData = e(key, plaintextData) + */ + union { + + /* 128-bit session key diversifier */ + uint8_t skd[16]; + struct { + uint8_t skdm[8]; + uint8_t skds[8]; + }; + }; +}; +#endif /* CONFIG_BT_CTLR_LE_ENC */ + +/* LLCP Procedure Context */ +struct proc_ctx { + /* Must be the first for sys_slist to work */ + sys_snode_t node; + + /* PROC_ */ + enum llcp_proc proc; + + enum pdu_data_llctrl_type response_opcode; + + /* Procedure FSM */ + uint8_t state; + + /* Expected opcode to be received next */ + enum pdu_data_llctrl_type rx_opcode; + + /* Last transmitted opcode used for unknown/reject */ + enum pdu_data_llctrl_type tx_opcode; + + /* Instant collision */ + int collision; + + /* Procedure pause */ + int pause; + +#if defined(LLCP_TX_CTRL_BUF_QUEUE_ENABLE) + /* Wait list next pointer */ + sys_snode_t wait_node; + + /* Procedure wait reason */ + enum llcp_wait_reason wait_reason; +#endif /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ + + /* TX node awaiting ack */ + struct node_tx *tx_ack; + + /* + * This flag is set to 1 when we are finished with the control + * procedure and it is safe to release the context ctx + */ + int done; + + /* Procedure data */ + union { + /* Used by Minimum Used Channels Procedure */ +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) + struct { + uint8_t phys; + uint8_t min_used_chans; + } muc; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + +#if defined(CONFIG_BT_CTLR_LE_ENC) + /* Used by Encryption Procedure */ + struct llcp_enc enc; +#endif /* CONFIG_BT_CTLR_LE_ENC */ + +#if defined(CONFIG_BT_CTLR_PHY) + /* PHY Update */ + struct { + uint8_t tx:3; + uint8_t rx:3; + uint8_t flags:1; + uint8_t host_initiated:1; + uint8_t ntf_pu:1; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + uint8_t ntf_dle:1; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + uint8_t error; + uint16_t instant; + uint8_t c_to_p_phy; + uint8_t p_to_c_phy; + } pu; +#endif /* CONFIG_BT_CTLR_PHY */ + + /* TODO(tosk): leave out some params below if !CONFIG_BT_CTLR_CONN_PARAM_REQ */ + /* Connection Update & Connection Parameter Request */ + struct { + uint8_t error; + uint8_t params_changed; + uint16_t instant; + uint8_t win_size; + uint16_t win_offset_us; + uint16_t interval_min; + uint16_t interval_max; + uint16_t latency; + uint16_t timeout; + uint8_t preferred_periodicity; + uint16_t reference_conn_event_count; + uint16_t offset0; + uint16_t offset1; + uint16_t offset2; + uint16_t offset3; + uint16_t offset4; + uint16_t offset5; + } cu; + + /* Use by ACL Termination Procedure */ + struct { + uint8_t error_code; + } term; + + /* Use by Channel Map Update Procedure */ + struct { + uint16_t instant; + uint8_t chm[5]; + } chmu; +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + /* Use by CTE Request Procedure */ + struct { + uint8_t type:2; + uint8_t min_len:5; + } cte_req; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + } data; + + struct { + uint8_t type; + } unknown_response; + + struct { + uint8_t reject_opcode; + uint8_t error_code; + } reject_ext_ind; +}; + +/* Procedure Incompatibility */ +enum proc_incompat { + /* Local procedure has not sent first PDU */ + INCOMPAT_NO_COLLISION, + + /* Local incompatible procedure has sent first PDU */ + INCOMPAT_RESOLVABLE, + + /* Local incompatible procedure has received first PDU */ + INCOMPAT_RESERVED, +}; + +/* Invalid LL Control PDU Opcode */ +#define ULL_LLCP_INVALID_OPCODE (0xFFU) + +static inline bool is_instant_passed(uint16_t instant, uint16_t event_count) +{ + /* + * BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 6, Part B + * 5.5.1 ACL Control Procedures + * When a periph receives such a PDU where (Instant – connEventCount) modulo + * 65536 is less than 32767 and Instant is not equal to connEventCount, the periph + * shall listen to all the connection events until it has confirmation that the central + * has received its acknowledgment of the PDU or connEventCount equals + * Instant. + * + * x % 2^n == x & (2^n - 1) + * + * 65535 = 2^16 - 1 + * 65536 = 2^16 + */ + + return ((instant - event_count) & 0xFFFFU) > 0x7FFFU; +} + +static inline bool is_instant_not_passed(uint16_t instant, uint16_t event_count) +{ + /* + * BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 6, Part B + * 5.5.1 ACL Control Procedures + * When a periph receives such a PDU where (Instant – connEventCount) modulo + * 65536 is less than 32767 and Instant is not equal to connEventCount, the periph + * shall listen to all the connection events until it has confirmation that the central + * has received its acknowledgment of the PDU or connEventCount equals + * Instant. + * + * x % 2^n == x & (2^n - 1) + * + * 65535 = 2^16 - 1 + * 65536 = 2^16 + */ + + return ((instant - event_count) & 0xFFFFU) < 0x7FFFU; +} + +static inline bool is_instant_reached_or_passed(uint16_t instant, uint16_t event_count) +{ + /* + * BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 6, Part B + * 5.5.1 ACL Control Procedures + * When a periph receives such a PDU where (Instant – connEventCount) modulo + * 65536 is less than 32767 and Instant is not equal to connEventCount, the periph + * shall listen to all the connection events until it has confirmation that the central + * has received its acknowledgment of the PDU or connEventCount equals + * Instant. + * + * x % 2^n == x & (2^n - 1) + * + * 65535 = 2^16 - 1 + * 65536 = 2^16 + */ + + return ((event_count - instant) & 0xFFFFU) <= 0x7FFFU; +} + +/* + * LLCP Resource Management + */ +bool llcp_ntf_alloc_is_available(void); +bool llcp_ntf_alloc_num_available(uint8_t count); +struct node_rx_pdu *llcp_ntf_alloc(void); +struct proc_ctx *llcp_create_local_procedure(enum llcp_proc proc); +struct proc_ctx *llcp_create_remote_procedure(enum llcp_proc proc); +bool llcp_tx_alloc_peek(struct ll_conn *conn, struct proc_ctx *ctx); +void llcp_tx_alloc_unpeek(struct proc_ctx *ctx); +struct node_tx *llcp_tx_alloc(struct ll_conn *conn, struct proc_ctx *ctx); + +/* + * ULL -> LLL Interface + */ +void llcp_tx_enqueue(struct ll_conn *conn, struct node_tx *tx); +void llcp_tx_pause_data(struct ll_conn *conn); +void llcp_tx_resume_data(struct ll_conn *conn); +void llcp_tx_flush(struct ll_conn *conn); + +/* + * LLCP Local Procedure Common + */ +void llcp_lp_comm_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx); +void llcp_lp_comm_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx); +void llcp_lp_comm_init_proc(struct proc_ctx *ctx); +void llcp_lp_comm_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param); + +/* + * LLCP Remote Procedure Common + */ +void llcp_rp_comm_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx); +void llcp_rp_comm_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx); +void llcp_rp_comm_init_proc(struct proc_ctx *ctx); +void llcp_rp_comm_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param); + +#if defined(CONFIG_BT_CTLR_LE_ENC) +/* + * LLCP Local Procedure Encryption + */ +void llcp_lp_enc_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx); +void llcp_lp_enc_init_proc(struct proc_ctx *ctx); +void llcp_lp_enc_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param); + +/* + * LLCP Remote Procedure Encryption + */ +void llcp_rp_enc_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx); +void llcp_rp_enc_init_proc(struct proc_ctx *ctx); +void llcp_rp_enc_ltk_req_reply(struct ll_conn *conn, struct proc_ctx *ctx); +void llcp_rp_enc_ltk_req_neg_reply(struct ll_conn *conn, struct proc_ctx *ctx); +void llcp_rp_enc_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param); +#endif /* CONFIG_BT_CTLR_LE_ENC */ + +#if defined(CONFIG_BT_CTLR_PHY) +/* + * LLCP Local Procedure PHY Update + */ +void llcp_lp_pu_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx); +void llcp_lp_pu_init_proc(struct proc_ctx *ctx); +void llcp_lp_pu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param); +void llcp_lp_pu_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, void *param); +#endif /* CONFIG_BT_CTLR_PHY */ + +/* + * LLCP Local Procedure Connection Update + */ +void llcp_lp_cu_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx); +void llcp_lp_cu_init_proc(struct proc_ctx *ctx); +void llcp_lp_cu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param); + +/* + * LLCP Local Channel Map Update + */ +void llcp_lp_chmu_init_proc(struct proc_ctx *ctx); +void llcp_lp_chmu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param); + +#if defined(CONFIG_BT_CTLR_PHY) +/* + * LLCP Remote Procedure PHY Update + */ +void llcp_rp_pu_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx); +void llcp_rp_pu_init_proc(struct proc_ctx *ctx); +void llcp_rp_pu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param); +void llcp_rp_pu_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, void *param); +#endif /* CONFIG_BT_CTLR_PHY */ + +/* + * LLCP Remote Procedure Connection Update + */ +void llcp_rp_cu_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx); +void llcp_rp_cu_init_proc(struct proc_ctx *ctx); +void llcp_rp_cu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param); +void llcp_rp_conn_param_req_reply(struct ll_conn *conn, struct proc_ctx *ctx); +void llcp_rp_conn_param_req_neg_reply(struct ll_conn *conn, struct proc_ctx *ctx); + +/* + * Terminate Helper + */ +void llcp_pdu_encode_terminate_ind(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_ntf_encode_terminate_ind(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_pdu_decode_terminate_ind(struct proc_ctx *ctx, struct pdu_data *pdu); + +/* + * LLCP Local Request + */ +struct proc_ctx *llcp_lr_peek(struct ll_conn *conn); +void llcp_lr_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx); +void llcp_lr_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx); +void llcp_lr_enqueue(struct ll_conn *conn, struct proc_ctx *ctx); +void llcp_lr_init(struct ll_conn *conn); +void llcp_lr_run(struct ll_conn *conn); +void llcp_lr_complete(struct ll_conn *conn); +void llcp_lr_connect(struct ll_conn *conn); +void llcp_lr_disconnect(struct ll_conn *conn); +void llcp_lr_abort(struct ll_conn *conn); + +/* + * LLCP Remote Request + */ +void llcp_rr_set_incompat(struct ll_conn *conn, enum proc_incompat incompat); +bool llcp_rr_get_collision(struct ll_conn *conn); +struct proc_ctx *llcp_rr_peek(struct ll_conn *conn); +void llcp_rr_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx); +void llcp_rr_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx); +void llcp_rr_init(struct ll_conn *conn); +void llcp_rr_prepare(struct ll_conn *conn, struct node_rx_pdu *rx); +void llcp_rr_run(struct ll_conn *conn); +void llcp_rr_complete(struct ll_conn *conn); +void llcp_rr_connect(struct ll_conn *conn); +void llcp_rr_disconnect(struct ll_conn *conn); +void llcp_rr_new(struct ll_conn *conn, struct node_rx_pdu *rx); + +#if defined(CONFIG_BT_CTLR_LE_PING) +/* + * LE Ping Procedure Helper + */ +void llcp_pdu_encode_ping_req(struct pdu_data *pdu); +void llcp_pdu_encode_ping_rsp(struct pdu_data *pdu); +#endif /* CONFIG_BT_CTLR_LE_PING */ +/* + * Unknown response helper + */ + +void llcp_pdu_encode_unknown_rsp(struct proc_ctx *ctx, + struct pdu_data *pdu); +void llcp_pdu_decode_unknown_rsp(struct proc_ctx *ctx, + struct pdu_data *pdu); +void llcp_ntf_encode_unknown_rsp(struct proc_ctx *ctx, + struct pdu_data *pdu); + +/* + * Feature Exchange Procedure Helper + */ +void llcp_pdu_encode_feature_req(struct ll_conn *conn, + struct pdu_data *pdu); +void llcp_pdu_encode_feature_rsp(struct ll_conn *conn, + struct pdu_data *pdu); +void llcp_ntf_encode_feature_rsp(struct ll_conn *conn, + struct pdu_data *pdu); +void llcp_ntf_encode_feature_req(struct ll_conn *conn, + struct pdu_data *pdu); +void llcp_pdu_decode_feature_req(struct ll_conn *conn, + struct pdu_data *pdu); +void llcp_pdu_decode_feature_rsp(struct ll_conn *conn, + struct pdu_data *pdu); + +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) +/* + * Minimum number of used channels Procedure Helper + */ +#if defined(CONFIG_BT_PERIPHERAL) +void llcp_pdu_encode_min_used_chans_ind(struct proc_ctx *ctx, struct pdu_data *pdu); +#endif /* CONFIG_BT_PERIPHERAL */ + +#if defined(CONFIG_BT_CENTRAL) +void llcp_pdu_decode_min_used_chans_ind(struct ll_conn *conn, struct pdu_data *pdu); +#endif /* CONFIG_BT_CENTRAL */ +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + +/* + * Version Exchange Procedure Helper + */ +void llcp_pdu_encode_version_ind(struct pdu_data *pdu); +void llcp_ntf_encode_version_ind(struct ll_conn *conn, struct pdu_data *pdu); +void llcp_pdu_decode_version_ind(struct ll_conn *conn, struct pdu_data *pdu); + +#if defined(CONFIG_BT_CTLR_LE_ENC) +/* + * Encryption Start Procedure Helper + */ +#if defined(CONFIG_BT_CENTRAL) +void llcp_pdu_encode_enc_req(struct proc_ctx *ctx, struct pdu_data *pdu); +#endif /* CONFIG_BT_CENTRAL */ + +#if defined(CONFIG_BT_PERIPHERAL) +void llcp_ntf_encode_enc_req(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_pdu_encode_enc_rsp(struct pdu_data *pdu); +void llcp_pdu_encode_start_enc_req(struct pdu_data *pdu); +#endif /* CONFIG_BT_PERIPHERAL */ + +void llcp_pdu_encode_start_enc_rsp(struct pdu_data *pdu); + +#if defined(CONFIG_BT_CENTRAL) +void llcp_pdu_encode_pause_enc_req(struct pdu_data *pdu); +#endif /* CONFIG_BT_CENTRAL */ + +void llcp_pdu_encode_pause_enc_rsp(struct pdu_data *pdu); +#endif /* CONFIG_BT_CTLR_LE_ENC */ + +void llcp_pdu_encode_reject_ind(struct pdu_data *pdu, uint8_t error_code); +void llcp_pdu_encode_reject_ext_ind(struct pdu_data *pdu, uint8_t reject_opcode, + uint8_t error_code); +void llcp_pdu_decode_reject_ext_ind(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_ntf_encode_reject_ext_ind(struct proc_ctx *ctx, struct pdu_data *pdu); + +/* + * PHY Update Procedure Helper + */ +void llcp_pdu_encode_phy_req(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_pdu_decode_phy_req(struct proc_ctx *ctx, struct pdu_data *pdu); + +#if defined(CONFIG_BT_PERIPHERAL) +void llcp_pdu_encode_phy_rsp(struct ll_conn *conn, struct pdu_data *pdu); +void llcp_pdu_decode_phy_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu); +#endif /* CONFIG_BT_PERIPHERAL */ + +#if defined(CONFIG_BT_CENTRAL) +void llcp_pdu_encode_phy_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_pdu_decode_phy_rsp(struct proc_ctx *ctx, struct pdu_data *pdu); +#endif /* CONFIG_BT_CENTRAL */ + +/* + * Connection Update Procedure Helper + */ +void llcp_pdu_encode_conn_param_req(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_pdu_decode_conn_param_req(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_pdu_encode_conn_param_rsp(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_pdu_decode_conn_param_rsp(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_pdu_encode_conn_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_pdu_decode_conn_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_proc_ctx_release(struct proc_ctx *ctx); + +/* + * Remote Channel Map Update Procedure Helper + */ +void llcp_pdu_encode_chan_map_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_pdu_decode_chan_map_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_rp_chmu_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx); +void llcp_rp_chmu_init_proc(struct proc_ctx *ctx); +void llcp_rp_chmu_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param); + +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +/* + * Data Length Update Procedure Helper + */ +void llcp_pdu_encode_length_req(struct ll_conn *conn, struct pdu_data *pdu); +void llcp_pdu_encode_length_rsp(struct ll_conn *conn, struct pdu_data *pdu); +void llcp_pdu_decode_length_req(struct ll_conn *conn, struct pdu_data *pdu); +void llcp_pdu_decode_length_rsp(struct ll_conn *conn, struct pdu_data *pdu); +void llcp_ntf_encode_length_change(struct ll_conn *conn, + struct pdu_data *pdu); + +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) +/* + * Constant Tone Request Procedure Helper + */ +void llcp_pdu_encode_cte_req(struct proc_ctx *ctx, struct pdu_data *pdu); +void llcp_ntf_encode_cte_req(struct ll_conn *conn, struct pdu_data *pdu); +void llcp_pdu_decode_cte_req(struct ll_conn *conn, struct pdu_data *pdu); +void llcp_pdu_encode_cte_rsp(struct pdu_data *pdu); +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + +#ifdef ZTEST_UNITTEST +bool lr_is_disconnected(struct ll_conn *conn); +bool lr_is_idle(struct ll_conn *conn); +bool rr_is_disconnected(struct ll_conn *conn); +bool rr_is_idle(struct ll_conn *conn); +int ctx_buffers_free(void); +#endif diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_local.c b/subsys/bluetooth/controller/ll_sw/ull_llcp_local.c new file mode 100644 index 00000000000..e55660ef09e --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_local.c @@ -0,0 +1,487 @@ +/* + * 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 "pdu.h" +#include "ll.h" +#include "ll_settings.h" + +#include "lll.h" +#include "lll/lll_df_types.h" +#include "lll_conn.h" + +#include "ull_tx_queue.h" + +#include "ull_conn_types.h" +#include "ull_llcp.h" +#include "ull_llcp_internal.h" +#include "ull_conn_internal.h" + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_ctlr_ull_llcp_local +#include "common/log.h" +#include +#include "hal/debug.h" + +static void lr_check_done(struct ll_conn *conn, struct proc_ctx *ctx); +static struct proc_ctx *lr_dequeue(struct ll_conn *conn); + +/* LLCP Local Request FSM State */ +enum lr_state { + LR_STATE_IDLE, + LR_STATE_ACTIVE, + LR_STATE_DISCONNECT, + LR_STATE_TERMINATE, +}; + +/* LLCP Local Request FSM Event */ +enum { + /* Procedure run */ + LR_EVT_RUN, + + /* Procedure completed */ + LR_EVT_COMPLETE, + + /* Link connected */ + LR_EVT_CONNECT, + + /* Link disconnected */ + LR_EVT_DISCONNECT, +}; + +static void lr_check_done(struct ll_conn *conn, struct proc_ctx *ctx) +{ + if (ctx->done) { + struct proc_ctx *ctx_header; + + ctx_header = llcp_lr_peek(conn); + LL_ASSERT(ctx_header == ctx); + + lr_dequeue(conn); + + if ((ctx->proc != PROC_CHAN_MAP_UPDATE) && (ctx->proc != PROC_CONN_UPDATE)) { + ull_conn_prt_clear(conn); + } + + llcp_proc_ctx_release(ctx); + } +} +/* + * LLCP Local Request FSM + */ + +static void lr_set_state(struct ll_conn *conn, enum lr_state state) +{ + conn->llcp.local.state = state; +} + +void llcp_lr_enqueue(struct ll_conn *conn, struct proc_ctx *ctx) +{ + sys_slist_append(&conn->llcp.local.pend_proc_list, &ctx->node); +} + +static struct proc_ctx *lr_dequeue(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = (struct proc_ctx *)sys_slist_get(&conn->llcp.local.pend_proc_list); + return ctx; +} + +struct proc_ctx *llcp_lr_peek(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = (struct proc_ctx *)sys_slist_peek_head(&conn->llcp.local.pend_proc_list); + return ctx; +} + +void llcp_lr_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx) +{ + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PROC_LE_PING: + llcp_lp_comm_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PROC_FEATURE_EXCHANGE: + llcp_lp_comm_rx(conn, ctx, rx); + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) + case PROC_MIN_USED_CHANS: + llcp_lp_comm_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + case PROC_VERSION_EXCHANGE: + llcp_lp_comm_rx(conn, ctx, rx); + break; +#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_CENTRAL) + case PROC_ENCRYPTION_START: + case PROC_ENCRYPTION_PAUSE: + llcp_lp_enc_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_CENTRAL */ +#ifdef CONFIG_BT_CTLR_PHY + case PROC_PHY_UPDATE: + llcp_lp_pu_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_PHY */ + case PROC_CONN_UPDATE: + case PROC_CONN_PARAM_REQ: + llcp_lp_cu_rx(conn, ctx, rx); + break; + case PROC_TERMINATE: + llcp_lp_comm_rx(conn, ctx, rx); + break; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + llcp_lp_comm_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: + llcp_lp_comm_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + break; + } + + lr_check_done(conn, ctx); +} + +void llcp_lr_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx) +{ + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) + case PROC_MIN_USED_CHANS: + llcp_lp_comm_tx_ack(conn, ctx, tx); + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + case PROC_TERMINATE: + llcp_lp_comm_tx_ack(conn, ctx, tx); + break; +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + llcp_lp_comm_tx_ack(conn, ctx, tx); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#ifdef CONFIG_BT_CTLR_PHY + case PROC_PHY_UPDATE: + llcp_lp_pu_tx_ack(conn, ctx, tx); + break; +#endif /* CONFIG_BT_CTLR_PHY */ + default: + break; + /* Ignore tx_ack */ + } + lr_check_done(conn, ctx); +} + +static void lr_act_run(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = llcp_lr_peek(conn); + + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PROC_LE_PING: + llcp_lp_comm_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PROC_FEATURE_EXCHANGE: + llcp_lp_comm_run(conn, ctx, NULL); + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) + case PROC_MIN_USED_CHANS: + llcp_lp_comm_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + case PROC_VERSION_EXCHANGE: + llcp_lp_comm_run(conn, ctx, NULL); + break; +#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_CENTRAL) + case PROC_ENCRYPTION_START: + case PROC_ENCRYPTION_PAUSE: + llcp_lp_enc_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_CENTRAL */ +#ifdef CONFIG_BT_CTLR_PHY + case PROC_PHY_UPDATE: + llcp_lp_pu_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_PHY */ + case PROC_CONN_UPDATE: + case PROC_CONN_PARAM_REQ: + llcp_lp_cu_run(conn, ctx, NULL); + break; + case PROC_TERMINATE: + llcp_lp_comm_run(conn, ctx, NULL); + break; +#if defined(CONFIG_BT_CENTRAL) + case PROC_CHAN_MAP_UPDATE: + llcp_lp_chmu_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CENTRAL */ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + llcp_lp_comm_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: + /* 3rd partam null? */ + llcp_lp_comm_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + break; + } + + lr_check_done(conn, ctx); +} + +static void lr_act_complete(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = llcp_lr_peek(conn); + + ctx->done = 1U; +} + +static void lr_act_connect(struct ll_conn *conn) +{ + /* TODO */ +} + +static void lr_act_disconnect(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = lr_dequeue(conn); + + /* + * we may have been disconnected in the + * middle of a control procedure, in + * which case we need to release context + */ + while (ctx != NULL) { + llcp_proc_ctx_release(ctx); + ctx = lr_dequeue(conn); + } +} + +static void lr_st_disconnect(struct ll_conn *conn, uint8_t evt, void *param) +{ + switch (evt) { + case LR_EVT_CONNECT: + lr_act_connect(conn); + lr_set_state(conn, LR_STATE_IDLE); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lr_st_idle(struct ll_conn *conn, uint8_t evt, void *param) +{ + struct proc_ctx *ctx; + + switch (evt) { + case LR_EVT_RUN: + ctx = llcp_lr_peek(conn); + if (ctx) { + lr_act_run(conn); + if (ctx->proc != PROC_TERMINATE) { + lr_set_state(conn, LR_STATE_ACTIVE); + } else { + lr_set_state(conn, LR_STATE_TERMINATE); + } + } + break; + case LR_EVT_DISCONNECT: + lr_act_disconnect(conn); + lr_set_state(conn, LR_STATE_DISCONNECT); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lr_st_active(struct ll_conn *conn, uint8_t evt, void *param) +{ + switch (evt) { + case LR_EVT_RUN: + if (llcp_lr_peek(conn)) { + lr_act_run(conn); + } + break; + case LR_EVT_COMPLETE: + lr_act_complete(conn); + lr_set_state(conn, LR_STATE_IDLE); + break; + case LR_EVT_DISCONNECT: + lr_act_disconnect(conn); + lr_set_state(conn, LR_STATE_DISCONNECT); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lr_st_terminate(struct ll_conn *conn, uint8_t evt, void *param) +{ + switch (evt) { + case LR_EVT_RUN: + if (llcp_lr_peek(conn)) { + lr_act_run(conn); + } + break; + case LR_EVT_COMPLETE: + lr_act_complete(conn); + lr_set_state(conn, LR_STATE_IDLE); + break; + case LR_EVT_DISCONNECT: + lr_act_disconnect(conn); + lr_set_state(conn, LR_STATE_DISCONNECT); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void lr_execute_fsm(struct ll_conn *conn, uint8_t evt, void *param) +{ + switch (conn->llcp.local.state) { + case LR_STATE_DISCONNECT: + lr_st_disconnect(conn, evt, param); + break; + case LR_STATE_IDLE: + lr_st_idle(conn, evt, param); + break; + case LR_STATE_ACTIVE: + lr_st_active(conn, evt, param); + break; + case LR_STATE_TERMINATE: + lr_st_terminate(conn, evt, param); + break; + default: + /* Unknown state */ + LL_ASSERT(0); + } +} + +void llcp_lr_init(struct ll_conn *conn) +{ + lr_set_state(conn, LR_STATE_DISCONNECT); +} + +void llcp_lr_run(struct ll_conn *conn) +{ + lr_execute_fsm(conn, LR_EVT_RUN, NULL); +} + +void llcp_lr_complete(struct ll_conn *conn) +{ + lr_execute_fsm(conn, LR_EVT_COMPLETE, NULL); +} + +void llcp_lr_connect(struct ll_conn *conn) +{ + lr_execute_fsm(conn, LR_EVT_CONNECT, NULL); +} + +void llcp_lr_disconnect(struct ll_conn *conn) +{ + lr_execute_fsm(conn, LR_EVT_DISCONNECT, NULL); +} + +void llcp_lr_abort(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + /* Flush all pending procedures */ + ctx = lr_dequeue(conn); + while (ctx) { + llcp_proc_ctx_release(ctx); + ctx = lr_dequeue(conn); + } + + /* TODO(thoh): Whats missing here ??? */ + ull_conn_prt_clear(conn); + llcp_rr_set_incompat(conn, 0U); + lr_set_state(conn, LR_STATE_IDLE); +} + +#ifdef ZTEST_UNITTEST + +bool lr_is_disconnected(struct ll_conn *conn) +{ + return conn->llcp.local.state == LR_STATE_DISCONNECT; +} + +bool lr_is_idle(struct ll_conn *conn) +{ + return conn->llcp.local.state == LR_STATE_IDLE; +} + +void test_int_local_pending_requests(void) +{ + struct ll_conn conn; + struct proc_ctx *peek_ctx; + struct proc_ctx *dequeue_ctx; + struct proc_ctx ctx; + + ull_cp_init(); + ull_tx_q_init(&conn.tx_q); + ull_llcp_init(&conn); + + peek_ctx = llcp_lr_peek(&conn); + zassert_is_null(peek_ctx, NULL); + + dequeue_ctx = lr_dequeue(&conn); + zassert_is_null(dequeue_ctx, NULL); + + llcp_lr_enqueue(&conn, &ctx); + peek_ctx = (struct proc_ctx *)sys_slist_peek_head(&conn.llcp.local.pend_proc_list); + zassert_equal_ptr(peek_ctx, &ctx, NULL); + + peek_ctx = llcp_lr_peek(&conn); + zassert_equal_ptr(peek_ctx, &ctx, NULL); + + dequeue_ctx = lr_dequeue(&conn); + zassert_equal_ptr(dequeue_ctx, &ctx, NULL); + + peek_ctx = llcp_lr_peek(&conn); + zassert_is_null(peek_ctx, NULL); + + dequeue_ctx = lr_dequeue(&conn); + zassert_is_null(dequeue_ctx, NULL); +} + +#endif diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_pdu.c b/subsys/bluetooth/controller/ll_sw/ull_llcp_pdu.c new file mode 100644 index 00000000000..c76b2efc5cd --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_pdu.c @@ -0,0 +1,752 @@ +/* + * 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 "pdu.h" +#include "ll.h" +#include "ll_settings.h" + +#include "lll.h" +#include "lll/lll_df_types.h" +#include "lll_conn.h" + +#include "ull_tx_queue.h" + +#include "ull_conn_types.h" +#include "ull_llcp.h" +#include "ull_llcp_internal.h" + +#include "ll_feat.h" + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_ctlr_ull_llcp_pdu +#include "common/log.h" +#include +#include "hal/debug.h" + +/* + * we need to filter on octet 0; the following mask has 7 octets + * with all bits set to 1, the last octet is 0x00 + */ +#define FEAT_FILT_OCTET0 0xFFFFFFFFFFFFFF00 + +#if defined(CONFIG_BT_CTLR_LE_PING) +/* + * LE Ping Procedure Helpers + */ + +void llcp_pdu_encode_ping_req(struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, ping_req) + + sizeof(struct pdu_data_llctrl_ping_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PING_REQ; +} + +void llcp_pdu_encode_ping_rsp(struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, ping_rsp) + + sizeof(struct pdu_data_llctrl_ping_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PING_RSP; +} +#endif /* CONFIG_BT_CTLR_LE_PING */ +/* + * Unknown response helper + */ + +void llcp_pdu_encode_unknown_rsp(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, unknown_rsp) + + sizeof(struct pdu_data_llctrl_unknown_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP; + + pdu->llctrl.unknown_rsp.type = ctx->unknown_response.type; +} + +void llcp_pdu_decode_unknown_rsp(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + ctx->unknown_response.type = pdu->llctrl.unknown_rsp.type; +} + +void llcp_ntf_encode_unknown_rsp(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_unknown_rsp *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, unknown_rsp) + + sizeof(struct pdu_data_llctrl_unknown_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP; + p = &pdu->llctrl.unknown_rsp; + p->type = ctx->unknown_response.type; +} + +/* + * Feature Exchange Procedure Helper + */ + +static void feature_filter(uint8_t *featuresin, uint64_t *featuresout) +{ + uint64_t feat; + + /* + * Note that in the split controller invalid bits are set + * to 1, in LLCP we set them to 0; the spec. does not + * define which value they should have + */ + feat = sys_get_le64(featuresin); + feat &= LL_FEAT_BIT_MASK; + feat &= LL_FEAT_BIT_MASK_VALID; + + *featuresout = feat; +} + +void llcp_pdu_encode_feature_req(struct ll_conn *conn, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_feature_req *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, feature_req) + + sizeof(struct pdu_data_llctrl_feature_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_FEATURE_REQ; + +#if defined(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG) && defined(CONFIG_BT_PERIPHERAL) + if (conn->lll.role == BT_HCI_ROLE_PERIPHERAL) { + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PER_INIT_FEAT_XCHG; + } +#endif /* CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG && CONFIG_BT_PERIPHERAL */ + + p = &pdu->llctrl.feature_req; + sys_put_le64(LL_FEAT, p->features); +} + +void llcp_pdu_encode_feature_rsp(struct ll_conn *conn, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_feature_rsp *p; + uint64_t feature_rsp = LL_FEAT; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, feature_rsp) + + sizeof(struct pdu_data_llctrl_feature_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_FEATURE_RSP; + + p = &pdu->llctrl.feature_rsp; + + /* + * we only filter on octet 0, remaining 7 octets are the features + * we support, as defined in LL_FEAT + */ + feature_rsp &= (FEAT_FILT_OCTET0 | conn->llcp.fex.features_used); + + sys_put_le64(feature_rsp, p->features); +} + +void llcp_ntf_encode_feature_rsp(struct ll_conn *conn, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_feature_rsp *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, feature_rsp) + + sizeof(struct pdu_data_llctrl_feature_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_FEATURE_RSP; + p = &pdu->llctrl.feature_rsp; + + sys_put_le64(conn->llcp.fex.features_peer, p->features); +} + +void llcp_pdu_decode_feature_req(struct ll_conn *conn, struct pdu_data *pdu) +{ + uint64_t featureset; + + feature_filter(pdu->llctrl.feature_req.features, &featureset); + conn->llcp.fex.features_used = LL_FEAT & featureset; + + featureset &= (FEAT_FILT_OCTET0 | conn->llcp.fex.features_used); + conn->llcp.fex.features_peer = featureset; + + conn->llcp.fex.valid = 1; +} + +void llcp_pdu_decode_feature_rsp(struct ll_conn *conn, struct pdu_data *pdu) +{ + uint64_t featureset; + + feature_filter(pdu->llctrl.feature_rsp.features, &featureset); + conn->llcp.fex.features_used = LL_FEAT & featureset; + + conn->llcp.fex.features_peer = featureset; + conn->llcp.fex.valid = 1; +} + +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) +/* + * Minimum used channels Procedure Helpers + */ +#if defined(CONFIG_BT_PERIPHERAL) +void llcp_pdu_encode_min_used_chans_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_min_used_chans_ind *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, min_used_chans_ind) + + sizeof(struct pdu_data_llctrl_min_used_chans_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_MIN_USED_CHAN_IND; + p = &pdu->llctrl.min_used_chans_ind; + p->phys = ctx->data.muc.phys; + p->min_used_chans = ctx->data.muc.min_used_chans; +} +#endif /* CONFIG_BT_PERIPHERAL */ + +#if defined(CONFIG_BT_CENTRAL) +void llcp_pdu_decode_min_used_chans_ind(struct ll_conn *conn, struct pdu_data *pdu) +{ + conn->llcp.muc.phys = pdu->llctrl.min_used_chans_ind.phys; + conn->llcp.muc.min_used_chans = pdu->llctrl.min_used_chans_ind.min_used_chans; +} +#endif /* CONFIG_BT_CENTRAL */ +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + +/* + * Termination Procedure Helper + */ +void llcp_pdu_encode_terminate_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_terminate_ind *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, terminate_ind) + + sizeof(struct pdu_data_llctrl_terminate_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_TERMINATE_IND; + p = &pdu->llctrl.terminate_ind; + p->error_code = ctx->data.term.error_code; +} + +void llcp_ntf_encode_terminate_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_terminate_ind *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, terminate_ind) + + sizeof(struct pdu_data_llctrl_terminate_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_TERMINATE_IND; + p = &pdu->llctrl.terminate_ind; + p->error_code = ctx->data.term.error_code; +} + +void llcp_pdu_decode_terminate_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + ctx->data.term.error_code = pdu->llctrl.terminate_ind.error_code; +} + +/* + * Version Exchange Procedure Helper + */ +void llcp_pdu_encode_version_ind(struct pdu_data *pdu) +{ + uint16_t cid; + uint16_t svn; + struct pdu_data_llctrl_version_ind *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, version_ind) + + sizeof(struct pdu_data_llctrl_version_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_VERSION_IND; + + p = &pdu->llctrl.version_ind; + p->version_number = LL_VERSION_NUMBER; + cid = sys_cpu_to_le16(ll_settings_company_id()); + svn = sys_cpu_to_le16(ll_settings_subversion_number()); + p->company_id = cid; + p->sub_version_number = svn; +} + +void llcp_ntf_encode_version_ind(struct ll_conn *conn, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_version_ind *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, version_ind) + + sizeof(struct pdu_data_llctrl_version_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_VERSION_IND; + + p = &pdu->llctrl.version_ind; + p->version_number = conn->llcp.vex.cached.version_number; + p->company_id = sys_cpu_to_le16(conn->llcp.vex.cached.company_id); + p->sub_version_number = sys_cpu_to_le16(conn->llcp.vex.cached.sub_version_number); +} + +void llcp_pdu_decode_version_ind(struct ll_conn *conn, struct pdu_data *pdu) +{ + conn->llcp.vex.valid = 1; + conn->llcp.vex.cached.version_number = pdu->llctrl.version_ind.version_number; + conn->llcp.vex.cached.company_id = sys_le16_to_cpu(pdu->llctrl.version_ind.company_id); + conn->llcp.vex.cached.sub_version_number = + sys_le16_to_cpu(pdu->llctrl.version_ind.sub_version_number); +} + +#if defined(CONFIG_BT_CTLR_LE_ENC) +/* + * Encryption Start Procedure Helper + */ + +static int csrand_get(void *buf, size_t len) +{ + if (k_is_in_isr()) { + return lll_csrand_isr_get(buf, len); + } else { + return lll_csrand_get(buf, len); + } +} + +#if defined(CONFIG_BT_CENTRAL) +void llcp_pdu_encode_enc_req(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_enc_req *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, enc_req) + sizeof(struct pdu_data_llctrl_enc_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_ENC_REQ; + + p = &pdu->llctrl.enc_req; + memcpy(p->rand, ctx->data.enc.rand, sizeof(p->rand)); + p->ediv[0] = ctx->data.enc.ediv[0]; + p->ediv[1] = ctx->data.enc.ediv[1]; + /* TODO(thoh): Optimize getting random data */ + csrand_get(p->skdm, sizeof(p->skdm)); + csrand_get(p->ivm, sizeof(p->ivm)); +} +#endif /* CONFIG_BT_CENTRAL */ + +#if defined(CONFIG_BT_PERIPHERAL) +void llcp_ntf_encode_enc_req(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_enc_req *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, enc_req) + sizeof(struct pdu_data_llctrl_enc_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_ENC_REQ; + + p = &pdu->llctrl.enc_req; + memcpy(p->rand, ctx->data.enc.rand, sizeof(p->rand)); + p->ediv[0] = ctx->data.enc.ediv[0]; + p->ediv[1] = ctx->data.enc.ediv[1]; +} + +void llcp_pdu_encode_enc_rsp(struct pdu_data *pdu) +{ + struct pdu_data_llctrl_enc_rsp *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, enc_rsp) + sizeof(struct pdu_data_llctrl_enc_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_ENC_RSP; + + p = &pdu->llctrl.enc_rsp; + /* TODO(thoh): Optimize getting random data */ + csrand_get(p->skds, sizeof(p->skds)); + csrand_get(p->ivs, sizeof(p->ivs)); +} + +void llcp_pdu_encode_start_enc_req(struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, start_enc_req) + + sizeof(struct pdu_data_llctrl_start_enc_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_START_ENC_REQ; +} +#endif /* CONFIG_BT_PERIPHERAL */ + +void llcp_pdu_encode_start_enc_rsp(struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, start_enc_rsp) + + sizeof(struct pdu_data_llctrl_start_enc_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_START_ENC_RSP; +} + +#if defined(CONFIG_BT_CENTRAL) +void llcp_pdu_encode_pause_enc_req(struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, pause_enc_req) + + sizeof(struct pdu_data_llctrl_pause_enc_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ; +} +#endif /* CONFIG_BT_CENTRAL */ + +void llcp_pdu_encode_pause_enc_rsp(struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, pause_enc_rsp) + + sizeof(struct pdu_data_llctrl_pause_enc_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP; +} +#endif /* CONFIG_BT_CTLR_LE_ENC */ + +void llcp_pdu_encode_reject_ind(struct pdu_data *pdu, uint8_t error_code) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, reject_ind) + + sizeof(struct pdu_data_llctrl_reject_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_REJECT_IND; + pdu->llctrl.reject_ind.error_code = error_code; +} + +void llcp_pdu_encode_reject_ext_ind(struct pdu_data *pdu, uint8_t reject_opcode, uint8_t error_code) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, reject_ext_ind) + + sizeof(struct pdu_data_llctrl_reject_ext_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND; + pdu->llctrl.reject_ext_ind.reject_opcode = reject_opcode; + pdu->llctrl.reject_ext_ind.error_code = error_code; +} + +void llcp_pdu_decode_reject_ext_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + ctx->reject_ext_ind.reject_opcode = pdu->llctrl.reject_ext_ind.reject_opcode; + ctx->reject_ext_ind.error_code = pdu->llctrl.reject_ext_ind.error_code; +} + +void llcp_ntf_encode_reject_ext_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_reject_ext_ind *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, reject_ext_ind) + + sizeof(struct pdu_data_llctrl_reject_ext_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND; + + p = (void *)&pdu->llctrl.reject_ext_ind; + p->error_code = ctx->reject_ext_ind.error_code; + p->reject_opcode = ctx->reject_ext_ind.reject_opcode; +} + +#ifdef CONFIG_BT_CTLR_PHY +/* + * PHY Update Procedure Helper + */ + +void llcp_pdu_encode_phy_req(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, phy_req) + sizeof(struct pdu_data_llctrl_phy_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PHY_REQ; + pdu->llctrl.phy_req.rx_phys = ctx->data.pu.rx; + pdu->llctrl.phy_req.tx_phys = ctx->data.pu.tx; +} + +void llcp_pdu_decode_phy_req(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + ctx->data.pu.rx = pdu->llctrl.phy_req.tx_phys; + ctx->data.pu.tx = pdu->llctrl.phy_req.rx_phys; +} + +#if defined(CONFIG_BT_PERIPHERAL) +void llcp_pdu_encode_phy_rsp(struct ll_conn *conn, struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, phy_rsp) + sizeof(struct pdu_data_llctrl_phy_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PHY_RSP; + pdu->llctrl.phy_rsp.rx_phys = conn->phy_pref_rx; + pdu->llctrl.phy_rsp.tx_phys = conn->phy_pref_tx; +} +void llcp_pdu_decode_phy_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + ctx->data.pu.instant = sys_le16_to_cpu(pdu->llctrl.phy_upd_ind.instant); + ctx->data.pu.c_to_p_phy = pdu->llctrl.phy_upd_ind.c_to_p_phy; + ctx->data.pu.p_to_c_phy = pdu->llctrl.phy_upd_ind.p_to_c_phy; +} +#endif /* CONFIG_BT_PERIPHERAL */ + +#if defined(CONFIG_BT_CENTRAL) +void llcp_pdu_encode_phy_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, phy_upd_ind) + + sizeof(struct pdu_data_llctrl_phy_upd_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND; + pdu->llctrl.phy_upd_ind.instant = sys_cpu_to_le16(ctx->data.pu.instant); + pdu->llctrl.phy_upd_ind.c_to_p_phy = ctx->data.pu.c_to_p_phy; + pdu->llctrl.phy_upd_ind.p_to_c_phy = ctx->data.pu.p_to_c_phy; +} +void llcp_pdu_decode_phy_rsp(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + ctx->data.pu.rx = pdu->llctrl.phy_rsp.tx_phys; + ctx->data.pu.tx = pdu->llctrl.phy_rsp.rx_phys; +} +#endif /* CONFIG_BT_CENTRAL */ +#endif /* CONFIG_BT_CTLR_PHY */ + +/* + * Connection Update Procedure Helper + */ +void llcp_pdu_encode_conn_param_req(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_conn_param_req *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, conn_param_req) + + sizeof(struct pdu_data_llctrl_conn_param_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ; + + p = (void *)&pdu->llctrl.conn_param_req; + p->interval_min = sys_cpu_to_le16(ctx->data.cu.interval_min); + p->interval_max = sys_cpu_to_le16(ctx->data.cu.interval_max); + p->latency = sys_cpu_to_le16(ctx->data.cu.latency); + p->timeout = sys_cpu_to_le16(ctx->data.cu.timeout); + p->preferred_periodicity = ctx->data.cu.preferred_periodicity; + p->reference_conn_event_count = sys_cpu_to_le16(ctx->data.cu.reference_conn_event_count); + p->offset0 = sys_cpu_to_le16(ctx->data.cu.offset0); + p->offset1 = sys_cpu_to_le16(ctx->data.cu.offset1); + p->offset2 = sys_cpu_to_le16(ctx->data.cu.offset2); + p->offset3 = sys_cpu_to_le16(ctx->data.cu.offset3); + p->offset4 = sys_cpu_to_le16(ctx->data.cu.offset4); + p->offset5 = sys_cpu_to_le16(ctx->data.cu.offset5); +} + +void llcp_pdu_encode_conn_param_rsp(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_conn_param_req *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, conn_param_rsp) + + sizeof(struct pdu_data_llctrl_conn_param_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_RSP; + + p = (void *)&pdu->llctrl.conn_param_rsp; + p->interval_min = sys_cpu_to_le16(ctx->data.cu.interval_min); + p->interval_max = sys_cpu_to_le16(ctx->data.cu.interval_max); + p->latency = sys_cpu_to_le16(ctx->data.cu.latency); + p->timeout = sys_cpu_to_le16(ctx->data.cu.timeout); + p->preferred_periodicity = ctx->data.cu.preferred_periodicity; + p->reference_conn_event_count = sys_cpu_to_le16(ctx->data.cu.reference_conn_event_count); + p->offset0 = sys_cpu_to_le16(ctx->data.cu.offset0); + p->offset1 = sys_cpu_to_le16(ctx->data.cu.offset1); + p->offset2 = sys_cpu_to_le16(ctx->data.cu.offset2); + p->offset3 = sys_cpu_to_le16(ctx->data.cu.offset3); + p->offset4 = sys_cpu_to_le16(ctx->data.cu.offset4); + p->offset5 = sys_cpu_to_le16(ctx->data.cu.offset5); +} + +void llcp_pdu_decode_conn_param_req(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_conn_param_req *p; + + p = (void *)&pdu->llctrl.conn_param_req; + ctx->data.cu.interval_min = sys_le16_to_cpu(p->interval_min); + ctx->data.cu.interval_max = sys_le16_to_cpu(p->interval_max); + ctx->data.cu.latency = sys_le16_to_cpu(p->latency); + ctx->data.cu.timeout = sys_le16_to_cpu(p->timeout); + ctx->data.cu.preferred_periodicity = p->preferred_periodicity; + ctx->data.cu.reference_conn_event_count = sys_le16_to_cpu(p->reference_conn_event_count); + ctx->data.cu.offset0 = sys_le16_to_cpu(p->offset0); + ctx->data.cu.offset1 = sys_le16_to_cpu(p->offset1); + ctx->data.cu.offset2 = sys_le16_to_cpu(p->offset2); + ctx->data.cu.offset3 = sys_le16_to_cpu(p->offset3); + ctx->data.cu.offset4 = sys_le16_to_cpu(p->offset4); + ctx->data.cu.offset5 = sys_le16_to_cpu(p->offset5); +} + +void llcp_pdu_decode_conn_param_rsp(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_conn_param_rsp *p; + + p = (void *)&pdu->llctrl.conn_param_req; + ctx->data.cu.interval_min = sys_le16_to_cpu(p->interval_min); + ctx->data.cu.interval_max = sys_le16_to_cpu(p->interval_max); + ctx->data.cu.latency = sys_le16_to_cpu(p->latency); + ctx->data.cu.timeout = sys_le16_to_cpu(p->timeout); + ctx->data.cu.preferred_periodicity = p->preferred_periodicity; + ctx->data.cu.reference_conn_event_count = sys_le16_to_cpu(p->reference_conn_event_count); + ctx->data.cu.offset0 = sys_le16_to_cpu(p->offset0); + ctx->data.cu.offset1 = sys_le16_to_cpu(p->offset1); + ctx->data.cu.offset2 = sys_le16_to_cpu(p->offset2); + ctx->data.cu.offset3 = sys_le16_to_cpu(p->offset3); + ctx->data.cu.offset4 = sys_le16_to_cpu(p->offset4); + ctx->data.cu.offset5 = sys_le16_to_cpu(p->offset5); +} + +void llcp_pdu_encode_conn_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_conn_update_ind *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, conn_update_ind) + + sizeof(struct pdu_data_llctrl_conn_update_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND; + + p = (void *)&pdu->llctrl.conn_update_ind; + p->win_size = ctx->data.cu.win_size; + p->win_offset = sys_cpu_to_le16(ctx->data.cu.win_offset_us / CONN_INT_UNIT_US); + p->latency = sys_cpu_to_le16(ctx->data.cu.latency); + p->interval = sys_cpu_to_le16(ctx->data.cu.interval_max); + p->timeout = sys_cpu_to_le16(ctx->data.cu.timeout); + p->instant = sys_cpu_to_le16(ctx->data.cu.instant); +} + +void llcp_pdu_decode_conn_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_conn_update_ind *p; + + p = (void *)&pdu->llctrl.conn_update_ind; + ctx->data.cu.win_size = p->win_size; + ctx->data.cu.win_offset_us = sys_le16_to_cpu(p->win_offset * CONN_INT_UNIT_US); + ctx->data.cu.latency = sys_le16_to_cpu(p->latency); + ctx->data.cu.interval_max = sys_le16_to_cpu(p->interval); + ctx->data.cu.timeout = sys_le16_to_cpu(p->timeout); + ctx->data.cu.instant = sys_le16_to_cpu(p->instant); +} + +/* + * Channel Map Update Procedure Helpers + */ +void llcp_pdu_encode_chan_map_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_chan_map_ind *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, chan_map_ind) + + sizeof(struct pdu_data_llctrl_chan_map_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CHAN_MAP_IND; + p = &pdu->llctrl.chan_map_ind; + p->instant = sys_cpu_to_le16(ctx->data.chmu.instant); + memcpy(p->chm, ctx->data.chmu.chm, sizeof(p->chm)); +} + +void llcp_pdu_decode_chan_map_update_ind(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + ctx->data.chmu.instant = sys_le16_to_cpu(pdu->llctrl.chan_map_ind.instant); + memcpy(ctx->data.chmu.chm, pdu->llctrl.chan_map_ind.chm, sizeof(ctx->data.chmu.chm)); +} + +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +/* + * Data Length Update Procedure Helpers + */ +void llcp_pdu_encode_length_req(struct ll_conn *conn, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_length_req *p = &pdu->llctrl.length_req; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, length_req) + + sizeof(struct pdu_data_llctrl_length_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_LENGTH_REQ; + p->max_rx_octets = sys_cpu_to_le16(conn->lll.dle.local.max_rx_octets); + p->max_tx_octets = sys_cpu_to_le16(conn->lll.dle.local.max_tx_octets); + p->max_rx_time = sys_cpu_to_le16(conn->lll.dle.local.max_rx_time); + p->max_tx_time = sys_cpu_to_le16(conn->lll.dle.local.max_tx_time); +} + +void llcp_pdu_encode_length_rsp(struct ll_conn *conn, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_length_rsp *p = &pdu->llctrl.length_rsp; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, length_rsp) + + sizeof(struct pdu_data_llctrl_length_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_LENGTH_RSP; + p->max_rx_octets = sys_cpu_to_le16(conn->lll.dle.local.max_rx_octets); + p->max_tx_octets = sys_cpu_to_le16(conn->lll.dle.local.max_tx_octets); + p->max_rx_time = sys_cpu_to_le16(conn->lll.dle.local.max_rx_time); + p->max_tx_time = sys_cpu_to_le16(conn->lll.dle.local.max_tx_time); +} + +void llcp_ntf_encode_length_change(struct ll_conn *conn, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_length_rsp *p = &pdu->llctrl.length_rsp; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, length_rsp) + + sizeof(struct pdu_data_llctrl_length_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_LENGTH_RSP; + p->max_rx_octets = sys_cpu_to_le16(conn->lll.dle.eff.max_rx_octets); + p->max_tx_octets = sys_cpu_to_le16(conn->lll.dle.eff.max_tx_octets); + p->max_rx_time = sys_cpu_to_le16(conn->lll.dle.eff.max_rx_time); + p->max_tx_time = sys_cpu_to_le16(conn->lll.dle.eff.max_tx_time); +} + +void llcp_pdu_decode_length_req(struct ll_conn *conn, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_length_req *p = &pdu->llctrl.length_req; + + conn->lll.dle.remote.max_rx_octets = sys_le16_to_cpu(p->max_rx_octets); + conn->lll.dle.remote.max_tx_octets = sys_le16_to_cpu(p->max_tx_octets); + conn->lll.dle.remote.max_rx_time = sys_le16_to_cpu(p->max_rx_time); + conn->lll.dle.remote.max_tx_time = sys_le16_to_cpu(p->max_tx_time); +} + +void llcp_pdu_decode_length_rsp(struct ll_conn *conn, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_length_rsp *p = &pdu->llctrl.length_rsp; + + conn->lll.dle.remote.max_rx_octets = sys_le16_to_cpu(p->max_rx_octets); + conn->lll.dle.remote.max_tx_octets = sys_le16_to_cpu(p->max_tx_octets); + conn->lll.dle.remote.max_rx_time = sys_le16_to_cpu(p->max_rx_time); + conn->lll.dle.remote.max_tx_time = sys_le16_to_cpu(p->max_tx_time); +} +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ + +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) +/* + * Constant Tone Request Procedure Helper + */ +void llcp_pdu_encode_cte_req(struct proc_ctx *ctx, struct pdu_data *pdu) +{ + struct pdu_data_llctrl_cte_req *p; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, cte_req) + sizeof(struct pdu_data_llctrl_cte_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CTE_REQ; + + p = &pdu->llctrl.cte_req; + p->min_cte_len_req = ctx->data.cte_req.min_len; + p->rfu = 0; /* Reserved for future use */ + p->cte_type_req = ctx->data.cte_req.type; +} + +void llcp_ntf_encode_cte_req(struct ll_conn *conn, struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, cte_rsp) + sizeof(struct pdu_data_llctrl_cte_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CTE_RSP; + + /* TODO add handling of IQ samples forwarding */ +} + +void llcp_pdu_decode_cte_req(struct ll_conn *conn, struct pdu_data *pdu) +{ + conn->llcp.cte_req.min_cte_len = pdu->llctrl.cte_req.min_cte_len_req; + conn->llcp.cte_req.cte_type = pdu->llctrl.cte_req.cte_type_req; +} + +void llcp_pdu_encode_cte_rsp(struct pdu_data *pdu) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, cte_rsp) + sizeof(struct pdu_data_llctrl_cte_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CTE_RSP; +} +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_phy.c b/subsys/bluetooth/controller/ll_sw/ull_llcp_phy.c new file mode 100644 index 00000000000..895222741d9 --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_phy.c @@ -0,0 +1,1124 @@ +/* + * 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 "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 "ull_tx_queue.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" + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_ctlr_ull_llcp_phy +#include "common/log.h" +#include +#include "hal/debug.h" + +/* LLCP Local Procedure PHY Update FSM states */ +enum { + LP_PU_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_INSTANT, + LP_PU_STATE_WAIT_NTF, +}; + +/* 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, + + /* 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, + 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_INSTANT, + RP_PU_STATE_WAIT_NTF, +}; + +/* 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, +}; + +/* 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); +} + +static uint16_t pu_event_counter(struct ll_conn *conn) +{ + struct lll_conn *lll; + uint16_t event_counter; + + /* TODO(thoh): Lazy hardcoded */ + uint16_t lazy = 0; + + /**/ + lll = &conn->lll; + + /* Calculate current event counter */ + event_counter = lll->event_counter + lll->latency_prepare + lazy; + + return event_counter; +} + +#if defined(CONFIG_BT_PERIPHERAL) +static uint8_t pu_check_update_ind(struct ll_conn *conn, struct proc_ctx *ctx) +{ + uint8_t ret = 0; + + /* 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, pu_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; + + if (0) { +#if defined(CONFIG_BT_PERIPHERAL) + } else if (lll->role == BT_HCI_ROLE_PERIPHERAL) { + if (ctx->data.pu.p_to_c_phy) { + lll->phy_tx = ctx->data.pu.p_to_c_phy; + } + if (ctx->data.pu.c_to_p_phy) { + lll->phy_rx = ctx->data.pu.c_to_p_phy; + } +#endif /* CONFIG_BT_PERIPHERAL */ +#if defined(CONFIG_BT_CENTRAL) + } else if (lll->role == BT_HCI_ROLE_CENTRAL) { + if (ctx->data.pu.p_to_c_phy) { + lll->phy_rx = ctx->data.pu.p_to_c_phy; + } + if (ctx->data.pu.c_to_p_phy) { + lll->phy_tx = ctx->data.pu.c_to_p_phy; + } +#endif /* CONFIG_BT_CENTRAL */ + } + + return (ctx->data.pu.c_to_p_phy || ctx->data.pu.p_to_c_phy); +} + +/* + * TODO: this is the same as calc_eff_time in ull_connections.c + */ +#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; + + 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, + lll->dle.local.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, + lll->dle.local.max_rx_time); + } + + if ((eff_tx_time != lll->dle.eff.max_tx_time) || + (eff_rx_time != lll->dle.eff.max_rx_time)) { + lll->dle.eff.max_tx_time = eff_tx_time; + lll->dle.eff.max_rx_time = eff_rx_time; + return 1; + } + + return 0; +} +#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; + } +} + +/* + * LLCP Local Procedure PHY Update FSM + */ + +static void lp_pu_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode) +{ + struct node_tx *tx; + struct pdu_data *pdu; + + /* Allocate tx node */ + tx = llcp_tx_alloc(conn, ctx); + LL_ASSERT(tx); + + pdu = (struct pdu_data *)tx->pdu; + + /* Encode LL Control PDU */ + switch (opcode) { + case PDU_DATA_LLCTRL_TYPE_PHY_REQ: + pu_set_preferred_phys(conn, ctx); + llcp_pdu_encode_phy_req(ctx, pdu); + break; +#if defined(CONFIG_BT_CENTRAL) + case PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND: + pu_prep_update_ind(conn, ctx); + llcp_pdu_encode_phy_update_ind(ctx, pdu); + break; +#endif /* CONFIG_BT_CENTRAL */ + default: + LL_ASSERT(0); + } + + /* Always 'request' the ACK signal */ + ctx->tx_ack = tx; + ctx->tx_opcode = pdu->llctrl.opcode; + + /* Enqueue LL Control PDU towards LLL */ + llcp_tx_enqueue(conn, tx); + + /* Update procedure timeout */ + ull_conn_prt_reload(conn, conn->procedure_reload); +} + +static void pu_ntf(struct ll_conn *conn, struct proc_ctx *ctx) +{ + struct node_rx_pdu *ntf; + struct node_rx_pu *pdu; + + /* Allocate ntf node */ + ntf = llcp_ntf_alloc(); + LL_ASSERT(ntf); + + 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; + + /* Enqueue notification towards LL */ + ll_rx_put(ntf->hdr.link, ntf); + ll_rx_sched(); +} + +#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; + + /* Allocate ntf node */ + ntf = llcp_ntf_alloc(); + 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(ntf->hdr.link, ntf); + ll_rx_sched(); +} +#endif + +static void lp_pu_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +#define NTF_DLE (ctx->data.pu.ntf_dle) +#else +#define NTF_DLE 0 +#endif + const uint8_t ntf_count = ctx->data.pu.ntf_pu + NTF_DLE; + /* when complete reset timing restrictions - idempotent + * (so no problem if we need to wait for NTF buffer) + */ + pu_reset_timing_restrict(conn); + + if (ntf_count && !llcp_ntf_alloc_num_available(ntf_count)) { + ctx->state = LP_PU_STATE_WAIT_NTF; + } else { + if (ctx->data.pu.ntf_pu) { + pu_ntf(conn, ctx); + } +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + if (ctx->data.pu.ntf_dle) { + pu_dle_ntf(conn, ctx); + } +#endif + llcp_lr_complete(conn); + ctx->state = LP_PU_STATE_IDLE; + } +} + +static void lp_pu_send_phy_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + if (llcp_rr_get_collision(conn) || !llcp_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_PU_STATE_WAIT_TX_PHY_REQ; + } else { + llcp_rr_set_incompat(conn, INCOMPAT_RESOLVABLE); + lp_pu_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_PHY_REQ); + llcp_tx_pause_data(conn); + ctx->state = LP_PU_STATE_WAIT_TX_ACK_PHY_REQ; + } +} + +#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_tx_alloc_peek(conn, ctx)) { + ctx->state = LP_PU_STATE_WAIT_TX_PHY_UPDATE_IND; + } else { + ctx->data.pu.instant = pu_event_counter(conn) + PHY_UPDATE_INSTANT_DELTA; + lp_pu_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; + ctx->state = LP_PU_STATE_WAIT_TX_ACK_PHY_UPDATE_IND; + } +} +#endif /* CONFIG_BT_CENTRAL */ + +static void lp_pu_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + /* TODO */ + 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: + /* TODO: should we swap the function call with variable declaration? */ + 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); + /* Combine with the 'Preferred' phys */ + pu_combine_phys(conn, ctx, tx_pref, rx_pref); + 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); + ctx->data.pu.error = BT_HCI_ERR_UNSUPP_REMOTE_FEATURE; + ctx->data.pu.ntf_pu = 1; + lp_pu_complete(conn, ctx, evt, param); + 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; + break; +#endif /* CONFIG_BT_PERIPHERAL */ + default: + /* Unknown role */ + LL_ASSERT(0); + } + llcp_tx_resume_data(conn); + 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 we clear procedure response timeout */ + ull_conn_prt_clear(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); + 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_pdu_decode_phy_update_ind(ctx, (struct pdu_data *)param); + const uint8_t end_procedure = pu_check_update_ind(conn, 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 we clear procedure response timeout */ + ull_conn_prt_clear(conn); + + ctx->state = LP_PU_STATE_WAIT_INSTANT; + } else { + llcp_rr_set_incompat(conn, INCOMPAT_NO_COLLISION); + ctx->data.pu.ntf_pu = ctx->data.pu.host_initiated; + lp_pu_complete(conn, ctx, evt, param); + } + break; + case LP_PU_EVT_REJECT: + llcp_rr_set_incompat(conn, INCOMPAT_NO_COLLISION); + ctx->data.pu.error = BT_HCI_ERR_LL_PROC_COLLISION; + ctx->data.pu.ntf_pu = 1; + lp_pu_complete(conn, ctx, evt, param); + 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, pu_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) +{ + /* TODO */ + 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_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case LP_PU_EVT_RUN: + lp_pu_complete(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +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_NTF: + lp_pu_st_wait_ntf(conn, ctx, evt, param); + break; + 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: + /* Unknown opcode */ + LL_ASSERT(0); + } +} + +void llcp_lp_pu_init_proc(struct proc_ctx *ctx) +{ + ctx->state = LP_PU_STATE_IDLE; +} + +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); +} + +/* + * LLCP Remote Procedure PHY Update FSM + */ +static void rp_pu_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode) +{ + struct node_tx *tx; + struct pdu_data *pdu; + + /* Allocate tx node */ + tx = llcp_tx_alloc(conn, ctx); + LL_ASSERT(tx); + + pdu = (struct pdu_data *)tx->pdu; + + /* Encode LL Control PDU */ + switch (opcode) { +#if defined(CONFIG_BT_PERIPHERAL) + case PDU_DATA_LLCTRL_TYPE_PHY_RSP: + llcp_pdu_encode_phy_rsp(conn, pdu); + break; +#endif /* CONFIG_BT_PERIPHERAL */ +#if defined(CONFIG_BT_CENTRAL) + case PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND: + pu_prep_update_ind(conn, ctx); + llcp_pdu_encode_phy_update_ind(ctx, pdu); + break; +#endif /* CONFIG_BT_CENTRAL */ + default: + LL_ASSERT(0); + } + + ctx->tx_ack = tx; + ctx->tx_opcode = pdu->llctrl.opcode; + + /* Enqueue LL Control PDU towards LLL */ + llcp_tx_enqueue(conn, tx); +} + +static void rp_pu_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) +#define NTF_DLE (ctx->data.pu.ntf_dle) +#else +#define NTF_DLE 0 +#endif + const uint8_t ntf_count = ctx->data.pu.ntf_pu + NTF_DLE; + /* when complete reset timing restrictions - idempotent + * (so no problem if we need to wait for NTF buffer) + */ + pu_reset_timing_restrict(conn); + + if ((ntf_count > 0) && !llcp_ntf_alloc_num_available(ntf_count)) { + ctx->state = RP_PU_STATE_WAIT_NTF; + } else { + if (ctx->data.pu.ntf_pu) { + pu_ntf(conn, ctx); + } +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + if (ctx->data.pu.ntf_dle) { + pu_dle_ntf(conn, ctx); + } +#endif + llcp_rr_complete(conn); + ctx->state = RP_PU_STATE_IDLE; + } +} + +#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_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_PU_STATE_WAIT_TX_PHY_UPDATE_IND; + } else { + ctx->data.pu.instant = pu_event_counter(conn) + PHY_UPDATE_INSTANT_DELTA; + rp_pu_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED; + ctx->state = RP_PU_STATE_WAIT_TX_ACK_PHY_UPDATE_IND; + } +} +#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_tx_alloc_peek(conn, ctx)) { + ctx->state = RP_PU_STATE_WAIT_TX_PHY_RSP; + } else { + rp_pu_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_PHY_RSP); + ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND; + ctx->state = RP_PU_STATE_WAIT_TX_ACK_PHY_RSP; + } +} +#endif /* CONFIG_BT_CENTRAL */ + +static void rp_pu_st_idle(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + /* TODO */ + 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); + switch (evt) { + case RP_PU_EVT_PHY_REQ: + switch (conn->lll.role) { +#if defined(CONFIG_BT_CENTRAL) + case BT_HCI_ROLE_CENTRAL: + 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); + 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); + + if (!end_procedure) { + /* Since at least one phy will change we clear procedure response timeout */ + ull_conn_prt_clear(conn); + + ctx->state = LP_PU_STATE_WAIT_INSTANT; + } else { + 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, pu_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) +{ + /* TODO */ + 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_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param) +{ + switch (evt) { + case RP_PU_EVT_RUN: + rp_pu_complete(conn, ctx, evt, param); + break; + default: + /* Ignore other evts */ + break; + } +} + +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_NTF: + rp_pu_st_wait_ntf(conn, ctx, evt, param); + break; + 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: + /* Unknown opcode */ + LL_ASSERT(0); + } +} + +void llcp_rp_pu_init_proc(struct proc_ctx *ctx) +{ + ctx->state = RP_PU_STATE_IDLE; +} + +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); +} diff --git a/subsys/bluetooth/controller/ll_sw/ull_llcp_remote.c b/subsys/bluetooth/controller/ll_sw/ull_llcp_remote.c new file mode 100644 index 00000000000..c156b51ec4b --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_llcp_remote.c @@ -0,0 +1,730 @@ +/* + * 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 "pdu.h" +#include "ll.h" +#include "ll_settings.h" + +#include "lll.h" +#include "lll/lll_df_types.h" +#include "lll_conn.h" + +#include "ull_tx_queue.h" + +#include "ull_conn_types.h" +#include "ull_conn_internal.h" +#include "ull_llcp.h" +#include "ull_llcp_internal.h" + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_ctlr_ull_llcp_remote +#include "common/log.h" +#include +#include "hal/debug.h" + +static void rr_check_done(struct ll_conn *conn, struct proc_ctx *ctx); +static struct proc_ctx *rr_dequeue(struct ll_conn *conn); +static void rr_abort(struct ll_conn *conn); + +/* LLCP Remote Request FSM State */ +enum rr_state { + RR_STATE_IDLE, + RR_STATE_REJECT, + RR_STATE_ACTIVE, + RR_STATE_DISCONNECT, + RR_STATE_TERMINATE, +}; + +/* LLCP Remote Request FSM Event */ +enum { + /* Procedure prepare */ + RR_EVT_PREPARE, + + /* Procedure run */ + RR_EVT_RUN, + + /* Procedure completed */ + RR_EVT_COMPLETE, + + /* Link connected */ + RR_EVT_CONNECT, + + /* Link disconnected */ + RR_EVT_DISCONNECT, +}; + +static bool proc_with_instant(struct proc_ctx *ctx) +{ + /* + * TODO: should we combine all the cases that return 0 + * and all the cases that return 1? + */ + switch (ctx->proc) { + case PROC_FEATURE_EXCHANGE: + return 0U; + case PROC_MIN_USED_CHANS: + return 0U; + case PROC_LE_PING: + return 0U; + case PROC_VERSION_EXCHANGE: + return 0U; + case PROC_ENCRYPTION_START: + case PROC_ENCRYPTION_PAUSE: + return 0U; + case PROC_PHY_UPDATE: + return 1U; + case PROC_CONN_UPDATE: + case PROC_CONN_PARAM_REQ: + return 1U; + case PROC_TERMINATE: + return 0U; + case PROC_CHAN_MAP_UPDATE: + return 1U; + case PROC_DATA_LENGTH_UPDATE: + return 0U; + case PROC_CTE_REQ: + return 0U; + default: + /* Unknown procedure */ + LL_ASSERT(0); + break; + } + + return 0U; +} + +static void rr_check_done(struct ll_conn *conn, struct proc_ctx *ctx) +{ + if (ctx->done) { + struct proc_ctx *ctx_header; + + ctx_header = llcp_rr_peek(conn); + LL_ASSERT(ctx_header == ctx); + + rr_dequeue(conn); + llcp_proc_ctx_release(ctx); + } +} +/* + * LLCP Remote Request FSM + */ + +static void rr_set_state(struct ll_conn *conn, enum rr_state state) +{ + conn->llcp.remote.state = state; +} + +void llcp_rr_set_incompat(struct ll_conn *conn, enum proc_incompat incompat) +{ + conn->llcp.remote.incompat = incompat; +} + +static enum proc_incompat rr_get_incompat(struct ll_conn *conn) +{ + return conn->llcp.remote.incompat; +} + +static void rr_set_collision(struct ll_conn *conn, bool collision) +{ + conn->llcp.remote.collision = collision; +} + +bool llcp_rr_get_collision(struct ll_conn *conn) +{ + return conn->llcp.remote.collision; +} + +static void rr_enqueue(struct ll_conn *conn, struct proc_ctx *ctx) +{ + sys_slist_append(&conn->llcp.remote.pend_proc_list, &ctx->node); +} + +static struct proc_ctx *rr_dequeue(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = (struct proc_ctx *)sys_slist_get(&conn->llcp.remote.pend_proc_list); + return ctx; +} + +struct proc_ctx *llcp_rr_peek(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = (struct proc_ctx *)sys_slist_peek_head(&conn->llcp.remote.pend_proc_list); + return ctx; +} + +void llcp_rr_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx) +{ + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PROC_LE_PING: + llcp_rp_comm_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PROC_FEATURE_EXCHANGE: + llcp_rp_comm_rx(conn, ctx, rx); + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) + case PROC_MIN_USED_CHANS: + llcp_rp_comm_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + case PROC_VERSION_EXCHANGE: + llcp_rp_comm_rx(conn, ctx, rx); + break; +#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_PERIPHERAL) + case PROC_ENCRYPTION_START: + case PROC_ENCRYPTION_PAUSE: + llcp_rp_enc_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_PERIPHERAL */ +#ifdef CONFIG_BT_CTLR_PHY + case PROC_PHY_UPDATE: + llcp_rp_pu_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_PHY */ + case PROC_CONN_UPDATE: + case PROC_CONN_PARAM_REQ: + llcp_rp_cu_rx(conn, ctx, rx); + break; + case PROC_TERMINATE: + llcp_rp_comm_rx(conn, ctx, rx); + break; +#if defined(CONFIG_BT_PERIPHERAL) + case PROC_CHAN_MAP_UPDATE: + llcp_rp_chmu_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_PERIPHERAL */ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + llcp_rp_comm_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: + llcp_rp_comm_rx(conn, ctx, rx); + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + break; + } + rr_check_done(conn, ctx); +} + +void llcp_rr_tx_ack(struct ll_conn *conn, struct proc_ctx *ctx, struct node_tx *tx) +{ + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + llcp_rp_comm_tx_ack(conn, ctx, tx); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#ifdef CONFIG_BT_CTLR_PHY + case PROC_PHY_UPDATE: + llcp_rp_pu_tx_ack(conn, ctx, tx); + break; +#endif /* CONFIG_BT_CTLR_PHY */ + default: + /* Ignore tx_ack */ + break; + } + + rr_check_done(conn, ctx); +} + +static void rr_act_run(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = llcp_rr_peek(conn); + + switch (ctx->proc) { +#if defined(CONFIG_BT_CTLR_LE_PING) + case PROC_LE_PING: + llcp_rp_comm_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_LE_PING */ + case PROC_FEATURE_EXCHANGE: + llcp_rp_comm_run(conn, ctx, NULL); + break; +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) + case PROC_MIN_USED_CHANS: + llcp_rp_comm_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ + case PROC_VERSION_EXCHANGE: + llcp_rp_comm_run(conn, ctx, NULL); + break; +#if defined(CONFIG_BT_CTLR_LE_ENC) && defined(CONFIG_BT_PERIPHERAL) + case PROC_ENCRYPTION_START: + case PROC_ENCRYPTION_PAUSE: + llcp_rp_enc_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_LE_ENC && CONFIG_BT_PERIPHERAL */ +#ifdef CONFIG_BT_CTLR_PHY + case PROC_PHY_UPDATE: + llcp_rp_pu_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_PHY */ + case PROC_CONN_UPDATE: + case PROC_CONN_PARAM_REQ: + llcp_rp_cu_run(conn, ctx, NULL); + break; + case PROC_TERMINATE: + llcp_rp_comm_run(conn, ctx, NULL); + break; +#if defined(CONFIG_BT_PERIPHERAL) + case PROC_CHAN_MAP_UPDATE: + llcp_rp_chmu_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_PERIPHERAL */ +#if defined(CONFIG_BT_CTLR_DATA_LENGTH) + case PROC_DATA_LENGTH_UPDATE: + llcp_rp_comm_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_DATA_LENGTH */ +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) + case PROC_CTE_REQ: + llcp_rp_comm_run(conn, ctx, NULL); + break; +#endif /* CONFIG_BT_CTLR_DF_CONN_CTE_REQ */ + default: + /* Unknown procedure */ + LL_ASSERT(0); + break; + } + + rr_check_done(conn, ctx); +} + +static void rr_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode) +{ + struct node_tx *tx; + struct pdu_data *pdu; + + /* Allocate tx node */ + tx = llcp_tx_alloc(conn, ctx); + LL_ASSERT(tx); + + pdu = (struct pdu_data *)tx->pdu; + + /* Encode LL Control PDU */ + switch (opcode) { + case PDU_DATA_LLCTRL_TYPE_REJECT_IND: + /* TODO(thoh): Select between LL_REJECT_IND and LL_REJECT_EXT_IND */ + llcp_pdu_encode_reject_ext_ind(pdu, conn->llcp.remote.reject_opcode, + BT_HCI_ERR_LL_PROC_COLLISION); + break; + default: + LL_ASSERT(0); + } + + ctx->tx_opcode = pdu->llctrl.opcode; + + /* Enqueue LL Control PDU towards LLL */ + llcp_tx_enqueue(conn, tx); +} + +static void rr_act_reject(struct ll_conn *conn) +{ + struct proc_ctx *ctx = llcp_rr_peek(conn); + + LL_ASSERT(ctx != NULL); + + if (!llcp_tx_alloc_peek(conn, ctx)) { + rr_set_state(conn, RR_STATE_REJECT); + } else { + rr_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_REJECT_IND); + + ctx->done = 1U; + rr_set_state(conn, RR_STATE_IDLE); + } +} + +static void rr_act_complete(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + rr_set_collision(conn, 0U); + + /* Dequeue pending request that just completed */ + ctx = llcp_rr_peek(conn); + LL_ASSERT(ctx != NULL); + + ctx->done = 1U; +} + +static void rr_act_connect(struct ll_conn *conn) +{ + /* TODO */ +} + +static void rr_act_disconnect(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + ctx = rr_dequeue(conn); + + /* + * we may have been disconnected in the + * middle of a control procedure, in which + * case we need to release all contexts + */ + while (ctx != NULL) { + llcp_proc_ctx_release(ctx); + ctx = rr_dequeue(conn); + } +} + +static void rr_st_disconnect(struct ll_conn *conn, uint8_t evt, void *param) +{ + switch (evt) { + case RR_EVT_CONNECT: + rr_act_connect(conn); + rr_set_state(conn, RR_STATE_IDLE); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rr_st_idle(struct ll_conn *conn, uint8_t evt, void *param) +{ + struct proc_ctx *ctx; + + switch (evt) { + case RR_EVT_PREPARE: + ctx = llcp_rr_peek(conn); + if (ctx) { + const enum proc_incompat incompat = rr_get_incompat(conn); + const bool periph = !!(conn->lll.role == BT_HCI_ROLE_PERIPHERAL); + const bool central = !!(conn->lll.role == BT_HCI_ROLE_CENTRAL); + const bool with_instant = proc_with_instant(ctx); + + if (ctx->proc == PROC_TERMINATE) { + /* Peer terminate overrides all */ + + /* Run remote procedure */ + rr_act_run(conn); + rr_set_state(conn, RR_STATE_TERMINATE); + } else if (incompat == INCOMPAT_NO_COLLISION) { + /* No collision + * => Run procedure + * + * Local incompatible procedure request is kept pending. + */ + + /* Pause local incompatible procedure */ + rr_set_collision(conn, with_instant); + + /* Run remote procedure */ + rr_act_run(conn); + rr_set_state(conn, RR_STATE_ACTIVE); + } else if (periph && incompat == INCOMPAT_RESOLVABLE) { + /* Slave collision + * => Run procedure + * + * Local periph procedure completes with error. + */ + + /* Run remote procedure */ + rr_act_run(conn); + rr_set_state(conn, RR_STATE_ACTIVE); + } else if (with_instant && central && incompat == INCOMPAT_RESOLVABLE) { + /* Master collision + * => Send reject + * + * Local central incompatible procedure continues unaffected. + */ + + /* Send reject */ + struct node_rx_pdu *rx = (struct node_rx_pdu *)param; + struct pdu_data *pdu = (struct pdu_data *)rx->pdu; + + conn->llcp.remote.reject_opcode = pdu->llctrl.opcode; + rr_act_reject(conn); + } else if (with_instant && incompat == INCOMPAT_RESERVED) { + /* Protocol violation. + * => Disconnect + * + */ + + /* TODO */ + LL_ASSERT(0); + } + } + break; + case RR_EVT_DISCONNECT: + rr_act_disconnect(conn); + rr_set_state(conn, RR_STATE_DISCONNECT); + break; + default: + /* Ignore other evts */ + break; + } +} +static void rr_st_reject(struct ll_conn *conn, uint8_t evt, void *param) +{ + /* TODO */ + LL_ASSERT(0); +} + +static void rr_st_active(struct ll_conn *conn, uint8_t evt, void *param) +{ + switch (evt) { + case RR_EVT_RUN: + if (llcp_rr_peek(conn)) { + rr_act_run(conn); + } + break; + case RR_EVT_COMPLETE: + rr_act_complete(conn); + rr_set_state(conn, RR_STATE_IDLE); + break; + case RR_EVT_DISCONNECT: + rr_act_disconnect(conn); + rr_set_state(conn, RR_STATE_DISCONNECT); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rr_st_terminate(struct ll_conn *conn, uint8_t evt, void *param) +{ + switch (evt) { + case RR_EVT_RUN: + if (llcp_rr_peek(conn)) { + rr_act_run(conn); + } + break; + case RR_EVT_COMPLETE: + rr_act_complete(conn); + rr_set_state(conn, RR_STATE_IDLE); + break; + case RR_EVT_DISCONNECT: + rr_act_disconnect(conn); + rr_set_state(conn, RR_STATE_DISCONNECT); + break; + default: + /* Ignore other evts */ + break; + } +} + +static void rr_execute_fsm(struct ll_conn *conn, uint8_t evt, void *param) +{ + switch (conn->llcp.remote.state) { + case RR_STATE_DISCONNECT: + rr_st_disconnect(conn, evt, param); + break; + case RR_STATE_IDLE: + rr_st_idle(conn, evt, param); + break; + case RR_STATE_REJECT: + rr_st_reject(conn, evt, param); + break; + case RR_STATE_ACTIVE: + rr_st_active(conn, evt, param); + break; + case RR_STATE_TERMINATE: + rr_st_terminate(conn, evt, param); + break; + default: + /* Unknown state */ + LL_ASSERT(0); + } +} + +void llcp_rr_init(struct ll_conn *conn) +{ + rr_set_state(conn, RR_STATE_DISCONNECT); +} + +void llcp_rr_prepare(struct ll_conn *conn, struct node_rx_pdu *rx) +{ + rr_execute_fsm(conn, RR_EVT_PREPARE, rx); +} + +void llcp_rr_run(struct ll_conn *conn) +{ + rr_execute_fsm(conn, RR_EVT_RUN, NULL); +} + +void llcp_rr_complete(struct ll_conn *conn) +{ + rr_execute_fsm(conn, RR_EVT_COMPLETE, NULL); +} + +void llcp_rr_connect(struct ll_conn *conn) +{ + rr_execute_fsm(conn, RR_EVT_CONNECT, NULL); +} + +void llcp_rr_disconnect(struct ll_conn *conn) +{ + rr_execute_fsm(conn, RR_EVT_DISCONNECT, NULL); +} + +void llcp_rr_new(struct ll_conn *conn, struct node_rx_pdu *rx) +{ + struct proc_ctx *ctx; + struct pdu_data *pdu; + uint8_t proc = PROC_UNKNOWN; + + pdu = (struct pdu_data *)rx->pdu; + + switch (pdu->llctrl.opcode) { + case PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND: + proc = PROC_CONN_UPDATE; + break; + case PDU_DATA_LLCTRL_TYPE_FEATURE_REQ: + case PDU_DATA_LLCTRL_TYPE_PER_INIT_FEAT_XCHG: + proc = PROC_FEATURE_EXCHANGE; + break; + case PDU_DATA_LLCTRL_TYPE_PING_REQ: + proc = PROC_LE_PING; + break; + case PDU_DATA_LLCTRL_TYPE_VERSION_IND: + proc = PROC_VERSION_EXCHANGE; + break; + case PDU_DATA_LLCTRL_TYPE_ENC_REQ: + proc = PROC_ENCRYPTION_START; + break; + case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ: + proc = PROC_ENCRYPTION_PAUSE; + break; + case PDU_DATA_LLCTRL_TYPE_PHY_REQ: + proc = PROC_PHY_UPDATE; + break; + case PDU_DATA_LLCTRL_TYPE_MIN_USED_CHAN_IND: + proc = PROC_MIN_USED_CHANS; + break; + case PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ: + proc = PROC_CONN_PARAM_REQ; + break; + case PDU_DATA_LLCTRL_TYPE_TERMINATE_IND: + proc = PROC_TERMINATE; + break; + case PDU_DATA_LLCTRL_TYPE_CHAN_MAP_IND: + proc = PROC_CHAN_MAP_UPDATE; + break; + case PDU_DATA_LLCTRL_TYPE_LENGTH_REQ: + proc = PROC_DATA_LENGTH_UPDATE; + break; + case PDU_DATA_LLCTRL_TYPE_CTE_REQ: + proc = PROC_CTE_REQ; + break; + default: + /* Unknown opcode */ + LL_ASSERT(0); + break; + } + + if (proc == PROC_TERMINATE) { + rr_abort(conn); + } + + ctx = llcp_create_remote_procedure(proc); + if (!ctx) { + return; + } + + /* Enqueue procedure */ + rr_enqueue(conn, ctx); + + /* Prepare procedure */ + llcp_rr_prepare(conn, rx); + + /* Handle PDU */ + ctx = llcp_rr_peek(conn); + if (ctx) { + llcp_rr_rx(conn, ctx, rx); + } +} + +static void rr_abort(struct ll_conn *conn) +{ + struct proc_ctx *ctx; + + /* Flush all pending procedures */ + ctx = rr_dequeue(conn); + while (ctx) { + llcp_proc_ctx_release(ctx); + ctx = rr_dequeue(conn); + } + + /* TODO(thoh): Whats missing here ??? */ + rr_set_collision(conn, 0U); + rr_set_state(conn, RR_STATE_IDLE); +} + +#ifdef ZTEST_UNITTEST + +bool rr_is_disconnected(struct ll_conn *conn) +{ + return conn->llcp.remote.state == RR_STATE_DISCONNECT; +} + +bool rr_is_idle(struct ll_conn *conn) +{ + return conn->llcp.remote.state == RR_STATE_IDLE; +} + +void test_int_remote_pending_requests(void) +{ + struct ll_conn conn; + struct proc_ctx *peek_ctx; + struct proc_ctx *dequeue_ctx; + struct proc_ctx ctx; + + ull_cp_init(); + ull_tx_q_init(&conn.tx_q); + ull_llcp_init(&conn); + + peek_ctx = llcp_rr_peek(&conn); + zassert_is_null(peek_ctx, NULL); + + dequeue_ctx = rr_dequeue(&conn); + zassert_is_null(dequeue_ctx, NULL); + + rr_enqueue(&conn, &ctx); + peek_ctx = (struct proc_ctx *)sys_slist_peek_head(&conn.llcp.remote.pend_proc_list); + zassert_equal_ptr(peek_ctx, &ctx, NULL); + + peek_ctx = llcp_rr_peek(&conn); + zassert_equal_ptr(peek_ctx, &ctx, NULL); + + dequeue_ctx = rr_dequeue(&conn); + zassert_equal_ptr(dequeue_ctx, &ctx, NULL); + + peek_ctx = llcp_rr_peek(&conn); + zassert_is_null(peek_ctx, NULL); + + dequeue_ctx = rr_dequeue(&conn); + zassert_is_null(dequeue_ctx, NULL); +} + +#endif diff --git a/subsys/bluetooth/controller/ll_sw/ull_peripheral.c b/subsys/bluetooth/controller/ll_sw/ull_peripheral.c index cf87a9b4ca3..2a4d37a1a80 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_peripheral.c +++ b/subsys/bluetooth/controller/ll_sw/ull_peripheral.c @@ -30,10 +30,14 @@ #include "lll_adv.h" #include "lll/lll_adv_pdu.h" #include "lll_chan.h" +#include "lll/lll_df_types.h" #include "lll_conn.h" #include "lll_peripheral.h" #include "lll_filter.h" -#include "lll/lll_df_types.h" + +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#include "ull_tx_queue.h" +#endif #include "ull_adv_types.h" #include "ull_conn_types.h" @@ -46,6 +50,10 @@ #include "ll.h" +#if (!defined(CONFIG_BT_LL_SW_LLCP_LEGACY)) +#include "ll_sw/ull_llcp.h" +#endif + #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) #define LOG_MODULE_NAME bt_ctlr_ull_periph #include "common/log.h" @@ -172,6 +180,11 @@ void ull_periph_setup(struct node_rx_hdr *rx, struct node_rx_ftr *ftr, win_delay_us = WIN_DELAY_LEGACY; } +#if (!defined(CONFIG_BT_LL_SW_LLCP_LEGACY)) + /* Set LLCP as connection-wise connected */ + ull_cp_state_set(conn, ULL_CP_CONNECTED); +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + /* calculate the window widening */ conn->periph.sca = pdu_adv->connect_ind.sca; lll->periph.window_widening_periodic_us = @@ -313,8 +326,13 @@ void ull_periph_setup(struct node_rx_hdr *rx, struct node_rx_ftr *ftr, #if defined(CONFIG_BT_CTLR_DATA_LENGTH) #if defined(CONFIG_BT_CTLR_PHY) +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) max_tx_time = lll->max_tx_time; max_rx_time = lll->max_rx_time; +#else + max_tx_time = lll->dle.local.max_tx_time; + max_rx_time = lll->dle.local.max_rx_time; +#endif #else /* !CONFIG_BT_CTLR_PHY */ max_tx_time = PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, PHY_1M); max_rx_time = PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, PHY_1M); @@ -544,6 +562,7 @@ uint8_t ll_start_enc_req_send(uint16_t handle, uint8_t error_code, return BT_HCI_ERR_UNKNOWN_CONN_ID; } +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) if (error_code) { if (conn->llcp_enc.refresh == 0U) { if ((conn->llcp_req == conn->llcp_ack) || @@ -575,6 +594,25 @@ uint8_t ll_start_enc_req_send(uint16_t handle, uint8_t error_code, conn->llcp.encryption.error_code = 0U; conn->llcp.encryption.state = LLCP_ENC_STATE_INPROG; } +#else /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + /* + * TODO: add info to the conn-structure + * - refresh + * - no procedure in progress + * - procedure type + * and use that info to decide if the cmd is allowed + * or if we should terminate the connection + * see BT 5.2 Vol. 6 part B chapter 5.1.3 + * see also ull_periph.c line 395-439 + * + * TODO: the ull_cp_ltx_req* functions should return success/fail status + */ + if (error_code) { + ull_cp_ltk_req_neq_reply(conn); + } else { + ull_cp_ltk_req_reply(conn, ltk); + } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ return 0; } @@ -639,3 +677,24 @@ static void ticker_update_latency_cancel_op_cb(uint32_t ticker_status, conn->periph.latency_cancel = 0U; } + +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) +uint8_t ll_set_min_used_chans(uint16_t handle, uint8_t const phys, + uint8_t const min_used_chans) +{ + struct ll_conn *conn; + + conn = ll_connected_get(handle); + if (!conn) { + return BT_HCI_ERR_UNKNOWN_CONN_ID; + } + + if (!conn->lll.role) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + return ull_cp_min_used_chans(conn, phys, min_used_chans); +} +#endif /* CONFIG_BT_CTLR_MIN_USED_CHAN */ +#endif /* !CONFIG_BT_LL_SW_LLCP_LEGACY */ diff --git a/subsys/bluetooth/controller/ll_sw/ull_scan.c b/subsys/bluetooth/controller/ll_sw/ull_scan.c index d54eda1c402..ae819be40b8 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_scan.c +++ b/subsys/bluetooth/controller/ll_sw/ull_scan.c @@ -34,11 +34,11 @@ #include "lll_filter.h" #include "ull_adv_types.h" -#include "ull_scan_types.h" #include "ull_filter.h" #include "ull_internal.h" #include "ull_adv_internal.h" +#include "ull_scan_types.h" #include "ull_scan_internal.h" #include "ull_sched_internal.h" diff --git a/subsys/bluetooth/controller/ll_sw/ull_scan_aux.c b/subsys/bluetooth/controller/ll_sw/ull_scan_aux.c index b613671f865..e75a3784f81 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_scan_aux.c +++ b/subsys/bluetooth/controller/ll_sw/ull_scan_aux.c @@ -13,6 +13,7 @@ #include "util/util.h" #include "hal/ticker.h" +#include "hal/ccm.h" #include "ticker/ticker.h" @@ -23,6 +24,7 @@ #include "lll_scan.h" #include "lll_scan_aux.h" #include "lll/lll_df_types.h" +#include "lll_conn.h" #include "lll_sync.h" #include "lll_sync_iso.h" diff --git a/subsys/bluetooth/controller/ll_sw/ull_sched.c b/subsys/bluetooth/controller/ll_sw/ull_sched.c index e77be0db5a0..76300fe55f5 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_sched.c +++ b/subsys/bluetooth/controller/ll_sw/ull_sched.c @@ -26,6 +26,10 @@ #include "lll/lll_df_types.h" #include "lll_conn.h" +#if !defined(CONFIG_BT_LL_SW_LLCP_LEGACY) +#include "ull_tx_queue.h" +#endif + #include "ull_scan_types.h" #include "ull_conn_types.h" @@ -38,10 +42,12 @@ #include "hal/debug.h" #if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) static void win_offset_calc(struct ll_conn *conn_curr, uint8_t is_select, uint32_t *ticks_to_offset_next, uint16_t conn_interval, uint8_t *offset_max, uint8_t *win_offset); +#endif #endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ static void after_mstr_offset_get(uint16_t conn_interval, uint32_t ticks_slot, uint32_t ticks_anchor, @@ -194,7 +200,13 @@ void ull_sched_mfy_win_offset_use(void *param) { struct ll_conn *conn = param; uint32_t ticks_slot_overhead; + + /* + * TODO: update when updating the connection update procedure + */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) uint16_t win_offset; +#endif if (IS_ENABLED(CONFIG_BT_CTLR_LOW_LAT)) { ticks_slot_overhead = MAX(conn->ull.ticks_active_to_start, @@ -203,6 +215,10 @@ void ull_sched_mfy_win_offset_use(void *param) ticks_slot_overhead = 0U; } + /* + * TODO: update when updating the connection update procedure + */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) after_mstr_offset_get(conn->lll.interval, (ticks_slot_overhead + conn->ull.ticks_slot), conn->llcp.conn_upd.ticks_anchor, @@ -214,6 +230,7 @@ void ull_sched_mfy_win_offset_use(void *param) /* move to offset calculated state */ conn->llcp_cu.state = LLCP_CUI_STATE_OFFS_RDY; +#endif } #if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) @@ -221,11 +238,22 @@ void ull_sched_mfy_free_win_offset_calc(void *param) { uint32_t ticks_to_offset_default = 0U; uint32_t *ticks_to_offset_next; - struct ll_conn *conn = param; + + /* + * TODO: update when updating the connection update procedure + */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) uint8_t offset_max = 6U; + struct ll_conn *conn = param; +#endif ticks_to_offset_next = &ticks_to_offset_default; + /* + * TODO: update when updating the connection update procedure + */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) + #if defined(CONFIG_BT_PERIPHERAL) if (conn->lll.role) { conn->llcp_conn_param.ticks_to_offset_next = @@ -242,19 +270,28 @@ void ull_sched_mfy_free_win_offset_calc(void *param) /* move to offset calculated state */ conn->llcp_conn_param.state = LLCP_CPR_STATE_OFFS_RDY; +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ } void ull_sched_mfy_win_offset_select(void *param) { #define OFFSET_S_MAX 6 #define OFFSET_M_MAX 6 + +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) uint16_t win_offset_m[OFFSET_M_MAX] = {0, }; uint8_t offset_m_max = OFFSET_M_MAX; struct ll_conn *conn = param; uint8_t offset_index_s = 0U; uint8_t has_offset_s = 0U; - uint32_t ticks_to_offset; uint16_t win_offset_s; + uint32_t ticks_to_offset; +#endif + + /* + * TODO: update when updating the connection update procedure + */ +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) ticks_to_offset = HAL_TICKER_US_TO_TICKS(conn->llcp_conn_param.offset0 * CONN_INT_UNIT_US); @@ -323,10 +360,15 @@ void ull_sched_mfy_win_offset_select(void *param) /* move to conn param reject */ conn->llcp_cu.state = LLCP_CUI_STATE_REJECT; } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + + #undef OFFSET_S_MAX #undef OFFSET_M_MAX } + +#if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) static void win_offset_calc(struct ll_conn *conn_curr, uint8_t is_select, uint32_t *ticks_to_offset_next, uint16_t conn_interval, uint8_t *offset_max, @@ -552,6 +594,8 @@ static void win_offset_calc(struct ll_conn *conn_curr, uint8_t is_select, *offset_max = offset_index; } +#endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ + #endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ static void after_mstr_offset_get(uint16_t conn_interval, uint32_t ticks_slot, diff --git a/subsys/bluetooth/controller/ll_sw/ull_sync.c b/subsys/bluetooth/controller/ll_sw/ull_sync.c index a233a29a6ff..e13a0b114c7 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_sync.c +++ b/subsys/bluetooth/controller/ll_sw/ull_sync.c @@ -29,6 +29,7 @@ #include "lll_chan.h" #include "lll_scan.h" #include "lll/lll_df_types.h" +#include "lll_conn.h" #include "lll_sync.h" #include "lll_sync_iso.h" diff --git a/subsys/bluetooth/controller/ll_sw/ull_tx_queue.c b/subsys/bluetooth/controller/ll_sw/ull_tx_queue.c new file mode 100644 index 00000000000..339771dd650 --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_tx_queue.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ull_tx_queue.h" + +void ull_tx_q_init(struct ull_tx_q *queue) +{ + queue->pause_data = 0; + sys_slist_init(&queue->tx_list); + sys_slist_init(&queue->data_list); +} + +void ull_tx_q_pause_data(struct ull_tx_q *queue) +{ + queue->pause_data = 1U; +} + +void ull_tx_q_resume_data(struct ull_tx_q *queue) +{ + queue->pause_data = 0U; + + /* move all paused data to the tail of tx list */ + sys_slist_merge_slist(&queue->tx_list, &queue->data_list); +} + +void ull_tx_q_enqueue_data(struct ull_tx_q *queue, struct node_tx *tx) +{ + sys_slist_t *list; + + if (queue->pause_data) { + /* enqueue data pdu into paused data wait list */ + list = &queue->data_list; + } else { + /* enqueue data pdu into tx list */ + list = &queue->tx_list; + } + + sys_slist_append(list, (sys_snode_t *)tx); +} + +void ull_tx_q_enqueue_ctrl(struct ull_tx_q *queue, struct node_tx *tx) +{ + /* enqueue ctrl pdu into tx list */ + sys_slist_append(&queue->tx_list, (sys_snode_t *)tx); +} + +struct node_tx *ull_tx_q_peek(struct ull_tx_q *queue) +{ + struct node_tx *tx; + + tx = (struct node_tx *)sys_slist_peek_head(&queue->tx_list); + + return tx; +} + +struct node_tx *ull_tx_q_dequeue(struct ull_tx_q *queue) +{ + struct node_tx *tx; + + tx = (struct node_tx *)sys_slist_get(&queue->tx_list); + + return tx; +} diff --git a/subsys/bluetooth/controller/ll_sw/ull_tx_queue.h b/subsys/bluetooth/controller/ll_sw/ull_tx_queue.h new file mode 100644 index 00000000000..e6df1c7e075 --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/ull_tx_queue.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +struct ull_tx_q { + uint8_t pause_data; /* Data pause state of the tx queue */ + + sys_slist_t tx_list; /* Data and control node_tx list */ + sys_slist_t data_list; /* Data node_tx wait list */ +}; + +/* Forward declaration of node_tx */ +struct node_tx; + +/** + * @brief Initialize a tx queue. + * + * @param ull_tx_q Address of tx queue. + */ +void ull_tx_q_init(struct ull_tx_q *queue); + +/** + * @brief Pause the data path of a tx queue. + * + * @param ull_tx_q Address of tx queue. + */ +void ull_tx_q_pause_data(struct ull_tx_q *queue); + +/** + * @brief Resume the data path of a tx queue + * + * @param ull_tx_q Address of tx queue. + */ +void ull_tx_q_resume_data(struct ull_tx_q *queue); + +/** + * @brief Enqueue a tx node in the data path of a tx queue + * + * @param ull_tx_q Address of tx queue. + * @param tx Address of tx node to enqueue. + */ +void ull_tx_q_enqueue_data(struct ull_tx_q *queue, struct node_tx *tx); + +/** + * @brief Enqueue a tx node in the control path of a tx queue + * + * @param ull_tx_q Address of tx queue. + * @param tx Address of tx node to enqueue. + */ +void ull_tx_q_enqueue_ctrl(struct ull_tx_q *queue, struct node_tx *tx); + +/** + * @brief Peek head tx node of tx queue. + * + * @param ull_tx_q Address of tx queue. + * + * @return Head tx node of the tx queue. + */ +struct node_tx *ull_tx_q_peek(struct ull_tx_q *queue); + +/** + * @brief Dequeue a tx node from a tx queue. + * + * @param ull_tx_q Address of tx queue. + * + * @return Head tx node of the tx queue. + */ +struct node_tx *ull_tx_q_dequeue(struct ull_tx_q *queue); diff --git a/subsys/bluetooth/controller/util/util.c b/subsys/bluetooth/controller/util/util.c index 0c5978aaee3..7cae323b0cf 100644 --- a/subsys/bluetooth/controller/util/util.c +++ b/subsys/bluetooth/controller/util/util.c @@ -27,7 +27,7 @@ * * @return popcnt of 'octets' */ -uint8_t util_ones_count_get(uint8_t *octets, uint8_t octets_len) +uint8_t util_ones_count_get(const uint8_t *octets, uint8_t octets_len) { uint8_t one_count = 0U; diff --git a/subsys/bluetooth/controller/util/util.h b/subsys/bluetooth/controller/util/util.h index 1fc3e112705..1fe7e15fcbc 100644 --- a/subsys/bluetooth/controller/util/util.h +++ b/subsys/bluetooth/controller/util/util.h @@ -13,7 +13,7 @@ #define TRIPLE_BUFFER_SIZE 3 #endif -uint8_t util_ones_count_get(uint8_t *octets, uint8_t octets_len); +uint8_t util_ones_count_get(const uint8_t *octets, uint8_t octets_len); int util_aa_le32(uint8_t *dst); void util_saa_le32(uint8_t *dst, uint8_t handle); void util_bis_aa_le32(uint8_t bis, uint8_t *saa, uint8_t *dst); diff --git a/tests/bluetooth/bsim_bt/_compile_permutate_kconfigs.sh b/tests/bluetooth/bsim_bt/_compile_permutate_kconfigs.sh new file mode 100755 index 00000000000..b450f4a3f66 --- /dev/null +++ b/tests/bluetooth/bsim_bt/_compile_permutate_kconfigs.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Copyright 2018 Oticon A/S +# SPDX-License-Identifier: Apache-2.0 + +# Compile with all permutations of a given set of KConfigs +# Specifically for going through possible combinations of +# optional control procedures + +#set -x #uncomment this line for debugging +# set DEBUG_PERMUTATE to 'true' for extra debug output +DEBUG_PERMUTATE=false + +: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}" +: "${BSIM_COMPONENTS_PATH:?BSIM_COMPONENTS_PATH must be defined}" +: "${ZEPHYR_BASE:?ZEPHYR_BASE must be set to point to the zephyr root\ + directory}" + +WORK_DIR="${WORK_DIR:-${ZEPHYR_BASE}/bsim_bt_out}" +BOARD="${BOARD:-nrf52_bsim}" +BOARD_ROOT="${BOARD_ROOT:-${ZEPHYR_BASE}}" + +mkdir -p ${WORK_DIR} + +source ${ZEPHYR_BASE}/tests/bluetooth/bsim_bt/compile.source + + +declare -a list=( +"CONFIG_BT_CENTRAL=" +"CONFIG_BT_PERIPHERAL=" +"CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG=" +"CONFIG_BT_DATA_LEN_UPDATE=" +"CONFIG_BT_PHY_UPDATE=" +"CONFIG_BT_CTLR_MIN_USED_CHAN=" +"CONFIG_BT_CTLR_LE_PING=" +"CONFIG_BT_CTLR_LE_ENC=" +"CONFIG_BT_CTLR_CONN_PARAM_REQ=" +) + +perm_compile() { + local -a results=() + # We set a unique exe-name, so that we don't overwrite the executables + # created by the compile scripts since that may mess up other tests + # We also delete the executable to avoid having artifacts from + # a previous run + local exe_name="bs_nrf52_bsim_tests_kconfig_perm" + local executable_name=${exe_name} + local executable_name=${BSIM_OUT_PATH}/bin/$executable_name + + rm -f ${executable_name} + + let idx=$2 + for (( j = 0; j < $1; j++ )); do + if (( idx % 2 )); then + results=("${results[@]}" "${list[$j]}n") + else + results=("${results[@]}" "${list[$j]}y") + fi + let idx\>\>=1 + done + printf '%s\n' "${results[@]}" > $3 + if test "$DEBUG_PERMUTATE" = "true"; then + echo "Compile with config overlay:" + cat $3 + fi + local app=tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app + local conf_file=prj_dut.conf + local conf_overlay=$3 + compile + if [ ! -f ${executable_name} ]; then + compile_failures=$(expr $compile_failures + 1) + fi +} +let n=${#list[@]} +temp_conf_file=$(mktemp -p ${WORK_DIR}) +# compile_failures will be equal to the number of failed compilations +let compile_failures=0 + +for (( i = 0; i < 2**n; i++ )); do + ## don't compile for CENTRAL=n AND PERIPHERAL=n + if (( (i & 0x3) != 0x3 )); then + perm_compile $n $i ${temp_conf_file} + fi +done + +# We set exit code based on type of failure +# 0 means all configurations compiled w/o error + +trap "{ rm "${temp_conf_file}" ; exit 255; }" SIGINT +trap "{ rm "${temp_conf_file}" ; exit 254; }" SIGTERM +trap "{ rm "${temp_conf_file}" ; exit 253; }" ERR +trap "{ rm "${temp_conf_file}" ; exit ${compile_failures}; }" EXIT diff --git a/tests/bluetooth/bsim_bt/compile.sh b/tests/bluetooth/bsim_bt/compile.sh index c5798161c0a..f67e966cf46 100755 --- a/tests/bluetooth/bsim_bt/compile.sh +++ b/tests/bluetooth/bsim_bt/compile.sh @@ -18,40 +18,7 @@ BOARD_ROOT="${BOARD_ROOT:-${ZEPHYR_BASE}}" mkdir -p ${WORK_DIR} -function compile(){ - local app_root="${app_root:-${ZEPHYR_BASE}}" - local conf_file="${conf_file:-prj.conf}" - local cmake_args="${cmake_args:-"-DCONFIG_COVERAGE=y"}" - local ninja_args="${ninja_args:-""}" - local cc_flags="${cc_flags:-"-Werror"}" - - local exe_name="${exe_name:-bs_${BOARD}_${app}_${conf_file}}" - local exe_name=${exe_name//\//_} - local exe_name=${exe_name//./_} - local exe_name=${BSIM_OUT_PATH}/bin/$exe_name - local map_file_name=${exe_name}.Tsymbols - - local this_dir=${WORK_DIR}/${app}/${conf_file} - - echo "Building $exe_name" - - # Set INCR_BUILD when calling to only do an incremental build - if [ ! -v INCR_BUILD ] || [ ! -d "${this_dir}" ]; then - [ -d "${this_dir}" ] && rm ${this_dir} -rf - mkdir -p ${this_dir} && cd ${this_dir} - cmake -GNinja -DBOARD_ROOT=${BOARD_ROOT} -DBOARD=${BOARD} \ - -DCONF_FILE=${conf_file} ${cmake_args} \ - -DCMAKE_C_FLAGS="${cc_flags}" ${app_root}/${app} \ - &> cmake.out || { cat cmake.out && return 0; } - else - cd ${this_dir} - fi - ninja ${ninja_args} &> ninja.out || { cat ninja.out && return 0; } - cp ${this_dir}/zephyr/zephyr.exe ${exe_name} - - nm ${exe_name} | grep -v " [U|w] " | sort | cut -d" " -f1,3 > ${map_file_name} - sed -i "1i $(wc -l ${map_file_name} | cut -d" " -f1)" ${map_file_name} -} +source ${ZEPHYR_BASE}/tests/bluetooth/bsim_bt/compile.source app=tests/bluetooth/bsim_bt/bsim_test_app conf_file=prj_split.conf \ compile @@ -63,6 +30,10 @@ app=tests/bluetooth/bsim_bt/bsim_test_multiple compile app=tests/bluetooth/bsim_bt/bsim_test_advx compile app=tests/bluetooth/bsim_bt/bsim_test_iso compile app=tests/bluetooth/bsim_bt/bsim_test_audio compile +app=tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app \ + conf_file=prj_dut_llcp.conf compile +app=tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app \ + conf_file=prj_tst_llcp.conf compile app=tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app \ conf_file=prj_dut.conf compile app=tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app \ diff --git a/tests/bluetooth/bsim_bt/compile.source b/tests/bluetooth/bsim_bt/compile.source new file mode 100644 index 00000000000..c8b3ad8e274 --- /dev/null +++ b/tests/bluetooth/bsim_bt/compile.source @@ -0,0 +1,41 @@ +# Copyright 2018 Oticon A/S +# SPDX-License-Identifier: Apache-2.0 + +function compile(){ + : "${app:?app must be defined}" + + local app_root="${app_root:-${ZEPHYR_BASE}}" + local conf_file="${conf_file:-prj.conf}" + local conf_overlay="${conf_overlay:-""}" + + local cmake_args="${cmake_args:-"-DCONFIG_COVERAGE=y \ + -DCONFIG_DEBUG_OPTIMIZATIONS=y \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON"}" + local ninja_args="${ninja_args:-""}" + + local exe_name="${exe_name:-bs_${BOARD}_${app}_${conf_file}}" + local exe_name=${exe_name//\//_} + local exe_name=${exe_name//./_} + local exe_name=${BSIM_OUT_PATH}/bin/$exe_name + local map_file_name=${exe_name}.Tsymbols + + local this_dir=${WORK_DIR}/${app}/${conf_file} + + echo "Building $exe_name" + + # Set INCR_BUILD when calling to only do an incremental build + if [ ! -v INCR_BUILD ] || [ ! -d "${this_dir}" ]; then + [ -d "${this_dir}" ] && rm ${this_dir} -rf + mkdir -p ${this_dir} && cd ${this_dir} + cmake -GNinja -DBOARD_ROOT=${BOARD_ROOT} -DBOARD=${BOARD} \ + -DCONF_FILE=${conf_file} -DOVERLAY_CONFIG=${conf_overlay} ${cmake_args} ${app_root}/${app} \ + &> cmake.out || { cat cmake.out && return 0; } + else + cd ${this_dir} + fi + ninja ${ninja_args} &> ninja.out || { cat ninja.out && return 0; } + cp ${this_dir}/zephyr/zephyr.exe ${exe_name} + + nm ${exe_name} | grep -v " [U|w] " | sort | cut -d" " -f1,3 > ${map_file_name} + sed -i "1i $(wc -l ${map_file_name} | cut -d" " -f1)" ${map_file_name} +} diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/gatt_test_app/prj.conf b/tests/bluetooth/bsim_bt/edtt_ble_test_app/gatt_test_app/prj.conf index bd1d139e91e..62a3902ab2e 100644 --- a/tests/bluetooth/bsim_bt/edtt_ble_test_app/gatt_test_app/prj.conf +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/gatt_test_app/prj.conf @@ -26,3 +26,5 @@ CONFIG_BT_CTLR_RX_BUFFERS=3 # To make DEVICE Name writable... CONFIG_BT_DEVICE_NAME_DYNAMIC=y + +CONFIG_BT_LL_SW_LLCP_LEGACY=y diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_dut.conf b/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_dut.conf index 27e2ebe27f4..7a49c4c3aa6 100644 --- a/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_dut.conf +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_dut.conf @@ -14,7 +14,7 @@ CONFIG_BT_BUF_ACL_RX_SIZE=60 CONFIG_BT_BUF_ACL_TX_SIZE=60 ## -## Enabling BT_CTRL_DTM_HCI requires BT_LL_SW which requires BT_CTRL +## Enabling BT_CTRL_DTM_HCI requires BT_LL_SW_SPLIT which requires BT_CTRL ## CONFIG_BT_CTLR=y @@ -25,3 +25,5 @@ CONFIG_BT_CTLR_PRIVACY=y CONFIG_BT_CTLR_FILTER_ACCEPT_LIST=y CONFIG_BT_CTLR_DTM_HCI=y CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 + +CONFIG_BT_LL_SW_LLCP_LEGACY=y diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_dut_llcp.conf b/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_dut_llcp.conf new file mode 100644 index 00000000000..723d3684502 --- /dev/null +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_dut_llcp.conf @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_BT=y +CONFIG_BT_HCI_RAW=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_BROADCASTER=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_OBSERVER=y +CONFIG_BT_DEBUG_LOG=y +CONFIG_BT_ECC=y +CONFIG_BT_TINYCRYPT_ECC=y + +CONFIG_BT_BUF_ACL_RX_SIZE=60 +CONFIG_BT_BUF_ACL_TX_SIZE=60 + +## +## Enabling BT_CTRL_DTM_HCI requires BT_LL_SW_SPLIT which requires BT_CTRL +## +CONFIG_BT_CTLR=y + +CONFIG_BT_LL_SW_SPLIT=y +CONFIG_BT_CTLR_CRYPTO=y +CONFIG_BT_CTLR_LE_ENC=y +CONFIG_BT_CTLR_PRIVACY=y +CONFIG_BT_CTLR_FILTER_ACCEPT_LIST=y +CONFIG_BT_CTLR_DTM_HCI=y +CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 + +CONFIG_BT_LL_SW_LLCP_LEGACY=n +CONFIG_BT_CTLR_ADVANCED_FEATURES=y +# LLCP Refactored controller does not support Advanced Scheduling yet +CONFIG_BT_CTLR_SCHED_ADVANCED=n diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_tst.conf b/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_tst.conf index 07c738bca64..2af3e7436f2 100644 --- a/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_tst.conf +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_tst.conf @@ -24,5 +24,6 @@ CONFIG_BT_CTLR_FILTER_ACCEPT_LIST=y CONFIG_BT_CTLR_DTM_HCI=y CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 +CONFIG_BT_LL_SW_LLCP_LEGACY=y CONFIG_BT_CTLR_ADVANCED_FEATURES=y CONFIG_BT_CTLR_PARAM_CHECK=n diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_tst_llcp.conf b/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_tst_llcp.conf new file mode 100644 index 00000000000..3993b6088b4 --- /dev/null +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_tst_llcp.conf @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_BT=y +CONFIG_BT_HCI_RAW=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_DEBUG_LOG=y +CONFIG_BT_ECC=y +CONFIG_BT_TINYCRYPT_ECC=y + +CONFIG_BT_BUF_ACL_RX_SIZE=60 +CONFIG_BT_BUF_ACL_TX_SIZE=60 + +## +## Enabling BT_CTRL_DTM_HCI requires BT_LL_SW_SPLIT which requires BT_CTRL +## +CONFIG_BT_CTLR=y + +CONFIG_BT_LL_SW_SPLIT=y +CONFIG_BT_CTLR_CRYPTO=y +CONFIG_BT_CTLR_LE_ENC=y +CONFIG_BT_CTLR_PRIVACY=y +CONFIG_BT_CTLR_FILTER_ACCEPT_LIST=y +CONFIG_BT_CTLR_DTM_HCI=y +CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 + +CONFIG_BT_LL_SW_LLCP_LEGACY=n +CONFIG_BT_CTLR_ADVANCED_FEATURES=y +# LLCP Refactored controller does not support Advanced Scheduling yet +CONFIG_BT_CTLR_SCHED_ADVANCED=n diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/_controller_tests_inner.sh b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/_controller_tests_inner.sh index 6e80f287ed0..8181de0a6c6 100755 --- a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/_controller_tests_inner.sh +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/_controller_tests_inner.sh @@ -2,18 +2,66 @@ # Copyright 2019 Oticon A/S # SPDX-License-Identifier: Apache-2.0 +# +# ENVIRONMENT CONFIGURATION +# ========================= +# +# This script can be configured with a number of environment variables. +# Values in [] are the default unless overridden. +# +# PROJECT CONFIGURATION +# --------------------- +# PRJ_CONF: Default bsim device configuration [prj_dut_conf] +# PRJ_CONF_1: bsim device 1 configuration [PRJ_CONF] +# PRJ_CONF_2: bsim device 2 configuration [PRJ_CONF] +# +# VERBOSITY +# --------- +# VERBOSITY_LEVEL: Global verbosity [2] +# VERBOSITY_LEVEL_EDTT: EDTT verbosity [VERBOSITY_LEVEL] +# VERBOSITY_LEVEL_BRIDGE: EDTT bridge verbosity [VERBOSITY_LEVEL] +# VERBOSITY_LEVEL_PHY: bsim phy verbosity [VERBOSITY_LEVEL] +# VERBOSITY_LEVEL_DEVS: Global bsim device verbosity [VERBOSITY_LEVEL] +# VERBOSITY_LEVEL_DEV1: bsim device 1 verbosity [VERBOSITY_LEVEL_DEVS] +# VERBOSITY_LEVEL_DEV1: bsim device 2 verbosity [VERBOSITY_LEVEL_DEVS] +# +# RR DEBUG SUPPORT +# ---------------- +# RR: Default run bsim device under rr [0] +# 0: disables; any other value enables. +# RR_1: Run bsim device 1 under rr [RR] +# RR_2: Run bsim device 2 under rr [RR] +# + + # Common part of the test scripts for some of the EDTT tests # in which 2 controller only builds of the stack are run against each other -VERBOSITY_LEVEL=2 +VERBOSITY_LEVEL=${VERBOSITY_LEVEL:-2} +VERBOSITY_LEVEL_EDTT=${VERBOSITY_LEVEL_EDTT:-${VERBOSITY_LEVEL}} +VERBOSITY_LEVEL_BRIDGE=${VERBOSITY_LEVEL_BRIDGE:-${VERBOSITY_LEVEL}} +VERBOSITY_LEVEL_PHY=${VERBOSITY_LEVEL_PHY:-${VERBOSITY_LEVEL}} +VERBOSITY_LEVEL_DEVS=${VERBOSITY_LEVEL_DEVS:-${VERBOSITY_LEVEL}} +VERBOSITY_LEVEL_DEV1=${VERBOSITY_LEVEL_1:-${VERBOSITY_LEVEL_DEVS}} +VERBOSITY_LEVEL_DEV2=${VERBOSITY_LEVEL_2:-${VERBOSITY_LEVEL_DEVS}} + PROCESS_IDS=""; EXIT_CODE=0 function Execute(){ + local rr= + if [ "rr" = "$1" ]; then + local devno=$2 + shift 2 + local exe=$(basename $1) + local out=/tmp/rr/$$-${SIMULATION_ID}-${exe}-d_${devno} + rm -rf ${out} + rr="rr record -o ${out}" + fi if [ ! -f $1 ]; then echo -e " \e[91m`pwd`/`basename $1` cannot be found (did you forget to\ compile it?)\e[39m" exit 1 fi - timeout 300 $@ & PROCESS_IDS="$PROCESS_IDS $!" + timeout 300 ${rr} $@ & PROCESS_IDS="$PROCESS_IDS $!" } : "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}" @@ -22,25 +70,47 @@ function Execute(){ #Give a default value to BOARD if it does not have one yet: BOARD="${BOARD:-nrf52_bsim}" +#Give a default value to PRJ_CONF_x if it does not have one yet: +PRJ_CONF="${PRJ_CONF:-prj_dut_conf}" +PRJ_CONF_1="${PRJ_CONF_1:-${PRJ_CONF}}" +PRJ_CONF_2="${PRJ_CONF_2:-${PRJ_CONF}}" + +#Give default value to RR_x if it does not have one yet: +RR="${RR:-0}" +RR_1="${RR_1:-${RR}}" +RR_2="${RR_2:-${RR}}" + +#Check if rr was requested and is available +if [ "${RR_1}" != "0" -o "${RR_2}" != "0" ]; then + if [ ! -x "$(command -v rr)" ]; then + echo 'error: rr cannot be found in $PATH.' >&2 + exit 1 + fi + + #Set RR_ARGS_x based on RR_x + [ "${RR_1}" != "0" ] && RR_ARGS_1="rr 1" + [ "${RR_2}" != "0" ] && RR_ARGS_2="rr 2" +fi + cd ${EDTT_PATH} Execute ./src/edttool.py -s=${SIMULATION_ID} -d=0 --transport bsim \ - -T $TEST_MODULE -C $TEST_FILE -v=${VERBOSITY_LEVEL} + -T $TEST_MODULE -C $TEST_FILE -v=${VERBOSITY_LEVEL_EDTT} -S cd ${BSIM_OUT_PATH}/bin Execute ./bs_device_EDTT_bridge -s=${SIMULATION_ID} -d=0 -AutoTerminate \ - -RxWait=2.5e3 -D=2 -dev0=1 -dev1=2 -v=${VERBOSITY_LEVEL} + -RxWait=2.5e3 -D=2 -dev0=1 -dev1=2 -v=${VERBOSITY_LEVEL_BRIDGE} Execute \ - ./bs_${BOARD}_tests_bluetooth_bsim_bt_edtt_ble_test_app_hci_test_app_prj_dut_conf\ - -s=${SIMULATION_ID} -d=1 -v=${VERBOSITY_LEVEL} -RealEncryption=1 + ${RR_ARGS_1} ./bs_${BOARD}_tests_bluetooth_bsim_bt_edtt_ble_test_app_hci_test_app_${PRJ_CONF_1}\ + -s=${SIMULATION_ID} -d=1 -v=${VERBOSITY_LEVEL_DEV1} -RealEncryption=1 Execute \ - ./bs_${BOARD}_tests_bluetooth_bsim_bt_edtt_ble_test_app_hci_test_app_prj_tst_conf\ - -s=${SIMULATION_ID} -d=2 -v=${VERBOSITY_LEVEL} -RealEncryption=1 + ${RR_ARGS_2} ./bs_${BOARD}_tests_bluetooth_bsim_bt_edtt_ble_test_app_hci_test_app_${PRJ_CONF_2}\ + -s=${SIMULATION_ID} -d=2 -v=${VERBOSITY_LEVEL_DEV2} -RealEncryption=1 -Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ +Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL_PHY} -s=${SIMULATION_ID} \ -D=3 -sim_length=3600e6 $@ for PROCESS_ID in $PROCESS_IDS; do diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/gap.sh b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/gap.sh index 94002a2b446..96b3a056fc3 100755 --- a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/gap.sh +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/gap.sh @@ -8,5 +8,7 @@ CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P)" export SIMULATION_ID="edtt_gap" export TEST_FILE=${CWD}"/gap.test_list" export TEST_MODULE="gap_verification" +export PRJ_CONF_1="prj_dut_conf" +export PRJ_CONF_2="prj_tst_conf" ${CWD}/_controller_tests_inner.sh diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/gap.test_list b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/gap.test_list index 97e1ad495a6..456b3a351b0 100644 --- a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/gap.test_list +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/gap.test_list @@ -20,7 +20,7 @@ GAP/CONN/ACEP/BV-01-C GAP/CONN/ACEP/BV-03-C GAP/CONN/ACEP/BV-04-C GAP/CONN/DCON/BV-01-C -GAP/CONN/ENC +#GAP/CONN/ENC GAP/CONN/GCEP/BV-01-C GAP/CONN/GCEP/BV-02-C GAP/CONN/GCEP/BV-05-C diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.llcp.sh b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.llcp.sh new file mode 100755 index 00000000000..8e23c68486c --- /dev/null +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.llcp.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Copyright 2019 Oticon A/S +# SPDX-License-Identifier: Apache-2.0 + +# HCI regression tests based on the EDTTool +CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P)" + +export SIMULATION_ID="edtt_hci_llcp" +export TEST_FILE=${CWD}"/hci.llcp.test_list" +export TEST_MODULE="hci_verification" +export PRJ_CONF_1="prj_dut_llcp_conf" +export PRJ_CONF_2="prj_tst_llcp_conf" + +${CWD}/_controller_tests_inner.sh diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.llcp.test_list b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.llcp.test_list new file mode 100644 index 00000000000..917373e0cb4 --- /dev/null +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.llcp.test_list @@ -0,0 +1,31 @@ +# Copyright 2019 Oticon A/S +# SPDX-License-Identifier: Apache-2.0 + +HCI/CCO/BV-07-C +HCI/CCO/BV-09-C # [Handling LE Set Data Length Command] +HCI/CCO/BV-10-C +HCI/CCO/BV-11-C +HCI/CCO/BV-12-C +HCI/CCO/BV-13-C +HCI/CCO/BV-14-C +HCI/CCO/BV-15-C +HCI/CCO/BV-18-C +HCI/CFC/BV-02-C +HCI/CIN/BV-01-C +HCI/CIN/BV-03-C +HCI/CIN/BV-04-C +HCI/CIN/BV-06-C +HCI/CIN/BV-09-C +HCI/CM/BV-01-C # [Handling LE Read Peer Resolvable Address Command] +HCI/CM/BV-02-C # [Handling LE Read Local Resolvable Address Command] +HCI/CM/BV-03-C # [Handling LE Read PHY Command] +HCI/DDI/BI-02-C +HCI/DDI/BV-03-C +HCI/DDI/BV-04-C +HCI/DSU/BV-02-C +HCI/DSU/BV-03-C # [Reset Command received in Slave Role] +HCI/DSU/BV-04-C +#HCI/DSU/BV-05-C +HCI/DSU/BV-06-C # [Reset Command received in Master Role] +# HCI/GEV/BV-01-C +HCI/HFC/BV-04-C # [Events enabled by LE Set Event Mask Command] diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.sh b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.sh index b75de64a25c..dbeb16c6154 100755 --- a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.sh +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.sh @@ -8,5 +8,7 @@ CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P)" export SIMULATION_ID="edtt_hci" export TEST_FILE=${CWD}"/hci.test_list" export TEST_MODULE="hci_verification" +export PRJ_CONF_1="prj_dut_conf" +export PRJ_CONF_2="prj_tst_conf" ${CWD}/_controller_tests_inner.sh diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.test_list b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.test_list index c68fc9c08c5..4b8553b5880 100644 --- a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.test_list +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.test_list @@ -27,5 +27,5 @@ HCI/DSU/BV-03-C HCI/DSU/BV-04-C HCI/DSU/BV-05-C HCI/DSU/BV-06-C -HCI/GEV/BV-01-C +#HCI/GEV/BV-01-C HCI/HFC/BV-04-C diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.1.llcp.sh b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.1.llcp.sh new file mode 100755 index 00000000000..43a8b326f60 --- /dev/null +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.1.llcp.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Copyright 2019 Oticon A/S +# SPDX-License-Identifier: Apache-2.0 + +# LL regression tests based on the EDTTool (part 1) + +CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P)" + +export SIMULATION_ID="edtt_ll_set1_llcp" +export TEST_FILE=${CWD}"/ll.set1.llcp.test_list" +export TEST_MODULE="ll_verification" +export PRJ_CONF_1="prj_dut_llcp_conf" +export PRJ_CONF_2="prj_tst_llcp_conf" + +${CWD}/_controller_tests_inner.sh diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.1.sh b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.1.sh index fae97cd3a0a..43ccb419415 100755 --- a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.1.sh +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.1.sh @@ -9,5 +9,7 @@ CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P)" export SIMULATION_ID="edtt_ll_set1" export TEST_FILE=${CWD}"/ll.set1.test_list" export TEST_MODULE="ll_verification" +export PRJ_CONF_1="prj_dut_conf" +export PRJ_CONF_2="prj_tst_conf" ${CWD}/_controller_tests_inner.sh diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.2.llcp.sh b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.2.llcp.sh new file mode 100755 index 00000000000..3eb627179b0 --- /dev/null +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.2.llcp.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Copyright 2019 Oticon A/S +# SPDX-License-Identifier: Apache-2.0 + +# LL regression tests based on the EDTTool (part 2) + +CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P)" + +export SIMULATION_ID="edtt_ll_set2_llcp" +export TEST_FILE=${CWD}"/ll.set2.llcp.test_list" +export TEST_MODULE="ll_verification" +export PRJ_CONF_1="prj_dut_llcp_conf" +export PRJ_CONF_2="prj_tst_llcp_conf" + +${CWD}/_controller_tests_inner.sh diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.2.sh b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.2.sh index 71713a14bd4..f99ec00e969 100755 --- a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.2.sh +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.2.sh @@ -9,5 +9,7 @@ CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P)" export SIMULATION_ID="edtt_ll_set2" export TEST_FILE=${CWD}"/ll.set2.test_list" export TEST_MODULE="ll_verification" +export PRJ_CONF_1="prj_dut_conf" +export PRJ_CONF_2="prj_tst_conf" ${CWD}/_controller_tests_inner.sh diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.set1.llcp.test_list b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.set1.llcp.test_list new file mode 100644 index 00000000000..49f07754108 --- /dev/null +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.set1.llcp.test_list @@ -0,0 +1,64 @@ +# Copyright 2019 Oticon A/S +# SPDX-License-Identifier: Apache-2.0 + +LL/CON/ADV/BV-01-C +LL/CON/ADV/BV-04-C +LL/CON/ADV/BV-09-C +LL/CON/ADV/BV-10-C +LL/CON/INI/BV-01-C +LL/CON/INI/BV-02-C +LL/CON/INI/BV-06-C +LL/CON/INI/BV-07-C +LL/CON/INI/BV-08-C +LL/CON/INI/BV-09-C +LL/CON/INI/BV-10-C +LL/CON/INI/BV-11-C +LL/CON/INI/BV-12-C +LL/CON/INI/BV-16-C +LL/CON/INI/BV-17-C +LL/CON/INI/BV-18-C +LL/CON/INI/BV-19-C +LL/CON/INI/BV-20-C +LL/CON/INI/BV-21-C +LL/CON/INI/BV-23-C +LL/CON/INI/BV-24-C +#LL/CON/MAS/BI-06-C #currently failing, to be investigated +LL/CON/MAS/BV-03-C +LL/CON/MAS/BV-04-C +LL/CON/MAS/BV-05-C +LL/CON/MAS/BV-07-C +LL/CON/MAS/BV-08-C +LL/CON/MAS/BV-09-C +LL/CON/MAS/BV-13-C +LL/CON/MAS/BV-20-C +LL/CON/MAS/BV-21-C +LL/CON/MAS/BV-23-C +LL/CON/MAS/BV-24-C +LL/CON/MAS/BV-25-C +LL/CON/MAS/BV-26-C +#LL/CON/MAS/BV-27-C #currently failing, to be investigated +LL/CON/MAS/BV-29-C +LL/CON/MAS/BV-30-C +LL/CON/MAS/BV-34-C +LL/CON/MAS/BV-35-C +LL/CON/MAS/BV-41-C +LL/CON/MAS/BV-43-C +#LL/CON/MAS/BV-73-C # requires update of EDTT due to change in DLE algorithm +#LL/CON/MAS/BV-74-C # requires update of EDTT due to change in DLE algorithm +#LL/CON/MAS/BV-76-C # requires update of EDTT due to change in DLE algorithm +#LL/CON/MAS/BV-77-C # requires update of EDTT due to change in DLE algorithm +#LL/CON/SLA/BI-08-C # requires update of EDTT due to change in DLE algorithm +LL/CON/SLA/BV-04-C +LL/CON/SLA/BV-05-C +LL/CON/SLA/BV-06-C +LL/CON/SLA/BV-10-C +LL/CON/SLA/BV-11-C +LL/CON/SLA/BV-12-C +LL/CON/SLA/BV-14-C +LL/CON/SLA/BV-19-C +LL/CON/SLA/BV-20-C +LL/CON/SLA/BV-22-C +LL/CON/SLA/BV-24-C +LL/CON/SLA/BV-25-C +LL/CON/SLA/BV-26-C +LL/CON/SLA/BV-27-C diff --git a/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.set2.llcp.test_list b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.set2.llcp.test_list new file mode 100644 index 00000000000..a361dd4255f --- /dev/null +++ b/tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.set2.llcp.test_list @@ -0,0 +1,62 @@ +# Copyright 2019 Oticon A/S +# SPDX-License-Identifier: Apache-2.0 + +LL/CON/SLA/BV-29-C +LL/CON/SLA/BV-33-C +LL/CON/SLA/BV-34-C +LL/CON/SLA/BV-40-C +LL/CON/SLA/BV-42-C +#LL/CON/SLA/BV-77-C # requires update in EDTT due to change in DLE algorithm +#LL/CON/SLA/BV-78-C # requires update in EDTT due to change in DLE algorithm +#LL/CON/SLA/BV-80-C # requires update in EDTT due to change in DLE algorithm +#LL/CON/SLA/BV-81-C # requires update in EDTT due to change in DLE algorithm +LL/DDI/ADV/BV-01-C +LL/DDI/ADV/BV-02-C +LL/DDI/ADV/BV-03-C +LL/DDI/ADV/BV-04-C +LL/DDI/ADV/BV-05-C +LL/DDI/ADV/BV-06-C +LL/DDI/ADV/BV-07-C +LL/DDI/ADV/BV-08-C +LL/DDI/ADV/BV-09-C +LL/DDI/ADV/BV-11-C +LL/DDI/ADV/BV-15-C +LL/DDI/ADV/BV-16-C +LL/DDI/ADV/BV-17-C +LL/DDI/ADV/BV-18-C +LL/DDI/ADV/BV-19-C +LL/DDI/ADV/BV-20-C +LL/DDI/SCN/BV-01-C +LL/DDI/SCN/BV-02-C +LL/DDI/SCN/BV-03-C +LL/DDI/SCN/BV-04-C +LL/DDI/SCN/BV-05-C +LL/DDI/SCN/BV-10-C +LL/DDI/SCN/BV-11-C +LL/DDI/SCN/BV-12-C +LL/DDI/SCN/BV-13-C +LL/DDI/SCN/BV-14-C +LL/DDI/SCN/BV-15-C +LL/DDI/SCN/BV-16-C +LL/DDI/SCN/BV-17-C +LL/DDI/SCN/BV-18-C +LL/DDI/SCN/BV-26-C +LL/DDI/SCN/BV-28-C +LL/SEC/ADV/BV-02-C +LL/SEC/ADV/BV-03-C +LL/SEC/ADV/BV-04-C +LL/SEC/ADV/BV-05-C +LL/SEC/ADV/BV-06-C +LL/SEC/ADV/BV-08-C +LL/SEC/ADV/BV-09-C +LL/SEC/ADV/BV-10-C +LL/SEC/ADV/BV-11-C +LL/SEC/ADV/BV-12-C +LL/SEC/ADV/BV-13-C +LL/SEC/ADV/BV-14-C +LL/SEC/ADV/BV-15-C +LL/SEC/ADV/BV-16-C +LL/SEC/ADV/BV-17-C +LL/SEC/ADV/BV-18-C +LL/SEC/ADV/BV-20-C +LL/SEC/SCN/BV-01-C diff --git a/tests/bluetooth/controller/common/defaults_cmake.txt b/tests/bluetooth/controller/common/defaults_cmake.txt new file mode 100644 index 00000000000..76659453b7c --- /dev/null +++ b/tests/bluetooth/controller/common/defaults_cmake.txt @@ -0,0 +1,64 @@ +# +# Common include directories and source files for bluetooth unit tests +# + +include_directories( + src + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/include + ${ZEPHYR_BASE}/tests/bluetooth/controller/common/include + ${ZEPHYR_BASE}/include/bluetooth + ${ZEPHYR_BASE}/subsys/bluetooth + ${ZEPHYR_BASE}/subsys/bluetooth/controller + ${ZEPHYR_BASE}/subsys/bluetooth/controller/util + ${ZEPHYR_BASE}/subsys/bluetooth/controller/include + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/nordic + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/nordic/lll +) + +FILE(GLOB ll_sw_sources + ${ZEPHYR_BASE}/subsys/bluetooth/controller/util/mem.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/util/memq.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_chan.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_tx_queue.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_llcp_local.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_llcp_remote.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_llcp_pdu.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_llcp_common.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_llcp_phy.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_llcp_enc.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_llcp_conn_upd.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_llcp_chmu.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_llcp.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_conn.c + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ll_addr.c +) + +FILE(GLOB mock_sources + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/kernel.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/ecb.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/mayfly.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/lll.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/lll_conn.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/ll_assert.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/util.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/ticker.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/ull.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/ull_periph.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/ull_central.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/ull_scan.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/mock_ctrl/src/lll_clock.c +) + +FILE(GLOB common_sources + ${ZEPHYR_BASE}/tests/bluetooth/controller/common/src/helper_pdu.c + ${ZEPHYR_BASE}/tests/bluetooth/controller/common/src/helper_util.c +) + +add_definitions(-include kconfig.h) +if(KCONFIG_OVERRIDE_FILE) + add_definitions(-include ${KCONFIG_OVERRIDE_FILE}) +endif() + +add_definitions(-include ztest.h) +add_definitions(-include soc.h) diff --git a/tests/bluetooth/controller/common/include/helper_features.h b/tests/bluetooth/controller/common/include/helper_features.h new file mode 100644 index 00000000000..f610683b4e4 --- /dev/null +++ b/tests/bluetooth/controller/common/include/helper_features.h @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * expected features on purpose defined here + * to keep implementation separate from test + */ +#if defined(CONFIG_BT_CTLR_LE_ENC) +#define FEAT_ENCODED 0x01 +#else +#define FEAT_ENCODED 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +#define FEAT_PARAM_REQ 0x02 +#else +#define FEAT_PARAM_REQ 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_EXT_REJ_IND) +#define FEAT_EXT_REJ 0x04 +#else +#define FEAT_EXT_REJ 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG) +#define FEAT_SLAVE_FREQ 0x08 +#else +#define FEAT_SLAVE_FREQ 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_LE_PING) +#define FEAT_PING 0x10 +#else +#define FEAT_PING 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_DATA_LENGTH_MAX) +#define FEAT_DLE 0x20 +#else +#define FEAT_DLE 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_PRIVACY) +#define FEAT_PRIVACY 0x40 +#else +#define FEAT_PRIVACY 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_EXT_SCAN_FP) +#define FEAT_EXT_SCAN 0x80 +#else +#define FEAT_EXT_SCAN 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_PHY_2M) +#define FEAT_PHY_2M 0x100 +#else +#define FEAT_PHY_2M 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_SMI_TX) +#define FEAT_SMI_TX 0x200 +#else +#define FEAT_SMI_TX 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_SMI_RX) +#define FEAT_SMI_RX 0x400 +#else +#define FEAT_SMI_RX 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_PHY_CODED) +#define FEAT_PHY_CODED 0x800 +#else +#define FEAT_PHY_CODED 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_EXT_ADV 0x1000 +#else +#define FEAT_EXT_ADV 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_PER_ADV 0x2000 +#else +#define FEAT_PER_ADV 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_CHAN_SEL_2) +#define FEAT_CHAN_SEL_ALGO2 0x4000 +#else +#define FEAT_CHAN_SEL_ALGO2 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_PWR_CLASS1 0x8000 +#else +#define FEAT_PWR_CLASS1 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_MIN_USED_CHAN) +#define FEAT_MIN_CHANN 0x10000 +#else +#define FEAT_MIN_CHANN 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_REQ) +#define FEAT_CONNECTION_CTE_REQ 0x20000 +#else +#define FEAT_CONNECTION_CTE_REQ 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RSP) +#define FEAT_CONNECTION_CTE_RSP 0x40000 +#else +#define FEAT_CONNECTION_CTE_RSP 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_CONNECTIONLESS_CTE_TX 0x80000 +#else +#define FEAT_CONNECTIONLESS_CTE_TX 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_CONNECTIONLESS_CTE_RX 0x100000 +#else +#define FEAT_CONNECTIONLESS_CTE_RX 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_TX) +#define FEAT_ANT_SWITCH_CTE_TX 0x200000 +#else +#define FEAT_ANT_SWITCH_CTE_TX 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_RX) +#define FEAT_ANT_SWITCH_CTE_RX 0x400000 +#else +#define FEAT_ANT_SWITCH_CTE_RX 0x00 +#endif + +#if defined(CONFIG_BT_CTLR_DF_CTE_RX) +#define FEAT_RX_CTE 0x800000 +#else +#define FEAT_RX_CTE 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_PER_ADV_SYNC_TX 0x1000000 +#else +#define FEAT_PER_ADV_SYNC_TX 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_PER_ADV_SYNC_RX 0x2000000 +#else +#define FEAT_PER_ADV_SYNC_RX 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_SLEEP_UPD 0x4000000 +#else +#define FEAT_SLEEP_UPD 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_RPK_VALID 0x8000000 +#else +#define FEAT_RPK_VALID 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_ISO_MASTER 0x10000000 +#else +#define FEAT_ISO_MASTER 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_ISO_SLAVE 0x20000000 +#else +#define FEAT_ISO_SLAVE 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_ISO_BROADCAST 0x40000000 +#else +#define FEAT_ISO_BROADCAST 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_ISO_RECEIVER 0x80000000 +#else +#define FEAT_ISO_RECEIVER 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_ISO_CHANNELS 0x100000000 +#else +#define FEAT_ISO_CHANNELS 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_LE_PWR_REQ 0x200000000 +#else +#define FEAT_LE_PWR_REQ 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_LE_PWR_IND 0x400000000 +#else +#define FEAT_LE_PWR_IND 0x00 +#endif + +#if defined(CONFIG_MISSING) +#define FEAT_LE_PATH_LOSS 0x800000000 +#else +#define FEAT_LE_PATH_LOSS 0x00 +#endif + +#define DEFAULT_FEATURE \ + (FEAT_ENCODED | FEAT_PARAM_REQ | FEAT_EXT_REJ | FEAT_SLAVE_FREQ | FEAT_PING | FEAT_DLE | \ + FEAT_PRIVACY | FEAT_EXT_SCAN | FEAT_PHY_2M | FEAT_SMI_TX | FEAT_SMI_RX | FEAT_PHY_CODED | \ + FEAT_EXT_ADV | FEAT_PER_ADV | FEAT_CHAN_SEL_ALGO2 | FEAT_PWR_CLASS1 | FEAT_MIN_CHANN | \ + FEAT_CONNECTION_CTE_REQ | FEAT_CONNECTION_CTE_RSP | FEAT_ANT_SWITCH_CTE_TX | \ + FEAT_ANT_SWITCH_CTE_RX | FEAT_RX_CTE | FEAT_PER_ADV_SYNC_TX | FEAT_PER_ADV_SYNC_RX | \ + FEAT_SLEEP_UPD | FEAT_RPK_VALID | FEAT_ISO_MASTER | FEAT_ISO_SLAVE | FEAT_ISO_BROADCAST | \ + FEAT_ISO_RECEIVER | FEAT_ISO_CHANNELS | FEAT_LE_PWR_REQ | FEAT_LE_PWR_IND | \ + FEAT_LE_PATH_LOSS) + +/* + * The following two are defined as per + * Core Spec V5.2 Volume 6, Part B, chapter 4.6 + * LL_FEAT_BIT_MASK_VALID does not account for the bits + * for the new features in V5.2 + * TODO: EXPECTED_FEAT_EXCH_VALID is not used at the moment + * but probably LL_FEAT_BIT_MASK_VALID should get this + * value in ll_feat.h + */ +#define EXPECTED_FEAT_EXCH_VALID 0x0000000FF787CF2F +#define FEAT_FILTER_OCTET0 0xFFFFFFFFFFFFFF00 +#define COMMON_FEAT_OCTET0(x) (FEAT_FILTER_OCTET0 | ((x) & ~FEAT_FILTER_OCTET0)) diff --git a/tests/bluetooth/controller/common/include/helper_pdu.h b/tests/bluetooth/controller/common/include/helper_pdu.h new file mode 100644 index 00000000000..8740da763a3 --- /dev/null +++ b/tests/bluetooth/controller/common/include/helper_pdu.h @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +void helper_pdu_encode_ping_req(struct pdu_data *pdu, void *param); +void helper_pdu_encode_ping_rsp(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_feature_req(struct pdu_data *pdu, void *param); +void helper_pdu_encode_slave_feature_req(struct pdu_data *pdu, void *param); +void helper_pdu_encode_feature_rsp(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_min_used_chans_ind(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_version_ind(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_enc_req(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_enc_rsp(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_start_enc_req(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_start_enc_rsp(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_pause_enc_req(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_pause_enc_rsp(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_reject_ext_ind(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_reject_ind(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_phy_req(struct pdu_data *pdu, void *param); +void helper_pdu_encode_phy_rsp(struct pdu_data *pdu, void *param); +void helper_pdu_encode_phy_update_ind(struct pdu_data *pdu, void *param); +void helper_pdu_encode_unknown_rsp(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_conn_param_req(struct pdu_data *pdu, void *param); +void helper_pdu_encode_conn_param_rsp(struct pdu_data *pdu, void *param); +void helper_pdu_encode_conn_update_ind(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_terminate_ind(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_channel_map_update_ind(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_length_req(struct pdu_data *pdu, void *param); +void helper_pdu_encode_length_rsp(struct pdu_data *pdu, void *param); + +void helper_pdu_encode_cte_req(struct pdu_data *pdu, void *param); +void helper_pdu_encode_cte_rsp(struct pdu_data *pdu, void *param); +void helper_node_encode_cte_rsp(struct node_rx_pdu *rx, void *param); + +void helper_pdu_verify_ping_req(const char *file, uint32_t line, struct pdu_data *pdu, void *param); +void helper_pdu_verify_ping_rsp(const char *file, uint32_t line, struct pdu_data *pdu, void *param); + +void helper_pdu_verify_feature_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); +void helper_pdu_verify_slave_feature_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); +void helper_pdu_verify_feature_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_min_used_chans_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_version_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_enc_req(const char *file, uint32_t line, struct pdu_data *pdu, void *param); + +void helper_pdu_ntf_verify_enc_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_enc_rsp(const char *file, uint32_t line, struct pdu_data *pdu, void *param); + +void helper_pdu_verify_start_enc_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_start_enc_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_pause_enc_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_pause_enc_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_node_verify_enc_refresh(const char *file, uint32_t line, struct node_rx_pdu *rx, + void *param); + +void helper_pdu_verify_reject_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_reject_ext_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_phy_req(const char *file, uint32_t line, struct pdu_data *pdu, void *param); +void helper_pdu_verify_phy_rsp(const char *file, uint32_t line, struct pdu_data *pdu, void *param); +void helper_pdu_verify_phy_update_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); +void helper_pdu_verify_unknown_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_terminate_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_channel_map_update_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_node_verify_phy_update(const char *file, uint32_t line, struct node_rx_pdu *rx, + void *param); +void helper_pdu_verify_conn_param_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); +void helper_pdu_verify_conn_param_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); +void helper_pdu_verify_conn_update_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); +void helper_node_verify_conn_update(const char *file, uint32_t line, struct node_rx_pdu *rx, + void *param); + +void helper_pdu_verify_length_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); +void helper_pdu_verify_length_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param); + +void helper_pdu_verify_cte_req(const char *file, uint32_t line, struct pdu_data *pdu, void *param); +void helper_pdu_verify_cte_rsp(const char *file, uint32_t line, struct pdu_data *pdu, void *param); +void helper_node_verify_cte_rsp(const char *file, uint32_t line, struct node_rx_pdu *rx, + void *param); + +enum helper_pdu_opcode { + LL_VERSION_IND, + LL_LE_PING_REQ, + LL_LE_PING_RSP, + LL_FEATURE_REQ, + LL_PERIPH_FEAT_XCHG, + LL_FEATURE_RSP, + LL_MIN_USED_CHANS_IND, + LL_REJECT_IND, + LL_REJECT_EXT_IND, + LL_ENC_REQ, + LL_ENC_RSP, + LL_START_ENC_REQ, + LL_START_ENC_RSP, + LL_PAUSE_ENC_REQ, + LL_PAUSE_ENC_RSP, + LL_PHY_REQ, + LL_PHY_RSP, + LL_PHY_UPDATE_IND, + LL_UNKNOWN_RSP, + LL_CONNECTION_UPDATE_IND, + LL_CONNECTION_PARAM_REQ, + LL_CONNECTION_PARAM_RSP, + LL_TERMINATE_IND, + LL_CHAN_MAP_UPDATE_IND, + LL_LENGTH_REQ, + LL_LENGTH_RSP, + LL_CTE_REQ, + LL_CTE_RSP, +}; + +enum helper_node_opcode { + NODE_PHY_UPDATE, + NODE_CONN_UPDATE, + NODE_ENC_REFRESH, + NODE_CTE_RSP, +}; + +typedef void(helper_pdu_encode_func_t)(struct pdu_data *data, void *param); +typedef void(helper_pdu_verify_func_t)(const char *file, uint32_t line, struct pdu_data *data, + void *param); +typedef void(helper_pdu_ntf_verify_func_t)(const char *file, uint32_t line, struct pdu_data *data, + void *param); +typedef void(helper_node_encode_func_t)(struct node_rx_pdu *rx, void *param); +typedef void(helper_node_verify_func_t)(const char *file, uint32_t line, struct node_rx_pdu *rx, + void *param); diff --git a/tests/bluetooth/controller/common/include/helper_util.h b/tests/bluetooth/controller/common/include/helper_util.h new file mode 100644 index 00000000000..ba6faba0908 --- /dev/null +++ b/tests/bluetooth/controller/common/include/helper_util.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +void test_print_conn(struct ll_conn *conn); + +void test_set_role(struct ll_conn *conn, uint8_t role); +void test_setup(struct ll_conn *conn); +void event_prepare(struct ll_conn *conn); +void event_tx_ack(struct ll_conn *conn, struct node_tx *tx); +void event_done(struct ll_conn *conn); +uint16_t event_counter(struct ll_conn *conn); + +#define lt_tx(_opcode, _conn, _param) lt_tx_real(__FILE__, __LINE__, _opcode, _conn, _param) +#define lt_rx(_opcode, _conn, _tx_ref, _param) \ + lt_rx_real(__FILE__, __LINE__, _opcode, _conn, _tx_ref, _param) +#define lt_rx_q_is_empty(_conn) lt_rx_q_is_empty_real(__FILE__, __LINE__, _conn) + +#define ut_rx_pdu(_opcode, _ntf_ref, _param) \ + ut_rx_pdu_real(__FILE__, __LINE__, _opcode, _ntf_ref, _param) +#define ut_rx_node(_opcode, _ntf_ref, _param) \ + ut_rx_node_real(__FILE__, __LINE__, _opcode, _ntf_ref, _param) +#define ut_rx_q_is_empty() ut_rx_q_is_empty_real(__FILE__, __LINE__) + +void lt_tx_real(const char *file, uint32_t line, enum helper_pdu_opcode opcode, + struct ll_conn *conn, void *param); +void lt_rx_real(const char *file, uint32_t line, enum helper_pdu_opcode opcode, + struct ll_conn *conn, struct node_tx **tx_ref, void *param); +void lt_rx_q_is_empty_real(const char *file, uint32_t line, struct ll_conn *conn); +void ut_rx_pdu_real(const char *file, uint32_t line, enum helper_pdu_opcode opcode, + struct node_rx_pdu **ntf_ref, void *param); +void ut_rx_node_real(const char *file, uint32_t line, enum helper_node_opcode opcode, + struct node_rx_pdu **ntf_ref, void *param); +void ut_rx_q_is_empty_real(const char *file, uint32_t line); diff --git a/tests/bluetooth/controller/common/src/helper_pdu.c b/tests/bluetooth/controller/common/src/helper_pdu.c new file mode 100644 index 00000000000..88a0d895350 --- /dev/null +++ b/tests/bluetooth/controller/common/src/helper_pdu.c @@ -0,0 +1,951 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "zephyr/types.h" +#include "ztest.h" +#include "kconfig.h" + +#include +#include +#include +#include + +#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 "ll_feat.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 "helper_pdu.h" +#include "helper_features.h" + +#define PDU_MEM_EQUAL(_f, _s, _p, _t) \ + zassert_mem_equal(_s._f, _p->_f, sizeof(_p->_f), _t "\nCalled at %s:%d\n", file, line); + +void helper_pdu_encode_ping_req(struct pdu_data *pdu, void *param) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, ping_req) + + sizeof(struct pdu_data_llctrl_ping_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PING_REQ; +} + +void helper_pdu_encode_ping_rsp(struct pdu_data *pdu, void *param) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, ping_rsp) + + sizeof(struct pdu_data_llctrl_ping_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PING_RSP; +} + +void helper_pdu_encode_feature_req(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_feature_req *feature_req = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, feature_req) + + sizeof(struct pdu_data_llctrl_feature_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_FEATURE_REQ; + for (int counter = 0; counter < 8; counter++) { + uint8_t expected_value = feature_req->features[counter]; + + pdu->llctrl.feature_req.features[counter] = expected_value; + } +} +void helper_pdu_encode_slave_feature_req(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_feature_req *feature_req = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, feature_req) + + sizeof(struct pdu_data_llctrl_feature_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PER_INIT_FEAT_XCHG; + + for (int counter = 0; counter < 8; counter++) { + uint8_t expected_value = feature_req->features[counter]; + + pdu->llctrl.feature_req.features[counter] = expected_value; + } +} + +void helper_pdu_encode_feature_rsp(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_feature_rsp *feature_rsp = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, feature_rsp) + + sizeof(struct pdu_data_llctrl_feature_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_FEATURE_RSP; + for (int counter = 0; counter < 8; counter++) { + uint8_t expected_value = feature_rsp->features[counter]; + + pdu->llctrl.feature_req.features[counter] = expected_value; + } +} + +void helper_pdu_encode_min_used_chans_ind(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_min_used_chans_ind *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, min_used_chans_ind) + + sizeof(struct pdu_data_llctrl_min_used_chans_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_MIN_USED_CHAN_IND; + pdu->llctrl.min_used_chans_ind.phys = p->phys; + pdu->llctrl.min_used_chans_ind.min_used_chans = p->min_used_chans; +} + +void helper_pdu_encode_version_ind(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_version_ind *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, version_ind) + + sizeof(struct pdu_data_llctrl_version_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_VERSION_IND; + pdu->llctrl.version_ind.version_number = p->version_number; + pdu->llctrl.version_ind.company_id = p->company_id; + pdu->llctrl.version_ind.sub_version_number = p->sub_version_number; +} + +void helper_pdu_encode_enc_req(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_enc_req *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, enc_req) + sizeof(struct pdu_data_llctrl_enc_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_ENC_REQ; + memcpy(pdu->llctrl.enc_req.rand, p->rand, sizeof(pdu->llctrl.enc_req.rand)); + memcpy(pdu->llctrl.enc_req.ediv, p->ediv, sizeof(pdu->llctrl.enc_req.ediv)); + memcpy(pdu->llctrl.enc_req.skdm, p->skdm, sizeof(pdu->llctrl.enc_req.skdm)); + memcpy(pdu->llctrl.enc_req.ivm, p->ivm, sizeof(pdu->llctrl.enc_req.ivm)); +} + +void helper_pdu_encode_enc_rsp(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_enc_rsp *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, enc_rsp) + sizeof(struct pdu_data_llctrl_enc_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_ENC_RSP; + memcpy(pdu->llctrl.enc_rsp.skds, p->skds, sizeof(pdu->llctrl.enc_rsp.skds)); + memcpy(pdu->llctrl.enc_rsp.ivs, p->ivs, sizeof(pdu->llctrl.enc_rsp.ivs)); +} + +void helper_pdu_encode_start_enc_req(struct pdu_data *pdu, void *param) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, start_enc_req) + + sizeof(struct pdu_data_llctrl_start_enc_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_START_ENC_REQ; +} + +void helper_pdu_encode_start_enc_rsp(struct pdu_data *pdu, void *param) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, start_enc_rsp) + + sizeof(struct pdu_data_llctrl_start_enc_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_START_ENC_RSP; +} + +void helper_pdu_encode_pause_enc_req(struct pdu_data *pdu, void *param) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, pause_enc_req) + + sizeof(struct pdu_data_llctrl_pause_enc_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ; +} + +void helper_pdu_encode_pause_enc_rsp(struct pdu_data *pdu, void *param) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, pause_enc_rsp) + + sizeof(struct pdu_data_llctrl_pause_enc_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP; +} + +void helper_pdu_encode_reject_ext_ind(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_reject_ext_ind *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, reject_ext_ind) + + sizeof(struct pdu_data_llctrl_reject_ext_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND; + pdu->llctrl.reject_ext_ind.reject_opcode = p->reject_opcode; + pdu->llctrl.reject_ext_ind.error_code = p->error_code; +} + +void helper_pdu_encode_reject_ind(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_reject_ind *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, reject_ind) + + sizeof(struct pdu_data_llctrl_reject_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_REJECT_IND; + pdu->llctrl.reject_ind.error_code = p->error_code; +} + +void helper_pdu_encode_phy_req(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_phy_req *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, phy_req) + sizeof(struct pdu_data_llctrl_phy_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PHY_REQ; + pdu->llctrl.phy_req.rx_phys = p->rx_phys; + pdu->llctrl.phy_req.tx_phys = p->tx_phys; +} + +void helper_pdu_encode_phy_rsp(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_phy_rsp *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, phy_rsp) + sizeof(struct pdu_data_llctrl_phy_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PHY_RSP; + pdu->llctrl.phy_rsp.rx_phys = p->rx_phys; + pdu->llctrl.phy_rsp.tx_phys = p->tx_phys; +} + +void helper_pdu_encode_phy_update_ind(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_phy_upd_ind *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, phy_upd_ind) + + sizeof(struct pdu_data_llctrl_phy_upd_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND; + pdu->llctrl.phy_upd_ind.instant = p->instant; + pdu->llctrl.phy_upd_ind.c_to_p_phy = p->c_to_p_phy; + pdu->llctrl.phy_upd_ind.p_to_c_phy = p->p_to_c_phy; +} + +void helper_pdu_encode_unknown_rsp(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_unknown_rsp *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, unknown_rsp) + + sizeof(struct pdu_data_llctrl_unknown_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP; + pdu->llctrl.unknown_rsp.type = p->type; +} + +void helper_pdu_encode_conn_param_req(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_conn_param_req *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, conn_param_req) + + sizeof(struct pdu_data_llctrl_conn_param_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ; + + pdu->llctrl.conn_param_req.interval_min = sys_cpu_to_le16(p->interval_min); + pdu->llctrl.conn_param_req.interval_max = sys_cpu_to_le16(p->interval_max); + pdu->llctrl.conn_param_req.latency = sys_cpu_to_le16(p->latency); + pdu->llctrl.conn_param_req.timeout = sys_cpu_to_le16(p->timeout); + pdu->llctrl.conn_param_req.preferred_periodicity = p->preferred_periodicity; + pdu->llctrl.conn_param_req.reference_conn_event_count = + sys_cpu_to_le16(p->reference_conn_event_count); + pdu->llctrl.conn_param_req.offset0 = sys_cpu_to_le16(p->offset0); + pdu->llctrl.conn_param_req.offset1 = sys_cpu_to_le16(p->offset1); + pdu->llctrl.conn_param_req.offset2 = sys_cpu_to_le16(p->offset2); + pdu->llctrl.conn_param_req.offset3 = sys_cpu_to_le16(p->offset3); + pdu->llctrl.conn_param_req.offset4 = sys_cpu_to_le16(p->offset4); + pdu->llctrl.conn_param_req.offset5 = sys_cpu_to_le16(p->offset5); +} + +void helper_pdu_encode_conn_param_rsp(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_conn_param_rsp *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, conn_param_rsp) + + sizeof(struct pdu_data_llctrl_conn_param_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_RSP; + + pdu->llctrl.conn_param_rsp.interval_min = sys_cpu_to_le16(p->interval_min); + pdu->llctrl.conn_param_rsp.interval_max = sys_cpu_to_le16(p->interval_max); + pdu->llctrl.conn_param_rsp.latency = sys_cpu_to_le16(p->latency); + pdu->llctrl.conn_param_rsp.timeout = sys_cpu_to_le16(p->timeout); + pdu->llctrl.conn_param_rsp.preferred_periodicity = p->preferred_periodicity; + pdu->llctrl.conn_param_rsp.reference_conn_event_count = + sys_cpu_to_le16(p->reference_conn_event_count); + pdu->llctrl.conn_param_rsp.offset0 = sys_cpu_to_le16(p->offset0); + pdu->llctrl.conn_param_rsp.offset1 = sys_cpu_to_le16(p->offset1); + pdu->llctrl.conn_param_rsp.offset2 = sys_cpu_to_le16(p->offset2); + pdu->llctrl.conn_param_rsp.offset3 = sys_cpu_to_le16(p->offset3); + pdu->llctrl.conn_param_rsp.offset4 = sys_cpu_to_le16(p->offset4); + pdu->llctrl.conn_param_rsp.offset5 = sys_cpu_to_le16(p->offset5); +} + +void helper_pdu_encode_conn_update_ind(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_conn_update_ind *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, conn_update_ind) + + sizeof(struct pdu_data_llctrl_conn_update_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND; + + pdu->llctrl.conn_update_ind.win_size = p->win_size; + pdu->llctrl.conn_update_ind.win_offset = sys_cpu_to_le16(p->win_offset); + pdu->llctrl.conn_update_ind.interval = sys_cpu_to_le16(p->interval); + pdu->llctrl.conn_update_ind.latency = sys_cpu_to_le16(p->latency); + pdu->llctrl.conn_update_ind.timeout = sys_cpu_to_le16(p->timeout); + pdu->llctrl.conn_update_ind.instant = sys_cpu_to_le16(p->instant); +} + +void helper_pdu_encode_terminate_ind(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_terminate_ind *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, terminate_ind) + + sizeof(struct pdu_data_llctrl_terminate_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_TERMINATE_IND; + pdu->llctrl.terminate_ind.error_code = p->error_code; +} + +void helper_pdu_encode_channel_map_update_ind(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_chan_map_ind *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, chan_map_ind) + + sizeof(struct pdu_data_llctrl_chan_map_ind); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CHAN_MAP_IND; + pdu->llctrl.chan_map_ind.instant = p->instant; + memcpy(pdu->llctrl.chan_map_ind.chm, p->chm, sizeof(pdu->llctrl.chan_map_ind.chm)); +} + +void helper_pdu_encode_length_req(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_length_req *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, length_req) + + sizeof(struct pdu_data_llctrl_length_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_LENGTH_REQ; + + pdu->llctrl.length_req.max_rx_octets = p->max_rx_octets; + pdu->llctrl.length_req.max_tx_octets = p->max_tx_octets; + pdu->llctrl.length_req.max_rx_time = p->max_rx_time; + pdu->llctrl.length_req.max_tx_time = p->max_tx_time; +} + +void helper_pdu_encode_length_rsp(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_length_rsp *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = offsetof(struct pdu_data_llctrl, length_rsp) + + sizeof(struct pdu_data_llctrl_length_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_LENGTH_RSP; + + pdu->llctrl.length_req.max_rx_octets = p->max_rx_octets; + pdu->llctrl.length_req.max_tx_octets = p->max_tx_octets; + pdu->llctrl.length_req.max_rx_time = p->max_rx_time; + pdu->llctrl.length_req.max_tx_time = p->max_tx_time; +} +void helper_pdu_encode_cte_req(struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_cte_req *p = param; + + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, cte_req) + sizeof(struct pdu_data_llctrl_cte_req); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CTE_REQ; + pdu->llctrl.cte_req.min_cte_len_req = p->min_cte_len_req; + pdu->llctrl.cte_req.cte_type_req = p->cte_type_req; +} + +void helper_pdu_encode_cte_rsp(struct pdu_data *pdu, void *param) +{ + pdu->ll_id = PDU_DATA_LLID_CTRL; + pdu->len = + offsetof(struct pdu_data_llctrl, cte_rsp) + sizeof(struct pdu_data_llctrl_cte_rsp); + pdu->llctrl.opcode = PDU_DATA_LLCTRL_TYPE_CTE_RSP; +} + +void helper_node_encode_cte_rsp(struct node_rx_pdu *rx, void *param) +{ + rx->hdr.rx_ftr.iq_report = (struct cte_conn_iq_report *)param; +} + +void helper_pdu_verify_version_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_version_ind *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_VERSION_IND, + "Not a LL_VERSION_IND.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.version_ind.version_number, p->version_number, + "Wrong version number.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.version_ind.company_id, p->company_id, + "Wrong company id.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.version_ind.sub_version_number, p->sub_version_number, + "Wrong sub version number.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_ping_req(const char *file, uint32_t line, struct pdu_data *pdu, void *param) +{ + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_PING_REQ, + "Not a LL_PING_REQ. Called at %s:%d\n", file, line); +} + +void helper_pdu_verify_ping_rsp(const char *file, uint32_t line, struct pdu_data *pdu, void *param) +{ + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_PING_RSP, + "Not a LL_PING_RSP.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_feature_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_feature_req *feature_req = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, NULL); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_FEATURE_REQ, + "Wrong opcode.\nCalled at %s:%d\n", file, line); + + for (int counter = 0; counter < 8; counter++) { + uint8_t expected_value = feature_req->features[counter]; + + zassert_equal(pdu->llctrl.feature_req.features[counter], expected_value, + "Wrong feature exchange data.\nAt %s:%d\n", file, line); + } +} + +void helper_pdu_verify_slave_feature_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_feature_req *feature_req = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, NULL); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_PER_INIT_FEAT_XCHG, NULL); + + for (int counter = 0; counter < 8; counter++) { + uint8_t expected_value = feature_req->features[counter]; + + zassert_equal(pdu->llctrl.feature_req.features[counter], expected_value, + "Wrong feature data\nCalled at %s:%d\n", file, line); + } +} + +void helper_pdu_verify_feature_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_feature_rsp *feature_rsp = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, NULL); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_FEATURE_RSP, + "Response: %d Expected: %d\n", pdu->llctrl.opcode, + PDU_DATA_LLCTRL_TYPE_FEATURE_RSP); + + for (int counter = 0; counter < 8; counter++) { + uint8_t expected_value = feature_rsp->features[counter]; + + zassert_equal(pdu->llctrl.feature_rsp.features[counter], expected_value, + "Wrong feature data\nCalled at %s:%d\n", file, line); + } +} + +void helper_pdu_verify_min_used_chans_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_min_used_chans_ind *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_MIN_USED_CHAN_IND, + "Not a MIN_USED_CHAN_IND.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.min_used_chans_ind.phys, p->phys, "Wrong PHY.\nCalled at %s:%d\n", + file, line); + zassert_equal(pdu->llctrl.min_used_chans_ind.min_used_chans, p->min_used_chans, + "Channel count\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_enc_req(const char *file, uint32_t line, struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_enc_req *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_ENC_REQ, + "Not a LL_ENC_REQ. Called at %s:%d\n", file, line); + + PDU_MEM_EQUAL(rand, pdu->llctrl.enc_req, p, "Rand mismatch."); + PDU_MEM_EQUAL(ediv, pdu->llctrl.enc_req, p, "EDIV mismatch."); + PDU_MEM_EQUAL(skdm, pdu->llctrl.enc_req, p, "SKDm mismatch."); + PDU_MEM_EQUAL(ivm, pdu->llctrl.enc_req, p, "IVm mismatch."); +} + +void helper_pdu_ntf_verify_enc_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_enc_req *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_ENC_REQ, + "Not a LL_ENC_REQ. Called at %s:%d\n", file, line); + + PDU_MEM_EQUAL(rand, pdu->llctrl.enc_req, p, "Rand mismatch."); + PDU_MEM_EQUAL(ediv, pdu->llctrl.enc_req, p, "EDIV mismatch."); +} + +void helper_pdu_verify_enc_rsp(const char *file, uint32_t line, struct pdu_data *pdu, void *param) +{ + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_ENC_RSP, + "Not a LL_ENC_RSP.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_start_enc_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_START_ENC_REQ, + "Not a LL_START_ENC_REQ.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_start_enc_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_START_ENC_RSP, + "Not a LL_START_ENC_RSP.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_pause_enc_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ, + "Not a LL_PAUSE_ENC_REQ.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_pause_enc_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP, + "Not a LL_PAUSE_ENC_RSP.\nCalled at %s:%d\n", file, line); +} + +void helper_node_verify_enc_refresh(const char *file, uint32_t line, struct node_rx_pdu *rx, + void *param) +{ + zassert_equal(rx->hdr.type, NODE_RX_TYPE_ENC_REFRESH, + "Not an ENC_REFRESH node.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_reject_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_reject_ind *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, reject_ind) + + sizeof(struct pdu_data_llctrl_reject_ind), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_REJECT_IND, + "Not a LL_REJECT_IND.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.reject_ind.error_code, p->error_code, + "Error code mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_reject_ext_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_reject_ext_ind *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, reject_ext_ind) + + sizeof(struct pdu_data_llctrl_reject_ext_ind), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND, + "Not a LL_REJECT_EXT_IND.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.reject_ext_ind.reject_opcode, p->reject_opcode, + "Reject opcode mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.reject_ext_ind.error_code, p->error_code, + "Error code mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_phy_req(const char *file, uint32_t line, struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_phy_req *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, phy_req) + + sizeof(struct pdu_data_llctrl_phy_req), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_PHY_REQ, + "Not a LL_PHY_REQ.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.phy_req.rx_phys, p->rx_phys, + "rx phys mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.phy_req.tx_phys, p->tx_phys, + "tx phys mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_phy_rsp(const char *file, uint32_t line, struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_phy_rsp *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, phy_rsp) + + sizeof(struct pdu_data_llctrl_phy_rsp), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_PHY_RSP, + "Not a LL_PHY_RSP.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.phy_rsp.rx_phys, p->rx_phys, + "rx phys mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.phy_rsp.tx_phys, p->tx_phys, + "tx phys mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_phy_update_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_phy_upd_ind *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, phy_upd_ind) + + sizeof(struct pdu_data_llctrl_phy_upd_ind), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_PHY_UPD_IND, + "Not a LL_PHY_UPDATE_IND.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.phy_upd_ind.instant, p->instant, + "instant mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.phy_upd_ind.c_to_p_phy, p->c_to_p_phy, + "c_to_p_phy mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.phy_upd_ind.p_to_c_phy, p->p_to_c_phy, + "p_to_c_phy mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_node_verify_phy_update(const char *file, uint32_t line, struct node_rx_pdu *rx, + void *param) +{ + struct node_rx_pu *pdu = (struct node_rx_pu *)rx->pdu; + struct node_rx_pu *p = param; + + zassert_equal(rx->hdr.type, NODE_RX_TYPE_PHY_UPDATE, + "Not a PHY_UPDATE node.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->status, p->status, "Status mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_unknown_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_unknown_rsp *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, unknown_rsp) + + sizeof(struct pdu_data_llctrl_unknown_rsp), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_UNKNOWN_RSP, + "Not a LL_UNKNOWN_RSP.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.unknown_rsp.type, p->type, "Type mismatch.\nCalled at %s:%d\n", + file, line); +} + +void helper_pdu_verify_conn_param_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_conn_param_req *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, conn_param_req) + + sizeof(struct pdu_data_llctrl_conn_param_req), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ, + "Not a LL_CONNECTION_PARAM_REQ.\nCalled at %s:%d\n", file, line); + + zassert_equal(pdu->llctrl.conn_param_req.interval_min, p->interval_min, + "Interval_min mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_req.interval_max, p->interval_max, + "Interval_max mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_req.latency, p->latency, + "Latency mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_req.timeout, p->timeout, + "Timeout mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_req.preferred_periodicity, p->preferred_periodicity, + "Preferred_periodicity mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_req.reference_conn_event_count, + p->reference_conn_event_count, + "Reference_conn_event_count mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_req.offset0, p->offset0, + "Offset0 mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_req.offset1, p->offset1, + "Offset1 mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_req.offset2, p->offset2, + "Offset2 mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_req.offset3, p->offset3, + "Offset3 mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_req.offset4, p->offset4, + "Offset4 mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_req.offset5, p->offset5, + "Offset5 mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_conn_param_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_conn_param_rsp *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, conn_param_rsp) + + sizeof(struct pdu_data_llctrl_conn_param_rsp), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_CONN_PARAM_RSP, + "Not a LL_CONNECTION_PARAM_RSP.\nCalled at %s:%d\n", file, line); + + zassert_equal(pdu->llctrl.conn_param_rsp.interval_min, p->interval_min, + "Interval_min mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_rsp.interval_max, p->interval_max, + "Interval_max mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_rsp.latency, p->latency, + "Latency mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_rsp.timeout, p->timeout, + "Timeout mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_rsp.preferred_periodicity, p->preferred_periodicity, + "Preferred_periodicity mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_rsp.reference_conn_event_count, + p->reference_conn_event_count, + "Reference_conn_event_count mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_rsp.offset0, p->offset0, + "Offset0 mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_rsp.offset1, p->offset1, + "Offset1 mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_rsp.offset2, p->offset2, + "Offset2 mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_rsp.offset3, p->offset3, + "Offset3 mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_rsp.offset4, p->offset4, + "Offset4 mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_param_rsp.offset5, p->offset5, + "Offset5 mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_conn_update_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_conn_update_ind *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, conn_update_ind) + + sizeof(struct pdu_data_llctrl_conn_update_ind), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND, + "Not a LL_CONNECTION_UPDATE_IND.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_update_ind.win_size, p->win_size, + "Win_size mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_update_ind.win_offset, p->win_offset, + "Win_offset mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_update_ind.latency, p->latency, + "Latency.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_update_ind.interval, p->interval, + "Interval mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_update_ind.timeout, p->timeout, + "Timeout mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.conn_update_ind.instant, p->instant, + "Instant mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_node_verify_conn_update(const char *file, uint32_t line, struct node_rx_pdu *rx, + void *param) +{ + struct node_rx_pu *pdu = (struct node_rx_pu *)rx->pdu; + struct node_rx_pu *p = param; + + zassert_equal(rx->hdr.type, NODE_RX_TYPE_CONN_UPDATE, + "Not a CONN_UPDATE node.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->status, p->status, "Status mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_terminate_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_terminate_ind *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, terminate_ind) + + sizeof(struct pdu_data_llctrl_terminate_ind), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_TERMINATE_IND, + "Not a LL_TERMINATE_IND.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.terminate_ind.error_code, p->error_code, + "Error code mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_channel_map_update_ind(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_chan_map_ind *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_CHAN_MAP_IND, + "Not a LL_CHANNEL_MAP_UPDATE_IND.\nCalled at %s:%d ( %d %d)\n", file, line, + pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_CHAN_MAP_IND); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, chan_map_ind) + + sizeof(struct pdu_data_llctrl_chan_map_ind), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.chan_map_ind.instant, p->instant, + "Instant mismatch.\nCalled at %s:%d\n", file, line); + zassert_mem_equal(pdu->llctrl.chan_map_ind.chm, p->chm, sizeof(p->chm), + "Channel Map mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_length_req(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_length_req *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, length_req) + + sizeof(struct pdu_data_llctrl_length_req), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_LENGTH_REQ, + "Not a LL_LENGTH_REQ.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.length_req.max_rx_octets, p->max_rx_octets, + "max_rx_octets mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.length_req.max_tx_octets, p->max_tx_octets, + "max_tx_octets mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.length_req.max_rx_time, p->max_rx_time, + "max_rx_time mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.length_req.max_tx_time, p->max_tx_time, + "max_tx_time mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_length_rsp(const char *file, uint32_t line, struct pdu_data *pdu, + void *param) +{ + struct pdu_data_llctrl_length_rsp *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, length_rsp) + + sizeof(struct pdu_data_llctrl_length_rsp), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_LENGTH_RSP, + "Not a LL_LENGTH_RSP.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.length_rsp.max_rx_octets, p->max_rx_octets, + "max_rx_octets mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.length_rsp.max_tx_octets, p->max_tx_octets, + "max_tx_octets mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.length_rsp.max_rx_time, p->max_rx_time, + "max_rx_time mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.length_rsp.max_tx_time, p->max_tx_time, + "max_tx_time mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_cte_req(const char *file, uint32_t line, struct pdu_data *pdu, void *param) +{ + struct pdu_data_llctrl_cte_req *p = param; + + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_CTE_REQ, + "Not a LL_CTE_REQ.\nCalled at %s:%d ( %d %d)\n", file, line, + pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_CTE_REQ); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, cte_req) + + sizeof(struct pdu_data_llctrl_cte_req), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.cte_req.min_cte_len_req, p->min_cte_len_req, + "Minimal CTE length request mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.cte_req.cte_type_req, p->cte_type_req, + "CTE type request mismatch.\nCalled at %s:%d\n", file, line); +} + +void helper_pdu_verify_cte_rsp(const char *file, uint32_t line, struct pdu_data *pdu, void *param) +{ + zassert_equal(pdu->ll_id, PDU_DATA_LLID_CTRL, "Not a Control PDU.\nCalled at %s:%d\n", file, + line); + zassert_equal(pdu->len, + offsetof(struct pdu_data_llctrl, cte_rsp) + + sizeof(struct pdu_data_llctrl_cte_rsp), + "Wrong length.\nCalled at %s:%d\n", file, line); + zassert_equal(pdu->llctrl.opcode, PDU_DATA_LLCTRL_TYPE_CTE_RSP, + "Not a LL_CTE_RSP.\nCalled at %s:%d\n", file, line); +} + +void helper_node_verify_cte_rsp(const char *file, uint32_t line, struct node_rx_pdu *rx, + void *param) +{ + struct cte_conn_iq_report *p_iq_report = param; + struct cte_conn_iq_report *rx_iq_report = rx->hdr.rx_ftr.iq_report; + + zassert_equal(rx_iq_report->cte_info.time, p_iq_report->cte_info.time, + "CTE Time mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(rx_iq_report->local_slot_durations, p_iq_report->local_slot_durations, + "Slot duration mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(rx_iq_report->packet_status, p_iq_report->packet_status, + "Packet status mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(rx_iq_report->rssi_ant_id, p_iq_report->rssi_ant_id, + "RSSI antenna id mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(rx_iq_report->sample_count, p_iq_report->sample_count, + "Sample count mismatch.\nCalled at %s:%d\n", file, line); + zassert_equal(memcmp(rx_iq_report->sample, p_iq_report->sample, p_iq_report->sample_count), + 0, "IQ samples mismatch.\nCalled at %s:%d\n", file, line); +} diff --git a/tests/bluetooth/controller/common/src/helper_util.c b/tests/bluetooth/controller/common/src/helper_util.c new file mode 100644 index 00000000000..25036346d68 --- /dev/null +++ b/tests/bluetooth/controller/common/src/helper_util.c @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2020 Demant + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "zephyr/types.h" +#include "ztest.h" +#include +#include "kconfig.h" + +#include +#include +#include + +#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 "ll_feat.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_conn_internal.h" +#include "ull_llcp_internal.h" +#include "ull_llcp.h" + +#include "helper_pdu.h" +#include "helper_util.h" + +static uint32_t event_active; +static uint16_t lazy; +sys_slist_t ut_rx_q; +static sys_slist_t lt_tx_q; + +#define PDU_DC_LL_HEADER_SIZE (offsetof(struct pdu_data, lldata)) +#define NODE_RX_HEADER_SIZE (offsetof(struct node_rx_pdu, pdu)) +#define NODE_RX_STRUCT_OVERHEAD (NODE_RX_HEADER_SIZE) +#define PDU_DATA_SIZE (PDU_DC_LL_HEADER_SIZE + LL_LENGTH_OCTETS_RX_MAX) +#define PDU_RX_NODE_SIZE WB_UP(NODE_RX_STRUCT_OVERHEAD + PDU_DATA_SIZE) + +helper_pdu_encode_func_t *const helper_pdu_encode[] = { + [LL_VERSION_IND] = helper_pdu_encode_version_ind, + [LL_LE_PING_REQ] = helper_pdu_encode_ping_req, + [LL_LE_PING_RSP] = helper_pdu_encode_ping_rsp, + [LL_FEATURE_REQ] = helper_pdu_encode_feature_req, + [LL_PERIPH_FEAT_XCHG] = helper_pdu_encode_slave_feature_req, + [LL_FEATURE_RSP] = helper_pdu_encode_feature_rsp, + [LL_MIN_USED_CHANS_IND] = helper_pdu_encode_min_used_chans_ind, + [LL_REJECT_IND] = helper_pdu_encode_reject_ind, + [LL_REJECT_EXT_IND] = helper_pdu_encode_reject_ext_ind, + [LL_ENC_REQ] = helper_pdu_encode_enc_req, + [LL_ENC_RSP] = helper_pdu_encode_enc_rsp, + [LL_START_ENC_REQ] = helper_pdu_encode_start_enc_req, + [LL_START_ENC_RSP] = helper_pdu_encode_start_enc_rsp, + [LL_PAUSE_ENC_REQ] = helper_pdu_encode_pause_enc_req, + [LL_PAUSE_ENC_RSP] = helper_pdu_encode_pause_enc_rsp, + [LL_PHY_REQ] = helper_pdu_encode_phy_req, + [LL_PHY_RSP] = helper_pdu_encode_phy_rsp, + [LL_PHY_UPDATE_IND] = helper_pdu_encode_phy_update_ind, + [LL_UNKNOWN_RSP] = helper_pdu_encode_unknown_rsp, + [LL_CONNECTION_UPDATE_IND] = helper_pdu_encode_conn_update_ind, + [LL_CONNECTION_PARAM_REQ] = helper_pdu_encode_conn_param_req, + [LL_CONNECTION_PARAM_RSP] = helper_pdu_encode_conn_param_rsp, + [LL_TERMINATE_IND] = helper_pdu_encode_terminate_ind, + [LL_CHAN_MAP_UPDATE_IND] = helper_pdu_encode_channel_map_update_ind, + [LL_LENGTH_REQ] = helper_pdu_encode_length_req, + [LL_LENGTH_RSP] = helper_pdu_encode_length_rsp, + [LL_CTE_REQ] = helper_pdu_encode_cte_req, + [LL_CTE_RSP] = helper_pdu_encode_cte_rsp, +}; + +helper_pdu_verify_func_t *const helper_pdu_verify[] = { + [LL_VERSION_IND] = helper_pdu_verify_version_ind, + [LL_LE_PING_REQ] = helper_pdu_verify_ping_req, + [LL_LE_PING_RSP] = helper_pdu_verify_ping_rsp, + [LL_FEATURE_REQ] = helper_pdu_verify_feature_req, + [LL_PERIPH_FEAT_XCHG] = helper_pdu_verify_slave_feature_req, + [LL_FEATURE_RSP] = helper_pdu_verify_feature_rsp, + [LL_MIN_USED_CHANS_IND] = helper_pdu_verify_min_used_chans_ind, + [LL_REJECT_IND] = helper_pdu_verify_reject_ind, + [LL_REJECT_EXT_IND] = helper_pdu_verify_reject_ext_ind, + [LL_ENC_REQ] = helper_pdu_verify_enc_req, + [LL_ENC_RSP] = helper_pdu_verify_enc_rsp, + [LL_START_ENC_REQ] = helper_pdu_verify_start_enc_req, + [LL_START_ENC_RSP] = helper_pdu_verify_start_enc_rsp, + [LL_PAUSE_ENC_REQ] = helper_pdu_verify_pause_enc_req, + [LL_PAUSE_ENC_RSP] = helper_pdu_verify_pause_enc_rsp, + [LL_PHY_REQ] = helper_pdu_verify_phy_req, + [LL_PHY_RSP] = helper_pdu_verify_phy_rsp, + [LL_PHY_UPDATE_IND] = helper_pdu_verify_phy_update_ind, + [LL_UNKNOWN_RSP] = helper_pdu_verify_unknown_rsp, + [LL_CONNECTION_UPDATE_IND] = helper_pdu_verify_conn_update_ind, + [LL_CONNECTION_PARAM_REQ] = helper_pdu_verify_conn_param_req, + [LL_CONNECTION_PARAM_RSP] = helper_pdu_verify_conn_param_rsp, + [LL_TERMINATE_IND] = helper_pdu_verify_terminate_ind, + [LL_CHAN_MAP_UPDATE_IND] = helper_pdu_verify_channel_map_update_ind, + [LL_LENGTH_REQ] = helper_pdu_verify_length_req, + [LL_LENGTH_RSP] = helper_pdu_verify_length_rsp, + [LL_CTE_REQ] = helper_pdu_verify_cte_req, + [LL_CTE_RSP] = helper_pdu_verify_cte_rsp, +}; + +helper_pdu_ntf_verify_func_t *const helper_pdu_ntf_verify[] = { + [LL_VERSION_IND] = NULL, + [LL_LE_PING_REQ] = NULL, + [LL_LE_PING_RSP] = NULL, + [LL_FEATURE_REQ] = NULL, + [LL_PERIPH_FEAT_XCHG] = NULL, + [LL_FEATURE_RSP] = NULL, + [LL_MIN_USED_CHANS_IND] = NULL, + [LL_REJECT_IND] = NULL, + [LL_REJECT_EXT_IND] = NULL, + [LL_ENC_REQ] = helper_pdu_ntf_verify_enc_req, + [LL_ENC_RSP] = NULL, + [LL_START_ENC_REQ] = NULL, + [LL_START_ENC_RSP] = NULL, + [LL_PHY_REQ] = NULL, + [LL_PHY_RSP] = NULL, + [LL_PHY_UPDATE_IND] = NULL, + [LL_UNKNOWN_RSP] = NULL, + [LL_CONNECTION_UPDATE_IND] = NULL, + [LL_CONNECTION_PARAM_REQ] = NULL, + [LL_CONNECTION_PARAM_RSP] = NULL, + [LL_TERMINATE_IND] = NULL, + [LL_CHAN_MAP_UPDATE_IND] = NULL, + [LL_LENGTH_REQ] = NULL, + [LL_LENGTH_RSP] = NULL, + [LL_CTE_REQ] = NULL, + /* TODO (ppryga): Add verification for RSP notification */ + [LL_CTE_RSP] = NULL, +}; + +helper_node_encode_func_t *const helper_node_encode[] = { + [LL_VERSION_IND] = NULL, + [LL_LE_PING_REQ] = NULL, + [LL_LE_PING_RSP] = NULL, + [LL_FEATURE_REQ] = NULL, + [LL_PERIPH_FEAT_XCHG] = NULL, + [LL_FEATURE_RSP] = NULL, + [LL_MIN_USED_CHANS_IND] = NULL, + [LL_REJECT_IND] = NULL, + [LL_REJECT_EXT_IND] = NULL, + [LL_ENC_REQ] = NULL, + [LL_ENC_RSP] = NULL, + [LL_START_ENC_REQ] = NULL, + [LL_START_ENC_RSP] = NULL, + [LL_PHY_REQ] = NULL, + [LL_PHY_RSP] = NULL, + [LL_PHY_UPDATE_IND] = NULL, + [LL_UNKNOWN_RSP] = NULL, + [LL_CONNECTION_UPDATE_IND] = NULL, + [LL_CONNECTION_PARAM_REQ] = NULL, + [LL_CONNECTION_PARAM_RSP] = NULL, + [LL_TERMINATE_IND] = NULL, + [LL_CHAN_MAP_UPDATE_IND] = NULL, + [LL_CTE_REQ] = NULL, + [LL_CTE_RSP] = helper_node_encode_cte_rsp, +}; + +helper_node_verify_func_t *const helper_node_verify[] = { + [NODE_PHY_UPDATE] = helper_node_verify_phy_update, + [NODE_CONN_UPDATE] = helper_node_verify_conn_update, + [NODE_ENC_REFRESH] = helper_node_verify_enc_refresh, + [NODE_CTE_RSP] = helper_node_verify_cte_rsp, +}; + +/* + * for debugging purpose only + */ +void test_print_conn(struct ll_conn *conn) +{ + printf("------------------>\n"); + printf("Mock structure\n"); + printf(" Role: %d\n", conn->lll.role); + printf(" event-count: %d\n", conn->lll.event_counter); + printf("LLCP structure\n"); + printf(" Local state: %d\n", conn->llcp.local.state); + printf(" Remote state: %d\n", conn->llcp.remote.state); + printf(" Collision: %d\n", conn->llcp.remote.collision); + printf(" Reject: %d\n", conn->llcp.remote.reject_opcode); + printf("--------------------->\n"); +} + +void test_setup(struct ll_conn *conn) +{ + ull_conn_init(); + + /**/ + memset(conn, 0x00, sizeof(*conn)); + + /* Initialize the upper test rx queue */ + sys_slist_init(&ut_rx_q); + + /* Initialize the lower tester tx queue */ + sys_slist_init(<_tx_q); + + /* Initialize the control procedure code */ + ull_cp_init(); + + /* Initialize the ULL TX Q */ + ull_tx_q_init(&conn->tx_q); + + /* Initialize the connection object */ + ull_llcp_init(conn); + + ll_reset(); + conn->lll.event_counter = 0; + event_active = 0; + lazy = 0; +} + + +void test_set_role(struct ll_conn *conn, uint8_t role) +{ + conn->lll.role = role; +} + +void event_prepare(struct ll_conn *conn) +{ + struct lll_conn *lll; + + /* Can only be called with no active event */ + zassert_equal(event_active, 0, "Called inside an active event"); + event_active = 1; + + /*** ULL Prepare ***/ + + /* Handle any LL Control Procedures */ + ull_cp_run(conn); + + /*** LLL Prepare ***/ + lll = &conn->lll; + + /* Save the latency for use in event */ + lll->latency_prepare += lll->latency; + + /* Calc current event counter value */ + uint16_t event_counter = lll->event_counter + lll->latency_prepare; + + /* Store the next event counter value */ + lll->event_counter = event_counter + 1; + + lll->latency_prepare = 0; + + /* Rest lazy */ + lazy = 0; +} + +void event_tx_ack(struct ll_conn *conn, struct node_tx *tx) +{ + /* Can only be called with active event */ + zassert_equal(event_active, 1, "Called outside an active event"); + + ull_cp_tx_ack(conn, tx); +} + +void event_done(struct ll_conn *conn) +{ + struct node_rx_pdu *rx; + + /* Can only be called with active event */ + zassert_equal(event_active, 1, "Called outside an active event"); + event_active = 0; + + while ((rx = (struct node_rx_pdu *)sys_slist_get(<_tx_q))) { + ull_cp_rx(conn, rx); + free(rx); + } +} + +uint16_t event_counter(struct ll_conn *conn) +{ + /* TODO(thoh): Mocked lll_conn */ + struct lll_conn *lll; + uint16_t event_counter; + + /**/ + lll = &conn->lll; + + /* Calculate current event counter */ + event_counter = lll->event_counter + lll->latency_prepare + lazy; + + /* If event_counter is called inside an event_prepare()/event_done() pair + * return the current event counter value (i.e. -1); + * otherwise return the next event counter value + */ + if (event_active) + event_counter--; + + return event_counter; +} + +void lt_tx_real(const char *file, uint32_t line, enum helper_pdu_opcode opcode, + struct ll_conn *conn, void *param) +{ + struct pdu_data *pdu; + struct node_rx_pdu *rx; + + rx = malloc(PDU_RX_NODE_SIZE); + zassert_not_null(rx, "Out of memory.\nCalled at %s:%d\n", file, line); + + /* Encode node_rx_pdu if required by particular procedure */ + if (helper_node_encode[opcode]) { + helper_node_encode[opcode](rx, param); + } + + pdu = (struct pdu_data *)rx->pdu; + zassert_not_null(helper_pdu_encode[opcode], "PDU encode function cannot be NULL\n"); + helper_pdu_encode[opcode](pdu, param); + + sys_slist_append(<_tx_q, (sys_snode_t *)rx); +} + +void lt_rx_real(const char *file, uint32_t line, enum helper_pdu_opcode opcode, + struct ll_conn *conn, struct node_tx **tx_ref, void *param) +{ + struct node_tx *tx; + struct pdu_data *pdu; + + tx = ull_tx_q_dequeue(&conn->tx_q); + + zassert_not_null(tx, "Tx Q empty.\nCalled at %s:%d\n", file, line); + + pdu = (struct pdu_data *)tx->pdu; + if (helper_pdu_verify[opcode]) { + helper_pdu_verify[opcode](file, line, pdu, param); + } + + *tx_ref = tx; +} + +void lt_rx_q_is_empty_real(const char *file, uint32_t line, struct ll_conn *conn) +{ + struct node_tx *tx; + + tx = ull_tx_q_dequeue(&conn->tx_q); + zassert_is_null(tx, "Tx Q not empty.\nCalled at %s:%d\n", file, line); +} + +void ut_rx_pdu_real(const char *file, uint32_t line, enum helper_pdu_opcode opcode, + struct node_rx_pdu **ntf_ref, void *param) +{ + struct pdu_data *pdu; + struct node_rx_pdu *ntf; + + ntf = (struct node_rx_pdu *)sys_slist_get(&ut_rx_q); + zassert_not_null(ntf, "Ntf Q empty.\nCalled at %s:%d\n", file, line); + + zassert_equal(ntf->hdr.type, NODE_RX_TYPE_DC_PDU, + "Ntf node is of the wrong type.\nCalled at %s:%d\n", file, line); + + pdu = (struct pdu_data *)ntf->pdu; + if (helper_pdu_ntf_verify[opcode]) { + helper_pdu_ntf_verify[opcode](file, line, pdu, param); + } else if (helper_pdu_verify[opcode]) { + helper_pdu_verify[opcode](file, line, pdu, param); + } + + *ntf_ref = ntf; +} + +void ut_rx_node_real(const char *file, uint32_t line, enum helper_node_opcode opcode, + struct node_rx_pdu **ntf_ref, void *param) +{ + struct node_rx_pdu *ntf; + + ntf = (struct node_rx_pdu *)sys_slist_get(&ut_rx_q); + zassert_not_null(ntf, "Ntf Q empty.\nCalled at %s:%d\n", file, line); + + zassert_not_equal(ntf->hdr.type, NODE_RX_TYPE_DC_PDU, + "Ntf node is of the wrong type.\nCalled at %s:%d\n", file, line); + + if (helper_node_verify[opcode]) { + helper_node_verify[opcode](file, line, ntf, param); + } + + *ntf_ref = ntf; +} + +void ut_rx_q_is_empty_real(const char *file, uint32_t line) +{ + struct node_rx_pdu *ntf; + + ntf = (struct node_rx_pdu *)sys_slist_get(&ut_rx_q); + zassert_is_null(ntf, "Ntf Q not empty.\nCalled at %s:%d\n", file, line); +} diff --git a/tests/bluetooth/controller/ctrl_api/CMakeLists.txt b/tests/bluetooth/controller/ctrl_api/CMakeLists.txt new file mode 100644 index 00000000000..d25798f7bcc --- /dev/null +++ b/tests/bluetooth/controller/ctrl_api/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_api) +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_api/src/main.c b/tests/bluetooth/controller/ctrl_api/src/main.c new file mode 100644 index 00000000000..e83d42fc1a7 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_api/src/main.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "kconfig.h" + +/* Kconfig Cheats */ + +#include +#include +#include +#include +#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 "ll_feat.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_llcp_internal.h" + +#include "helper_pdu.h" +#include "helper_util.h" + +struct ll_conn conn; + +/* + * Note API and internal test are not yet split out here + */ + +void test_api_init(void) +{ + ull_cp_init(); + ull_tx_q_init(&conn.tx_q); + + ull_llcp_init(&conn); + + zassert_true(lr_is_disconnected(&conn), NULL); + zassert_true(rr_is_disconnected(&conn), NULL); +} + +extern void test_int_mem_proc_ctx(void); +extern void test_int_mem_tx(void); +extern void test_int_create_proc(void); +extern void test_int_local_pending_requests(void); +extern void test_int_remote_pending_requests(void); + +void test_api_connect(void) +{ + ull_cp_init(); + ull_tx_q_init(&conn.tx_q); + ull_llcp_init(&conn); + + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + zassert_true(lr_is_idle(&conn), NULL); + zassert_true(rr_is_idle(&conn), NULL); +} + +void test_api_disconnect(void) +{ + ull_cp_init(); + ull_tx_q_init(&conn.tx_q); + ull_llcp_init(&conn); + + ull_cp_state_set(&conn, ULL_CP_DISCONNECTED); + zassert_true(lr_is_disconnected(&conn), NULL); + zassert_true(rr_is_disconnected(&conn), NULL); + + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + zassert_true(lr_is_idle(&conn), NULL); + zassert_true(rr_is_idle(&conn), NULL); + + ull_cp_state_set(&conn, ULL_CP_DISCONNECTED); + zassert_true(lr_is_disconnected(&conn), NULL); + zassert_true(rr_is_disconnected(&conn), NULL); +} + +void test_int_disconnect_loc(void) +{ + uint64_t err; + int nr_free_ctx; + struct node_tx *tx; + struct ll_conn conn; + + struct pdu_data_llctrl_version_ind local_version_ind = { + .version_number = LL_VERSION_NUMBER, + .company_id = CONFIG_BT_CTLR_COMPANY_ID, + .sub_version_number = CONFIG_BT_CTLR_SUBVERSION_NUMBER, + }; + + test_setup(&conn); + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + nr_free_ctx = ctx_buffers_free(); + zassert_equal(nr_free_ctx, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, NULL); + + err = ull_cp_version_exchange(&conn); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + nr_free_ctx = ctx_buffers_free(); + zassert_equal(nr_free_ctx, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM - 1, NULL); + + event_prepare(&conn); + lt_rx(LL_VERSION_IND, &conn, &tx, &local_version_ind); + lt_rx_q_is_empty(&conn); + event_done(&conn); + + /* + * Now we disconnect before getting a response + */ + ull_cp_state_set(&conn, ULL_CP_DISCONNECTED); + + nr_free_ctx = ctx_buffers_free(); + zassert_equal(nr_free_ctx, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, NULL); + + ut_rx_q_is_empty(); + + /* + * nothing should happen when running a new event + */ + event_prepare(&conn); + event_done(&conn); + + nr_free_ctx = ctx_buffers_free(); + zassert_equal(nr_free_ctx, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, NULL); + + /* + * all buffers should still be empty + */ + lt_rx_q_is_empty(&conn); + ut_rx_q_is_empty(); +} + +void test_int_disconnect_rem(void) +{ + int nr_free_ctx; + struct pdu_data_llctrl_version_ind remote_version_ind = { + .version_number = 0x55, + .company_id = 0xABCD, + .sub_version_number = 0x1234, + }; + struct ll_conn conn; + + test_setup(&conn); + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + nr_free_ctx = ctx_buffers_free(); + zassert_equal(nr_free_ctx, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, NULL); + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + lt_tx(LL_VERSION_IND, &conn, &remote_version_ind); + + nr_free_ctx = ctx_buffers_free(); + zassert_equal(nr_free_ctx, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, NULL); + + /* Disconnect before we reply */ + + /* Done */ + event_done(&conn); + + ull_cp_state_set(&conn, ULL_CP_DISCONNECTED); + + /* Prepare */ + event_prepare(&conn); + + /* Done */ + event_done(&conn); + + nr_free_ctx = ctx_buffers_free(); + zassert_equal(nr_free_ctx, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, NULL); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); +} + +void test_main(void) +{ + ztest_test_suite(internal, ztest_unit_test(test_int_mem_proc_ctx), + ztest_unit_test(test_int_mem_tx), ztest_unit_test(test_int_create_proc), + ztest_unit_test(test_int_local_pending_requests), + ztest_unit_test(test_int_remote_pending_requests), + ztest_unit_test(test_int_disconnect_loc), + ztest_unit_test(test_int_disconnect_rem)); + + ztest_test_suite(public, ztest_unit_test(test_api_init), ztest_unit_test(test_api_connect), + ztest_unit_test(test_api_disconnect)); + + ztest_run_test_suite(internal); + ztest_run_test_suite(public); +} diff --git a/tests/bluetooth/controller/ctrl_api/testcase.yaml b/tests/bluetooth/controller/ctrl_api/testcase.yaml new file mode 100644 index 00000000000..f65b74faa8c --- /dev/null +++ b/tests/bluetooth/controller/ctrl_api/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: test_framework bluetooth bt_api bt_ull_llcp +tests: + bluetooth.controller.ctrl_api.test: + type: unit diff --git a/tests/bluetooth/controller/ctrl_chmu/CMakeLists.txt b/tests/bluetooth/controller/ctrl_chmu/CMakeLists.txt new file mode 100644 index 00000000000..e369c939287 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_chmu/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_ctrl_ull_conn) +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_chmu/src/main.c b/tests/bluetooth/controller/ctrl_chmu/src/main.c new file mode 100644 index 00000000000..6b95c1b13c9 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_chmu/src/main.c @@ -0,0 +1,228 @@ +/* + * 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 "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" + +static struct ll_conn conn; + +static void setup(void) +{ + test_setup(&conn); +} + +static bool is_instant_reached(struct ll_conn *conn, uint16_t instant) +{ + return ((event_counter(conn) - instant) & 0xFFFF) <= 0x7FFF; +} + +void test_channel_map_update_mas_loc(void) +{ + uint8_t chm[5] = { 0x00, 0x04, 0x05, 0x06, 0x00 }; + /* TODO should test setup set this to valid value? */ + uint8_t defchm[5] = {}; + uint8_t err; + struct node_tx *tx; + struct pdu_data *pdu; + uint16_t instant; + struct pdu_data_llctrl_chan_map_ind chmu_ind = { + .instant = 6, + .chm = { 0x00, 0x04, 0x05, 0x06, 0x00 }, + }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + err = ull_cp_chan_map_update(&conn, chm); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CHAN_MAP_UPDATE_IND, &conn, &tx, &chmu_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.chan_map_ind.instant); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* spin conn events */ + 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(); + + /* check if using old channel map */ + zassert_mem_equal(conn.lll.data_chan_map, defchm, sizeof(conn.lll.data_chan_map), + "Channel map invalid"); + } + + /* 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 no host notification */ + ut_rx_q_is_empty(); + + /* at this point new channel map shall be in use */ + zassert_mem_equal(conn.lll.data_chan_map, chm, sizeof(conn.lll.data_chan_map), + "Channel map invalid"); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_channel_map_update_sla_rem(void) +{ + uint8_t chm[5] = { 0x00, 0x04, 0x05, 0x06, 0x00 }; + /* TODO should test setup set this to valid value? */ + uint8_t defchm[5] = {}; + struct pdu_data_llctrl_chan_map_ind chmu_ind = { + .instant = 6, + .chm = { 0x00, 0x04, 0x05, 0x06, 0x00 }, + }; + uint16_t instant = 6; + + /* 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_CHAN_MAP_UPDATE_IND, &conn, &chmu_ind); + + /* Done */ + event_done(&conn); + + /* spin conn events */ + 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(); + + /* check if using old channel map */ + zassert_mem_equal(conn.lll.data_chan_map, defchm, sizeof(conn.lll.data_chan_map), + "Channel map invalid"); + } + + /* 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 no host notification */ + ut_rx_q_is_empty(); + + /* at this point new channel map shall be in use */ + zassert_mem_equal(conn.lll.data_chan_map, chm, sizeof(conn.lll.data_chan_map), + "Channel map invalid"); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_channel_map_update_sla_loc(void) +{ + uint8_t err; + uint8_t chm[5] = { 0x00, 0x06, 0x06, 0x06, 0x00 }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + err = ull_cp_chan_map_update(&conn, chm); + zassert_equal(err, BT_HCI_ERR_CMD_DISALLOWED, NULL); + + 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(chmu, + ztest_unit_test_setup_teardown(test_channel_map_update_mas_loc, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_channel_map_update_sla_rem, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_channel_map_update_sla_loc, setup, + unit_test_noop)); + + ztest_run_test_suite(chmu); +} diff --git a/tests/bluetooth/controller/ctrl_chmu/testcase.yaml b/tests/bluetooth/controller/ctrl_chmu/testcase.yaml new file mode 100644 index 00000000000..636061b8e38 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_chmu/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: test_framework bluetooth bt_chmu bt_ull_llcp +tests: + bluetooth.controller.ctrl_chmu.test: + type: unit diff --git a/tests/bluetooth/controller/ctrl_conn_update/CMakeLists.txt b/tests/bluetooth/controller/ctrl_conn_update/CMakeLists.txt new file mode 100644 index 00000000000..e369c939287 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_conn_update/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_ctrl_ull_conn) +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_conn_update/src/kconfig_override.h b/tests/bluetooth/controller/ctrl_conn_update/src/kconfig_override.h new file mode 100644 index 00000000000..1d325e6349e --- /dev/null +++ b/tests/bluetooth/controller/ctrl_conn_update/src/kconfig_override.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2021 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Override common Kconfig settings + */ + +#ifdef CONFIG_BT_CTLR_CONN_PARAM_REQ +#undef CONFIG_BT_CTLR_CONN_PARAM_REQ +#endif diff --git a/tests/bluetooth/controller/ctrl_conn_update/src/main.c b/tests/bluetooth/controller/ctrl_conn_update/src/main.c new file mode 100644 index 00000000000..958281a38a3 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_conn_update/src/main.c @@ -0,0 +1,2653 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#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 "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" + +/* Default connection values */ +#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) */ + +/* Default conn_update_ind PDU */ +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 }; + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +/* Default conn_param_req PDU */ +struct pdu_data_llctrl_conn_param_req conn_param_req = { .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 }; + +/* Default conn_param_rsp PDU */ +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 }; + +/* Different PDU contents for (B) */ + +/* Default conn_param_req PDU (B) */ +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 +}; + +/* Default conn_param_rsp PDU (B) */ +struct pdu_data_llctrl_conn_param_rsp conn_param_rsp_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 +}; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + +/* Default conn_update_ind PDU (B) */ +struct pdu_data_llctrl_conn_update_ind conn_update_ind_B = { + .win_size = 1U, + .win_offset = 0U, + .interval = INTVL_MAX, + .latency = LATENCY + 1U, /* differentiate parameter */ + .timeout = TIMEOUT + 1U, /* differentiate parameter */ + .instant = 6U +}; + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +struct pdu_data_llctrl_conn_param_req *req_B = &conn_param_req_B; +struct pdu_data_llctrl_conn_param_rsp *rsp_B = &conn_param_rsp_B; +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + +struct pdu_data_llctrl_conn_update_ind *cu_ind_B = &conn_update_ind_B; + +static struct ll_conn conn; + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +static void test_unmask_feature_conn_param_req(struct ll_conn *conn) +{ + conn->llcp.fex.features_used &= ~BIT64(BT_LE_FEAT_BIT_CONN_PARAM_REQ); +} + +static bool test_get_feature_conn_param_req(struct ll_conn *conn) +{ + return (conn->llcp.fex.features_used & BIT64(BT_LE_FEAT_BIT_CONN_PARAM_REQ)); +} +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + +static void setup(void) +{ + test_setup(&conn); + + /* Initialize lll conn parameters (different from new) */ + struct lll_conn *lll = &conn.lll; + + lll->interval = 0; + lll->latency = 0; + conn.supervision_reload = 1U; +} + +static bool is_instant_reached(struct ll_conn *conn, uint16_t instant) +{ + return ((event_counter(conn) - instant) & 0xFFFF) <= 0x7FFF; +} + +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) +/* + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Host accepts. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | LL_CONNECTION_PARAM_REQ | + * | |-------------------------->| + * | | | + * | | LL_CONNECTION_PARAM_RSP | + * | |<--------------------------| + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |-------------------------->| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_mas_loc_accept(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + uint16_t instant; + + struct node_rx_pu cu = { .status = BT_HCI_ERR_SUCCESS }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, &conn_param_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_CONNECTION_PARAM_RSP, &conn, &conn_param_rsp); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + conn_update_ind.instant = event_counter(&conn) + 6U; + lt_rx(LL_CONNECTION_UPDATE_IND, &conn, &tx, &conn_update_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.conn_update_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_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Host rejects. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | LL_CONNECTION_PARAM_REQ | + * | |-------------------------->| + * | | | + * | | LL_REJECT_EXT_IND | + * | |<--------------------------| + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_mas_loc_reject(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ, + .error_code = BT_HCI_ERR_UNACCEPT_CONN_PARAM + }; + + struct node_rx_pu cu = { .status = BT_HCI_ERR_UNACCEPT_CONN_PARAM }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, &conn_param_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_REJECT_EXT_IND, &conn, &reject_ext_ind); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* There should be one host notification */ + ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Host is legacy. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | LL_CONNECTION_PARAM_REQ | + * | |-------------------------->| + * | | | + * | | LL_REJECT_EXT_IND | + * | |<--------------------------| + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |-------------------------->| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_mas_loc_remote_legacy(void) +{ + bool feature_bit_param_req; + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + uint16_t instant; + + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ, + .error_code = BT_HCI_ERR_UNSUPP_REMOTE_FEATURE + }; + + struct node_rx_pu cu = { .status = BT_HCI_ERR_SUCCESS }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, &conn_param_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_REJECT_EXT_IND, &conn, &reject_ext_ind); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Prepare */ + event_prepare(&conn); + + /* Check that feature Param Reg. is unmasked */ + feature_bit_param_req = test_get_feature_conn_param_req(&conn); + zassert_equal(feature_bit_param_req, false, "Feature bit not unmasked"); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_UPDATE_IND, &conn, &tx, &conn_update_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.conn_update_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_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Controller do not + * support Connection Parameters Request procedure, features not exchanged. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | LL_CONNECTION_PARAM_REQ | + * | |-------------------------->| + * | | | + * | | LL_UNKNOWN_RSP | + * | |<--------------------------| + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |-------------------------->| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_mas_loc_unsupp_wo_feat_exch(void) +{ + bool feature_bit_param_req; + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + uint16_t instant; + + struct pdu_data_llctrl_unknown_rsp unknown_rsp = { + .type = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ + }; + + struct node_rx_pu cu = { .status = BT_HCI_ERR_SUCCESS }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, &conn_param_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_UNKNOWN_RSP, &conn, &unknown_rsp); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Prepare */ + event_prepare(&conn); + + /* Check that feature Param Reg. is unmasked */ + feature_bit_param_req = test_get_feature_conn_param_req(&conn); + zassert_equal(feature_bit_param_req, false, "Feature bit not unmasked"); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_UPDATE_IND, &conn, &tx, &conn_update_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.conn_update_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_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Controller do not + * support Connection Parameters Request procedure, features exchanged. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | LL_CONNECTION_UPDATE_IND | + * | |-------------------------->| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_mas_loc_unsupp_w_feat_exch(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + uint16_t instant; + + struct node_rx_pu cu = { .status = BT_HCI_ERR_SUCCESS }; + + /* Disable feature */ + test_unmask_feature_conn_param_req(&conn); + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + conn_update_ind.instant = event_counter(&conn) + 6U; + lt_rx(LL_CONNECTION_UPDATE_IND, &conn, &tx, &conn_update_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.conn_update_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_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * (A) + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Host accepts. + * + * and + * + * (B) + * Slave-initiated Connection Parameters Request procedure. + * Procedure collides and is rejected. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | LL_CONNECTION_PARAM_REQ | (A) + * | |-------------------------->| + * | | | + * | | LL_CONNECTION_PARAM_REQ | (B) + * | |<--------------------------| + * | | | + * | <---------------------> | + * | < PROCEDURE COLLISION > | + * | <---------------------> | + * | | | + * | | LL_REJECT_EXT_IND | (B) + * | |-------------------------->| + * | | | + * | | LL_CONNECTION_PARAM_RSP | (A) + * | |<--------------------------| + * | | | + * | | LL_CONNECTION_UPDATE_IND | (A) + * | |-------------------------->| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_mas_loc_collision(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + uint16_t instant; + + struct node_rx_pu cu = { .status = BT_HCI_ERR_SUCCESS }; + + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ, + .error_code = BT_HCI_ERR_LL_PROC_COLLISION + }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* (A) Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* (A) Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, &conn_param_req); + lt_rx_q_is_empty(&conn); + + /* (B) Rx */ + lt_tx(LL_CONNECTION_PARAM_REQ, &conn, req_B); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /**/ + + /* Prepare */ + event_prepare(&conn); + + /* (B) Tx Queue should have one LL Control PDU */ + lt_rx(LL_REJECT_EXT_IND, &conn, &tx, &reject_ext_ind); + lt_rx_q_is_empty(&conn); + + /* 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_CONNECTION_PARAM_RSP, &conn, &conn_param_rsp); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* (A) Tx Queue should have one LL Control PDU */ + conn_update_ind.instant = event_counter(&conn) + 6U; + lt_rx(LL_CONNECTION_UPDATE_IND, &conn, &tx, &conn_update_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.conn_update_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_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * Slave-initiated Connection Parameters Request procedure. + * Slave requests change in LE connection parameters, master’s Host accepts. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_CONNECTION_PARAM_REQ | + * | |<--------------------------| + * | | | + * | LE Remote Connection | | + * | Parameter Request | | + * |<--------------------------| | + * | LE Remote Connection | | + * | Parameter Request | | + * | Reply | | + * |-------------------------->| | + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |-------------------------->| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_mas_rem_accept(void) +{ + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + uint16_t instant; + + struct node_rx_pu cu = { .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_CONNECTION_PARAM_REQ, &conn, &conn_param_req); + + /* Done */ + event_done(&conn); + + /*******************/ + + /* There should be one host notification */ + ut_rx_pdu(LL_CONNECTION_PARAM_REQ, &ntf, &conn_param_req); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /*******************/ + + ull_cp_conn_param_req_reply(&conn); + + /*******************/ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + conn_update_ind.instant = event_counter(&conn) + 6U; + lt_rx(LL_CONNECTION_UPDATE_IND, &conn, &tx, &conn_update_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.conn_update_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_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * Slave-initiated Connection Parameters Request procedure. + * Slave requests change in LE connection parameters, master’s Host rejects. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_CONNECTION_PARAM_REQ | + * | |<--------------------------| + * | | | + * | LE Remote Connection | | + * | Parameter Request | | + * |<--------------------------| | + * | LE Remote Connection | | + * | Parameter Request | | + * | Negative Reply | | + * |-------------------------->| | + * | | | + * | | LL_REJECT_EXT_IND | + * | |-------------------------->| + * | | | + */ +void test_conn_update_mas_rem_reject(void) +{ + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ, + .error_code = BT_HCI_ERR_UNACCEPT_CONN_PARAM + }; + + /* 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_CONNECTION_PARAM_REQ, &conn, &conn_param_req); + + /* Done */ + event_done(&conn); + + /*******************/ + + /* There should be one host notification */ + ut_rx_pdu(LL_CONNECTION_PARAM_REQ, &ntf, &conn_param_req); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /*******************/ + + ull_cp_conn_param_req_neg_reply(&conn, BT_HCI_ERR_UNACCEPT_CONN_PARAM); + + /*******************/ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_REJECT_EXT_IND, &conn, &tx, &reject_ext_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* Slave-initiated Connection Parameters Request procedure. + * Slave requests change in LE connection parameters, master’s Controller do not + * support Connection Parameters Request procedure. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_CONNECTION_PARAM_REQ | + * | |<--------------------------| + * | | | + * | | LL_UNKNOWN_RSP | + * | |-------------------------->| + * | | | + */ +void test_conn_update_mas_rem_unsupp_feat(void) +{ + /* TODO(thoh): Implement when Remote Request machine has feature + * checking + */ +} + +/* + * (A) + * Slave-initiated Connection Parameters Request procedure. + * Slave requests change in LE connection parameters, master’s Host accepts. + * + * and + * + * (B) + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Host accepts. + * + * NOTE: + * Master-initiated Connection Parameters Request procedure is paused. + * Slave-initiated Connection Parameters Request procedure is finished. + * Master-initiated Connection Parameters Request procedure is resumed. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_CONNECTION_PARAM_REQ | + * | |<--------------------------| (A) + * | | | + * | LE Connection Update | | + * |-------------------------->| | (B) + * | | | + * | <------------------------> | + * | < LOCAL PROCEDURE PAUSED > | + * | <------------------------> | + * | | | + * | LE Remote Connection | | + * | Parameter Request | | + * |<--------------------------| | (A) + * | LE Remote Connection | | + * | Parameter Request | | + * | Reply | | + * |-------------------------->| | (A) + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |-------------------------->| (A) + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | (A) + * | | | + * | <-------------------------> | + * | < LOCAL PROCEDURE RESUMED > | + * | <-------------------------> | + * | | | + * | | LL_CONNECTION_PARAM_REQ | + * | |-------------------------->| (B) + * | | | + * | | LL_CONNECTION_PARAM_RSP | + * | |<--------------------------| (B) + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |-------------------------->| (B) + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | (B) + * | | | + */ +void test_conn_update_mas_rem_collision(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + uint16_t instant; + + struct node_rx_pu cu = { .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); + + /* (A) Rx */ + lt_tx(LL_CONNECTION_PARAM_REQ, &conn, &conn_param_req); + + /* Done */ + event_done(&conn); + + /*******************/ + + /* (B) Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, req_B->interval_min, req_B->interval_max, req_B->latency, + req_B->timeout); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* (B) 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_pdu(LL_CONNECTION_PARAM_REQ, &ntf, &conn_param_req); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /*******************/ + + /* (A) */ + ull_cp_conn_param_req_reply(&conn); + + /*******************/ + + /* Prepare */ + event_prepare(&conn); + + /* (A) Tx Queue should have one LL Control PDU */ + conn_update_ind.instant = event_counter(&conn) + 6U; + lt_rx(LL_CONNECTION_UPDATE_IND, &conn, &tx, &conn_update_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.conn_update_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); + + /* (B) Tx Queue should have one LL Control PDU */ + req_B->reference_conn_event_count = event_counter(&conn) - 1; + lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, req_B); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* (A) There should be one host notification */ + ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /* Prepare */ + event_prepare(&conn); + + /* (B) Rx */ + lt_tx(LL_CONNECTION_PARAM_RSP, &conn, rsp_B); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* (B) Tx Queue should have one LL Control PDU */ + conn_update_ind_B.instant = event_counter(&conn) + 6U; + lt_rx(LL_CONNECTION_UPDATE_IND, &conn, &tx, &conn_update_ind_B); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.conn_update_ind.instant); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* */ + while (!is_instant_reached(&conn, instant)) { + /* Prepare */ + event_prepare(&conn); + + /* (B) Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* (B) There should NOT be a host notification */ + ut_rx_q_is_empty(); + } + + /* Prepare */ + event_prepare(&conn); + + /* (B) Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* (B) There should be one host notification */ + ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * Slave-initiated Connection Parameters Request procedure. + * Slave requests change in LE connection parameters, master’s Host accepts. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_S | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | LL_CONNECTION_PARAM_REQ | + * | |-------------------------->| + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |<--------------------------| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_sla_loc_accept(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + uint16_t instant; + + struct node_rx_pu cu = { .status = BT_HCI_ERR_SUCCESS }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, &conn_param_req); + lt_rx_q_is_empty(&conn); + + /* 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 */ + instant = conn_update_ind.instant; + lt_tx(LL_CONNECTION_UPDATE_IND, &conn, &conn_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_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * Slave-initiated Connection Parameters Request procedure. + * Slave requests change in LE connection parameters, master’s Host rejects. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_S | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | LL_CONNECTION_PARAM_REQ | + * | |-------------------------->| + * | | | + * | | LL_REJECT_EXT_IND | + * | |<--------------------------| + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_sla_loc_reject(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct node_rx_pu cu = { .status = BT_HCI_ERR_UNACCEPT_CONN_PARAM }; + + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ, + .error_code = BT_HCI_ERR_UNACCEPT_CONN_PARAM + }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, &conn_param_req); + lt_rx_q_is_empty(&conn); + + /* 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 */ + lt_tx(LL_REJECT_EXT_IND, &conn, &reject_ext_ind); + + /* Done */ + event_done(&conn); + + /* There should be one host notification */ + ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * Slave-initiated Connection Parameters Request procedure. + * Slave requests change in LE connection parameters, master’s Controller do not + * support Connection Parameters Request procedure, features not exchanged. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_S | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | LL_CONNECTION_PARAM_REQ | + * | |-------------------------->| + * | | | + * | | LL_UNKNOWN_RSP | + * | |<--------------------------| + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_sla_loc_unsupp_feat_wo_feat_exch(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct node_rx_pu cu = { .status = BT_HCI_ERR_UNSUPP_REMOTE_FEATURE }; + + struct pdu_data_llctrl_unknown_rsp unknown_rsp = { + .type = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ + }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, &conn_param_req); + lt_rx_q_is_empty(&conn); + + /* 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 */ + lt_tx(LL_UNKNOWN_RSP, &conn, &unknown_rsp); + + /* Done */ + event_done(&conn); + + /* There should be one host notification */ + ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * Slave-initiated Connection Parameters Request procedure. + * Slave requests change in LE connection parameters, master’s Controller do not + * support Connection Parameters Request procedure, features exchanged. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_S | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | LL_CONNECTION_UPDATE_IND | + * | |-------------------------->| + * | | | + */ +void test_conn_update_sla_loc_unsupp_feat_w_feat_exch(void) +{ + uint8_t err; + + /* Disable feature */ + test_unmask_feature_conn_param_req(&conn); + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_UNSUPP_REMOTE_FEATURE, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have no LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should be no host notification */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* + * (A) + * Slave-initiated Connection Parameters Request procedure. + * Procedure collides and is rejected. + * + * and + * + * (B) + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Host accepts. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_S | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | (A) + * | | LL_CONNECTION_PARAM_REQ | + * | |-------------------------->| (A) + * | | | + * | | LL_CONNECTION_PARAM_REQ | + * | |<--------------------------| (B) + * | | | + * | <---------------------> | + * | < PROCEDURE COLLISION > | + * | <---------------------> | + * | | | + * | LE Remote Connection | | + * | Parameter Request | | + * |<--------------------------| | (B) + * | | | + * | LE Remote Connection | | + * | Parameter Request | | + * | Reply | | + * |-------------------------->| | (B) + * | | | + * | | LL_REJECT_EXT_IND | + * | |-------------------------->| (A) + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | (A) + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |<--------------------------| (B) + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | (B) + */ +void test_conn_update_sla_loc_collision(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + uint16_t instant; + + struct node_rx_pu cu1 = { .status = BT_HCI_ERR_LL_PROC_COLLISION }; + + struct node_rx_pu cu2 = { .status = BT_HCI_ERR_SUCCESS }; + + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ, + .error_code = BT_HCI_ERR_LL_PROC_COLLISION + }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* (A) Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* (A) Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, &conn_param_req); + lt_rx_q_is_empty(&conn); + + /* (B) Rx */ + lt_tx(LL_CONNECTION_PARAM_REQ, &conn, req_B); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /*******************/ + + /* (B) There should be one host notification */ + ut_rx_pdu(LL_CONNECTION_PARAM_REQ, &ntf, req_B); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /*******************/ + + /* (B) */ + ull_cp_conn_param_req_reply(&conn); + + /*******************/ + + /* Prepare */ + event_prepare(&conn); + + /* (B) Tx Queue should have one LL Control PDU */ + rsp_B->reference_conn_event_count = req_B->reference_conn_event_count; + lt_rx(LL_CONNECTION_PARAM_RSP, &conn, &tx, rsp_B); + lt_rx_q_is_empty(&conn); + + /* (A) Rx */ + lt_tx(LL_REJECT_EXT_IND, &conn, &reject_ext_ind); + + /* Done */ + event_done(&conn); + + /* (A) There should be one host notification */ + ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu1); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /* Prepare */ + event_prepare(&conn); + + /* (B) Rx */ + cu_ind_B->instant = instant = event_counter(&conn) + 6; + lt_tx(LL_CONNECTION_UPDATE_IND, &conn, cu_ind_B); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* */ + while (!is_instant_reached(&conn, instant)) { + /* Prepare */ + event_prepare(&conn); + + /* (B) Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* (B) There should NOT be a host notification */ + ut_rx_q_is_empty(); + } + + /* Prepare */ + event_prepare(&conn); + + /* (B) Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* (B) There should be one host notification */ + ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu2); + 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()); +} + +/* + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Host accepts. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_S | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_CONNECTION_PARAM_REQ | + * | |<--------------------------| + * | | | + * | LE Remote Connection | | + * | Parameter Request | | + * |<--------------------------| | + * | LE Remote Connection | | + * | Parameter Request | | + * | Reply | | + * |-------------------------->| | + * | | | + * | | LL_CONNECTION_PARAM_RSP | + * | |-------------------------->| + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |<--------------------------| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_sla_rem_accept(void) +{ + struct node_tx *tx; + struct node_rx_pdu *ntf; + uint16_t instant; + + struct node_rx_pu cu = { .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_CONNECTION_PARAM_REQ, &conn, &conn_param_req); + + /* Done */ + event_done(&conn); + + /*******************/ + + /* There should be one host notification */ + ut_rx_pdu(LL_CONNECTION_PARAM_REQ, &ntf, &conn_param_req); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /*******************/ + + ull_cp_conn_param_req_reply(&conn); + + /*******************/ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_RSP, &conn, &tx, &conn_param_rsp); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + instant = conn_update_ind.instant; + lt_tx(LL_CONNECTION_UPDATE_IND, &conn, &conn_update_ind); + + /* 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_CONN_UPDATE, &ntf, &cu); + 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()); +} + +/* + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Host rejects. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_S | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_CONNECTION_PARAM_REQ | + * | |<--------------------------| + * | | | + * | LE Remote Connection | | + * | Parameter Request | | + * |<--------------------------| | + * | LE Remote Connection | | + * | Parameter Request | | + * | Negative Reply | | + * |-------------------------->| | + * | | | + * | | LL_REJECT_EXT_IND | + * | |-------------------------->| + * | | | + */ +void test_conn_update_sla_rem_reject(void) +{ + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ, + .error_code = BT_HCI_ERR_UNACCEPT_CONN_PARAM + }; + + /* 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_CONNECTION_PARAM_REQ, &conn, &conn_param_req); + + /* Done */ + event_done(&conn); + + /*******************/ + + /* There should be one host notification */ + ut_rx_pdu(LL_CONNECTION_PARAM_REQ, &ntf, &conn_param_req); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /*******************/ + + ull_cp_conn_param_req_neg_reply(&conn, BT_HCI_ERR_UNACCEPT_CONN_PARAM); + + /*******************/ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_REJECT_EXT_IND, &conn, &tx, &reject_ext_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Controller do not + * support Connection Parameters Request procedure. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_S | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_CONNECTION_PARAM_REQ | + * | |<--------------------------| + * | | | + * | | LL_UNKNOWN_RSP | + * | |-------------------------->| + * | | | + */ +void test_conn_update_sla_rem_unsupp_feat(void) +{ + /* TODO(thoh): Implement when Remote Request machine has feature + * checking + */ +} + +/* + * (A) + * Master-initiated Connection Parameters Request procedure. + * Master requests change in LE connection parameters, slave’s Host accepts. + * + * and + * + * (B) + * Slave-initiated Connection Parameters Request procedure. + * Slave requests change in LE connection parameters, master’s Host accepts. + * + * NOTE: + * Slave-initiated Connection Parameters Request procedure is paused. + * Master-initiated Connection Parameters Request procedure is finished. + * Slave-initiated Connection Parameters Request procedure is resumed. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_S | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_CONNECTION_PARAM_REQ | + * | |<--------------------------| (A) + * | | | + * | LE Connection Update | | + * |-------------------------->| | (B) + * | | | + * | <------------------------> | + * | < LOCAL PROCEDURE PAUSED > | + * | <------------------------> | + * | | | + * | LE Remote Connection | | + * | Parameter Request | | + * |<--------------------------| | (A) + * | LE Remote Connection | | + * | Parameter Request | | + * | Reply | | + * |-------------------------->| | (A) + * | | | + * | | LL_CONNECTION_PARAM_RSP | + * | |-------------------------->| (A) + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |<--------------------------| (A) + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | (A) + * | | | + * | <-------------------------> | + * | < LOCAL PROCEDURE RESUMED > | + * | <-------------------------> | + * | | | + * | | LL_CONNECTION_PARAM_REQ | + * | |-------------------------->| (B) + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |<--------------------------| (B) + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | (B) + * | | | + */ +void test_conn_update_sla_rem_collision(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + uint16_t instant; + + struct node_rx_pu cu = { .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); + + /* (A) Rx */ + lt_tx(LL_CONNECTION_PARAM_REQ, &conn, &conn_param_req); + + /* Done */ + event_done(&conn); + + /*******************/ + + /* (B) Initiate a Connection Parameter Request Procedure */ + err = ull_cp_conn_update(&conn, req_B->interval_min, req_B->interval_max, req_B->latency, + req_B->timeout); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /*******************/ + + /* Prepare */ + event_prepare(&conn); + + /* (B) 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_pdu(LL_CONNECTION_PARAM_REQ, &ntf, &conn_param_req); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /*******************/ + + /* (A) */ + ull_cp_conn_param_req_reply(&conn); + + /*******************/ + + /* Prepare */ + event_prepare(&conn); + + /* (A) Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_RSP, &conn, &tx, &conn_param_rsp); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* (A) Rx */ + instant = conn_update_ind.instant; + lt_tx(LL_CONNECTION_UPDATE_IND, &conn, &conn_update_ind); + + /* Done */ + event_done(&conn); + + /* 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); + + /* (B) Tx Queue should have one LL Control PDU */ + lt_rx(LL_CONNECTION_PARAM_REQ, &conn, &tx, req_B); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* (A) There should be one host notification */ + ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /* Prepare */ + event_prepare(&conn); + + /* (B) Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* (B) Rx */ + lt_tx(LL_CONNECTION_UPDATE_IND, &conn, cu_ind_B); + + /* Done */ + event_done(&conn); + + /* */ + while (!is_instant_reached(&conn, instant)) { + /* Prepare */ + event_prepare(&conn); + + /* (B) Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* (B) There should NOT be a host notification */ + ut_rx_q_is_empty(); + } + + /* Prepare */ + event_prepare(&conn); + + /* (B) Tx Queue should NOT have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* (B) There should be one host notification */ + ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu); + 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()); +} +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ + +/* + * Parameter Request Procedure not supported. + * Master-initiated Connection Update procedure. + * Master requests update of LE connection. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | LL_CONNECTION_UPDATE_IND | + * | |-------------------------->| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + * | (If conn. parameters are | | + * | unchanged, host should | | + * | not receive a ntf.) | | + * | | | + */ +void test_conn_update_mas_loc_accept_no_param_req(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data *pdu; + uint16_t instant; + + /* Test with and without parameter change */ + uint8_t parameters_changed = 1U; + + struct node_rx_pu cu = { .status = BT_HCI_ERR_SUCCESS }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + do { + /* Initiate a Connection Update Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + conn_update_ind.instant = event_counter(&conn) + 6U; + lt_rx(LL_CONNECTION_UPDATE_IND, &conn, &tx, &conn_update_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Save Instant */ + pdu = (struct pdu_data *)tx->pdu; + instant = sys_le16_to_cpu(pdu->llctrl.conn_update_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); + + if (parameters_changed == 0U) { + /* There should NOT be a host notification */ + ut_rx_q_is_empty(); + } else { + /* There should be one host notification */ + ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + } + } while (parameters_changed-- > 0U); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* + * Parameter Request Procedure not supported. + * Slave-initiated Connection Update procedure. + * Master receives Connection Update parameters. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_M | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |<--------------------------| + * | | | + * | | LL_UNKNOWN_RSP | + * | |-------------------------->| + * | | | + * | | | + */ +void test_conn_update_mas_rem_accept_no_param_req(void) +{ + struct node_tx *tx; + + struct pdu_data_llctrl_unknown_rsp unknown_rsp = { + .type = PDU_DATA_LLCTRL_TYPE_CONN_UPDATE_IND + }; + + /* 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_CONNECTION_UPDATE_IND, &conn, &conn_update_ind); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_UNKNOWN_RSP, &conn, &tx, &unknown_rsp); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should NOT be a host notification */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* + * Parameter Request Procedure not supported. + * Master-initiated Connection Update procedure. + * Slave receives Connection Update parameters. + * + * +-----+ +-------+ +-----+ + * | UT | | LL_S | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_CONNECTION_UPDATE_IND | + * | |<--------------------------| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection Update | | + * | Complete | | + * |<--------------------------| | + * | | | + * | (If conn. parameters are | | + * | unchanged, host should | | + * | not receive a ntf.) | | + * | | | + */ +void test_conn_update_sla_rem_accept_no_param_req(void) +{ + struct node_rx_pdu *ntf; + uint16_t instant; + + /* Test with and without parameter change */ + uint8_t parameters_changed = 1U; + + struct node_rx_pu cu = { .status = BT_HCI_ERR_SUCCESS }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + do { + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + instant = conn_update_ind.instant; + lt_tx(LL_CONNECTION_UPDATE_IND, &conn, &conn_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); + + if (parameters_changed == 0U) { + /* There should NOT be a host notification */ + ut_rx_q_is_empty(); + } else { + /* There should be one host notification */ + ut_rx_node(NODE_CONN_UPDATE, &ntf, &cu); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + } + } while (parameters_changed-- > 0U); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* + * Parameter Request Procedure not supported. + * Slave-initiated Connection Update procedure (not allowed). + * + * +-----+ +-------+ +-----+ + * | UT | | LL_S | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | LE Connection Update | | + * |-------------------------->| | + * | | | + * | ERR CMD Disallowed | | + * |<--------------------------| | + * | | | + */ +void test_conn_update_sla_loc_disallowed_no_param_req(void) +{ + uint8_t err; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Connection Update Procedure */ + err = ull_cp_conn_update(&conn, INTVL_MIN, INTVL_MAX, LATENCY, TIMEOUT); + zassert_equal(err, BT_HCI_ERR_CMD_DISALLOWED, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have no LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should be no host notification */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_main(void) +{ +#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ) + ztest_test_suite( + mas_loc, + ztest_unit_test_setup_teardown(test_conn_update_mas_loc_accept, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_mas_loc_reject, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_mas_loc_remote_legacy, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_mas_loc_unsupp_wo_feat_exch, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_mas_loc_unsupp_w_feat_exch, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_mas_loc_collision, setup, + unit_test_noop)); + + ztest_test_suite(mas_rem, + ztest_unit_test_setup_teardown(test_conn_update_mas_rem_accept, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_mas_rem_reject, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_mas_rem_unsupp_feat, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_mas_rem_collision, setup, + unit_test_noop)); + + ztest_test_suite( + sla_loc, + ztest_unit_test_setup_teardown(test_conn_update_sla_loc_accept, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_sla_loc_reject, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_sla_loc_unsupp_feat_wo_feat_exch, + setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_sla_loc_unsupp_feat_w_feat_exch, + setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_sla_loc_collision, setup, + unit_test_noop)); + + ztest_test_suite(sla_rem, + ztest_unit_test_setup_teardown(test_conn_update_sla_rem_accept, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_sla_rem_reject, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_sla_rem_unsupp_feat, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_conn_update_sla_rem_collision, setup, + unit_test_noop)); + + ztest_run_test_suite(mas_loc); + ztest_run_test_suite(mas_rem); + ztest_run_test_suite(sla_loc); + ztest_run_test_suite(sla_rem); + +#else /* !CONFIG_BT_CTLR_CONN_PARAM_REQ */ + + ztest_test_suite(mas_loc_no_param_req, ztest_unit_test_setup_teardown( + test_conn_update_mas_loc_accept_no_param_req, + setup, unit_test_noop)); + + ztest_test_suite(mas_rem_no_param_req, ztest_unit_test_setup_teardown( + test_conn_update_mas_rem_accept_no_param_req, + setup, unit_test_noop)); + + ztest_test_suite( + sla_loc_no_param_req, + ztest_unit_test_setup_teardown(test_conn_update_sla_loc_disallowed_no_param_req, + setup, unit_test_noop)); + + ztest_test_suite(sla_rem_no_param_req, ztest_unit_test_setup_teardown( + test_conn_update_sla_rem_accept_no_param_req, + setup, unit_test_noop)); + + ztest_run_test_suite(mas_loc_no_param_req); + ztest_run_test_suite(mas_rem_no_param_req); + ztest_run_test_suite(sla_loc_no_param_req); + ztest_run_test_suite(sla_rem_no_param_req); + +#endif /* CONFIG_BT_CTLR_CONN_PARAM_REQ */ +} diff --git a/tests/bluetooth/controller/ctrl_conn_update/testcase.yaml b/tests/bluetooth/controller/ctrl_conn_update/testcase.yaml new file mode 100644 index 00000000000..d2d838d2fc7 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_conn_update/testcase.yaml @@ -0,0 +1,9 @@ +common: + tags: test_framework bluetooth bt_conn_update bt_conn_param_req bt_ull_llcp +tests: + bluetooth.controller.ctrl_conn_update.test: + type: unit + + bluetooth.controller.ctrl_conn_update.no_param_req_test: + type: unit + extra_args: KCONFIG_OVERRIDE_FILE="kconfig_override.h" diff --git a/tests/bluetooth/controller/ctrl_cte_req/CMakeLists.txt b/tests/bluetooth/controller/ctrl_cte_req/CMakeLists.txt new file mode 100644 index 00000000000..90f09d1b9b4 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_cte_req/CMakeLists.txt @@ -0,0 +1,20 @@ +# +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_le_cte_req) +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}) +set(CMAKE_BUILD_TYPE Debug) diff --git a/tests/bluetooth/controller/ctrl_cte_req/src/main.c b/tests/bluetooth/controller/ctrl_cte_req/src/main.c new file mode 100644 index 00000000000..967d4b3ac7d --- /dev/null +++ b/tests/bluetooth/controller/ctrl_cte_req/src/main.c @@ -0,0 +1,626 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "kconfig.h" + +#include +#include +#include +#include +#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" + +struct ll_conn conn; + +static void setup(void) +{ + test_setup(&conn); +} + +/* Tests of successful execution of CTE Request Procedure */ + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start initiation | | + * | CTE Reqest Proc. | | + * |--------------------------->| | + * | | | + * | | LL_LE_CTE_REQ | + * | |------------------>| + * | | | + * | | LL_LE_CTE_RSP | + * | |<------------------| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection IQ Report | | + * |<---------------------------| | + * | | | + * | | | + */ +void test_cte_req_central_local(void) +{ + uint8_t err; + struct node_tx *tx; + + struct pdu_data_llctrl_cte_req local_cte_req = { + .cte_type_req = BT_HCI_LE_AOA_CTE, + .min_cte_len_req = BT_HCI_LE_CTE_LEN_MIN, + }; + struct pdu_data_llctrl_cte_rsp remote_cte_rsp = {}; + struct node_rx_pdu *ntf; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate an CTE Request Procedure */ + err = ull_cp_cte_req(&conn, local_cte_req.min_cte_len_req, local_cte_req.cte_type_req); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CTE_REQ, &conn, &tx, &local_cte_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_CTE_RSP, &conn, &remote_cte_rsp); + + /* Done */ + event_done(&conn); + + /* Receive notification of sampled CTE response */ + ut_rx_pdu(LL_CTE_RSP, &ntf, &remote_cte_rsp); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start initiator | | + * | CTE Reqest Proc. | | + * |--------------------------->| | + * | | | + * | | LL_LE_CTE_REQ | + * | |------------------>| + * | | | + * | | LL_LE_CTE_RSP | + * | |<------------------| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE Connection IQ Report | | + * |<---------------------------| | + * | | | + * | | | + */ +void test_cte_req_peripheral_local(void) +{ + uint8_t err; + struct node_tx *tx; + + struct pdu_data_llctrl_cte_req local_cte_req = { + .cte_type_req = BT_HCI_LE_AOA_CTE, + .min_cte_len_req = BT_HCI_LE_CTE_LEN_MIN, + }; + + struct pdu_data_llctrl_cte_rsp remote_cte_rsp = {}; + struct node_rx_pdu *ntf; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate an CTE Request Procedure */ + err = ull_cp_cte_req(&conn, local_cte_req.min_cte_len_req, local_cte_req.cte_type_req); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CTE_REQ, &conn, &tx, &local_cte_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_CTE_RSP, &conn, &remote_cte_rsp); + + /* Done */ + event_done(&conn); + + /* Receive notification of sampled CTE response */ + ut_rx_pdu(LL_CTE_RSP, &ntf, &remote_cte_rsp); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start responder | | + * | CTE Reqest Proc. | | + * |--------------------------->| | + * | | | + * | | LL_LE_CTE_REQ | + * | |<------------------| + * | | | + * | | LL_LE_CTE_RSP | + * | |------------------>| + * | | | + * | | | + */ +void test_cte_req_central_remote(void) +{ + struct node_tx *tx; + + struct pdu_data_llctrl_cte_req local_cte_req = { + .cte_type_req = BT_HCI_LE_AOA_CTE, + .min_cte_len_req = BT_HCI_LE_CTE_LEN_MIN, + }; + + struct pdu_data_llctrl_cte_rsp remote_cte_rsp = {}; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Enable response for CTE request */ + ull_cp_cte_rsp_enable(&conn, true, BT_HCI_LE_CTE_LEN_MAX, + (BT_HCI_LE_AOA_CTE | BT_HCI_LE_AOD_CTE_1US | BT_HCI_LE_AOD_CTE_2US)); + + /* Prepare */ + event_prepare(&conn); + + /* Tx */ + lt_tx(LL_CTE_REQ, &conn, &local_cte_req); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CTE_RSP, &conn, &tx, &remote_cte_rsp); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start responder | | + * | CTE Reqest Proc . | | + * |--------------------------->| | + * | | | + * | | LL_LE_CTE_REQ | + * | |<------------------| + * | | | + * | | LL_LE_CTE_RSP | + * | |------------------>| + * | | | + * | | | + */ +void test_cte_req_peripheral_remote(void) +{ + struct node_tx *tx; + + struct pdu_data_llctrl_cte_req local_cte_req = { + .cte_type_req = BT_HCI_LE_AOA_CTE, + .min_cte_len_req = BT_HCI_LE_CTE_LEN_MIN, + }; + + struct pdu_data_llctrl_cte_rsp remote_cte_rsp = {}; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Enable response for CTE request */ + ull_cp_cte_rsp_enable(&conn, true, BT_HCI_LE_CTE_LEN_MAX, + (BT_HCI_LE_AOA_CTE | BT_HCI_LE_AOD_CTE_1US | BT_HCI_LE_AOD_CTE_2US)); + + /* Prepare */ + event_prepare(&conn); + + /* Tx */ + lt_tx(LL_CTE_REQ, &conn, &local_cte_req); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CTE_RSP, &conn, &tx, &remote_cte_rsp); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* Tests of expected failures during execution of CTE Request Procedure */ + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start initiation | | + * | CTE Reqest Proc. | | + * |--------------------------->| | + * | | | + * | | LL_LE_CTE_REQ | + * | |------------------------------->| + * | | | + * | | LL_REJECT_EXT_IND | + * | | BT_HCI_ERR_UNSUPP_LL_PARAM_VAL | + * | | or BT_HCI_ERR_INVALID_LL_PARAM | + * | |<-------------------------------| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE CTE Request Failed | | + * |<---------------------------| | + * | | | + * | | | + */ +void test_cte_req_rejected_inv_ll_param_central_local(void) +{ + uint8_t err; + struct node_tx *tx; + + struct pdu_data_llctrl_cte_req local_cte_req = { + .cte_type_req = BT_HCI_LE_AOD_CTE_1US, + .min_cte_len_req = BT_HCI_LE_CTE_LEN_MIN, + }; + struct pdu_data_llctrl_reject_ext_ind remote_reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CTE_REQ, + .error_code = BT_HCI_ERR_UNSUPP_LL_PARAM_VAL, + }; + struct node_rx_pdu *ntf; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate an CTE Request Procedure */ + err = ull_cp_cte_req(&conn, local_cte_req.min_cte_len_req, local_cte_req.cte_type_req); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CTE_REQ, &conn, &tx, &local_cte_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_REJECT_EXT_IND, &conn, &remote_reject_ext_ind); + + /* Done */ + event_done(&conn); + + /* Receive notification of sampled CTE response */ + ut_rx_pdu(LL_REJECT_EXT_IND, &ntf, &remote_reject_ext_ind); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start initiation | | + * | CTE Reqest Proc. | | + * |--------------------------->| | + * | | | + * | | LL_LE_CTE_REQ | + * | |------------------------------->| + * | | | + * | | LL_REJECT_EXT_IND | + * | | BT_HCI_ERR_UNSUPP_LL_PARAM_VAL | + * | | or BT_HCI_ERR_INVALID_LL_PARAM | + * | |<-------------------------------| + * | | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | | | + * | LE CTE Request Failed | | + * |<---------------------------| | + * | | | + * | | | + */ +void test_cte_req_rejected_inv_ll_param_peripheral_local(void) +{ + uint8_t err; + struct node_tx *tx; + + struct pdu_data_llctrl_cte_req local_cte_req = { + .cte_type_req = BT_HCI_LE_AOD_CTE_1US, + .min_cte_len_req = BT_HCI_LE_CTE_LEN_MIN, + }; + struct pdu_data_llctrl_reject_ext_ind remote_reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CTE_REQ, + .error_code = BT_HCI_ERR_UNSUPP_LL_PARAM_VAL, + }; + struct node_rx_pdu *ntf; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate an CTE Request Procedure */ + err = ull_cp_cte_req(&conn, local_cte_req.min_cte_len_req, local_cte_req.cte_type_req); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CTE_REQ, &conn, &tx, &local_cte_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_REJECT_EXT_IND, &conn, &remote_reject_ext_ind); + + /* Done */ + event_done(&conn); + + /* Receive notification of sampled CTE response */ + ut_rx_pdu(LL_REJECT_EXT_IND, &ntf, &remote_reject_ext_ind); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start initiation | | + * | CTE Reqest Proc. | | + * |--------------------------->| | + * | | | + * | | LL_LE_CTE_REQ | + * | |<-------------------------------| + * | | | + * | | LL_REJECT_EXT_IND | + * | | BT_HCI_ERR_UNSUPP_LL_PARAM_VAL | + * | |------------------------------->| + * | | | + */ +void test_cte_req_reject_inv_ll_param_central_remote(void) +{ + struct node_tx *tx; + + struct pdu_data_llctrl_cte_req local_cte_req = { + .cte_type_req = BT_HCI_LE_AOD_CTE_2US, + .min_cte_len_req = BT_HCI_LE_CTE_LEN_MIN, + }; + + struct pdu_data_llctrl_reject_ext_ind remote_reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CTE_REQ, + .error_code = BT_HCI_ERR_UNSUPP_LL_PARAM_VAL, + }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Enable response for CTE request */ + ull_cp_cte_rsp_enable(&conn, true, BT_HCI_LE_CTE_LEN_MAX, + (BT_HCI_LE_AOA_CTE | BT_HCI_LE_AOD_CTE_1US)); + + /* Prepare */ + event_prepare(&conn); + + /* Tx */ + lt_tx(LL_CTE_REQ, &conn, &local_cte_req); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_REJECT_EXT_IND, &conn, &tx, &remote_reject_ext_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start initiation | | + * | CTE Reqest Proc. | | + * |--------------------------->| | + * | | | + * | | LL_LE_CTE_REQ | + * | |<-------------------------------| + * | | | + * | | LL_REJECT_EXT_IND | + * | | BT_HCI_ERR_UNSUPP_LL_PARAM_VAL | + * | |------------------------------->| + * | | | + */ +void test_cte_req_reject_inv_ll_param_peripheral_remote(void) +{ + struct node_tx *tx; + + struct pdu_data_llctrl_cte_req local_cte_req = { + .cte_type_req = BT_HCI_LE_AOD_CTE_2US, + .min_cte_len_req = BT_HCI_LE_CTE_LEN_MIN, + }; + + struct pdu_data_llctrl_reject_ext_ind remote_reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_CTE_REQ, + .error_code = BT_HCI_ERR_UNSUPP_LL_PARAM_VAL, + }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Enable response for CTE request */ + ull_cp_cte_rsp_enable(&conn, true, BT_HCI_LE_CTE_LEN_MAX, + (BT_HCI_LE_AOA_CTE | BT_HCI_LE_AOD_CTE_1US)); + + /* Prepare */ + event_prepare(&conn); + + /* Tx */ + lt_tx(LL_CTE_REQ, &conn, &local_cte_req); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_REJECT_EXT_IND, &conn, &tx, &remote_reject_ext_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + 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( + cte_req, + ztest_unit_test_setup_teardown(test_cte_req_central_local, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_cte_req_peripheral_local, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_cte_req_central_remote, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_cte_req_peripheral_remote, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_cte_req_rejected_inv_ll_param_central_local, + setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_cte_req_rejected_inv_ll_param_peripheral_local, + setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_cte_req_reject_inv_ll_param_central_remote, + setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_cte_req_reject_inv_ll_param_peripheral_remote, + setup, unit_test_noop)); + + ztest_run_test_suite(cte_req); +} diff --git a/tests/bluetooth/controller/ctrl_cte_req/testcase.yaml b/tests/bluetooth/controller/ctrl_cte_req/testcase.yaml new file mode 100644 index 00000000000..ed24491d20a --- /dev/null +++ b/tests/bluetooth/controller/ctrl_cte_req/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: test_framework bluetooth bt_le_cte_req bt_ull_llcp +tests: + bluetooth.controller.ctrl_cte_req.test: + type: unit diff --git a/tests/bluetooth/controller/ctrl_data_length_update/CMakeLists.txt b/tests/bluetooth/controller/ctrl_data_length_update/CMakeLists.txt new file mode 100644 index 00000000000..eba411fa31f --- /dev/null +++ b/tests/bluetooth/controller/ctrl_data_length_update/CMakeLists.txt @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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 +) +if(CONFIG_BT_CTLR_PHY_CODED) + add_compile_definitions(CONFIG_BT_CTLR_PHY_CODED) +endif(CONFIG_BT_CTLR_PHY_CODED) + +if(CONFIG_BT_CTLR_PHY) + add_compile_definitions(CONFIG_BT_CTLR_PHY) +endif(CONFIG_BT_CTLR_PHY) + +project(bluetooth_ull_llcp_data_length_update) +find_package(ZephyrUnittest HINTS $ENV{ZEPHYR_BASE}) +include(${ZEPHYR_BASE}/tests/bluetooth/controller/common/defaults_cmake.txt) +if(NOT CONFIG_BT_CTLR_PHY) +list(REMOVE_ITEM ll_sw_sources ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/ull_llcp_phy.c) +endif() +target_sources(testbinary PRIVATE ${ll_sw_sources} ${mock_sources} ${common_sources}) diff --git a/tests/bluetooth/controller/ctrl_data_length_update/src/kconfig_override.h b/tests/bluetooth/controller/ctrl_data_length_update/src/kconfig_override.h new file mode 100644 index 00000000000..cf1b1c587c4 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_data_length_update/src/kconfig_override.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2020 Demant + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Common Kconfig settings + */ + +#ifdef CONFIG_BT_CTLR_PHY +#undef CONFIG_BT_CTLR_PHY +#endif diff --git a/tests/bluetooth/controller/ctrl_data_length_update/src/main.c b/tests/bluetooth/controller/ctrl_data_length_update/src/main.c new file mode 100644 index 00000000000..ee89ae0923f --- /dev/null +++ b/tests/bluetooth/controller/ctrl_data_length_update/src/main.c @@ -0,0 +1,673 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#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 "pdu.h" +#include "ll.h" +#include "ll_feat.h" +#include "ll_settings.h" + +#include "lll.h" +#include "lll_df_types.h" +#include "lll_conn.h" + +#include "ull_tx_queue.h" +#include "ull_internal.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" +#include "helper_features.h" + +struct ll_conn conn; + +static void setup(void) +{ + test_setup(&conn); +} + +/*C + * Locally triggered Data Length Update procedure + * + * +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start | | + * | Data Length Update Proc. | | + * |--------------------------->| | + * | | (251,2120,211,1800) | + * | | LL_DATA_LENGTH_UPDATE_REQ | + * | |----------------------------->| + * | | (201,1720,251,2120) | + * | | LL_DATA_LENGTH_UPDATE_RSP | + * | |<-----------------------------| + * | (251,2120,201,1720) | | + * | Data Length Update Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + */ + +void test_data_length_update_mas_loc(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_length_req local_length_req = { 251, 2120, 211, 1800 }; + struct pdu_data_llctrl_length_rsp remote_length_rsp = { 201, 1720, 251, 2120 }; + struct pdu_data_llctrl_length_rsp length_ntf = { 251, 2120, 201, 1720 }; + + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + /* Init DLE data */ + ull_conn_default_tx_octets_set(251); + ull_conn_default_tx_time_set(2120); + ull_dle_init(&conn, PHY_1M); + + /* Steal all ntf buffers, so as to check that the wait_ntf mechanism works */ + while (ll_pdu_rx_alloc_peek(1)) { + ntf = ll_pdu_rx_alloc(); + /* Make sure we use a correct type or the release won't work */ + ntf->hdr.type = NODE_RX_TYPE_DC_PDU; + } + + /* Initiate a Data Length Update Procedure */ + err = ull_cp_data_length_update(&conn, 211, 1800); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + event_prepare(&conn); + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LENGTH_REQ, &conn, &tx, &local_length_req); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Rx */ + lt_tx(LL_LENGTH_RSP, &conn, &remote_length_rsp); + + event_done(&conn); + + ut_rx_q_is_empty(); + + /* Release Ntf, so next cycle will generate NTF and complete procedure */ + ull_cp_release_ntf(ntf); + + event_prepare(&conn); + event_done(&conn); + + /* There should be one host notification */ + ut_rx_pdu(LL_LENGTH_RSP, &ntf, &length_ntf); + ut_rx_q_is_empty(); + zassert_equal(conn.lll.event_counter, 2, "Wrong event-count %d\n", + conn.lll.event_counter); +} + +/* + * Locally triggered Data Length Update procedure - with no update to eff and thus no ntf + * + * +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start | | + * | Data Length Update Proc. | | + * |--------------------------->| | + * | | (251,2120,211,1800) | + * | | LL_DATA_LENGTH_UPDATE_REQ | + * | |----------------------------->| + * | | (27,328,27,328) | + * | | LL_DATA_LENGTH_UPDATE_RSP | + * | |<-----------------------------| + * | | | + */ +void test_data_length_update_mas_loc_no_eff_change(void) +{ + uint8_t err; + struct node_tx *tx; + + struct pdu_data_llctrl_length_req local_length_req = { 251, 2120, 211, 1800 }; + struct pdu_data_llctrl_length_rsp remote_length_rsp = { 27, 328, 27, 328 }; + + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + /* Init DLE data */ + ull_conn_default_tx_octets_set(251); + ull_conn_default_tx_time_set(2120); + ull_dle_init(&conn, PHY_1M); + + /* Initiate a Data Length Update Procedure */ + err = ull_cp_data_length_update(&conn, 211, 1800); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + event_prepare(&conn); + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LENGTH_REQ, &conn, &tx, &local_length_req); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Rx */ + lt_tx(LL_LENGTH_RSP, &conn, &remote_length_rsp); + + event_done(&conn); + + /* There should be no host notification */ + ut_rx_q_is_empty(); + zassert_equal(conn.lll.event_counter, 1, "Wrong event-count %d\n", + conn.lll.event_counter); +} +/* + * Locally triggered Data Length Update procedure - + * - first updating effective DLE and then without update to eff and thus no ntf + * + * +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start | | + * | Data Length Update Proc. | | + * |--------------------------->| | + * | | (251,2120,221,1800) | + * | | LL_DATA_LENGTH_UPDATE_REQ | + * | |----------------------------->| + * | | (101,920,251,2120) | + * | | LL_DATA_LENGTH_UPDATE_RSP | + * | |<-----------------------------| + * | (251,2120,101,920) | | + * | Data Length Update Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + * | Start | | + * | Data Length Update Proc. | | + * |--------------------------->| | + * | | (251,2120,211,1800) | + * | | LL_DATA_LENGTH_UPDATE_REQ | + * | |----------------------------->| + * | | (101, 920,251,2120) | + * | | LL_DATA_LENGTH_UPDATE_RSP | + * | |<-----------------------------| + * | | | + */ + +void test_data_length_update_mas_loc_no_eff_change2(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_length_req local_length_req = { 251, 2120, 211, 1800 }; + struct pdu_data_llctrl_length_rsp remote_length_rsp = { 101, 920, 251, 2120 }; + struct pdu_data_llctrl_length_rsp length_ntf = { 251, 2120, 101, 920 }; + struct pdu_data_llctrl_length_req local_length_req2 = { 251, 2120, 211, 1800 }; + struct pdu_data_llctrl_length_rsp remote_length_rsp2 = { 101, 920, 251, 2120 }; + + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + /* Init DLE data */ + ull_conn_default_tx_octets_set(251); + ull_conn_default_tx_time_set(2120); + ull_dle_init(&conn, PHY_1M); + + /* Initiate a Data Length Update Procedure */ + err = ull_cp_data_length_update(&conn, 211, 1800); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + event_prepare(&conn); + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LENGTH_REQ, &conn, &tx, &local_length_req); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Rx */ + lt_tx(LL_LENGTH_RSP, &conn, &remote_length_rsp); + + event_done(&conn); + + /* There should be one host notification */ + ut_rx_pdu(LL_LENGTH_RSP, &ntf, &length_ntf); + ut_rx_q_is_empty(); + zassert_equal(conn.lll.event_counter, 1, "Wrong event-count %d\n", + conn.lll.event_counter); + + /* Now lets generate another DLU, but one that should not result in + * change to effective numbers, thus not generate NTF + */ + err = ull_cp_data_length_update(&conn, 211, 1800); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + event_prepare(&conn); + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LENGTH_REQ, &conn, &tx, &local_length_req2); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Rx */ + lt_tx(LL_LENGTH_RSP, &conn, &remote_length_rsp2); + + event_done(&conn); + + /* There should be no host notification */ + ut_rx_q_is_empty(); + zassert_equal(conn.lll.event_counter, 2, "Wrong event-count %d\n", + conn.lll.event_counter); +} + +void test_data_length_update_sla_loc(void) +{ + uint64_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_length_req local_length_req = { 251, 2120, 211, 1800 }; + struct pdu_data_llctrl_length_rsp remote_length_rsp = { 211, 1800, 251, 2120 }; + struct pdu_data_llctrl_length_rsp length_ntf = { 251, 2120, 211, 1800 }; + + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + /* Init DLE data */ + ull_conn_default_tx_octets_set(251); + ull_conn_default_tx_time_set(2120); + ull_dle_init(&conn, PHY_1M); + + /* Initiate a Data Length Update Procedure */ + err = ull_cp_data_length_update(&conn, 211, 1800); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + event_prepare(&conn); + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LENGTH_REQ, &conn, &tx, &local_length_req); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Rx */ + lt_tx(LL_LENGTH_RSP, &conn, &remote_length_rsp); + + event_done(&conn); + + /* There should be one host notification */ + ut_rx_pdu(LL_LENGTH_RSP, &ntf, &length_ntf); + ut_rx_q_is_empty(); + zassert_equal(conn.lll.event_counter, 1, "Wrong event-count %d\n", + conn.lll.event_counter); +} + +/* + * Remotely triggered Data Length Update procedure + * + * +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | (27, 328, 251, 2120) | + * | | LL_DATA_LENGTH_UPDATE_REQ | + * | |<-----------------------------| + * | | (251, 2120, 211, 1800) | + * | | LL_DATA_LENGTH_UPDATE_RSP | + * | |----------------------------->| + * | (251,2120,27,328) | | + * | Data Length Changed | | + * |<---------------------------| | + * | | | + */ + +void test_data_length_update_mas_rem(void) +{ + struct node_tx *tx; + + struct pdu_data_llctrl_length_req remote_length_req = { 27, 328, 251, 2120 }; + struct pdu_data_llctrl_length_rsp local_length_rsp = { 251, 2120, 211, 1800 }; + struct pdu_data_llctrl_length_rsp length_ntf = { 251, 2120, 27, 328 }; + + struct node_rx_pdu *ntf; + + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + /* Init DLE data */ + ull_conn_default_tx_octets_set(211); + ull_conn_default_tx_time_set(1800); + ull_dle_init(&conn, PHY_1M); + + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_tx(LL_LENGTH_REQ, &conn, &remote_length_req); + + event_done(&conn); + + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LENGTH_RSP, &conn, &tx, &local_length_rsp); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + event_done(&conn); + + ut_rx_pdu(LL_LENGTH_RSP, &ntf, &length_ntf); + ut_rx_q_is_empty(); +} + +/* + * Remotely triggered Data Length Update procedure + * + * +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | (27, 328, 201, 1720) | + * | | LL_DATA_LENGTH_UPDATE_REQ | + * | |<-----------------------------| + * | | | + * | | (251, 2120, 211, 1800) | + * | | LL_DATA_LENGTH_UPDATE_RSP | + * | |----------------------------->| + * | (201,1720,27,328) | | + * | Data Length Changed | | + * |<---------------------------| | + * | | | + */ + +void test_data_length_update_sla_rem(void) +{ + struct node_tx *tx; + + struct pdu_data_llctrl_length_req remote_length_req = { 27, 328, 201, 1720 }; + struct pdu_data_llctrl_length_rsp local_length_rsp = { 251, 2120, 211, 1800 }; + struct pdu_data_llctrl_length_rsp length_ntf = { 201, 1720, 27, 328 }; + struct node_rx_pdu *ntf; + + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + /* Init DLE data */ + ull_conn_default_tx_octets_set(211); + ull_conn_default_tx_time_set(1800); + ull_dle_init(&conn, PHY_1M); + + /* Steal all ntf buffers, so as to check that the wait_ntf mechanism works */ + while (ll_pdu_rx_alloc_peek(1)) { + ntf = ll_pdu_rx_alloc(); + /* Make sure we use a correct type or the release won't work */ + ntf->hdr.type = NODE_RX_TYPE_DC_PDU; + } + + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_tx(LL_LENGTH_REQ, &conn, &remote_length_req); + + event_done(&conn); + + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LENGTH_RSP, &conn, &tx, &local_length_rsp); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + event_done(&conn); + ut_rx_q_is_empty(); + + /* Release Ntf, so next cycle will generate NTF and complete procedure */ + ull_cp_release_ntf(ntf); + + event_prepare(&conn); + event_done(&conn); + + ut_rx_pdu(LL_LENGTH_RSP, &ntf, &length_ntf); + ut_rx_q_is_empty(); +} + +/* + * Remotely triggered Data Length Update procedure with local request piggy back + * + * +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | (27, 328, 211, 1800) | + * | | LL_DATA_LENGTH_UPDATE_REQ | + * | |<-----------------------------| + * | Start | | + * | Data Length Update Proc. | | + * |--------------------------->| | + * | | | + * | | (251, 2120, 211, 1800) | + * | | LL_DATA_LENGTH_UPDATE_RSP | + * | |----------------------------->| + * | (211,1800,27,328) | | + * | Data Length Changed | | + * |<---------------------------| | + * | | | + */ + +void test_data_length_update_sla_rem_and_loc(void) +{ + uint64_t err; + struct node_tx *tx; + struct proc_ctx *ctx = NULL; + + struct pdu_data_llctrl_length_req remote_length_req = { 27, 328, 211, 1800 }; + struct pdu_data_llctrl_length_rsp local_length_rsp = { 251, 2120, 211, 1800 }; + struct pdu_data_llctrl_length_rsp length_ntf = { 211, 1800, 27, 328 }; + struct node_rx_pdu *ntf; + + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + /* Init DLE data */ + ull_conn_default_tx_octets_set(211); + ull_conn_default_tx_time_set(1800); + ull_dle_init(&conn, PHY_1M); + + /* Allocate dummy procedure used to steal all buffers */ + ctx = llcp_create_local_procedure(PROC_VERSION_EXCHANGE); + + /* Steal all tx buffers */ + while (llcp_tx_alloc_peek(&conn, ctx)) { + tx = llcp_tx_alloc(&conn, ctx); + zassert_not_null(tx, NULL); + } + + /* Dummy remove, as above loop might queue up ctx */ + llcp_tx_alloc_unpeek(ctx); + + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_tx(LL_LENGTH_REQ, &conn, &remote_length_req); + + event_done(&conn); + + event_prepare(&conn); + + /* Tx Queue should have no LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Initiate a Data Length Update Procedure */ + err = ull_cp_data_length_update(&conn, 211, 1800); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + event_done(&conn); + + ull_cp_release_tx(&conn, tx); + + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LENGTH_RSP, &conn, &tx, &local_length_rsp); + lt_rx_q_is_empty(&conn); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + event_done(&conn); + + ut_rx_pdu(LL_LENGTH_RSP, &ntf, &length_ntf); + ut_rx_q_is_empty(); +} + +void test_data_length_update_dle_max_time_get(void) +{ + uint16_t max_time = 0xffff; + uint16_t max_octets = 211; + +#ifdef CONFIG_BT_CTLR_PHY + max_time = 2120; +#endif + conn.llcp.fex.valid = 0; + + ull_dle_local_tx_update(&conn, max_octets, max_time); + +#ifdef CONFIG_BT_CTLR_PHY +#ifdef CONFIG_BT_CTLR_PHY_CODED + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 2120, "max_tx_time mismatch.\n"); +#else + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 2120, "max_tx_time mismatch.\n"); +#endif +#else + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 1800, "max_tx_time mismatch.\n"); +#endif + + /* Emulate complete feat exch without CODED */ + conn.llcp.fex.valid = 1; + conn.llcp.fex.features_used = 0; + ull_dle_local_tx_update(&conn, max_octets, max_time); + +#ifdef CONFIG_BT_CTLR_PHY +#ifdef CONFIG_BT_CTLR_PHY_CODED + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 2120, "max_tx_time mismatch.\n"); +#else + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 2120, "max_tx_time mismatch.\n"); +#endif +#else + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 1800, "max_tx_time mismatch.\n"); +#endif + + /* Check the case of CODED PHY support */ + conn.llcp.fex.features_used = LL_FEAT_BIT_PHY_CODED; + ull_dle_local_tx_update(&conn, max_octets, max_time); + +#ifdef CONFIG_BT_CTLR_PHY +#ifdef CONFIG_BT_CTLR_PHY_CODED + zassert_equal(conn.lll.dle.local.max_rx_time, 17040, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 2120, "max_tx_time mismatch.\n"); +#else + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 2120, "max_tx_time mismatch.\n"); +#endif +#else + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 1800, "max_tx_time mismatch.\n"); +#endif + + /* Finally check that MAX on max_tx_time works */ + max_time = 20000; + ull_dle_local_tx_update(&conn, max_octets, max_time); + +#ifdef CONFIG_BT_CTLR_PHY +#ifdef CONFIG_BT_CTLR_PHY_CODED + zassert_equal(conn.lll.dle.local.max_rx_time, 17040, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 17040, "max_tx_time mismatch.\n"); +#else + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 2120, "max_tx_time mismatch.\n"); +#endif +#else + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 1800, "max_tx_time mismatch.\n"); +#endif + + /* Check that MIN works */ + max_time = 20; + max_octets = 2; + ull_dle_local_tx_update(&conn, max_octets, max_time); + +#ifdef CONFIG_BT_CTLR_PHY +#ifdef CONFIG_BT_CTLR_PHY_CODED + zassert_equal(conn.lll.dle.local.max_rx_time, 17040, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 328, "max_tx_time mismatch.\n"); +#else + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 328, "max_tx_time mismatch.\n"); +#endif +#else + zassert_equal(conn.lll.dle.local.max_rx_time, 2120, "max_rx_time mismatch.\n"); + zassert_equal(conn.lll.dle.local.max_tx_time, 328, "max_tx_time mismatch.\n"); +#endif +} + +void test_main(void) +{ + ztest_test_suite( + data_length_update_master, + ztest_unit_test_setup_teardown(test_data_length_update_mas_loc, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_data_length_update_mas_loc_no_eff_change, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_data_length_update_mas_loc_no_eff_change2, + setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_data_length_update_mas_rem, setup, + unit_test_noop)); + + ztest_test_suite(data_length_update_slave, + ztest_unit_test_setup_teardown(test_data_length_update_sla_loc, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_data_length_update_sla_rem, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_data_length_update_sla_rem_and_loc, + setup, unit_test_noop) + ); + + ztest_test_suite(data_length_update_util, + ztest_unit_test_setup_teardown(test_data_length_update_dle_max_time_get, + setup, unit_test_noop)); + + ztest_run_test_suite(data_length_update_master); + ztest_run_test_suite(data_length_update_slave); + ztest_run_test_suite(data_length_update_util); +} diff --git a/tests/bluetooth/controller/ctrl_data_length_update/testcase.yaml b/tests/bluetooth/controller/ctrl_data_length_update/testcase.yaml new file mode 100644 index 00000000000..683b9c84c6a --- /dev/null +++ b/tests/bluetooth/controller/ctrl_data_length_update/testcase.yaml @@ -0,0 +1,12 @@ +common: + tags: test_framework bluetooth bt_data_length_update bt_ull_llcp +tests: + bluetooth.controller.ctrl_data_length_update.test: + type: unit + extra_args: CONFIG_BT_CTLR_PHY=y + bluetooth.controller.ctrl_data_length_update.test_codedphy: + type: unit + extra_args: CONFIG_BT_CTLR_PHY=y CONFIG_BT_CTLR_PHY_CODED=y + bluetooth.controller.ctrl_data_length_update.test_nophy: + type: unit + extra_args: KCONFIG_OVERRIDE_FILE="kconfig_override.h" diff --git a/tests/bluetooth/controller/ctrl_encrypt/CMakeLists.txt b/tests/bluetooth/controller/ctrl_encrypt/CMakeLists.txt new file mode 100644 index 00000000000..de93f6fca4c --- /dev/null +++ b/tests/bluetooth/controller/ctrl_encrypt/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_encrypt) +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_encrypt/src/main.c b/tests/bluetooth/controller/ctrl_encrypt/src/main.c new file mode 100644 index 00000000000..5d0a018e2a8 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_encrypt/src/main.c @@ -0,0 +1,1775 @@ +/* + * 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 "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_internal.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" + +/* Tx/Rx pause flag */ +#define RESUMED 0U +#define PAUSED 1U + +/* Tx/Rx encryption flag */ +#define UNENCRYPTED 0U +#define ENCRYPTED 1U + +/* Check Rx Pause and Encryption state */ +#define CHECK_RX_PE_STATE(_conn, _pause, _enc) \ + do { \ + zassert_equal(_conn.pause_rx_data, _pause, "Rx Data pause state is wrong.");\ + zassert_equal(_conn.lll.enc_rx, _enc, "Rx Encryption state is wrong."); \ + } while (0) + +/* Check Tx Pause and Encryption state */ +#define CHECK_TX_PE_STATE(_conn, _pause, _enc) \ + do { \ + zassert_equal(_conn.tx_q.pause_data, _pause, "Tx Data pause state is wrong.");\ + zassert_equal(_conn.lll.enc_tx, _enc, "Tx Encryption state is wrong."); \ + } while (0) + +/* CCM direction flag */ +#define CCM_DIR_M_TO_S 1U +#define CCM_DIR_S_TO_M 0U + +/* Check Rx CCM state */ +#define CHECK_RX_CCM_STATE(_conn, _sk_be, _iv, _cnt, _dir) \ + do { \ + zassert_mem_equal(_conn.lll.ccm_rx.key, _sk_be, sizeof(_sk_be), \ + "CCM Rx SK not equal to expected SK"); \ + zassert_mem_equal(_conn.lll.ccm_rx.iv, _iv, sizeof(_iv), \ + "CCM Rx IV not equal to (IVm | IVs)"); \ + zassert_equal(_conn.lll.ccm_rx.counter, _cnt, "CCM Rx Counter is wrong"); \ + zassert_equal(_conn.lll.ccm_rx.direction, _dir, "CCM Rx Direction is wrong");\ + } while (0) + +/* Check Tx CCM state */ +#define CHECK_TX_CCM_STATE(_conn, _sk_be, _iv, _cnt, _dir) \ + do { \ + zassert_mem_equal(_conn.lll.ccm_tx.key, _sk_be, sizeof(_sk_be), \ + "CCM Tx SK not equal to expected SK"); \ + zassert_mem_equal(_conn.lll.ccm_tx.iv, _iv, sizeof(_iv), \ + "CCM Tx IV not equal to (IVm | IVs)"); \ + zassert_equal(_conn.lll.ccm_tx.counter, _cnt, "CCM Tx Counter is wrong"); \ + zassert_equal(_conn.lll.ccm_tx.direction, _dir, "CCM Tx Direction is wrong");\ + } while (0) + +struct ll_conn conn; + +static void setup(void) +{ + test_setup(&conn); +} + +void ecb_encrypt(uint8_t const *const key_le, uint8_t const *const clear_text_le, + uint8_t *const cipher_text_le, uint8_t *const cipher_text_be) +{ + ztest_check_expected_data(key_le, 16); + ztest_check_expected_data(clear_text_le, 16); + if (cipher_text_le) { + ztest_copy_return_data(cipher_text_le, 16); + } + + if (cipher_text_be) { + ztest_copy_return_data(cipher_text_be, 16); + } +} + +int lll_csrand_get(void *buf, size_t len) +{ + ztest_check_expected_value(len); + ztest_copy_return_data(buf, len); + return ztest_get_return_value(); +} + +/* BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 6, Part C + * 1 ENCRYPTION SAMPLE DATA + */ +#define RAND 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90 +#define EDIV 0x24, 0x74 +#define LTK \ + 0x4C, 0x68, 0x38, 0x41, 0x39, 0xF5, 0x74, 0xD8, 0x36, 0xBC, 0xF3, 0x4E, 0x9D, 0xFB, 0x01,\ + 0xBF +#define SKDM 0xAC, 0xBD, 0xCE, 0xDF, 0xE0, 0xF1, 0x02, 0x13 +#define SKDS 0x02, 0x13, 0x24, 0x35, 0x46, 0x57, 0x68, 0x79 +#define IVM 0xBA, 0xDC, 0xAB, 0x24 +#define IVS 0xDE, 0xAF, 0xBA, 0xBE + +#define SK_BE \ + 0x66, 0xC6, 0xC2, 0x27, 0x8E, 0x3B, 0x8E, 0x05, 0x3E, 0x7E, 0xA3, 0x26, 0x52, 0x1B, 0xAD,\ + 0x99 +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Initiate | | + * | Encryption Start Proc. | | + * |--------------------------->| | + * | -----------------\ | | + * | | Empty Tx queue |-| | + * | |----------------| | | + * | | | + * | | LL_ENC_REQ | + * | |-------------------->| + * | | | + * | | LL_ENC_RSP | + * | |<--------------------| + * | | | + * | | LL_START_ENC_REQ | + * | |<--------------------| + * | ----------------\ | | + * | | Tx Encryption |-| | + * | | Rx Decryption | | | + * | |---------------| | | + * | | | + * | | LL_START_ENC_RSP | + * | |-------------------->| + * | | | + * | | LL_START_ENC_RSP | + * | |<--------------------| + * | | | + * | Encryption Start Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + */ +void test_encryption_start_mas_loc(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + const uint8_t rand[] = { RAND }; + const uint8_t ediv[] = { EDIV }; + const uint8_t ltk[] = { LTK }; + const uint8_t skd[] = { SKDM, SKDS }; + const uint8_t sk_be[] = { SK_BE }; + const uint8_t iv[] = { IVM, IVS }; + + /* Prepare expected LL_ENC_REQ */ + struct pdu_data_llctrl_enc_req exp_enc_req = { + .rand = { RAND }, + .ediv = { EDIV }, + .skdm = { SKDM }, + .ivm = { IVM }, + }; + + /* Prepare LL_ENC_RSP */ + struct pdu_data_llctrl_enc_rsp enc_rsp = { .skds = { SKDS }, .ivs = { IVS } }; + + /* Prepare mocked call(s) to lll_csrand_get */ + /* First call for SKDm */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_req.skdm)); + ztest_return_data(lll_csrand_get, buf, exp_enc_req.skdm); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_req.skdm)); + /* Second call for IVm */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_req.ivm)); + ztest_return_data(lll_csrand_get, buf, exp_enc_req.ivm); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_req.ivm)); + + /* Prepare mocked call to ecb_encrypt */ + ztest_expect_data(ecb_encrypt, key_le, ltk); + ztest_expect_data(ecb_encrypt, clear_text_le, skd); + ztest_return_data(ecb_encrypt, cipher_text_be, sk_be); + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Initiate an Encryption Start Procedure */ + err = ull_cp_encryption_start(&conn, rand, ediv, ltk); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_ENC_REQ, &conn, &tx, &exp_enc_req); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Rx */ + lt_tx(LL_ENC_RSP, &conn, &enc_rsp); + + /* Rx */ + lt_tx(LL_START_ENC_REQ, &conn, NULL); + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Tx paused & enc. */ + + /* CCM Tx/Rx SK should match SK */ + /* CCM Tx/Rx IV should match the IV */ + /* CCM Tx/Rx Counter should be zero */ + /* CCM Rx Direction should be S->M */ + /* CCM Tx Direction should be M->S */ + CHECK_RX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_S_TO_M); + CHECK_TX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_M_TO_S); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_START_ENC_RSP, &conn, &tx, NULL); + lt_rx_q_is_empty(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Tx paused & enc. */ + + /* Rx */ + lt_tx(LL_START_ENC_RSP, &conn, NULL); + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Rx enc. */ + CHECK_TX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Tx enc. */ + + /* There should be one host notification */ + ut_rx_pdu(LL_START_ENC_RSP, &ntf, NULL); + 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()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | -----------------\ | | + * | | Reserver all |-| | + * | | Tx/Ntf buffers | | | + * | |----------------| | | + * | | | + * | Initiate | | + * | Encryption Start Proc. | | + * |--------------------------->| | + * | -----------------\ | | + * | | Empty Tx queue |-| | + * | |----------------| | | + * | | | + * | | LL_ENC_REQ | + * | |-------------------->| + * | | | + * | | LL_ENC_RSP | + * | |<--------------------| + * | | | + * | | LL_START_ENC_REQ | + * | |<--------------------| + * | ----------------\ | | + * | | Tx Encryption |-| | + * | | Rx Decryption | | | + * | |---------------| | | + * | | | + * | | LL_START_ENC_RSP | + * | |-------------------->| + * | | | + * | | LL_START_ENC_RSP | + * | |<--------------------| + * | | | + * | Encryption Start Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + */ +void test_encryption_start_mas_loc_limited_memory(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct proc_ctx *ctx = NULL; + + const uint8_t rand[] = { RAND }; + const uint8_t ediv[] = { EDIV }; + const uint8_t ltk[] = { LTK }; + const uint8_t skd[] = { SKDM, SKDS }; + const uint8_t sk_be[] = { SK_BE }; + const uint8_t iv[] = { IVM, IVS }; + + /* Prepare expected LL_ENC_REQ */ + struct pdu_data_llctrl_enc_req exp_enc_req = { + .rand = { RAND }, + .ediv = { EDIV }, + .skdm = { SKDM }, + .ivm = { IVM }, + }; + + /* Prepare LL_ENC_RSP */ + struct pdu_data_llctrl_enc_rsp enc_rsp = { .skds = { SKDS }, .ivs = { IVS } }; + + /* Prepare mocked call(s) to lll_csrand_get */ + /* First call for SKDm */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_req.skdm)); + ztest_return_data(lll_csrand_get, buf, exp_enc_req.skdm); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_req.skdm)); + /* Second call for IVm */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_req.ivm)); + ztest_return_data(lll_csrand_get, buf, exp_enc_req.ivm); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_req.ivm)); + + /* Prepare mocked call to ecb_encrypt */ + ztest_expect_data(ecb_encrypt, key_le, ltk); + ztest_expect_data(ecb_encrypt, clear_text_le, skd); + ztest_return_data(ecb_encrypt, cipher_text_be, sk_be); + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Allocate dummy procedure used to steal all buffers */ + ctx = llcp_create_local_procedure(PROC_VERSION_EXCHANGE); + + /* Steal all tx buffers */ + while (llcp_tx_alloc_peek(&conn, ctx)) { + tx = llcp_tx_alloc(&conn, ctx); + zassert_not_null(tx, NULL); + } + + /* Dummy remove, as above loop might queue up ctx */ + llcp_tx_alloc_unpeek(ctx); + + /* Steal all ntf buffers */ + while (ll_pdu_rx_alloc_peek(1)) { + ntf = ll_pdu_rx_alloc(); + /* Make sure we use a correct type or the release won't work */ + ntf->hdr.type = NODE_RX_TYPE_DC_PDU; + } + + /* Initiate an Encryption Start Procedure */ + err = ull_cp_encryption_start(&conn, rand, ediv, ltk); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have no LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_ENC_REQ, &conn, &tx, &exp_enc_req); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Rx */ + lt_tx(LL_ENC_RSP, &conn, &enc_rsp); + + /* Rx */ + lt_tx(LL_START_ENC_REQ, &conn, NULL); + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Tx Queue should have no LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have no LL Control PDU */ + lt_rx(LL_START_ENC_RSP, &conn, &tx, NULL); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Tx paused & enc. */ + + /* CCM Tx/Rx SK should match SK */ + /* CCM Tx/Rx IV should match the IV */ + /* CCM Tx/Rx Counter should be zero */ + /* CCM Tx Direction should be M->S */ + /* CCM Rx Direction should be S->M */ + CHECK_RX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_S_TO_M); + CHECK_TX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_M_TO_S); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Rx */ + lt_tx(LL_START_ENC_RSP, &conn, NULL); + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Rx enc. */ + CHECK_TX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Tx enc. */ + + /* There should be no host notifications */ + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /* Prepare */ + event_prepare(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Rx enc. */ + CHECK_TX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Tx enc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Rx enc. */ + CHECK_TX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Tx enc. */ + + /* There should be one host notification */ + ut_rx_pdu(LL_START_ENC_RSP, &ntf, NULL); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /* Tx Encryption should be enabled */ + zassert_equal(conn.lll.enc_tx, 1U, NULL); + + /* Rx Decryption should be enabled */ + zassert_equal(conn.lll.enc_rx, 1U, NULL); + + /* Release dummy procedure */ + llcp_proc_ctx_release(ctx); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Initiate | | + * | Encryption Start Proc. | | + * |--------------------------->| | + * | -----------------\ | | + * | | Empty Tx queue |-| | + * | |----------------| | | + * | | | + * | | LL_ENC_REQ | + * | |-------------------->| + * | | | + * | | LL_ENC_RSP | + * | |<--------------------| + * | | | + * | | LL_REJECT_EXT_IND | + * | |<--------------------| + * | | | + * | Encryption Start Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + */ +void test_encryption_start_mas_loc_no_ltk(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + const uint8_t rand[] = { RAND }; + const uint8_t ediv[] = { EDIV }; + const uint8_t ltk[] = { LTK }; + + /* Prepare expected LL_ENC_REQ */ + struct pdu_data_llctrl_enc_req exp_enc_req = { + .rand = { RAND }, + .ediv = { EDIV }, + .skdm = { SKDM }, + .ivm = { IVM }, + }; + + /* Prepare LL_ENC_RSP */ + struct pdu_data_llctrl_enc_rsp enc_rsp = { .skds = { SKDS }, .ivs = { IVS } }; + + /* Prepare mocked call(s) to lll_csrand_get */ + /* First call for SKDm */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_req.skdm)); + ztest_return_data(lll_csrand_get, buf, exp_enc_req.skdm); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_req.skdm)); + /* Second call for IVm */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_req.ivm)); + ztest_return_data(lll_csrand_get, buf, exp_enc_req.ivm); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_req.ivm)); + + struct pdu_data_llctrl_reject_ind reject_ind = { .error_code = + BT_HCI_ERR_PIN_OR_KEY_MISSING }; + + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_ENC_REQ, + .error_code = BT_HCI_ERR_PIN_OR_KEY_MISSING + }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Initiate an Encryption Start Procedure */ + err = ull_cp_encryption_start(&conn, rand, ediv, ltk); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_ENC_REQ, &conn, &tx, &exp_enc_req); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Rx */ + lt_tx(LL_ENC_RSP, &conn, &enc_rsp); + + /* Rx */ + lt_tx(LL_REJECT_EXT_IND, &conn, &reject_ext_ind); + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* There should be one host notification */ + ut_rx_pdu(LL_REJECT_IND, &ntf, &reject_ind); + 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()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Initiate | | + * | Encryption Start Proc. | | + * |--------------------------->| | + * | -----------------\ | | + * | | Empty Tx queue |-| | + * | |----------------| | | + * | | | + * | | LL_ENC_REQ | + * | |-------------------->| + * | | | + * | | LL_ENC_RSP | + * | |<--------------------| + * | | | + * | | LL_REJECT_IND | + * | |<--------------------| + * | | | + * | Encryption Start Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + */ +void test_encryption_start_mas_loc_no_ltk_2(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + const uint8_t rand[] = { RAND }; + const uint8_t ediv[] = { EDIV }; + const uint8_t ltk[] = { LTK }; + + /* Prepare expected LL_ENC_REQ */ + struct pdu_data_llctrl_enc_req exp_enc_req = { + .rand = { RAND }, + .ediv = { EDIV }, + .skdm = { SKDM }, + .ivm = { IVM }, + }; + + /* Prepare LL_ENC_RSP */ + struct pdu_data_llctrl_enc_rsp enc_rsp = { .skds = { SKDS }, .ivs = { IVS } }; + + /* Prepare mocked call(s) to lll_csrand_get */ + /* First call for SKDm */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_req.skdm)); + ztest_return_data(lll_csrand_get, buf, exp_enc_req.skdm); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_req.skdm)); + /* Second call for IVm */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_req.ivm)); + ztest_return_data(lll_csrand_get, buf, exp_enc_req.ivm); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_req.ivm)); + + struct pdu_data_llctrl_reject_ind reject_ind = { .error_code = + BT_HCI_ERR_PIN_OR_KEY_MISSING }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Initiate an Encryption Start Procedure */ + err = ull_cp_encryption_start(&conn, rand, ediv, ltk); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_ENC_REQ, &conn, &tx, &exp_enc_req); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Rx */ + lt_tx(LL_ENC_RSP, &conn, &enc_rsp); + + /* Rx */ + lt_tx(LL_REJECT_IND, &conn, &reject_ind); + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* There should be one host notification */ + ut_rx_pdu(LL_REJECT_IND, &ntf, &reject_ind); + 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()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_ENC_REQ | + * | |<--------------------| + * | -----------------\ | | + * | | Empty Tx queue |-| | + * | |----------------| | | + * | | | + * | | LL_ENC_RSP | + * | |-------------------->| + * | | | + * | LTK Request | | + * |<----------------------| | + * | | | + * | LTK Request Reply | | + * |---------------------->| | + * | | | + * | | LL_START_ENC_REQ | + * | |-------------------->| + * | ----------------\ | | + * | | Rx Decryption |-| | + * | |---------------| | | + * | | | + * | | LL_START_ENC_RSP | + * | |<--------------------| + * | | | + * | Encryption Change | | + * |<----------------------| | + * | | | + * | | LL_START_ENC_RSP | + * | |-------------------->| + * | ----------------\ | | + * | | Tx Encryption |-| | + * | |---------------| | | + * | | | + */ +void test_encryption_start_sla_rem(void) +{ + struct node_tx *tx; + struct node_rx_pdu *ntf; + + const uint8_t ltk[] = { LTK }; + const uint8_t skd[] = { SKDM, SKDS }; + const uint8_t sk_be[] = { SK_BE }; + const uint8_t iv[] = { IVM, IVS }; + + /* Prepare LL_ENC_REQ */ + struct pdu_data_llctrl_enc_req enc_req = { + .rand = { RAND }, + .ediv = { EDIV }, + .skdm = { SKDM }, + .ivm = { IVM }, + }; + + struct pdu_data_llctrl_enc_rsp exp_enc_rsp = { + .skds = { SKDS }, + .ivs = { IVS }, + }; + + /* Prepare mocked call(s) to lll_csrand_get */ + /* First call for SKDs */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_rsp.skds)); + ztest_return_data(lll_csrand_get, buf, exp_enc_rsp.skds); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_rsp.skds)); + /* Second call for IVs */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_rsp.ivs)); + ztest_return_data(lll_csrand_get, buf, exp_enc_rsp.ivs); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_rsp.ivs)); + + /* Prepare mocked call to ecb_encrypt */ + ztest_expect_data(ecb_encrypt, key_le, ltk); + ztest_expect_data(ecb_encrypt, clear_text_le, skd); + ztest_return_data(ecb_encrypt, cipher_text_be, sk_be); + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Prepare */ + event_prepare(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Rx */ + lt_tx(LL_ENC_REQ, &conn, &enc_req); + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Prepare */ + event_prepare(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_ENC_RSP, &conn, &tx, &exp_enc_rsp); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* There should be a host notification */ + ut_rx_pdu(LL_ENC_REQ, &ntf, &enc_req); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /* LTK request reply */ + ull_cp_ltk_req_reply(&conn, ltk); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_START_ENC_REQ, &conn, &tx, NULL); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* CCM Rx SK should match SK */ + /* CCM Rx IV should match the IV */ + /* CCM Rx Counter should be zero */ + /* CCM Rx Direction should be M->S */ + CHECK_RX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_M_TO_S); + + /* Prepare */ + event_prepare(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Rx */ + lt_tx(LL_START_ENC_RSP, &conn, NULL); + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Rx enc. */ + CHECK_TX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Tx enc. */ + + /* There should be a host notification */ + ut_rx_pdu(LL_START_ENC_RSP, &ntf, NULL); + ut_rx_q_is_empty(); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_START_ENC_RSP, &conn, &tx, NULL); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Rx enc. */ + CHECK_TX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Tx enc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Rx enc. */ + CHECK_TX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Tx enc. */ + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* CCM Tx SK should match SK */ + /* CCM Tx IV should match the IV */ + /* CCM Tx Counter should be zero */ + /* CCM Tx Direction should be S->M */ + CHECK_TX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_S_TO_M); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | -----------------\ | | + * | | Reserver all |-| | + * | | Tx/Ntf buffers | | | + * | |----------------| | | + * | | | + * | | LL_ENC_REQ | + * | |<--------------------| + * | -----------------\ | | + * | | Empty Tx queue |-| | + * | |----------------| | | + * | | | + * | | LL_ENC_RSP | + * | |-------------------->| + * | | | + * | LTK Request | | + * |<----------------------| | + * | | | + * | LTK Request Reply | | + * |---------------------->| | + * | | | + * | | LL_START_ENC_REQ | + * | |-------------------->| + * | ----------------\ | | + * | | Rx Decryption |-| | + * | |---------------| | | + * | | | + * | | LL_START_ENC_RSP | + * | |<--------------------| + * | | | + * | Encryption Change | | + * |<----------------------| | + * | | | + * | | LL_START_ENC_RSP | + * | |-------------------->| + * | ----------------\ | | + * | | Tx Encryption |-| | + * | |---------------| | | + * | | | + */ +void test_encryption_start_sla_rem_limited_memory(void) +{ + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct proc_ctx *ctx = NULL; + + const uint8_t ltk[] = { LTK }; + const uint8_t skd[] = { SKDM, SKDS }; + const uint8_t sk_be[] = { SK_BE }; + const uint8_t iv[] = { IVM, IVS }; + + /* Prepare LL_ENC_REQ */ + struct pdu_data_llctrl_enc_req enc_req = { + .rand = { RAND }, + .ediv = { EDIV }, + .skdm = { SKDM }, + .ivm = { IVM }, + }; + + struct pdu_data_llctrl_enc_rsp exp_enc_rsp = { + .skds = { SKDS }, + .ivs = { IVS }, + }; + + /* Prepare mocked call(s) to lll_csrand_get */ + /* First call for SKDs */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_rsp.skds)); + ztest_return_data(lll_csrand_get, buf, exp_enc_rsp.skds); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_rsp.skds)); + /* Second call for IVs */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_rsp.ivs)); + ztest_return_data(lll_csrand_get, buf, exp_enc_rsp.ivs); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_rsp.ivs)); + + /* Prepare mocked call to ecb_encrypt */ + ztest_expect_data(ecb_encrypt, key_le, ltk); + ztest_expect_data(ecb_encrypt, clear_text_le, skd); + ztest_return_data(ecb_encrypt, cipher_text_be, sk_be); + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Allocate dummy procedure used to steal all buffers */ + ctx = llcp_create_local_procedure(PROC_VERSION_EXCHANGE); + + /* Steal all tx buffers */ + while (llcp_tx_alloc_peek(&conn, ctx)) { + tx = llcp_tx_alloc(&conn, ctx); + zassert_not_null(tx, NULL); + } + + /* Dummy remove, as above loop might queue up ctx */ + llcp_tx_alloc_unpeek(ctx); + + /* Steal all ntf buffers */ + while (ll_pdu_rx_alloc_peek(1)) { + ntf = ll_pdu_rx_alloc(); + /* Make sure we use a correct type or the release won't work */ + ntf->hdr.type = NODE_RX_TYPE_DC_PDU; + } + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + lt_tx(LL_ENC_REQ, &conn, &enc_req); + + /* Tx Queue should not have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Release tx */ + ull_cp_release_tx(&conn, tx); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_ENC_RSP, &conn, &tx, &exp_enc_rsp); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* There should not be a host notification */ + ut_rx_q_is_empty(); + + /* Release ntf */ + ull_cp_release_ntf(ntf); + + /* Prepare */ + event_prepare(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* There should be one host notification */ + ut_rx_pdu(LL_ENC_REQ, &ntf, &enc_req); + ut_rx_q_is_empty(); + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* LTK request reply */ + ull_cp_ltk_req_reply(&conn, ltk); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should not have one LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Release tx */ + ull_cp_release_tx(&conn, tx); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_START_ENC_REQ, &conn, &tx, NULL); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* CCM Rx SK should match SK */ + /* CCM Rx IV should match the IV */ + /* CCM Rx Counter should be zero */ + /* CCM Rx Direction should be M->S */ + CHECK_RX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_M_TO_S); + + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + lt_tx(LL_START_ENC_RSP, &conn, NULL); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* There should not be a host notification */ + ut_rx_q_is_empty(); + + /* Release ntf */ + ull_cp_release_ntf(ntf); + + /* Prepare */ + event_prepare(&conn); + + /* There should be one host notification */ + ut_rx_pdu(LL_START_ENC_RSP, &ntf, NULL); + ut_rx_q_is_empty(); + + /* Tx Queue should not have a LL Control PDU */ + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, ENCRYPTED); /* Rx paused & enc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Release tx */ + ull_cp_release_tx(&conn, tx); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_START_ENC_RSP, &conn, &tx, NULL); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Rx enc. */ + CHECK_TX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Tx enc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Rx enc. */ + CHECK_TX_PE_STATE(conn, RESUMED, ENCRYPTED); /* Tx enc. */ + + /* CCM Tx SK should match SK */ + /* CCM Tx IV should match the IV */ + /* CCM Tx Counter should be zero */ + /* CCM Tx Direction should be S->M */ + CHECK_TX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_S_TO_M); + + /* Release dummy procedure */ + llcp_proc_ctx_release(ctx); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_ENC_REQ | + * | |<--------------------| + * | -----------------\ | | + * | | Empty Tx queue |-| | + * | |----------------| | | + * | | | + * | | LL_ENC_RSP | + * | |-------------------->| + * | | | + * | LTK Request | | + * |<----------------------| | + * | | | + * | LTK Request Reply | | + * |---------------------->| | + * | | | + * | | LL_REJECT_EXT_IND | + * | |-------------------->| + */ +void test_encryption_start_sla_rem_no_ltk(void) +{ + struct node_tx *tx; + struct node_rx_pdu *ntf; + + /* Prepare LL_ENC_REQ */ + struct pdu_data_llctrl_enc_req enc_req = { + .rand = { RAND }, + .ediv = { EDIV }, + .skdm = { SKDM }, + .ivm = { IVM }, + }; + + struct pdu_data_llctrl_enc_rsp exp_enc_rsp = { + .skds = { SKDS }, + .ivs = { IVS }, + }; + + struct pdu_data_llctrl_reject_ext_ind reject_ext_ind = { + .reject_opcode = PDU_DATA_LLCTRL_TYPE_ENC_REQ, + .error_code = BT_HCI_ERR_PIN_OR_KEY_MISSING + }; + + /* Prepare mocked call(s) to lll_csrand_get */ + /* First call for SKDs */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_rsp.skds)); + ztest_return_data(lll_csrand_get, buf, exp_enc_rsp.skds); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_rsp.skds)); + /* Second call for IVs */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_rsp.ivs)); + ztest_return_data(lll_csrand_get, buf, exp_enc_rsp.ivs); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_rsp.ivs)); + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + lt_tx(LL_ENC_REQ, &conn, &enc_req); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_ENC_RSP, &conn, &tx, &exp_enc_rsp); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Rx paused & unenc. */ + CHECK_TX_PE_STATE(conn, PAUSED, UNENCRYPTED); /* Tx paused & unenc. */ + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* There should be a host notification */ + ut_rx_pdu(LL_ENC_REQ, &ntf, &enc_req); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /* LTK request reply */ + ull_cp_ltk_req_neq_reply(&conn); + + /* Check state */ + /* TODO(thoh): THIS IS WRONG! */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_REJECT_EXT_IND, &conn, &tx, &reject_ext_ind); + lt_rx_q_is_empty(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Done */ + event_done(&conn); + + /* Check state */ + CHECK_RX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Rx unenc. */ + CHECK_TX_PE_STATE(conn, RESUMED, UNENCRYPTED); /* Tx unenc. */ + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notification */ + ut_rx_q_is_empty(); + + /* Note that for this test the context is not released */ + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM - 1, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_encryption_pause_mas_loc(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + const uint8_t rand[] = { RAND }; + const uint8_t ediv[] = { EDIV }; + const uint8_t ltk[] = { LTK }; + const uint8_t skd[] = { SKDM, SKDS }; + const uint8_t sk_be[] = { SK_BE }; + const uint8_t iv[] = { IVM, IVS }; + + /* Prepare expected LL_ENC_REQ */ + struct pdu_data_llctrl_enc_req exp_enc_req = { + .rand = { RAND }, + .ediv = { EDIV }, + .skdm = { SKDM }, + .ivm = { IVM }, + }; + + /* Prepare LL_ENC_RSP */ + struct pdu_data_llctrl_enc_rsp enc_rsp = { .skds = { SKDS }, .ivs = { IVS } }; + + /* Prepare mocked call(s) to lll_csrand_get */ + /* First call for SKDm */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_req.skdm)); + ztest_return_data(lll_csrand_get, buf, exp_enc_req.skdm); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_req.skdm)); + /* Second call for IVm */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_req.ivm)); + ztest_return_data(lll_csrand_get, buf, exp_enc_req.ivm); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_req.ivm)); + + /* Prepare mocked call to ecb_encrypt */ + ztest_expect_data(ecb_encrypt, key_le, ltk); + ztest_expect_data(ecb_encrypt, clear_text_le, skd); + ztest_return_data(ecb_encrypt, cipher_text_be, sk_be); + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Fake that encryption is already active */ + conn.lll.enc_rx = 1U; + conn.lll.enc_tx = 1U; + + /**** ENCRYPTED ****/ + + /* Initiate an Encryption Pause Procedure */ + err = ull_cp_encryption_pause(&conn, rand, ediv, ltk); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_PAUSE_ENC_REQ, &conn, &tx, NULL); + lt_rx_q_is_empty(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Rx */ + lt_tx(LL_PAUSE_ENC_RSP, &conn, NULL); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_PAUSE_ENC_RSP, &conn, &tx, NULL); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Tx Encryption should be disabled */ + zassert_equal(conn.lll.enc_tx, 0U, NULL); + + /* Rx Decryption should be disabled */ + zassert_equal(conn.lll.enc_rx, 0U, NULL); + + /**** UNENCRYPTED ****/ + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_ENC_REQ, &conn, &tx, &exp_enc_req); + lt_rx_q_is_empty(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Rx */ + lt_tx(LL_ENC_RSP, &conn, &enc_rsp); + + /* Rx */ + lt_tx(LL_START_ENC_REQ, &conn, NULL); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_START_ENC_RSP, &conn, &tx, NULL); + lt_rx_q_is_empty(&conn); + + /* CCM Tx/Rx SK should match SK */ + /* CCM Tx/Rx IV should match the IV */ + /* CCM Tx/Rx Counter should be zero */ + /* CCM Rx Direction should be S->M */ + /* CCM Tx Direction should be M->S */ + CHECK_RX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_S_TO_M); + CHECK_TX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_M_TO_S); + + /* Tx Encryption should be enabled */ + zassert_equal(conn.lll.enc_tx, 1U, NULL); + + /* Rx Decryption should be enabled */ + zassert_equal(conn.lll.enc_rx, 1U, NULL); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Rx */ + lt_tx(LL_START_ENC_RSP, &conn, NULL); + + /* Done */ + event_done(&conn); + + /* There should be one host notification */ + ut_rx_node(NODE_ENC_REFRESH, &ntf, NULL); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /* Tx Encryption should be enabled */ + zassert_equal(conn.lll.enc_tx, 1U, NULL); + + /* Rx Decryption should be enabled */ + zassert_equal(conn.lll.enc_rx, 1U, NULL); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_encryption_pause_sla_rem(void) +{ + struct node_tx *tx; + struct node_rx_pdu *ntf; + + const uint8_t ltk[] = { LTK }; + const uint8_t skd[] = { SKDM, SKDS }; + const uint8_t sk_be[] = { SK_BE }; + const uint8_t iv[] = { IVM, IVS }; + + /* Prepare LL_ENC_REQ */ + struct pdu_data_llctrl_enc_req enc_req = { + .rand = { RAND }, + .ediv = { EDIV }, + .skdm = { SKDM }, + .ivm = { IVM }, + }; + + struct pdu_data_llctrl_enc_rsp exp_enc_rsp = { + .skds = { SKDS }, + .ivs = { IVS }, + }; + + /* Prepare mocked call(s) to lll_csrand_get */ + /* First call for SKDs */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_rsp.skds)); + ztest_return_data(lll_csrand_get, buf, exp_enc_rsp.skds); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_rsp.skds)); + /* Second call for IVs */ + ztest_returns_value(lll_csrand_get, sizeof(exp_enc_rsp.ivs)); + ztest_return_data(lll_csrand_get, buf, exp_enc_rsp.ivs); + ztest_expect_value(lll_csrand_get, len, sizeof(exp_enc_rsp.ivs)); + + /* Prepare mocked call to ecb_encrypt */ + ztest_expect_data(ecb_encrypt, key_le, ltk); + ztest_expect_data(ecb_encrypt, clear_text_le, skd); + ztest_return_data(ecb_encrypt, cipher_text_be, sk_be); + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Fake that encryption is already active */ + conn.lll.enc_rx = 1U; + conn.lll.enc_tx = 1U; + + /**** ENCRYPTED ****/ + + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + lt_tx(LL_PAUSE_ENC_REQ, &conn, NULL); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_PAUSE_ENC_RSP, &conn, &tx, NULL); + lt_rx_q_is_empty(&conn); + + /* Rx Decryption should be disabled */ + zassert_equal(conn.lll.enc_rx, 0U, NULL); + + /* Rx */ + lt_tx(LL_PAUSE_ENC_RSP, &conn, NULL); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* Tx Encryption should be disabled */ + zassert_equal(conn.lll.enc_tx, 0U, NULL); + + /**** UNENCRYPTED ****/ + + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + lt_tx(LL_ENC_REQ, &conn, &enc_req); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_ENC_RSP, &conn, &tx, &exp_enc_rsp); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* There should be a host notification */ + ut_rx_pdu(LL_ENC_REQ, &ntf, &enc_req); + ut_rx_q_is_empty(); + + /* Release Ntf */ + ull_cp_release_ntf(ntf); + + /* LTK request reply */ + ull_cp_ltk_req_reply(&conn, ltk); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_START_ENC_REQ, &conn, &tx, NULL); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* CCM Rx SK should match SK */ + /* CCM Rx IV should match the IV */ + /* CCM Rx Counter should be zero */ + /* CCM Rx Direction should be M->S */ + CHECK_RX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_M_TO_S); + + /* Rx Decryption should be enabled */ + zassert_equal(conn.lll.enc_rx, 1U, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + lt_tx(LL_START_ENC_RSP, &conn, NULL); + + /* Done */ + event_done(&conn); + + /* There should be a host notification */ + ut_rx_node(NODE_ENC_REFRESH, &ntf, NULL); + ut_rx_q_is_empty(); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_START_ENC_RSP, &conn, &tx, NULL); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release Tx */ + ull_cp_release_tx(&conn, tx); + + /* CCM Tx SK should match SK */ + /* CCM Tx IV should match the IV */ + /* CCM Tx Counter should be zero */ + /* CCM Tx Direction should be S->M */ + CHECK_TX_CCM_STATE(conn, sk_be, iv, 0U, CCM_DIR_S_TO_M); + + /* Tx Encryption should be enabled */ + zassert_equal(conn.lll.enc_tx, 1U, NULL); + + 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( + encryption_start, + ztest_unit_test_setup_teardown(test_encryption_start_mas_loc, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_encryption_start_mas_loc_limited_memory, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_encryption_start_mas_loc_no_ltk, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_encryption_start_mas_loc_no_ltk_2, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_encryption_start_sla_rem, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_encryption_start_sla_rem_limited_memory, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_encryption_start_sla_rem_no_ltk, setup, + unit_test_noop)); + + ztest_test_suite(encryption_pause, + ztest_unit_test_setup_teardown(test_encryption_pause_mas_loc, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_encryption_pause_sla_rem, setup, + unit_test_noop)); + + ztest_run_test_suite(encryption_start); + ztest_run_test_suite(encryption_pause); +} diff --git a/tests/bluetooth/controller/ctrl_encrypt/testcase.yaml b/tests/bluetooth/controller/ctrl_encrypt/testcase.yaml new file mode 100644 index 00000000000..945be026c98 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_encrypt/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: test_framework bluetooth bt_encrypt bt_ull_llcp +tests: + bluetooth.controller.ctrl_encrypt.test: + type: unit diff --git a/tests/bluetooth/controller/ctrl_feature_exchange/CMakeLists.txt b/tests/bluetooth/controller/ctrl_feature_exchange/CMakeLists.txt new file mode 100644 index 00000000000..b01e7bcaee7 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_feature_exchange/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_feature_exchange) +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_feature_exchange/src/main.c b/tests/bluetooth/controller/ctrl_feature_exchange/src/main.c new file mode 100644 index 00000000000..4b001a698f7 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_feature_exchange/src/main.c @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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 "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_internal.h" +#include "ull_llcp.h" +#include "ull_llcp_internal.h" +#include "ull_conn_internal.h" + +#include "ll_feat.h" + +#include "helper_pdu.h" +#include "helper_util.h" +#include "helper_features.h" + +struct ll_conn conn; + +static void setup(void) +{ + test_setup(&conn); +} + +/* + * +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start | | + * | Feature Exchange Proc. | | + * |--------------------------->| | + * | | | + * | | LL_FEATURE_REQ | + * | |------------------>| + * | | | + * | | LL_FEATURE_RSP | + * | |<------------------| + * | | | + * | Feature Exchange Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + */ +void test_feature_exchange_mas_loc(void) +{ + uint64_t err; + uint64_t set_featureset[] = { DEFAULT_FEATURE, DEFAULT_FEATURE }; + uint64_t rsp_featureset[] = { + (LL_FEAT_BIT_MASK_VALID & FEAT_FILTER_OCTET0) | DEFAULT_FEATURE, 0x0 + }; + uint64_t exp_rsp_featureset[] = { ((LL_FEAT_BIT_MASK_VALID & FEAT_FILTER_OCTET0) | + DEFAULT_FEATURE) & + LL_FEAT_BIT_MASK_VALID, + 0x0 }; + int feat_to_test = ARRAY_SIZE(set_featureset); + + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_feature_req local_feature_req; + struct pdu_data_llctrl_feature_rsp remote_feature_rsp; + struct pdu_data_llctrl_feature_rsp exp_remote_feature_rsp; + int feat_counter; + + for (feat_counter = 0; feat_counter < feat_to_test; feat_counter++) { + sys_put_le64(set_featureset[feat_counter], local_feature_req.features); + + sys_put_le64(rsp_featureset[feat_counter], remote_feature_rsp.features); + + sys_put_le64(exp_rsp_featureset[feat_counter], exp_remote_feature_rsp.features); + + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Feature Exchange Procedure */ + err = ull_cp_feature_exchange(&conn); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + event_prepare(&conn); + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_FEATURE_REQ, &conn, &tx, &local_feature_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_FEATURE_RSP, &conn, &remote_feature_rsp); + + event_done(&conn); + /* There should be one host notification */ + + ut_rx_pdu(LL_FEATURE_RSP, &ntf, &exp_remote_feature_rsp); + + ut_rx_q_is_empty(); + + ull_cp_release_tx(&conn, tx); + ull_cp_release_ntf(ntf); + } + zassert_equal(conn.lll.event_counter, feat_to_test, "Wrong event-count %d\n", + conn.lll.event_counter); + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_feature_exchange_mas_loc_2(void) +{ + uint8_t err; + + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + err = ull_cp_feature_exchange(&conn); + for (int i = 0U; i < CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM; i++) { + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + err = ull_cp_feature_exchange(&conn); + } + + zassert_not_equal(err, BT_HCI_ERR_SUCCESS, NULL); + zassert_equal(ctx_buffers_free(), 0, "Free CTX buffers %d", ctx_buffers_free()); +} + +/* + * +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_PERIPH_FEAT_XCHG | + * | |<------------------------| + * | | | + * | | LL_FEATURE_RSP | + * | |------------------------>| + * | | | + */ +#define MAS_REM_NR_OF_EVENTS 2 +void test_feature_exchange_mas_rem(void) +{ + uint64_t set_featureset[] = { + DEFAULT_FEATURE, + LL_FEAT_BIT_MASK_VALID, + EXPECTED_FEAT_EXCH_VALID, + 0xFFFFFFFFFFFFFFFF, + 0x0 }; + uint64_t exp_featureset[] = { DEFAULT_FEATURE & COMMON_FEAT_OCTET0(LL_FEAT_BIT_MASK_VALID), + DEFAULT_FEATURE & COMMON_FEAT_OCTET0(LL_FEAT_BIT_MASK_VALID), + DEFAULT_FEATURE & + COMMON_FEAT_OCTET0(EXPECTED_FEAT_EXCH_VALID), + DEFAULT_FEATURE & COMMON_FEAT_OCTET0(LL_FEAT_BIT_MASK_VALID), + DEFAULT_FEATURE & 0xFFFFFFFFFFFFFF00 }; + int feat_to_test = ARRAY_SIZE(set_featureset); + struct node_tx *tx; + + struct pdu_data_llctrl_feature_req remote_feature_req; + struct pdu_data_llctrl_feature_rsp local_feature_rsp; + + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + for (int feat_count = 0; feat_count < feat_to_test; feat_count++) { + sys_put_le64(set_featureset[feat_count], remote_feature_req.features); + sys_put_le64(exp_featureset[feat_count], local_feature_rsp.features); + + event_prepare(&conn); + + lt_tx(LL_PERIPH_FEAT_XCHG, &conn, &remote_feature_req); + + event_done(&conn); + + event_prepare(&conn); + + lt_rx(LL_FEATURE_RSP, &conn, &tx, &local_feature_rsp); + lt_rx_q_is_empty(&conn); + + event_done(&conn); + + ut_rx_q_is_empty(); + + ull_cp_release_tx(&conn, tx); + } + zassert_equal(conn.lll.event_counter, MAS_REM_NR_OF_EVENTS * (feat_to_test), + "Wrong event-count %d\n", conn.lll.event_counter); + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +#define MAS_REM_2_NR_OF_EVENTS 3 +void test_feature_exchange_mas_rem_2(void) +{ + /* + * we could combine some of the following, + * but in reality we should add some more + * test cases + */ + uint64_t set_featureset[] = { + DEFAULT_FEATURE, + LL_FEAT_BIT_MASK_VALID, + EXPECTED_FEAT_EXCH_VALID, + 0xFFFFFFFFFFFFFFFF, + 0x0 }; + uint64_t exp_featureset[] = { DEFAULT_FEATURE & COMMON_FEAT_OCTET0(LL_FEAT_BIT_MASK_VALID), + DEFAULT_FEATURE & COMMON_FEAT_OCTET0(LL_FEAT_BIT_MASK_VALID), + DEFAULT_FEATURE & + COMMON_FEAT_OCTET0(EXPECTED_FEAT_EXCH_VALID), + DEFAULT_FEATURE & COMMON_FEAT_OCTET0(LL_FEAT_BIT_MASK_VALID), + DEFAULT_FEATURE & 0xFFFFFFFFFFFFFF00 }; + uint64_t ut_featureset[] = { + DEFAULT_FEATURE, + DEFAULT_FEATURE, + DEFAULT_FEATURE, + DEFAULT_FEATURE, + DEFAULT_FEATURE }; + uint64_t ut_exp_featureset[] = { + DEFAULT_FEATURE & LL_FEAT_BIT_MASK_VALID, DEFAULT_FEATURE & LL_FEAT_BIT_MASK_VALID, + DEFAULT_FEATURE & LL_FEAT_BIT_MASK_VALID, DEFAULT_FEATURE & LL_FEAT_BIT_MASK_VALID, + (DEFAULT_FEATURE & LL_FEAT_BIT_MASK_VALID) & 0xFFFFFFFFFFFFFF00 + }; + + int feat_to_test = ARRAY_SIZE(set_featureset); + uint64_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_feature_req remote_feature_req; + struct pdu_data_llctrl_feature_rsp local_feature_rsp; + struct pdu_data_llctrl_feature_req ut_feature_req; + struct pdu_data_llctrl_feature_req ut_feature_rsp; + + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + for (int feat_count = 0; feat_count < feat_to_test; feat_count++) { + sys_put_le64(set_featureset[feat_count], remote_feature_req.features); + sys_put_le64(exp_featureset[feat_count], local_feature_rsp.features); + sys_put_le64(ut_featureset[feat_count], ut_feature_req.features); + sys_put_le64(ut_exp_featureset[feat_count], ut_feature_rsp.features); + + err = ull_cp_feature_exchange(&conn); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + event_prepare(&conn); + lt_tx(LL_PERIPH_FEAT_XCHG, &conn, &remote_feature_req); + event_done(&conn); + + event_prepare(&conn); + lt_rx(LL_FEATURE_REQ, &conn, &tx, &ut_feature_req); + lt_tx(LL_FEATURE_RSP, &conn, &local_feature_rsp); + event_done(&conn); + + ull_cp_release_tx(&conn, tx); + + event_prepare(&conn); + lt_rx(LL_FEATURE_RSP, &conn, &tx, &local_feature_rsp); + event_done(&conn); + + ut_rx_pdu(LL_FEATURE_RSP, &ntf, &ut_feature_rsp); + + /* + * at the end of a loop all queues should be empty + */ + ut_rx_q_is_empty(); + lt_rx_q_is_empty(&conn); + + ull_cp_release_tx(&conn, tx); + ull_cp_release_ntf(ntf); + } + + zassert_equal(conn.lll.event_counter, MAS_REM_2_NR_OF_EVENTS * (feat_to_test), + "Wrong event-count %d\n", conn.lll.event_counter); + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_slave_feature_exchange_sla_loc(void) +{ + uint64_t err; + uint64_t featureset; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_feature_req local_feature_req; + struct pdu_data_llctrl_feature_rsp remote_feature_rsp; + + featureset = DEFAULT_FEATURE; + sys_put_le64(featureset, local_feature_req.features); + featureset &= LL_FEAT_BIT_MASK_VALID; + sys_put_le64(featureset, remote_feature_rsp.features); + + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Steal all ntf buffers, so as to check that the wait_ntf mechanism works */ + while (ll_pdu_rx_alloc_peek(1)) { + ntf = ll_pdu_rx_alloc(); + /* Make sure we use a correct type or the release won't work */ + ntf->hdr.type = NODE_RX_TYPE_DC_PDU; + } + + /* Initiate a Feature Exchange Procedure */ + err = ull_cp_feature_exchange(&conn); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + event_prepare(&conn); + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_PERIPH_FEAT_XCHG, &conn, &tx, &local_feature_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_FEATURE_RSP, &conn, &remote_feature_rsp); + + event_done(&conn); + + ut_rx_q_is_empty(); + + /* Release Ntf, so next cycle will generate NTF and complete procedure */ + ull_cp_release_ntf(ntf); + + event_prepare(&conn); + event_done(&conn); + + /* There should be one host notification */ + + ut_rx_pdu(LL_FEATURE_RSP, &ntf, &remote_feature_rsp); + ut_rx_q_is_empty(); + zassert_equal(conn.lll.event_counter, 2, "Wrong event-count %d\n", + conn.lll.event_counter); + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_feature_exchange_sla_loc_unknown_rsp(void) +{ + uint64_t err; + uint64_t featureset; + struct node_tx *tx; + + struct pdu_data_llctrl_feature_req local_feature_req; + + struct node_rx_pdu *ntf; + struct pdu_data_llctrl_unknown_rsp unknown_rsp = { + .type = PDU_DATA_LLCTRL_TYPE_PER_INIT_FEAT_XCHG + }; + + featureset = DEFAULT_FEATURE; + sys_put_le64(featureset, local_feature_req.features); + + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Steal all ntf buffers, so as to check that the wait_ntf mechanism works */ + while (ll_pdu_rx_alloc_peek(1)) { + ntf = ll_pdu_rx_alloc(); + /* Make sure we use a correct type or the release won't work */ + ntf->hdr.type = NODE_RX_TYPE_DC_PDU; + } + + /* Initiate a Feature Exchange Procedure */ + + event_prepare(&conn); + err = ull_cp_feature_exchange(&conn); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + event_done(&conn); + + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_PERIPH_FEAT_XCHG, &conn, &tx, &local_feature_req); + lt_rx_q_is_empty(&conn); + + /* Rx Commented out for know, handling of UNKNOWN response will come in an update */ + + lt_tx(LL_UNKNOWN_RSP, &conn, &unknown_rsp); + + event_done(&conn); + + ut_rx_q_is_empty(); + + /* Release Ntf, so next cycle will generate NTF and complete procedure */ + ull_cp_release_ntf(ntf); + + event_prepare(&conn); + event_done(&conn); + + ut_rx_pdu(LL_UNKNOWN_RSP, &ntf, &unknown_rsp); + ut_rx_q_is_empty(); + zassert_equal(conn.lll.event_counter, 3, "Wrong event-count %d\n", + conn.lll.event_counter); + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_hci_main(void); + +void test_main(void) +{ + ztest_test_suite(feature_exchange_master, + ztest_unit_test_setup_teardown(test_feature_exchange_mas_loc, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_feature_exchange_mas_loc_2, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_feature_exchange_mas_rem, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_feature_exchange_mas_rem_2, setup, + unit_test_noop)); + + ztest_test_suite(feature_exchange_slave, + ztest_unit_test_setup_teardown(test_slave_feature_exchange_sla_loc, setup, + unit_test_noop)); + + ztest_test_suite(feature_exchange_unknown, + ztest_unit_test_setup_teardown(test_feature_exchange_sla_loc_unknown_rsp, + setup, unit_test_noop)); + + ztest_run_test_suite(feature_exchange_master); + ztest_run_test_suite(feature_exchange_slave); + ztest_run_test_suite(feature_exchange_unknown); + + test_hci_main(); +} diff --git a/tests/bluetooth/controller/ctrl_feature_exchange/src/main_hci.c b/tests/bluetooth/controller/ctrl_feature_exchange/src/main_hci.c new file mode 100644 index 00000000000..41eddcc0138 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_feature_exchange/src/main_hci.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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 "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_llcp_internal.h" +#include "ull_conn_internal.h" + +#include "ll_feat.h" + +#include "helper_pdu.h" +#include "helper_util.h" +#include "helper_features.h" + +struct ll_conn *conn_from_pool; + +static void setup(void) +{ + ull_conn_init(); + + conn_from_pool = ll_conn_acquire(); + zassert_not_null(conn_from_pool, "Could not allocate connection memory", NULL); + + test_setup(conn_from_pool); +} + +/* + * +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start | | + * | Feature Exchange Proc. | | + * |--------------------------->| | + * | | | + * | | LL_FEATURE_REQ | + * | |------------------>| + * | | | + * | | LL_FEATURE_RSP | + * | |<------------------| + * | | | + * | Feature Exchange Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + */ +void test_hci_feature_exchange_mas_loc(void) +{ + uint64_t err; + uint64_t set_featureset[] = { + DEFAULT_FEATURE, + DEFAULT_FEATURE }; + uint64_t rsp_featureset[] = { ((LL_FEAT_BIT_MASK_VALID & FEAT_FILTER_OCTET0) | + DEFAULT_FEATURE) & + LL_FEAT_BIT_MASK_VALID, + 0x0 }; + int feat_to_test = ARRAY_SIZE(set_featureset); + + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_feature_req local_feature_req; + struct pdu_data_llctrl_feature_rsp remote_feature_rsp; + int feat_counter; + uint16_t conn_handle; + + for (feat_counter = 0; feat_counter < feat_to_test; feat_counter++) { + conn_handle = ll_conn_handle_get(conn_from_pool); + + sys_put_le64(set_featureset[feat_counter], local_feature_req.features); + sys_put_le64(rsp_featureset[feat_counter], remote_feature_rsp.features); + + test_set_role(conn_from_pool, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(conn_from_pool, ULL_CP_CONNECTED); + + /* Initiate a Feature Exchange Procedure via HCI */ + err = ll_feature_req_send(conn_handle); + + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Error: %d", err); + + event_prepare(conn_from_pool); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_FEATURE_REQ, conn_from_pool, &tx, &local_feature_req); + lt_rx_q_is_empty(conn_from_pool); + + /* Rx */ + lt_tx(LL_FEATURE_RSP, conn_from_pool, &remote_feature_rsp); + + event_done(conn_from_pool); + /* There should be one host notification */ + + ut_rx_pdu(LL_FEATURE_RSP, &ntf, &remote_feature_rsp); + + ut_rx_q_is_empty(); + + zassert_equal(conn_from_pool->lll.event_counter, feat_counter + 1, + "Wrong event count %d\n", conn_from_pool->lll.event_counter); + + ull_cp_release_tx(conn_from_pool, tx); + ull_cp_release_ntf(ntf); + + ll_conn_release(conn_from_pool); + } + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_hci_feature_exchange_wrong_handle(void) +{ + uint16_t conn_handle; + uint64_t err; + int ctx_counter; + struct proc_ctx *ctx; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + err = ll_feature_req_send(conn_handle + 1); + + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, "Wrong reply for wrong handle\n"); + + ctx_counter = 0; + do { + ctx = llcp_create_local_procedure(PROC_FEATURE_EXCHANGE); + ctx_counter++; + } while (ctx != NULL); + zassert_equal(ctx_counter, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM + 1, + "Error in setup of test\n"); + + err = ll_feature_req_send(conn_handle); + zassert_equal(err, BT_HCI_ERR_CMD_DISALLOWED, "Wrong reply for wrong handle\n"); + + zassert_equal(ctx_buffers_free(), 0, "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_hci_main(void) +{ + ztest_test_suite(hci_feature_exchange_master, + ztest_unit_test_setup_teardown(test_hci_feature_exchange_mas_loc, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_hci_feature_exchange_wrong_handle, + setup, unit_test_noop) + + ); + + ztest_run_test_suite(hci_feature_exchange_master); +} diff --git a/tests/bluetooth/controller/ctrl_feature_exchange/testcase.yaml b/tests/bluetooth/controller/ctrl_feature_exchange/testcase.yaml new file mode 100644 index 00000000000..6fdf65cb30b --- /dev/null +++ b/tests/bluetooth/controller/ctrl_feature_exchange/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: test_framework bluetooth bt_feature_exchange bt_ull_llcp +tests: + bluetooth.controller.ctrl_feature_exchange.test: + type: unit diff --git a/tests/bluetooth/controller/ctrl_hci/CMakeLists.txt b/tests/bluetooth/controller/ctrl_hci/CMakeLists.txt new file mode 100644 index 00000000000..b01e7bcaee7 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_hci/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_feature_exchange) +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_hci/prj.conf b/tests/bluetooth/controller/ctrl_hci/prj.conf new file mode 100644 index 00000000000..6c82c568107 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_hci/prj.conf @@ -0,0 +1,6 @@ +CONFIG_TEST=y +CONFIG_ZTEST=y +CONFIG_ZTEST_ASSERT_VERBOSE=1 +CONFIG_ZTEST_STACKSIZE=4096 +CONFIG_ZTEST_MOCKING=y +CONFIG_ZTEST_PARAMETER_COUNT=32 diff --git a/tests/bluetooth/controller/ctrl_hci/src/main.c b/tests/bluetooth/controller/ctrl_hci/src/main.c new file mode 100644 index 00000000000..31499a88107 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_hci/src/main.c @@ -0,0 +1,530 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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 "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 "ll_feat.h" + +#include "helper_pdu.h" +#include "helper_util.h" +#include "helper_features.h" + +struct ll_conn *conn_from_pool; + +static void setup(void) +{ + ull_conn_init(); + + conn_from_pool = ll_conn_acquire(); + zassert_not_null(conn_from_pool, "Could not allocate connection memory", NULL); + + test_setup(conn_from_pool); +} + +/* + * +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start | | + * | Feature Exchange Proc. | | + * |--------------------------->| | + * | | | + * | | LL_FEATURE_REQ | + * | |------------------>| + * | | | + * | | LL_FEATURE_RSP | + * | |<------------------| + * | | | + * | Feature Exchange Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + */ +void test_hci_feature_exchange(void) +{ + uint64_t err; + uint64_t set_feature = DEFAULT_FEATURE; + uint64_t rsp_feature = ((LL_FEAT_BIT_MASK_VALID & FEAT_FILTER_OCTET0) | DEFAULT_FEATURE) & + LL_FEAT_BIT_MASK_VALID; + + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_feature_req local_feature_req; + struct pdu_data_llctrl_feature_rsp remote_feature_rsp; + + uint16_t conn_handle; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + test_set_role(conn_from_pool, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(conn_from_pool, ULL_CP_CONNECTED); + + sys_put_le64(set_feature, local_feature_req.features); + sys_put_le64(rsp_feature, remote_feature_rsp.features); + + /* Initiate a Feature Exchange Procedure via HCI */ + err = ll_feature_req_send(conn_handle); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Error: %d", err); + + /* basically copied from unit-test for feature exchange */ + event_prepare(conn_from_pool); + lt_rx(LL_FEATURE_REQ, conn_from_pool, &tx, &local_feature_req); + lt_rx_q_is_empty(conn_from_pool); + lt_tx(LL_FEATURE_RSP, conn_from_pool, &remote_feature_rsp); + event_done(conn_from_pool); + ut_rx_pdu(LL_FEATURE_RSP, &ntf, &remote_feature_rsp); + ut_rx_q_is_empty(); + zassert_equal(conn_from_pool->lll.event_counter, 1, "Wrong event count %d\n", + conn_from_pool->lll.event_counter); + ull_cp_release_tx(conn_from_pool, tx); + ull_cp_release_ntf(ntf); + + ll_conn_release(conn_from_pool); +} + +void test_hci_feature_exchange_wrong_handle(void) +{ + uint16_t conn_handle; + uint64_t err; + int ctx_counter; + struct proc_ctx *ctx; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + err = ll_feature_req_send(conn_handle + 1); + + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, "Wrong reply for wrong handle\n"); + + ctx_counter = 0; + do { + ctx = llcp_create_local_procedure(PROC_FEATURE_EXCHANGE); + ctx_counter++; + } while (ctx != NULL); + zassert_equal(ctx_counter, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM + 1, + "Error in setup of test\n"); + + err = ll_feature_req_send(conn_handle); + zassert_equal(err, BT_HCI_ERR_CMD_DISALLOWED, "Wrong reply for wrong handle\n"); +} + +void test_hci_version_ind(void) +{ + uint64_t err; + uint16_t conn_handle; + struct node_tx *tx; + struct node_rx_pdu *ntf; + struct pdu_data_llctrl_version_ind local_pdu = { + .version_number = LL_VERSION_NUMBER, + .company_id = CONFIG_BT_CTLR_COMPANY_ID, + .sub_version_number = CONFIG_BT_CTLR_SUBVERSION_NUMBER, + }; + + struct pdu_data_llctrl_version_ind remote_pdu = { + .version_number = 0x55, + .company_id = 0xABCD, + .sub_version_number = 0x1234, + }; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + test_set_role(conn_from_pool, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(conn_from_pool, ULL_CP_CONNECTED); + + err = ll_version_ind_send(conn_handle); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Error: %d", err); + + event_prepare(conn_from_pool); + lt_rx(LL_VERSION_IND, conn_from_pool, &tx, &local_pdu); + lt_rx_q_is_empty(conn_from_pool); + lt_tx(LL_VERSION_IND, conn_from_pool, &remote_pdu); + event_done(conn_from_pool); + ut_rx_pdu(LL_VERSION_IND, &ntf, &remote_pdu); + ut_rx_q_is_empty(); + zassert_equal(conn_from_pool->lll.event_counter, 1, "Wrong event count %d\n", + conn_from_pool->lll.event_counter); + ull_cp_release_tx(conn_from_pool, tx); + ull_cp_release_ntf(ntf); + + ll_conn_release(conn_from_pool); +} + +void test_hci_version_ind_wrong_handle(void) +{ + uint16_t conn_handle; + uint64_t err; + int ctx_counter; + struct proc_ctx *ctx; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + err = ll_version_ind_send(conn_handle + 1); + + zassert_equal(err, BT_HCI_ERR_CMD_DISALLOWED, "Wrong reply for wrong handle\n"); + + ctx_counter = 0; + do { + ctx = llcp_create_local_procedure(PROC_VERSION_EXCHANGE); + ctx_counter++; + } while (ctx != NULL); + zassert_equal(ctx_counter, CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM + 1, + "Error in setup of test\n"); + + err = ll_version_ind_send(conn_handle); + zassert_equal(err, BT_HCI_ERR_CMD_DISALLOWED, "Wrong reply for wrong handle\n"); +} + +void test_hci_apto(void) +{ + uint16_t conn_handle; + uint64_t err; + + uint16_t apto; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + test_set_role(conn_from_pool, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(conn_from_pool, ULL_CP_CONNECTED); + + conn_from_pool->apto_reload = 100; + conn_from_pool->lll.interval = 10; + err = ll_apto_get(conn_handle, &apto); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + zassert_equal(apto, 125, "Apto is %d", apto); + + err = ll_apto_get(conn_handle + 1, &apto); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, NULL); + + err = ll_apto_set(conn_handle, 1000); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + zassert_equal(conn_from_pool->apto_reload, 800, "Apto reload is %d", + conn_from_pool->apto_reload); + + err = ll_apto_get(conn_handle + 1, 0x00); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, NULL); +} + +void test_hci_phy(void) +{ + uint16_t conn_handle; + uint64_t err; + + uint8_t phy_tx, phy_rx; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + test_set_role(conn_from_pool, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(conn_from_pool, ULL_CP_CONNECTED); + + err = ll_phy_req_send(conn_handle + 1, 0x00, 0x00, 0x00); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, NULL); + conn_from_pool->llcp.fex.features_used = 0x00; + conn_from_pool->llcp.fex.valid = 1; + err = ll_phy_req_send(conn_handle, 0x03, 0xFF, 0x03); + zassert_equal(err, BT_HCI_ERR_UNSUPP_REMOTE_FEATURE, "Errorcode %d", err); + + conn_from_pool->llcp.fex.features_used = 0xFFFF; + err = ll_phy_req_send(conn_handle, 0x03, 0xFF, 0x03); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); + err = ll_phy_get(conn_handle + 1, &phy_tx, &phy_rx); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, NULL); + + conn_from_pool->lll.phy_rx = 0x3; + conn_from_pool->lll.phy_tx = 0x7; + err = ll_phy_get(conn_handle, &phy_tx, &phy_rx); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + zassert_equal(phy_tx, 0x07, NULL); + zassert_equal(phy_rx, 0x03, NULL); + + err = ll_phy_default_set(0x00, 0x00); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + phy_tx = ull_conn_default_phy_tx_get(); + phy_rx = ull_conn_default_phy_rx_get(); + zassert_equal(phy_tx, 0x00, NULL); + zassert_equal(phy_rx, 0x00, NULL); + err = ll_phy_default_set(0x01, 0x03); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + phy_tx = ull_conn_default_phy_tx_get(); + phy_rx = ull_conn_default_phy_rx_get(); + zassert_equal(phy_tx, 0x01, NULL); + zassert_equal(phy_rx, 0x03, NULL); +} + +void test_hci_dle(void) +{ + uint16_t conn_handle; + uint64_t err; + + uint16_t tx_octets, tx_time; + uint16_t max_tx_octets, max_tx_time; + uint16_t max_rx_octets, max_rx_time; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + test_set_role(conn_from_pool, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(conn_from_pool, ULL_CP_CONNECTED); + + tx_octets = 251; + tx_time = 2400; + + conn_from_pool->llcp.fex.features_used = 0x00; + err = ll_length_req_send(conn_handle, tx_octets, tx_time); + zassert_equal(err, BT_HCI_ERR_UNSUPP_REMOTE_FEATURE, "Errorcode %d", err); + conn_from_pool->llcp.fex.features_used = 0xFFFFFFFF; + err = ll_length_req_send(conn_handle + 1, tx_octets, tx_time); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, "Errorcode %d", err); + + ll_length_max_get(&max_tx_octets, &max_tx_time, &max_rx_octets, &max_rx_time); + zassert_equal(max_tx_octets, LL_LENGTH_OCTETS_RX_MAX, NULL); + zassert_equal(max_rx_octets, LL_LENGTH_OCTETS_RX_MAX, NULL); + zassert_equal(max_tx_time, 2120, "Actual time is %d", max_tx_time); + zassert_equal(max_rx_time, 2120, "Actual time is %d", max_rx_time); + + err = ll_length_default_set(0x00, 0x00); + ll_length_default_get(&max_tx_octets, &max_tx_time); + zassert_equal(err, 00, NULL); + zassert_equal(max_tx_octets, 0x00, NULL); + zassert_equal(max_tx_time, 0x00, NULL); + err = ll_length_default_set(0x10, 0x3FF); + ll_length_default_get(&max_tx_octets, &max_tx_time); + zassert_equal(err, 00, NULL); + zassert_equal(max_tx_octets, 0x10, NULL); + zassert_equal(max_tx_time, 0x3FF, NULL); + max_tx_octets = ull_conn_default_tx_octets_get(); + max_tx_time = ull_conn_default_tx_time_get(); + zassert_equal(max_tx_octets, 0x10, NULL); + zassert_equal(max_tx_time, 0x3FF, NULL); +} + +void test_hci_terminate(void) +{ + uint16_t conn_handle; + uint64_t err; + + uint8_t reason; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + test_set_role(conn_from_pool, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(conn_from_pool, ULL_CP_CONNECTED); + + reason = 0x01; + err = ll_terminate_ind_send(conn_handle + 1, reason); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, "Errorcode %d", err); + err = ll_terminate_ind_send(conn_handle, reason); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); +} + +void test_hci_conn_update(void) +{ + uint16_t conn_handle; + uint8_t err; + + uint8_t cmd, status; + uint16_t interval_min, interval_max, latency, timeout; + + uint8_t unknown_cmds[3U] = { 1U, 3U, 255U }; + + cmd = 0x00; + status = 0x00; + interval_min = 10U; + interval_max = 100U; + latency = 5U; + timeout = 1000U; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + test_set_role(conn_from_pool, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(conn_from_pool, ULL_CP_CONNECTED); + + /* Unknown Connection ID */ + err = ll_conn_update(conn_handle + 1, cmd, status, interval_min, interval_max, latency, + timeout); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, "Errorcode %d", err); + + /* Unknown commands */ + for (uint8_t i = 0U; i < sizeof(unknown_cmds); i++) { + err = ll_conn_update(conn_handle, unknown_cmds[i], status, interval_min, + interval_max, latency, timeout); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CMD, "Errorcode %d", err); + } + + /* Connection Update or Connecton Parameter Req. */ + conn_from_pool->llcp.fex.features_used |= BIT64(BT_LE_FEAT_BIT_CONN_PARAM_REQ); + err = ll_conn_update(conn_handle, cmd, status, interval_min, interval_max, latency, + timeout); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); + + conn_from_pool->llcp.fex.features_used &= ~BIT64(BT_LE_FEAT_BIT_CONN_PARAM_REQ); + err = ll_conn_update(conn_handle, cmd, status, interval_min, interval_max, latency, + timeout); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); + + /* Connecton Parameter Req. Reply */ + cmd = 2U; + conn_from_pool->llcp.fex.features_used |= BIT64(BT_LE_FEAT_BIT_CONN_PARAM_REQ); + err = ll_conn_update(conn_handle, cmd, status, interval_min, interval_max, latency, + timeout); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); + + /* Connecton Parameter Req. Neg. Reply */ + status = 0x01; + conn_from_pool->llcp.fex.features_used |= BIT64(BT_LE_FEAT_BIT_CONN_PARAM_REQ); + err = ll_conn_update(conn_handle, cmd, status, 0U, 0U, 0U, 0U); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); +} + +void test_hci_chmap(void) +{ + uint16_t conn_handle; + uint64_t err; + uint8_t chmap[5]; + uint8_t chmap_zero[5] = {}; + uint8_t chmap_test[5] = { 0x42, 0x00, 0x42, 0x00, 0x00 }; + + err = ll_chm_update(chmap_zero); + zassert_equal(err, BT_HCI_ERR_INVALID_PARAM, "Errorcode %d", err); + + conn_handle = ll_conn_handle_get(conn_from_pool); + + test_set_role(conn_from_pool, BT_HCI_ROLE_PERIPHERAL); + ull_cp_state_set(conn_from_pool, ULL_CP_CONNECTED); + + err = ll_chm_get(conn_handle + 1, chmap); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, "Errorcode %d", err); + + err = ll_chm_get(conn_handle, chmap); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); + /* TODO test should initialize conn with default map */ + zassert_mem_equal(chmap, chmap_zero, sizeof(chmap), "Channel map invalid"); + + test_set_role(conn_from_pool, BT_HCI_ROLE_CENTRAL); + + err = ll_chm_get(conn_handle, chmap); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); + /* TODO test should initialize conn with default map */ + zassert_mem_equal(chmap, chmap_zero, sizeof(chmap), "Channel map invalid"); + + err = ll_chm_update(chmap_test); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); + + err = ll_chm_get(conn_handle, chmap); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); + zassert_mem_equal(chmap, chmap_test, sizeof(chmap), "Channel map invalid"); +} + +void test_hci_rssi(void) +{ + uint16_t conn_handle; + uint64_t err; + + uint8_t rssi; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + test_set_role(conn_from_pool, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(conn_from_pool, ULL_CP_CONNECTED); + + /* + * TODO: add ll_chm_update + */ + + err = ll_rssi_get(conn_handle + 1, &rssi); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, "Errorcode %d", err); + + err = ll_rssi_get(conn_handle, &rssi); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CMD, "Errorcode %d", err); +} + +void test_hci_enc(void) +{ + uint16_t conn_handle; + uint64_t err; + + uint8_t rand_nr; + uint8_t ediv; + uint8_t error_code; + uint8_t ltk[5]; + + conn_handle = ll_conn_handle_get(conn_from_pool); + + test_set_role(conn_from_pool, BT_HCI_ROLE_CENTRAL); + /* Connect */ + ull_cp_state_set(conn_from_pool, ULL_CP_CONNECTED); + + error_code = 0; + + err = ll_enc_req_send(conn_handle + 1, &rand_nr, &ediv, <k[0]); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, "Errorcode %d", err); + err = ll_enc_req_send(conn_handle, &rand_nr, &ediv, <k[0]); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); + + test_set_role(conn_from_pool, BT_HCI_ROLE_PERIPHERAL); + err = ll_start_enc_req_send(conn_handle + 1, error_code, <k[0]); + zassert_equal(err, BT_HCI_ERR_UNKNOWN_CONN_ID, "Errorcode %d", err); + err = ll_start_enc_req_send(conn_handle, error_code, <k[0]); + zassert_equal(err, BT_HCI_ERR_SUCCESS, "Errorcode %d", err); +} + +void test_main(void) +{ + ztest_test_suite( + hci_interface, + ztest_unit_test_setup_teardown(test_hci_feature_exchange, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_hci_feature_exchange_wrong_handle, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_hci_version_ind, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_hci_apto, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_hci_phy, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_hci_dle, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_hci_terminate, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_hci_conn_update, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_hci_chmap, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_hci_rssi, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_hci_enc, setup, unit_test_noop) + + ); + + ztest_run_test_suite(hci_interface); +} diff --git a/tests/bluetooth/controller/ctrl_hci/testcase.yaml b/tests/bluetooth/controller/ctrl_hci/testcase.yaml new file mode 100644 index 00000000000..4edaba310ce --- /dev/null +++ b/tests/bluetooth/controller/ctrl_hci/testcase.yaml @@ -0,0 +1,6 @@ +common: + tags: test_framework bluetooth bt_ctrl_hci bt_ull_llcp +tests: + bluetooth.controller.ctrl_hci.test: + skip: true + type: unit diff --git a/tests/bluetooth/controller/ctrl_le_ping/CMakeLists.txt b/tests/bluetooth/controller/ctrl_le_ping/CMakeLists.txt new file mode 100644 index 00000000000..3c7a6bd262f --- /dev/null +++ b/tests/bluetooth/controller/ctrl_le_ping/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_le_ping) +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_le_ping/src/main.c b/tests/bluetooth/controller/ctrl_le_ping/src/main.c new file mode 100644 index 00000000000..6f9752a9c89 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_le_ping/src/main.c @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "kconfig.h" + +#include +#include +#include +#include +#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" + +struct ll_conn conn; + +static void setup(void) +{ + test_setup(&conn); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start | | + * | LE Ping Proc. | | + * |--------------------------->| | + * | | | + * | | LL_LE_PING_REQ | + * | |------------------>| + * | | | + * | | LL_LE_PING_RSP | + * | |<------------------| + * | | | + * | | | + */ +void test_ping_mas_loc(void) +{ + uint8_t err; + struct node_tx *tx; + + struct pdu_data_llctrl_ping_req local_ping_req = {}; + + struct pdu_data_llctrl_ping_rsp remote_ping_rsp = {}; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate an LE Ping Procedure */ + err = ull_cp_le_ping(&conn); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LE_PING_REQ, &conn, &tx, &local_ping_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_LE_PING_RSP, &conn, &remote_ping_rsp); + + /* Done */ + event_done(&conn); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start | | + * | LE Ping Proc. | | + * |--------------------------->| | + * | | | + * | | LL_LE_PING_REQ | + * | |------------------>| + * | | | + * | | LL_LE_PING_RSP | + * | |<------------------| + * | | | + * | | | + */ +void test_ping_sla_loc(void) +{ + uint8_t err; + struct node_tx *tx; + + struct pdu_data_llctrl_ping_req local_ping_req = {}; + + struct pdu_data_llctrl_ping_rsp remote_ping_rsp = {}; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate an LE Ping Procedure */ + err = ull_cp_le_ping(&conn); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LE_PING_REQ, &conn, &tx, &local_ping_req); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_LE_PING_RSP, &conn, &remote_ping_rsp); + + /* Done */ + event_done(&conn); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_LE_PING_REQ | + * | |<------------------| + * | | | + * | | LL_LE_PING_RSP | + * | |------------------>| + * | | | + */ +void test_ping_mas_rem(void) +{ + struct node_tx *tx; + + struct pdu_data_llctrl_ping_req local_ping_req = {}; + + struct pdu_data_llctrl_ping_rsp remote_ping_rsp = {}; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Prepare */ + event_prepare(&conn); + + /* Tx */ + lt_tx(LL_LE_PING_REQ, &conn, &local_ping_req); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LE_PING_RSP, &conn, &tx, &remote_ping_rsp); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_LE_PING_REQ | + * | |<------------------| + * | | | + * | | LL_LE_PING_RSP | + * | |------------------>| + * | | | + */ +void test_ping_sla_rem(void) +{ + struct node_tx *tx; + + struct pdu_data_llctrl_ping_req local_ping_req = {}; + + struct pdu_data_llctrl_ping_rsp remote_ping_rsp = {}; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Prepare */ + event_prepare(&conn); + + /* Tx */ + lt_tx(LL_LE_PING_REQ, &conn, &local_ping_req); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_LE_PING_RSP, &conn, &tx, &remote_ping_rsp); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + 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(ping, + ztest_unit_test_setup_teardown(test_ping_mas_loc, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_ping_sla_loc, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_ping_mas_rem, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_ping_sla_rem, setup, unit_test_noop)); + + ztest_run_test_suite(ping); +} diff --git a/tests/bluetooth/controller/ctrl_le_ping/testcase.yaml b/tests/bluetooth/controller/ctrl_le_ping/testcase.yaml new file mode 100644 index 00000000000..a3af4a5dac2 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_le_ping/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: test_framework bluetooth bt_le_ping bt_ull_llcp +tests: + bluetooth.controller.ctrl_le_ping.test: + type: unit diff --git a/tests/bluetooth/controller/ctrl_min_used_chans/CMakeLists.txt b/tests/bluetooth/controller/ctrl_min_used_chans/CMakeLists.txt new file mode 100644 index 00000000000..8afeba311e1 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_min_used_chans/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_min_used_chans) +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_min_used_chans/src/main.c b/tests/bluetooth/controller/ctrl_min_used_chans/src/main.c new file mode 100644 index 00000000000..39e6a279a69 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_min_used_chans/src/main.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "kconfig.h" + +/* Kconfig Cheats */ + +#include +#include +#include +#include +#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" + +struct ll_conn conn; + +static void setup(void) +{ + test_setup(&conn); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start | | + * | Min used chans Proc. | | + * |--------------------------->| | + * | | | + * | | LL_MIN_USED_CHANS_IND | + * | |------------------------>| + * | | 'll_ack'| + * | | | + * | | | + */ +void test_min_used_chans_sla_loc(void) +{ + uint8_t err; + struct node_tx *tx; + + struct pdu_data_llctrl_min_used_chans_ind local_muc_ind = { .phys = 1, + .min_used_chans = 2 }; + + struct pdu_data_llctrl_min_used_chans_ind remote_muc_ind = { .phys = 1, + .min_used_chans = 2 }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_PERIPHERAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Min number of Used Channels Procedure */ + err = ull_cp_min_used_chans(&conn, 1, 2); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_MIN_USED_CHANS_IND, &conn, &tx, &local_muc_ind); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_MIN_USED_CHANS_IND, &conn, &remote_muc_ind); + + /* TX Ack */ + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_min_used_chans_mas_loc(void) +{ + uint8_t err; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Min number of Used Channels Procedure */ + err = ull_cp_min_used_chans(&conn, 1, 2); + zassert_equal(err, BT_HCI_ERR_CMD_DISALLOWED, NULL); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_min_used_chans_mas_rem(void) +{ + struct pdu_data_llctrl_min_used_chans_ind remote_muc_ind = { .phys = 1, + .min_used_chans = 2 }; + struct pdu_data_llctrl_chan_map_ind ch_map_ind = { .chm = { 0xff, 0xff, 0xff, 0xff, 0x1f }, + .instant = 7 }; + + struct node_tx *tx; + + /* 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_MIN_USED_CHANS_IND, &conn, &remote_muc_ind); + + /* Emulate a phy to trigger channel map update */ + conn.lll.phy_tx = 0x7; + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_CHAN_MAP_UPDATE_IND, &conn, &tx, &ch_map_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM - 1, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_main(void) +{ + ztest_test_suite( + muc, + ztest_unit_test_setup_teardown(test_min_used_chans_sla_loc, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_min_used_chans_mas_loc, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_min_used_chans_mas_rem, setup, unit_test_noop)); + + ztest_run_test_suite(muc); +} diff --git a/tests/bluetooth/controller/ctrl_min_used_chans/testcase.yaml b/tests/bluetooth/controller/ctrl_min_used_chans/testcase.yaml new file mode 100644 index 00000000000..77b22fc0ecd --- /dev/null +++ b/tests/bluetooth/controller/ctrl_min_used_chans/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: test_framework bluetooth bt_min_used_chans bt_ull_llcp +tests: + bluetooth.controller.ctrl_min_used_chans.test: + type: unit diff --git a/tests/bluetooth/controller/ctrl_phy_update/CMakeLists.txt b/tests/bluetooth/controller/ctrl_phy_update/CMakeLists.txt new file mode 100644 index 00000000000..8cf1ba0730d --- /dev/null +++ b/tests/bluetooth/controller/ctrl_phy_update/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_phy_update) +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_phy_update/src/main.c b/tests/bluetooth/controller/ctrl_phy_update/src/main.c new file mode 100644 index 00000000000..f4c6f1a6704 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_phy_update/src/main.c @@ -0,0 +1,1060 @@ +/* + * 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 "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); +} diff --git a/tests/bluetooth/controller/ctrl_phy_update/testcase.yaml b/tests/bluetooth/controller/ctrl_phy_update/testcase.yaml new file mode 100644 index 00000000000..662d8ae6fbd --- /dev/null +++ b/tests/bluetooth/controller/ctrl_phy_update/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: test_framework bluetooth bt_phy_update bt_ull_llcp +tests: + bluetooth.controller.ctrl_phy_update.test: + type: unit diff --git a/tests/bluetooth/controller/ctrl_terminate/CMakeLists.txt b/tests/bluetooth/controller/ctrl_terminate/CMakeLists.txt new file mode 100644 index 00000000000..51475192f4d --- /dev/null +++ b/tests/bluetooth/controller/ctrl_terminate/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_terminate) +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_terminate/src/main.c b/tests/bluetooth/controller/ctrl_terminate/src/main.c new file mode 100644 index 00000000000..d3c962978ac --- /dev/null +++ b/tests/bluetooth/controller/ctrl_terminate/src/main.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2020 Demant + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "kconfig.h" + +/* Kconfig Cheats */ + +#include +#include +#include +#include +#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" + +struct ll_conn conn; + +static void setup(void) +{ + test_setup(&conn); +} + +static void test_terminate_rem(uint8_t role) +{ + struct pdu_data_llctrl_terminate_ind remote_terminate_ind = { + .error_code = 0x05, + }; + + /* Role */ + test_set_role(&conn, role); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Prepare */ + event_prepare(&conn); + + /* Rx */ + lt_tx(LL_TERMINATE_IND, &conn, &remote_terminate_ind); + + /* Done */ + event_done(&conn); + + /* There should be no host notification */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_terminate_mas_rem(void) +{ + test_terminate_rem(BT_HCI_ROLE_CENTRAL); +} + +void test_terminate_sla_rem(void) +{ + test_terminate_rem(BT_HCI_ROLE_PERIPHERAL); +} + +void test_terminate_loc(uint8_t role) +{ + uint8_t err; + struct node_tx *tx; + + struct pdu_data_llctrl_terminate_ind local_terminate_ind = { + .error_code = 0x06, + }; + + /* Role */ + test_set_role(&conn, role); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate an LE Ping Procedure */ + err = ull_cp_terminate(&conn, 0x06); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_TERMINATE_IND, &conn, &tx, &local_terminate_ind); + lt_rx_q_is_empty(&conn); + + /* RX Ack */ + event_tx_ack(&conn, tx); + + /* Done */ + event_done(&conn); + + /* Release tx node */ + ull_cp_release_tx(&conn, tx); + + /* There should be no host notification */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_terminate_mas_loc(void) +{ + test_terminate_loc(BT_HCI_ROLE_CENTRAL); +} + +void test_terminate_sla_loc(void) +{ + test_terminate_loc(BT_HCI_ROLE_PERIPHERAL); +} + +void test_main(void) +{ + ztest_test_suite( + term, + ztest_unit_test_setup_teardown(test_terminate_mas_rem, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_terminate_sla_rem, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_terminate_mas_loc, setup, unit_test_noop), + ztest_unit_test_setup_teardown(test_terminate_sla_loc, setup, unit_test_noop)); + + ztest_run_test_suite(term); +} diff --git a/tests/bluetooth/controller/ctrl_terminate/testcase.yaml b/tests/bluetooth/controller/ctrl_terminate/testcase.yaml new file mode 100644 index 00000000000..90386b08ce7 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_terminate/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: test_framework bluetooth bt_terminate bt_ull_llcp +tests: + bluetooth.controller.ctrl_terminate.test: + type: unit diff --git a/tests/bluetooth/controller/ctrl_tx_buffer_alloc/CMakeLists.txt b/tests/bluetooth/controller/ctrl_tx_buffer_alloc/CMakeLists.txt new file mode 100644 index 00000000000..246a44883a7 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_tx_buffer_alloc/CMakeLists.txt @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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 +) +if(CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM GREATER_EQUAL 0) + add_compile_definitions(CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM=${CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM}) +endif(CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM) + +project(bluetooth_ull_llcp_tx_buffer_alloc) +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_tx_buffer_alloc/src/kconfig_override.h b/tests/bluetooth/controller/ctrl_tx_buffer_alloc/src/kconfig_override.h new file mode 100644 index 00000000000..8c377dd1c8e --- /dev/null +++ b/tests/bluetooth/controller/ctrl_tx_buffer_alloc/src/kconfig_override.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2020 Demant + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Common Kconfig settings + */ + +#ifdef CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM +#undef CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM +#define CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM 0 +#endif /* CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM */ diff --git a/tests/bluetooth/controller/ctrl_tx_buffer_alloc/src/kconfig_override_max_common.h b/tests/bluetooth/controller/ctrl_tx_buffer_alloc/src/kconfig_override_max_common.h new file mode 100644 index 00000000000..f19817c6007 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_tx_buffer_alloc/src/kconfig_override_max_common.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2020 Demant + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Common Kconfig settings + */ + +#ifdef CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM +#undef CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM +#define CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM \ + (CONFIG_BT_CTLR_LLCP_CONN * CONFIG_BT_CTLR_LLCP_TX_PER_CONN_TX_CTRL_BUF_NUM_MAX) +#endif /* CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM */ diff --git a/tests/bluetooth/controller/ctrl_tx_buffer_alloc/src/main.c b/tests/bluetooth/controller/ctrl_tx_buffer_alloc/src/main.c new file mode 100644 index 00000000000..5e6d7ee4169 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_tx_buffer_alloc/src/main.c @@ -0,0 +1,158 @@ +/* + * 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 "pdu.h" +#include "ll.h" +#include "ll_settings.h" + +#include "lll.h" +#include "lll/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" + + +static struct ll_conn conn[CONFIG_BT_CTLR_LLCP_CONN]; + +static void setup(void) +{ + ull_conn_init(); + test_setup(&conn[0]); +} +void test_tx_buffer_alloc(void) +{ + struct proc_ctx *ctxs[CONFIG_BT_CTLR_LLCP_CONN]; + struct node_tx *tx[CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM + + CONFIG_BT_CTLR_LLCP_CONN * CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM + 3]; + uint16_t tx_alloc_idx = 0; + int i; + + for (int ctx_idx = 0; ctx_idx < CONFIG_BT_CTLR_LLCP_CONN; ctx_idx++) { + ctxs[ctx_idx] = llcp_create_local_procedure(PROC_VERSION_EXCHANGE); + } + +#if defined(LLCP_TX_CTRL_BUF_QUEUE_ENABLE) + /* Check alloc flow */ + for (i = 0; i < CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM; i++) { + zassert_true(llcp_tx_alloc_peek(&conn[0], ctxs[0]), NULL); + tx[tx_alloc_idx] = llcp_tx_alloc(&conn[0], ctxs[0]); + zassert_not_null(tx[tx_alloc_idx], NULL); + tx_alloc_idx++; + } + for (i = 0; i < CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM; i++) { + zassert_true(llcp_tx_alloc_peek(&conn[0], ctxs[0]), NULL); + tx[tx_alloc_idx] = llcp_tx_alloc(&conn[0], ctxs[0]); + zassert_not_null(tx[tx_alloc_idx], NULL); + tx_alloc_idx++; + } + zassert_false(llcp_tx_alloc_peek(&conn[0], ctxs[0]), NULL); + zassert_equal(ctxs[0]->wait_reason, WAITING_FOR_TX_BUFFER, NULL); + + for (int j = 1; j < CONFIG_BT_CTLR_LLCP_CONN; j++) { + /* Now global pool is exausted, but conn pool is not */ + for (i = 0; i < CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM; i++) { + zassert_true(llcp_tx_alloc_peek(&conn[j], ctxs[j]), NULL); + tx[tx_alloc_idx] = llcp_tx_alloc(&conn[j], ctxs[j]); + zassert_not_null(tx[tx_alloc_idx], NULL); + tx_alloc_idx++; + } + + zassert_false(llcp_tx_alloc_peek(&conn[j], ctxs[j]), NULL); + zassert_equal(ctxs[j]->wait_reason, WAITING_FOR_TX_BUFFER, NULL); + } + + ull_cp_release_tx(&conn[0], tx[1]); + + /* global pool is now 'open' again, but ctxs[1] is NOT next in line */ + zassert_false(llcp_tx_alloc_peek(&conn[1], ctxs[1]), NULL); + + /* ... ctxs[0] is */ + zassert_true(llcp_tx_alloc_peek(&conn[0], ctxs[0]), NULL); + tx[tx_alloc_idx] = llcp_tx_alloc(&conn[0], ctxs[0]); + zassert_not_null(tx[tx_alloc_idx], NULL); + tx_alloc_idx++; + ull_cp_release_tx(&conn[0], tx[tx_alloc_idx - 1]); + + /* global pool does not allow as ctxs[2] is NOT next up */ + zassert_false(llcp_tx_alloc_peek(&conn[2], ctxs[2]), NULL); + +#if (CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0) + /* Release conn[2] held tx, to confirm alloc is allowed after releasing pre-alloted buf */ + zassert_true(!(conn[2].llcp.tx_buffer_alloc < + CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM), NULL); + ull_cp_release_tx(&conn[2], tx[CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM + + CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM + + CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM]); + zassert_true((conn[2].llcp.tx_buffer_alloc < + CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM), NULL); + + /* global pool does not allow as ctxs[2] is not next up, but pre alloted is now avail */ + zassert_equal(ctxs[2]->wait_reason, WAITING_FOR_TX_BUFFER, NULL); + zassert_not_null(ctxs[2]->wait_node.next, NULL); + zassert_true(llcp_tx_alloc_peek(&conn[2], ctxs[2]), NULL); + tx[tx_alloc_idx] = llcp_tx_alloc(&conn[2], ctxs[2]); + zassert_not_null(tx[tx_alloc_idx], NULL); + tx_alloc_idx++; + + /* No longer waiting in line */ + zassert_equal(ctxs[2]->wait_reason, WAITING_FOR_NOTHING, NULL); + zassert_is_null(ctxs[2]->wait_node.next, NULL); +#endif /* (CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM > 0) */ + + /* now ctxs[1] is next up */ + zassert_true(llcp_tx_alloc_peek(&conn[1], ctxs[1]), NULL); + tx[tx_alloc_idx] = llcp_tx_alloc(&conn[0], ctxs[0]); + zassert_not_null(tx[tx_alloc_idx], NULL); + tx_alloc_idx++; + +#else /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ + /* Test that there are excactly LLCP_CONN * LLCP_TX_CTRL_BUF_NUM_MAX + * buffers available + */ + for (i = 0; + i < CONFIG_BT_CTLR_LLCP_TX_PER_CONN_TX_CTRL_BUF_NUM_MAX * CONFIG_BT_CTLR_LLCP_CONN; + i++) { + zassert_true(llcp_tx_alloc_peek(&conn[0], ctxs[0]), NULL); + tx[tx_alloc_idx] = llcp_tx_alloc(&conn[0], ctxs[0]); + zassert_not_null(tx[tx_alloc_idx], NULL); + tx_alloc_idx++; + } + zassert_false(llcp_tx_alloc_peek(&conn[0], ctxs[0]), NULL); +#endif /* LLCP_TX_CTRL_BUF_QUEUE_ENABLE */ +} + +void test_main(void) +{ + ztest_test_suite( + tx_buffer_alloc, ztest_unit_test_setup_teardown(test_tx_buffer_alloc, setup, + unit_test_noop)); + + ztest_run_test_suite(tx_buffer_alloc); +} diff --git a/tests/bluetooth/controller/ctrl_tx_buffer_alloc/testcase.yaml b/tests/bluetooth/controller/ctrl_tx_buffer_alloc/testcase.yaml new file mode 100644 index 00000000000..8d651c4283c --- /dev/null +++ b/tests/bluetooth/controller/ctrl_tx_buffer_alloc/testcase.yaml @@ -0,0 +1,21 @@ +common: + tags: test_framework bluetooth bt_tx_buffer_alloc bt_ull_llcp +tests: + bluetooth.controller.ctrl_tx_buffer_alloc.test_0_per_conn: + type: unit + extra_args: CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM=0 + bluetooth.controller.ctrl_tx_buffer_alloc.test_1_per_conn: + type: unit + extra_args: CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM=1 + bluetooth.controller.ctrl_tx_buffer_alloc.test_2_per_conn: + type: unit + extra_args: CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM=2 + bluetooth.controller.ctrl_tx_buffer_alloc.test_3_per_conn: + type: unit + extra_args: CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM=3 + bluetooth.controller.ctrl_tx_buffer_alloc.test_max_per_conn_alloc: + type: unit + extra_args: CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM=4 + bluetooth.controller.ctrl_tx_buffer_alloc.test_max_common_alloc: + type: unit + extra_args: KCONFIG_OVERRIDE_FILE="kconfig_override_max_common.h" diff --git a/tests/bluetooth/controller/ctrl_tx_queue/CMakeLists.txt b/tests/bluetooth/controller/ctrl_tx_queue/CMakeLists.txt new file mode 100644 index 00000000000..128d841d4d2 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_tx_queue/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_tx_queue) +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_tx_queue/src/main.c b/tests/bluetooth/controller/ctrl_tx_queue/src/main.c new file mode 100644 index 00000000000..71794446d94 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_tx_queue/src/main.c @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include + +#include "util/util.h" +#include "util/memq.h" +#include "pdu.h" +#include "lll.h" + +#include "lll_df_types.h" +/* mock ccm which is used in lll_conn.h */ +struct ccm { +}; +#include "lll_conn.h" + +#include "ull_tx_queue.h" + +#define SIZE 10U + +void test_init(void) +{ + struct ull_tx_q tx_q; + struct node_tx *node; + + ull_tx_q_init(&tx_q); + zassert_equal(0U, tx_q.pause_data, "pause_data must be zero on init"); + + /* Tx Queue shall be empty */ + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, NULL, ""); +} + +/* + * (1) Enqueue ctrl nodes. + * Dequeue and verify order of the ctrl nodes from (1). + * Verify Tx Queue is empty. + */ +void test_ctrl(void) +{ + struct ull_tx_q tx_q; + struct node_tx *node; + struct node_tx ctrl_nodes1[SIZE] = { 0 }; + + ull_tx_q_init(&tx_q); + + /* Enqueue ctrl nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_ctrl(&tx_q, &ctrl_nodes1[i]); + } + + /* Dequeue ctrl nodes */ + for (int i = 0U; i < SIZE; i++) { + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &ctrl_nodes1[i], NULL); + } + + /* Tx Queue shall be empty */ + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, NULL, ""); +} + +/* + * (1) Enqueue data nodes. + * Dequeue and verify order of the data nodes from (1). + * Verify Tx Queue is empty. + */ +void test_data(void) +{ + struct ull_tx_q tx_q; + struct node_tx *node; + struct node_tx nodes[SIZE] = { 0 }; + + ull_tx_q_init(&tx_q); + + /* Enqueue data nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_data(&tx_q, &nodes[i]); + } + + /* Dequeue data nodes */ + for (int i = 0U; i < SIZE; i++) { + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &nodes[i], NULL); + } + + /* Tx Queue shall be empty */ + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, NULL, ""); +} + +/* + * (1) Enqueue ctrl and data nodes interleaved. + * Dequeue and verify order of the data and ctrl nodes from (1). + * Verify Tx Queue is empty. + */ +void test_ctrl_and_data_1(void) +{ + struct ull_tx_q tx_q; + struct node_tx *node; + struct node_tx ctrl_nodes1[SIZE] = { 0 }; + struct node_tx data_nodes1[SIZE] = { 0 }; + + ull_tx_q_init(&tx_q); + + /* Enqueue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_ctrl(&tx_q, &ctrl_nodes1[i]); + ull_tx_q_enqueue_data(&tx_q, &data_nodes1[i]); + } + + /* Dequeue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &ctrl_nodes1[i], NULL); + + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &data_nodes1[i], NULL); + } + + /* Tx Queue shall be empty */ + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, NULL, ""); +} + +/* + * (1) Enqueue ctrl and data nodes interleaved. + * Pause Tx Queue. + * (2) Enqueue data nodes. + * Dequeue and verify order of the data and ctrl nodes from (1). + * Verify Tx Queue is empty. + */ +void test_ctrl_and_data_2(void) +{ + struct ull_tx_q tx_q; + struct node_tx *node; + struct node_tx ctrl_nodes1[SIZE] = { 0 }; + struct node_tx data_nodes1[SIZE] = { 0 }; + struct node_tx data_nodes2[SIZE] = { 0 }; + + ull_tx_q_init(&tx_q); + + /* Enqueue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_ctrl(&tx_q, &ctrl_nodes1[i]); + ull_tx_q_enqueue_data(&tx_q, &data_nodes1[i]); + } + + /* Pause Tx Queue */ + ull_tx_q_pause_data(&tx_q); + + /* Enqueue data nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_data(&tx_q, &data_nodes2[i]); + } + + /* Dequeue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &ctrl_nodes1[i], NULL); + + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &data_nodes1[i], NULL); + } + + /* Tx Queue shall be empty */ + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, NULL, ""); +} + +/* + * (1) Enqueue ctrl and data nodes interleaved. + * Pause Tx Queue. + * (2) Enqueue ctrl and data nodes interleaved. + * Dequeue and verify order of ctrl and data nodes from (1). + * Dequeue and verify order of ctrl nodes from (2). + * Verify Tx Queue is empty. + */ +void test_ctrl_and_data_3(void) +{ + struct ull_tx_q tx_q; + struct node_tx *node; + struct node_tx ctrl_nodes1[SIZE] = { 0 }; + struct node_tx ctrl_nodes2[SIZE] = { 0 }; + struct node_tx data_nodes1[SIZE] = { 0 }; + struct node_tx data_nodes2[SIZE] = { 0 }; + + ull_tx_q_init(&tx_q); + + /* Enqueue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_ctrl(&tx_q, &ctrl_nodes1[i]); + ull_tx_q_enqueue_data(&tx_q, &data_nodes1[i]); + } + + /* Pause Tx Queue */ + ull_tx_q_pause_data(&tx_q); + + /* Enqueue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_ctrl(&tx_q, &ctrl_nodes2[i]); + ull_tx_q_enqueue_data(&tx_q, &data_nodes2[i]); + } + + /* Dequeue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &ctrl_nodes1[i], NULL); + + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &data_nodes1[i], NULL); + } + + /* Dequeue ctrl nodes */ + for (int i = 0U; i < SIZE; i++) { + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &ctrl_nodes2[i], NULL); + } + + /* Tx Queue shall be empty */ + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, NULL, ""); +} + +/* + * (1) Enqueue ctrl and data nodes interleaved. + * Pause Tx Queue. + * (2) Enqueue ctrl and data nodes interleaved. + * Resume Tx Queue. + * Dequeue and verify order of ctrl and data nodes from (1). + * Dequeue and verify order of ctrl nodes from (2). + * Dequeue and verify order of data nodes from (2). + * Verify Tx Queue is empty. + */ +void test_ctrl_and_data_4(void) +{ + struct ull_tx_q tx_q; + struct node_tx *node; + struct node_tx ctrl_nodes1[SIZE] = { 0 }; + struct node_tx ctrl_nodes2[SIZE] = { 0 }; + struct node_tx data_nodes1[SIZE] = { 0 }; + struct node_tx data_nodes2[SIZE] = { 0 }; + + ull_tx_q_init(&tx_q); + + /* Enqueue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_ctrl(&tx_q, &ctrl_nodes1[i]); + ull_tx_q_enqueue_data(&tx_q, &data_nodes1[i]); + } + + /* Pause Tx Queue */ + ull_tx_q_pause_data(&tx_q); + + /* Enqueue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_ctrl(&tx_q, &ctrl_nodes2[i]); + ull_tx_q_enqueue_data(&tx_q, &data_nodes2[i]); + } + + /* Resume Tx Queue */ + ull_tx_q_resume_data(&tx_q); + + /* Dequeue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &ctrl_nodes1[i], NULL); + + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &data_nodes1[i], NULL); + } + + /* Dequeue ctrl nodes */ + for (int i = 0U; i < SIZE; i++) { + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &ctrl_nodes2[i], NULL); + } + + /* Dequeue data nodes */ + for (int i = 0U; i < SIZE; i++) { + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &data_nodes2[i], NULL); + } + + /* Tx Queue shall be empty */ + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, NULL, ""); +} + +/* + * (1) Enqueue ctrl and data nodes interleaved. + * Pause Tx Queue. + * (2) Enqueue ctrl and data nodes interleaved. + * Resume Tx Queue. + * (3) Enqueue ctrl and data nodes interleaved. + * Dequeue and verify order of ctrl and data nodes from (1). + * Dequeue and verify order of ctrl nodes from (2). + * Dequeue and verify order of data nodes from (2). + * Dequeue and verify order of ctrl and data nodes from (3). + * Verify Tx Queue is empty. + */ +void test_ctrl_and_data_5(void) +{ + struct ull_tx_q tx_q; + struct node_tx *node; + struct node_tx ctrl_nodes1[SIZE] = { 0 }; + struct node_tx ctrl_nodes2[SIZE] = { 0 }; + struct node_tx ctrl_nodes3[SIZE] = { 0 }; + struct node_tx data_nodes1[SIZE] = { 0 }; + struct node_tx data_nodes2[SIZE] = { 0 }; + struct node_tx data_nodes3[SIZE] = { 0 }; + + ull_tx_q_init(&tx_q); + + /* Enqueue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_ctrl(&tx_q, &ctrl_nodes1[i]); + ull_tx_q_enqueue_data(&tx_q, &data_nodes1[i]); + } + + /* Pause Tx Queue */ + ull_tx_q_pause_data(&tx_q); + + /* Enqueue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_ctrl(&tx_q, &ctrl_nodes2[i]); + ull_tx_q_enqueue_data(&tx_q, &data_nodes2[i]); + } + + /* Resume Tx Queue */ + ull_tx_q_resume_data(&tx_q); + + /* Enqueue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + ull_tx_q_enqueue_ctrl(&tx_q, &ctrl_nodes3[i]); + ull_tx_q_enqueue_data(&tx_q, &data_nodes3[i]); + } + + /* Dequeue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + struct node_tx *node; + + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &ctrl_nodes1[i], NULL); + + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &data_nodes1[i], NULL); + } + + /* Dequeue ctrl nodes */ + for (int i = 0U; i < SIZE; i++) { + struct node_tx *node = ull_tx_q_dequeue(&tx_q); + + zassert_equal_ptr(node, &ctrl_nodes2[i], NULL); + } + + /* Dequeue data nodes */ + for (int i = 0U; i < SIZE; i++) { + struct node_tx *node = ull_tx_q_dequeue(&tx_q); + + zassert_equal_ptr(node, &data_nodes2[i], NULL); + } + + /* Dequeue ctrl and data nodes */ + for (int i = 0U; i < SIZE; i++) { + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &ctrl_nodes3[i], NULL); + + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, &data_nodes3[i], NULL); + } + + /* Tx Queue shall be empty */ + node = ull_tx_q_dequeue(&tx_q); + zassert_equal_ptr(node, NULL, ""); +} + +void test_main(void) +{ + ztest_test_suite(test, ztest_unit_test(test_init), ztest_unit_test(test_ctrl), + ztest_unit_test(test_data), ztest_unit_test(test_ctrl_and_data_1), + ztest_unit_test(test_ctrl_and_data_2), + ztest_unit_test(test_ctrl_and_data_3), + ztest_unit_test(test_ctrl_and_data_4), + ztest_unit_test(test_ctrl_and_data_5)); + ztest_run_test_suite(test); +} diff --git a/tests/bluetooth/controller/ctrl_tx_queue/testcase.yaml b/tests/bluetooth/controller/ctrl_tx_queue/testcase.yaml new file mode 100644 index 00000000000..a9f45269d7f --- /dev/null +++ b/tests/bluetooth/controller/ctrl_tx_queue/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: bluetooth bt_ull_llcp +tests: + bluetooth.ctrl_tx_queue.test: + type: unit diff --git a/tests/bluetooth/controller/ctrl_version/CMakeLists.txt b/tests/bluetooth/controller/ctrl_version/CMakeLists.txt new file mode 100644 index 00000000000..35c06be2f57 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_version/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +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_version) +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_version/src/main.c b/tests/bluetooth/controller/ctrl_version/src/main.c new file mode 100644 index 00000000000..94aa399b368 --- /dev/null +++ b/tests/bluetooth/controller/ctrl_version/src/main.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "kconfig.h" + +/* Kconfig Cheats */ + +#include +#include +#include +#include +#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 "ll_feat.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_llcp_internal.h" + +#include "helper_pdu.h" +#include "helper_util.h" + +struct ll_conn conn; + +static void setup(void) +{ + test_setup(&conn); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start | | + * | Version Exchange Proc. | | + * |--------------------------->| | + * | | | + * | | LL_VERSION_IND | + * | |------------------>| + * | | | + * | | LL_VERSION_IND | + * | |<------------------| + * | | | + * | Version Exchange Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + */ +void test_version_exchange_mas_loc(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_version_ind local_version_ind = { + .version_number = LL_VERSION_NUMBER, + .company_id = CONFIG_BT_CTLR_COMPANY_ID, + .sub_version_number = CONFIG_BT_CTLR_SUBVERSION_NUMBER, + }; + + struct pdu_data_llctrl_version_ind remote_version_ind = { + .version_number = 0x55, + .company_id = 0xABCD, + .sub_version_number = 0x1234, + }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Version Exchange Procedure */ + err = ull_cp_version_exchange(&conn); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_VERSION_IND, &conn, &tx, &local_version_ind); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_VERSION_IND, &conn, &remote_version_ind); + + /* Done */ + event_done(&conn); + + /* There should be one host notification */ + ut_rx_pdu(LL_VERSION_IND, &ntf, &remote_version_ind); + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_version_exchange_mas_loc_2(void) +{ + uint8_t err; + + ull_cp_init(); + ull_tx_q_init(&conn.tx_q); + ull_llcp_init(&conn); + + err = ull_cp_version_exchange(&conn); + + for (int i = 0U; i < CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM; i++) { + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + err = ull_cp_version_exchange(&conn); + } + + zassert_not_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + zassert_equal(ctx_buffers_free(), 0, "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_VERSION_IND | + * | |<------------------| + * | | | + * | | LL_VERSION_IND | + * | |------------------>| + * | | | + */ +void test_version_exchange_mas_rem(void) +{ + struct node_tx *tx; + + struct pdu_data_llctrl_version_ind local_version_ind = { + .version_number = LL_VERSION_NUMBER, + .company_id = CONFIG_BT_CTLR_COMPANY_ID, + .sub_version_number = CONFIG_BT_CTLR_SUBVERSION_NUMBER, + }; + + struct pdu_data_llctrl_version_ind remote_version_ind = { + .version_number = 0x55, + .company_id = 0xABCD, + .sub_version_number = 0x1234, + }; + + /* 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_VERSION_IND, &conn, &remote_version_ind); + + /* Done */ + event_done(&conn); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_VERSION_IND, &conn, &tx, &local_version_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should not be a host notifications */ + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | | LL_VERSION_IND | + * | |<------------------| + * | | | + * | | LL_VERSION_IND | + * | |------------------>| + * | | | + * | Start | | + * | Version Exchange Proc. | | + * |--------------------------->| | + * | | | + * | Version Exchange Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + */ +void test_version_exchange_mas_rem_2(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_version_ind local_version_ind = { + .version_number = LL_VERSION_NUMBER, + .company_id = CONFIG_BT_CTLR_COMPANY_ID, + .sub_version_number = CONFIG_BT_CTLR_SUBVERSION_NUMBER, + }; + + struct pdu_data_llctrl_version_ind remote_version_ind = { + .version_number = 0x55, + .company_id = 0xABCD, + .sub_version_number = 0x1234, + }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Rx */ + lt_tx(LL_VERSION_IND, &conn, &remote_version_ind); + + /* Initiate a Version Exchange Procedure */ + err = ull_cp_version_exchange(&conn); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_VERSION_IND, &conn, &tx, &local_version_ind); + lt_rx_q_is_empty(&conn); + + /* Done */ + event_done(&conn); + + /* There should be one host notification */ + ut_rx_pdu(LL_VERSION_IND, &ntf, &remote_version_ind); + ut_rx_q_is_empty(); + + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM, + "Free CTX buffers %d", ctx_buffers_free()); +} + +/* +-----+ +-------+ +-----+ + * | UT | | LL_A | | LT | + * +-----+ +-------+ +-----+ + * | | | + * | Start | | + * | Version Exchange Proc. | | + * |--------------------------->| | + * | | | + * | | LL_VERSION_IND | + * | |------------------>| + * | | | + * | | LL_VERSION_IND | + * | |<------------------| + * | | | + * | Version Exchange Proc. | | + * | Complete | | + * |<---------------------------| | + * | Start | | + * | Version Exchange Proc. | | + * |--------------------------->| | + * | | | + * | Version Exchange Proc. | | + * | Complete | | + * |<---------------------------| | + * | | | + */ +void test_version_exchange_mas_loc_twice(void) +{ + uint8_t err; + struct node_tx *tx; + struct node_rx_pdu *ntf; + + struct pdu_data_llctrl_version_ind local_version_ind = { + .version_number = LL_VERSION_NUMBER, + .company_id = CONFIG_BT_CTLR_COMPANY_ID, + .sub_version_number = CONFIG_BT_CTLR_SUBVERSION_NUMBER, + }; + + struct pdu_data_llctrl_version_ind remote_version_ind = { + .version_number = 0x55, + .company_id = 0xABCD, + .sub_version_number = 0x1234, + }; + + /* Role */ + test_set_role(&conn, BT_HCI_ROLE_CENTRAL); + + /* Connect */ + ull_cp_state_set(&conn, ULL_CP_CONNECTED); + + /* Initiate a Version Exchange Procedure */ + err = ull_cp_version_exchange(&conn); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Initiate a Version Exchange Procedure */ + err = ull_cp_version_exchange(&conn); + zassert_equal(err, BT_HCI_ERR_SUCCESS, NULL); + + /* Prepare */ + event_prepare(&conn); + + /* Tx Queue should have one LL Control PDU */ + lt_rx(LL_VERSION_IND, &conn, &tx, &local_version_ind); + lt_rx_q_is_empty(&conn); + + /* Rx */ + lt_tx(LL_VERSION_IND, &conn, &remote_version_ind); + + /* Done */ + event_done(&conn); + + /* There should be one host notification */ + ut_rx_pdu(LL_VERSION_IND, &ntf, &remote_version_ind); + ut_rx_q_is_empty(); + + /* Prepare */ + event_prepare(&conn); + + /* Done */ + event_done(&conn); + + /* Cached values should be used, no over the air comm */ + lt_rx_q_is_empty(&conn); + + /* There should be one host notification */ + ut_rx_pdu(LL_VERSION_IND, &ntf, &remote_version_ind); + ut_rx_q_is_empty(); + + /* Note that one context buffer is not freed for this test */ + zassert_equal(ctx_buffers_free(), CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM - 1, + "Free CTX buffers %d", ctx_buffers_free()); +} + +void test_main(void) +{ + ztest_test_suite(version_exchange, + ztest_unit_test_setup_teardown(test_version_exchange_mas_loc, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_version_exchange_mas_loc_2, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_version_exchange_mas_rem, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_version_exchange_mas_rem_2, setup, + unit_test_noop), + ztest_unit_test_setup_teardown(test_version_exchange_mas_loc_twice, setup, + unit_test_noop)); + + ztest_run_test_suite(version_exchange); +} diff --git a/tests/bluetooth/controller/ctrl_version/testcase.yaml b/tests/bluetooth/controller/ctrl_version/testcase.yaml new file mode 100644 index 00000000000..7875b9da75e --- /dev/null +++ b/tests/bluetooth/controller/ctrl_version/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: test_framework bluetooth bt_version_exchange bt_ull_llcp +tests: + bluetooth.controller.ctrl_version.test: + type: unit diff --git a/tests/bluetooth/controller/mock_ctrl/include/hal/cpu_vendor_hal.h b/tests/bluetooth/controller/mock_ctrl/include/hal/cpu_vendor_hal.h new file mode 100644 index 00000000000..1ddfd173fee --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/hal/cpu_vendor_hal.h @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define cpu_dsb() +#define cpu_dmb() diff --git a/tests/bluetooth/controller/mock_ctrl/include/hal/debug_vendor_hal.h b/tests/bluetooth/controller/mock_ctrl/include/hal/debug_vendor_hal.h new file mode 100644 index 00000000000..ed22f2a72b5 --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/hal/debug_vendor_hal.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Dummy header file to avoid compiler errors + * intentionally left blank + */ diff --git a/tests/bluetooth/controller/mock_ctrl/include/hal/radio_vendor_hal.h b/tests/bluetooth/controller/mock_ctrl/include/hal/radio_vendor_hal.h new file mode 100644 index 00000000000..ed22f2a72b5 --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/hal/radio_vendor_hal.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Dummy header file to avoid compiler errors + * intentionally left blank + */ diff --git a/tests/bluetooth/controller/mock_ctrl/include/hal/ticker_vendor_hal.h b/tests/bluetooth/controller/mock_ctrl/include/hal/ticker_vendor_hal.h new file mode 100644 index 00000000000..bbd4b3e462c --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/hal/ticker_vendor_hal.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#define HAL_TICKER_CNTR_CLK_FREQ_HZ 32768U + +/* Macro defining the minimum counter compare offset */ +#define HAL_TICKER_CNTR_CMP_OFFSET_MIN 3 + +/* Macro defining the max. counter update latency in ticks */ +#define HAL_TICKER_CNTR_SET_LATENCY 0 + +/* Macro to translate microseconds to tick units. + * NOTE: This returns the floor value. + */ +#define HAL_TICKER_US_TO_TICKS(x) \ + (((uint32_t)(((uint64_t)(x) * 1000000000UL) / 30517578125UL)) & HAL_TICKER_CNTR_MASK) + +/* Macro returning remainder in nanoseconds */ +#define HAL_TICKER_REMAINDER(x) \ + ((((uint64_t)(x) * 1000000000UL) - ((uint64_t)HAL_TICKER_US_TO_TICKS(x) * 30517578125UL)) /\ + 1000UL) + +/* Macro to translate tick units to microseconds. */ +#define HAL_TICKER_TICKS_TO_US(x) ((uint32_t)(((uint64_t)(x) * 30517578125UL) / 1000000000UL)) + +/* Macro defines the h/w supported most significant bit */ +#define HAL_TICKER_CNTR_MSBIT 23 + +/* Macro defining the HW supported counter bits */ +#define HAL_TICKER_CNTR_MASK 0x00FFFFFF + +/* Macro defining the remainder resolution/range + * ~ 1000000 * HAL_TICKER_TICKS_TO_US(1) + */ +#define HAL_TICKER_REMAINDER_RANGE HAL_TICKER_TICKS_TO_US(1000000) + +/* Macro defining the margin for positioning re-scheduled nodes */ +#define HAL_TICKER_RESCHEDULE_MARGIN HAL_TICKER_US_TO_TICKS(150) diff --git a/tests/bluetooth/controller/mock_ctrl/include/kconfig.h b/tests/bluetooth/controller/mock_ctrl/include/kconfig.h new file mode 100644 index 00000000000..ccfe1f81c1d --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/kconfig.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2020 Demant + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Common Kconfig settings + */ + +#ifndef CONFIG_BT_LL_SW_SPLIT +#define CONFIG_BT_LL_SW_SPLIT y +#endif + +#define CONFIG_BT_CONN +#define CONFIG_BT_MAX_CONN 4 + +/* ensure that proper configuration is set */ + +#ifndef CONFIG_BT_PERIPHERAL +#define CONFIG_BT_PERIPHERAL y +#endif +#ifndef CONFIG_BT_CENTRAL +#define CONFIG_BT_CENTRAL y +#endif + +#ifndef CONFIG_BT_CTLR_PHY +#define CONFIG_BT_CTLR_PHY 1 +#endif +#ifndef CONFIG_BT_CTLR_PHY_2M +#define CONFIG_BT_CTLR_PHY_2M y +#endif + +#ifndef CONFIG_BT_CTLR_LOW_LAT +#define CONFIG_BT_CTLR_LOW_LAT y +#endif + +#ifndef CONFIG_BT_CTLR_ULL_HIGH_PRIO +#define CONFIG_BT_CTLR_ULL_HIGH_PRIO 1 +#endif + +#ifndef CONFIG_BT_CTLR_ULL_LOW_PRIO +#define CONFIG_BT_CTLR_ULL_LOW_PRIO 1 +#endif + +#ifndef CONFIG_BT_CTLR_DATA_LENGTH +#define CONFIG_BT_CTLR_DATA_LENGTH y +#endif +#ifndef CONFIG_BT_CTLR_DATA_LENGTH_MAX +#define CONFIG_BT_CTLR_DATA_LENGTH_MAX 251 +#endif + +#ifndef CONFIG_BT_CTLR_LE_ENC +#define CONFIG_BT_CTLR_LE_ENC y +#endif + +#ifndef CONFIG_BT_CTLR_LE_PING +#define CONFIG_BT_CTLR_LE_PING y +#endif + +#ifndef CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG +#define CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG y +#endif + +#ifndef CONFIG_BT_CTLR_CONN_RSSI +#define CONFIG_BT_CTLR_CONN_RSSI y +#endif + +#ifndef CONFIG_BT_CTLR_MIN_USED_CHAN +#define CONFIG_BT_CTLR_MIN_USED_CHAN y +#endif + +#ifndef CONFIG_BT_CTLR_CONN_PARAM_REQ +#define CONFIG_BT_CTLR_CONN_PARAM_REQ y +#endif + +#ifndef CONFIG_BT_CTLR_XTAL_ADVANCED +#define CONFIG_BT_CTLR_XTAL_ADVANCED y +#endif + +#define CONFIG_BT_CTLR_LLCP_CONN 4 + +#ifndef CONFIG_BT_CTLR_LLCP_TX_PER_CONN_TX_CTRL_BUF_NUM_MAX +#define CONFIG_BT_CTLR_LLCP_TX_PER_CONN_TX_CTRL_BUF_NUM_MAX (4) +#endif + +#ifndef CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM +#define CONFIG_BT_CTLR_LLCP_PROC_CTX_BUF_NUM CONFIG_BT_CTLR_LLCP_CONN +#endif + +#ifndef CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM +#define CONFIG_BT_CTLR_LLCP_COMMON_TX_CTRL_BUF_NUM 2 +#endif + +#ifndef CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM +#define CONFIG_BT_CTLR_LLCP_PER_CONN_TX_CTRL_BUF_NUM 1 +#endif + +/* + * Direction finding related Kconfig settings + */ + +/* Direction finding non LE Features configs */ +#ifndef CONFIG_BT_CTLR_DF +#define CONFIG_BT_CTLR_DF y +#endif + +#ifndef CONFIG_BT_CTLR_DF_CTE_TX +#define CONFIG_BT_CTLR_DF_CTE_TX y +#endif + +#ifndef CONFIG_BT_CTLR_DF_CTE_RX_SAMPLE_1US +#define CONFIG_BT_CTLR_DF_CTE_RX_SAMPLE_1US y +#endif + +#ifndef CONFIG_BT_CTLR_DF_ANT_SWITCH_1US +#define CONFIG_BT_CTLR_DF_ANT_SWITCH_1US y +#endif + +/* Direction finding LE Features configs */ +#ifndef CONFIG_BT_CTLR_DF_CONN_CTE_REQ +#define CONFIG_BT_CTLR_DF_CONN_CTE_REQ y +#endif + +#ifndef CONFIG_BT_CTLR_DF_CONN_CTE_RSP +#define CONFIG_BT_CTLR_DF_CONN_CTE_RSP y +#endif + +#ifndef CONFIG_BT_CTLR_DF_ANT_SWITCH_TX +#define CONFIG_BT_CTLR_DF_ANT_SWITCH_TX y +#endif + +#ifndef CONFIG_BT_CTLR_DF_ANT_SWITCH_RX +#define CONFIG_BT_CTLR_DF_ANT_SWITCH_RX y +#endif + +#ifndef CONFIG_BT_CTLR_DF_CTE_RX +#define CONFIG_BT_CTLR_DF_CTE_RX y +#endif + +#ifndef CONFIG_BT_CTLR_DF_MAX_ANT_SW_PATTERN_LEN +#define CONFIG_BT_CTLR_DF_MAX_ANT_SW_PATTERN_LEN 39 +#endif + +/* Kconfig Cheats */ +#define CONFIG_BT_LOG_LEVEL 1 +#define CONFIG_BT_CTLR_COMPANY_ID 0x1234 +#define CONFIG_BT_CTLR_SUBVERSION_NUMBER 0x5678 +#define CONFIG_NET_BUF_USER_DATA_SIZE 4096 +#define CONFIG_BT_CTLR_ASSERT_HANDLER y +#define CONFIG_BT_BUF_ACL_TX_COUNT 7 +#define CONFIG_BT_BUF_ACL_TX_SIZE 27 +#define CONFIG_BT_CTLR_RX_BUFFERS 7 diff --git a/tests/bluetooth/controller/mock_ctrl/include/lll/lll_adv_pdu.h b/tests/bluetooth/controller/mock_ctrl/include/lll/lll_adv_pdu.h new file mode 100644 index 00000000000..5fe9872e5ad --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/lll/lll_adv_pdu.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018-2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +int lll_adv_data_init(struct lll_adv_pdu *pdu); +int lll_adv_data_reset(struct lll_adv_pdu *pdu); +int lll_adv_data_release(struct lll_adv_pdu *pdu); + +static inline void lll_adv_pdu_enqueue(struct lll_adv_pdu *pdu, uint8_t idx) +{ + pdu->last = idx; +} + +struct pdu_adv *lll_adv_pdu_alloc(struct lll_adv_pdu *pdu, uint8_t *idx); + +static inline struct pdu_adv *lll_adv_data_alloc(struct lll_adv *lll, uint8_t *idx) +{ + return lll_adv_pdu_alloc(&lll->adv_data, idx); +} + +static inline void lll_adv_data_enqueue(struct lll_adv *lll, uint8_t idx) +{ + lll_adv_pdu_enqueue(&lll->adv_data, idx); +} + +static inline struct pdu_adv *lll_adv_data_peek(struct lll_adv *lll) +{ + return (void *)lll->adv_data.pdu[lll->adv_data.last]; +} + +static inline struct pdu_adv *lll_adv_data_curr_get(struct lll_adv *lll) +{ + return (void *)lll->adv_data.pdu[lll->adv_data.first]; +} + +static inline struct pdu_adv *lll_adv_scan_rsp_alloc(struct lll_adv *lll, uint8_t *idx) +{ + return lll_adv_pdu_alloc(&lll->scan_rsp, idx); +} + +static inline void lll_adv_scan_rsp_enqueue(struct lll_adv *lll, uint8_t idx) +{ + lll_adv_pdu_enqueue(&lll->scan_rsp, idx); +} + +static inline struct pdu_adv *lll_adv_scan_rsp_peek(struct lll_adv *lll) +{ + return (void *)lll->scan_rsp.pdu[lll->scan_rsp.last]; +} + +#if defined(CONFIG_BT_CTLR_ADV_EXT) +static inline struct pdu_adv *lll_adv_aux_data_alloc(struct lll_adv_aux *lll, uint8_t *idx) +{ + return lll_adv_pdu_alloc(&lll->data, idx); +} + +static inline void lll_adv_aux_data_enqueue(struct lll_adv_aux *lll, uint8_t idx) +{ + lll_adv_pdu_enqueue(&lll->data, idx); +} + +static inline struct pdu_adv *lll_adv_aux_data_peek(struct lll_adv_aux *lll) +{ + return (void *)lll->data.pdu[lll->data.last]; +} + +static inline struct pdu_adv *lll_adv_aux_data_curr_get(struct lll_adv_aux *lll) +{ + return (void *)lll->data.pdu[lll->data.first]; +} + +#if defined(CONFIG_BT_CTLR_ADV_PERIODIC) +int lll_adv_and_extra_data_release(struct lll_adv_pdu *pdu); + +struct pdu_adv *lll_adv_pdu_and_extra_data_alloc(struct lll_adv_pdu *pdu, void **extra_data, + uint8_t *idx); + +static inline struct pdu_adv *lll_adv_sync_data_alloc(struct lll_adv_sync *lll, void **extra_data, + uint8_t *idx) +{ +#if defined(CONFIG_BT_CTLR_ADV_EXT_PDU_EXTRA_DATA_MEMORY) + return lll_adv_pdu_and_extra_data_alloc(&lll->data, extra_data, idx); +#else + return lll_adv_pdu_alloc(&lll->data, idx); +#endif /* CONFIG_BT_CTLR_ADV_EXT_PDU_EXTRA_DATA_MEMORY */ +} + +static inline void lll_adv_sync_data_release(struct lll_adv_sync *lll) +{ +#if defined(CONFIG_BT_CTLR_ADV_EXT_PDU_EXTRA_DATA_MEMORY) + lll_adv_and_extra_data_release(&lll->data); +#else + lll_adv_data_release(&lll->data); +#endif /* CONFIG_BT_CTLR_ADV_EXT_PDU_EXTRA_DATA_MEMORY */ +} + +static inline void lll_adv_sync_data_enqueue(struct lll_adv_sync *lll, uint8_t idx) +{ + lll_adv_pdu_enqueue(&lll->data, idx); +} + +static inline struct pdu_adv *lll_adv_sync_data_peek(struct lll_adv_sync *lll, void **extra_data) +{ + uint8_t last = lll->data.last; + +#if defined(CONFIG_BT_CTLR_ADV_EXT_PDU_EXTRA_DATA_MEMORY) + if (extra_data) { + *extra_data = lll->data.extra_data[last]; + } +#endif /* CONFIG_BT_CTLR_ADV_EXT_PDU_EXTRA_DATA_MEMORY */ + + return (void *)lll->data.pdu[last]; +} +#endif /* CONFIG_BT_CTLR_ADV_PERIODIC */ +#endif /* CONFIG_BT_CTLR_ADV_EXT */ diff --git a/tests/bluetooth/controller/mock_ctrl/include/lll/lll_adv_types.h b/tests/bluetooth/controller/mock_ctrl/include/lll/lll_adv_types.h new file mode 100644 index 00000000000..24f0e87259c --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/lll/lll_adv_types.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Structure used to double buffer pointers of AD Data PDU buffer. + * The first and last members are used to make modification to AD data to be + * context safe. Thread always appends or updates the buffer pointed to + * the array element indexed by the member last. + * LLL in the ISR context, checks, traverses to the valid pointer indexed + * by the member first, such that the buffer is the latest committed by + * the thread context. + */ +struct lll_adv_pdu { + uint8_t volatile first; + uint8_t last; + uint8_t *pdu[DOUBLE_BUFFER_SIZE]; +#if defined(CONFIG_BT_CTLR_ADV_EXT_PDU_EXTRA_DATA_MEMORY) + /* This is a storage for LLL configuration that may be + * changed while LLL advertising role is started. + * Also it makes the configuration data to be in sync + * with extended advertising PDU e.g. CTE TX configuration + * and CTEInfo field. + */ + void *extra_data[DOUBLE_BUFFER_SIZE]; +#endif /* CONFIG_BT_CTLR_ADV_EXT_PDU_EXTRA_DATA_MEMORY */ +}; diff --git a/tests/bluetooth/controller/mock_ctrl/include/lll_clock.h b/tests/bluetooth/controller/mock_ctrl/include/lll_clock.h new file mode 100644 index 00000000000..b4e739cdad5 --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/lll_clock.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2018-2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +int lll_clock_init(void); +int lll_clock_wait(void); +int lll_hfclock_on(void); +int lll_hfclock_on_wait(void); +int lll_hfclock_off(void); +uint32_t lll_clock_ppm_local_get(void); +uint32_t lll_clock_ppm_get(uint8_t sca); diff --git a/tests/bluetooth/controller/mock_ctrl/include/lll_master.h b/tests/bluetooth/controller/mock_ctrl/include/lll_master.h new file mode 100644 index 00000000000..64d73c1a171 --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/lll_master.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2018-2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +int lll_master_init(void); +int lll_master_reset(void); +void lll_master_prepare(void *param); diff --git a/tests/bluetooth/controller/mock_ctrl/include/lll_slave.h b/tests/bluetooth/controller/mock_ctrl/include/lll_slave.h new file mode 100644 index 00000000000..ed22f2a72b5 --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/lll_slave.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Dummy header file to avoid compiler errors + * intentionally left blank + */ diff --git a/tests/bluetooth/controller/mock_ctrl/include/soc.h b/tests/bluetooth/controller/mock_ctrl/include/soc.h new file mode 100644 index 00000000000..ed22f2a72b5 --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/soc.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Dummy header file to avoid compiler errors + * intentionally left blank + */ diff --git a/tests/bluetooth/controller/mock_ctrl/include/util.h b/tests/bluetooth/controller/mock_ctrl/include/util.h new file mode 100644 index 00000000000..8133a75c20e --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/include/util.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2016 Nordic Semiconductor ASA + * Copyright (c) 2016 Vinayak Kariappa Chettimada + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef DOUBLE_BUFFER_SIZE +#define DOUBLE_BUFFER_SIZE 2 +#endif + +#ifndef TRIPLE_BUFFER_SIZE +#define TRIPLE_BUFFER_SIZE 3 +#endif + +#include + +uint8_t util_ones_count_get(uint8_t *octets, uint8_t octets_len); + +int util_rand(void *buf, size_t len); + +int util_aa_le32(uint8_t *dst); diff --git a/tests/bluetooth/controller/mock_ctrl/src/ecb.c b/tests/bluetooth/controller/mock_ctrl/src/ecb.c new file mode 100644 index 00000000000..180f5d77f5f --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/ecb.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2021 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "hal/ecb.h" + +__attribute__((weak)) void ecb_encrypt(uint8_t const *const key_le, + uint8_t const *const clear_text_le, + uint8_t *const cipher_text_le, uint8_t *const cipher_text_be) +{ +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/kernel.c b/tests/bluetooth/controller/mock_ctrl/src/kernel.c new file mode 100644 index 00000000000..de253ad016b --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/kernel.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2021 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +bool k_is_in_isr(void) +{ + return 0; +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/ll.c b/tests/bluetooth/controller/mock_ctrl/src/ll.c new file mode 100644 index 00000000000..d81881ab846 --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/ll.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "kconfig.h" + +#include +#include +#include +#include + +#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 "ll_feat.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" + +extern sys_slist_t ut_rx_q; diff --git a/tests/bluetooth/controller/mock_ctrl/src/ll_assert.c b/tests/bluetooth/controller/mock_ctrl/src/ll_assert.c new file mode 100644 index 00000000000..d88a622407d --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/ll_assert.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "zephyr/types.h" +#include "ztest.h" +#include +#include "kconfig.h" + +void bt_ctlr_assert_handle(char *file, uint32_t line) +{ + printf("Assertion failed in %s:%d\n", file, line); + exit(-1); +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/lll.c b/tests/bluetooth/controller/mock_ctrl/src/lll.c new file mode 100644 index 00000000000..f84ee026cef --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/lll.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * Copyright (c) 2020 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "kconfig.h" + +#include +#include +#include +#include + +#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 "ll_feat.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" + +extern sys_slist_t ut_rx_q; + +__attribute__((weak)) int lll_csrand_get(void *buf, size_t len) +{ + *(int *)buf = 0; + return 0; +} + +__attribute__((weak)) int lll_csrand_isr_get(void *buf, size_t len) +{ + *(int *)buf = 0; + return 0; +} + +uint32_t lll_radio_tx_ready_delay_get(uint8_t phy, uint8_t flags) +{ + return 0; +} + +void lll_disable(void *param) +{ +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/lll_clock.c b/tests/bluetooth/controller/mock_ctrl/src/lll_clock.c new file mode 100644 index 00000000000..e1c11b02f7a --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/lll_clock.c @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_ctlr_lll_clock +#include "common/log.h" +#include "hal/debug.h" + +/* Clock setup timeouts are unlikely, below values are experimental */ +#define LFCLOCK_TIMEOUT_MS 500 +#define HFCLOCK_TIMEOUT_MS 2 + +int lll_clock_init(void) +{ + return 0; +} + +int lll_clock_wait(void) +{ + return 0; +} + +int lll_hfclock_on(void) +{ + return 0; +} + +int lll_hfclock_on_wait(void) +{ + return 0; +} + +int lll_hfclock_off(void) +{ + return 0; +} + +uint32_t lll_clock_ppm_local_get(void) +{ + return 0; +} + +uint32_t lll_clock_ppm_get(uint8_t sca) +{ + ARG_UNUSED(sca); + return 0; +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/lll_conn.c b/tests/bluetooth/controller/mock_ctrl/src/lll_conn.c new file mode 100644 index 00000000000..ab914f090ba --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/lll_conn.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include + +#include "hal/cpu.h" +#include "hal/ccm.h" +#include "hal/radio.h" + +#include "util/util.h" +#include "util/mem.h" +#include "util/memq.h" +#include "util/mfifo.h" + +#include "pdu.h" + +#include "lll.h" +#include "lll_df_types.h" +#include "lll_conn.h" + +#include "lll_internal.h" +#include "lll_tim_internal.h" +#include "lll_prof_internal.h" + +void lll_conn_flush(uint16_t handle, struct lll_conn *lll) +{ +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/mayfly.c b/tests/bluetooth/controller/mock_ctrl/src/mayfly.c new file mode 100644 index 00000000000..762ae130efb --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/mayfly.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 Demant + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "memq.h" +#include "mayfly.h" + +void mayfly_init(void) +{ +} + +void mayfly_enable(uint8_t caller_id, uint8_t callee_id, uint8_t enable) +{ +} + +uint32_t mayfly_is_enabled(uint8_t caller_id, uint8_t callee_id) +{ + ARG_UNUSED(caller_id); + ARG_UNUSED(callee_id); + + return 0; +} + +uint32_t mayfly_enqueue(uint8_t caller_id, uint8_t callee_id, uint8_t chain, struct mayfly *m) +{ + return 0; +} + +void mayfly_run(uint8_t callee_id) +{ +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/ticker.c b/tests/bluetooth/controller/mock_ctrl/src/ticker.c new file mode 100644 index 00000000000..b9cd58caa4d --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/ticker.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 Nordic Semiconductor ASA + * Copyright (c) 2016 Vinayak Kariappa Chettimada + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "ticker/ticker.h" + +uint32_t ticker_update(uint8_t instance_index, uint8_t user_id, uint8_t ticker_id, + uint32_t ticks_drift_plus, uint32_t ticks_drift_minus, + uint32_t ticks_slot_plus, uint32_t ticks_slot_minus, uint16_t lazy, + uint8_t force, ticker_op_func fp_op_func, void *op_context) +{ + return TICKER_STATUS_SUCCESS; +} + +uint32_t ticker_start(uint8_t instance_index, uint8_t user_id, uint8_t ticker_id, + uint32_t ticks_anchor, uint32_t ticks_first, uint32_t ticks_periodic, + uint32_t remainder_periodic, uint16_t lazy, uint32_t ticks_slot, + ticker_timeout_func fp_timeout_func, void *context, ticker_op_func fp_op_func, + void *op_context) +{ + return TICKER_STATUS_SUCCESS; +} + +uint32_t ticker_stop(uint8_t instance_index, uint8_t user_id, uint8_t ticker_id, + ticker_op_func fp_op_func, void *op_context) +{ + return TICKER_STATUS_SUCCESS; +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/ull.c b/tests/bluetooth/controller/mock_ctrl/src/ull.c new file mode 100644 index 00000000000..c63193200c5 --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/ull.c @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +#include "hal/cpu_vendor_hal.h" +#include "hal/ccm.h" + +#include "util/mem.h" +#include "util/mfifo.h" +#include "util/memq.h" +#include "util.h" + +#include "pdu.h" +#include "ll.h" +#include "ll_feat.h" +#include "ll_settings.h" +#include "lll.h" +#include "lll_vendor.h" +#include "lll/lll_adv_types.h" +#include "lll_adv.h" +#include "lll/lll_adv_pdu.h" +#include "lll_scan.h" +#include "lll_sync.h" +#include "lll/lll_df_types.h" +#include "lll_conn.h" + +#define EVENT_DONE_MAX 3 +/* Backing storage for elements in mfifo_done */ +static struct { + void *free; + uint8_t pool[sizeof(struct node_rx_event_done) * EVENT_DONE_MAX]; +} mem_done; + +static struct { + void *free; + uint8_t pool[sizeof(memq_link_t) * EVENT_DONE_MAX]; +} mem_link_done; + +#if defined(CONFIG_BT_CTLR_PHY) && defined(CONFIG_BT_CTLR_DATA_LENGTH) +#define LL_PDU_RX_CNT (3 + 128) +#else +#define LL_PDU_RX_CNT (2 + 128) +#endif + +#define PDU_RX_CNT (CONFIG_BT_CTLR_RX_BUFFERS + 3) +#define RX_CNT (PDU_RX_CNT + LL_PDU_RX_CNT) + +static MFIFO_DEFINE(pdu_rx_free, sizeof(void *), PDU_RX_CNT); + +#if defined(CONFIG_BT_RX_USER_PDU_LEN) +#define PDU_RX_USER_PDU_OCTETS_MAX (CONFIG_BT_RX_USER_PDU_LEN) +#else +#define PDU_RX_USER_PDU_OCTETS_MAX 0 +#endif +#define NODE_RX_HEADER_SIZE (offsetof(struct node_rx_pdu, pdu)) +#define NODE_RX_STRUCT_OVERHEAD (NODE_RX_HEADER_SIZE) + +#define PDU_ADVERTIZE_SIZE (PDU_AC_LL_SIZE_MAX + PDU_AC_LL_SIZE_EXTRA) +#define PDU_DATA_SIZE (PDU_DC_LL_HEADER_SIZE + LL_LENGTH_OCTETS_RX_MAX) + +#define PDU_RX_NODE_POOL_ELEMENT_SIZE \ + MROUND(NODE_RX_STRUCT_OVERHEAD + \ + MAX(MAX(PDU_ADVERTIZE_SIZE, PDU_DATA_SIZE), PDU_RX_USER_PDU_OCTETS_MAX)) + +/* + * just a big number + */ +#define PDU_RX_POOL_SIZE 16384 + +static struct { + void *free; + uint8_t pool[PDU_RX_POOL_SIZE]; +} mem_pdu_rx; + +/* + * just a big number + */ +#define LINK_RX_POOL_SIZE 16384 +static struct { + uint8_t quota_pdu; /* Number of un-utilized buffers */ + + void *free; + uint8_t pool[LINK_RX_POOL_SIZE]; +} mem_link_rx; + +static MEMQ_DECLARE(ull_rx); +static MEMQ_DECLARE(ll_rx); + +#if defined(CONFIG_BT_CONN) +static MFIFO_DEFINE(ll_pdu_rx_free, sizeof(void *), LL_PDU_RX_CNT); +#endif /* CONFIG_BT_CONN */ + +#ifdef ZTEST_UNITTEST +extern sys_slist_t ut_rx_q; +#else +sys_slist_t ut_rx_q; +#endif + +static inline int init_reset(void); +static inline void rx_alloc(uint8_t max); +static inline void ll_rx_link_inc_quota(int8_t delta); + +void ll_reset(void) +{ + MFIFO_INIT(ll_pdu_rx_free); + init_reset(); +} + +void ll_rx_mem_release(void **node_rx) +{ + struct node_rx_hdr *rx; + + rx = *node_rx; + while (rx) { + struct node_rx_hdr *rx_free; + + rx_free = rx; + rx = rx->next; + + switch (rx_free->type) { + case NODE_RX_TYPE_DC_PDU: + ll_rx_link_inc_quota(1); + mem_release(rx_free, &mem_pdu_rx.free); + break; + default: + __ASSERT(0, "Tried to release unknown rx node type"); + break; + } + } + + *node_rx = rx; + + rx_alloc(UINT8_MAX); +} + +static inline void ll_rx_link_inc_quota(int8_t delta) +{ + mem_link_rx.quota_pdu += delta; +} + +void *ll_rx_link_alloc(void) +{ + return mem_acquire(&mem_link_rx.free); +} + +void ll_rx_link_release(void *link) +{ + mem_release(link, &mem_link_rx.free); +} + +void *ll_rx_alloc(void) +{ + return mem_acquire(&mem_pdu_rx.free); +} + +void ll_rx_release(void *node_rx) +{ + mem_release(node_rx, &mem_pdu_rx.free); +} + +void ll_rx_put(memq_link_t *link, void *rx) +{ + sys_slist_append(&ut_rx_q, (sys_snode_t *)rx); +} + +void ll_rx_sched(void) +{ +} + +void *ll_pdu_rx_alloc_peek(uint8_t count) +{ + if (count > MFIFO_AVAIL_COUNT_GET(ll_pdu_rx_free)) { + return NULL; + } + + return MFIFO_DEQUEUE_PEEK(ll_pdu_rx_free); +} + +void *ll_pdu_rx_alloc(void) +{ + return MFIFO_DEQUEUE(ll_pdu_rx_free); +} + +void ll_tx_ack_put(uint16_t handle, struct node_tx *node) +{ +} + +void ull_ticker_status_give(uint32_t status, void *param) +{ +} + +uint32_t ull_ticker_status_take(uint32_t ret, uint32_t volatile *ret_cb) +{ + return *ret_cb; +} + +void *ull_disable_mark(void *param) +{ + return NULL; +} + +void *ull_disable_unmark(void *param) +{ + return NULL; +} + +void *ull_disable_mark_get(void) +{ + return NULL; +} + +int ull_ticker_stop_with_mark(uint8_t ticker_handle, void *param, void *lll_disable) +{ + return 0; +} + +void *ull_update_mark(void *param) +{ + return NULL; +} + +void *ull_update_unmark(void *param) +{ + return NULL; +} + +void *ull_update_mark_get(void) +{ + return NULL; +} + +int ull_disable(void *lll) +{ + return 0; +} + +void ull_rx_put(memq_link_t *link, void *rx) +{ +} + +void ull_rx_sched(void) +{ +} + +/* Forward declaration */ +struct node_rx_event_done; +void ull_drift_ticks_get(struct node_rx_event_done *done, uint32_t *ticks_drift_plus, + uint32_t *ticks_drift_minus) +{ +} + +static inline int init_reset(void) +{ + memq_link_t *link; + + /* Initialize done pool. */ + mem_init(mem_done.pool, sizeof(struct node_rx_event_done), EVENT_DONE_MAX, &mem_done.free); + + /* Initialize done link pool. */ + mem_init(mem_link_done.pool, sizeof(memq_link_t), EVENT_DONE_MAX, &mem_link_done.free); + + /* Initialize rx pool. */ + mem_init(mem_pdu_rx.pool, (PDU_RX_NODE_POOL_ELEMENT_SIZE), + sizeof(mem_pdu_rx.pool) / (PDU_RX_NODE_POOL_ELEMENT_SIZE), &mem_pdu_rx.free); + + /* Initialize rx link pool. */ + mem_init(mem_link_rx.pool, sizeof(memq_link_t), + sizeof(mem_link_rx.pool) / sizeof(memq_link_t), &mem_link_rx.free); + + /* Acquire a link to initialize ull rx memq */ + link = mem_acquire(&mem_link_rx.free); + + /* Initialize ull rx memq */ + MEMQ_INIT(ull_rx, link); + + /* Acquire a link to initialize ll rx memq */ + link = mem_acquire(&mem_link_rx.free); + + /* Initialize ll rx memq */ + MEMQ_INIT(ll_rx, link); + + /* Allocate rx free buffers */ + mem_link_rx.quota_pdu = RX_CNT; + rx_alloc(UINT8_MAX); + + return 0; +} + +static inline void rx_alloc(uint8_t max) +{ + uint8_t idx; + +#if defined(CONFIG_BT_CONN) + while (mem_link_rx.quota_pdu && MFIFO_ENQUEUE_IDX_GET(ll_pdu_rx_free, &idx)) { + memq_link_t *link; + struct node_rx_hdr *rx; + + link = mem_acquire(&mem_link_rx.free); + if (!link) { + break; + } + + rx = mem_acquire(&mem_pdu_rx.free); + if (!rx) { + mem_release(link, &mem_link_rx.free); + break; + } + + link->mem = NULL; + rx->link = link; + + MFIFO_BY_IDX_ENQUEUE(ll_pdu_rx_free, idx, rx); + + ll_rx_link_inc_quota(-1); + } +#endif /* CONFIG_BT_CONN */ + + if (max > mem_link_rx.quota_pdu) { + max = mem_link_rx.quota_pdu; + } + + while ((max--) && MFIFO_ENQUEUE_IDX_GET(pdu_rx_free, &idx)) { + memq_link_t *link; + struct node_rx_hdr *rx; + + link = mem_acquire(&mem_link_rx.free); + if (!link) { + break; + } + + rx = mem_acquire(&mem_pdu_rx.free); + if (!rx) { + mem_release(link, &mem_link_rx.free); + break; + } + + rx->link = link; + + MFIFO_BY_IDX_ENQUEUE(pdu_rx_free, idx, rx); + + ll_rx_link_inc_quota(-1); + } +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/ull_central.c b/tests/bluetooth/controller/mock_ctrl/src/ull_central.c new file mode 100644 index 00000000000..c07b89efb3b --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/ull_central.c @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "zephyr/types.h" +#include "util/util.h" +#include "util/mem.h" +#include "util/memq.h" + +#include "pdu.h" + +#include "hal/ccm.h" +#include "lll.h" +#include "lll_df_types.h" +#include "lll_conn.h" + +void ull_central_setup(memq_link_t *link, struct node_rx_hdr *rx, struct node_rx_ftr *ftr, + struct lll_conn *lll) +{ +} + +void ull_central_ticker_cb(uint32_t ticks_at_expire, uint32_t remainder, uint16_t lazy, void *param) +{ +} + + +uint8_t ull_central_chm_update(void) +{ + return 0; +} + + +int ull_central_reset(void) +{ + return 0; +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/ull_periph.c b/tests/bluetooth/controller/mock_ctrl/src/ull_periph.c new file mode 100644 index 00000000000..ad46fa0397a --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/ull_periph.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "zephyr/types.h" +#include "util/util.h" +#include "util/mem.h" +#include "util/memq.h" + +#include "pdu.h" + +#include "hal/ccm.h" +#include "lll.h" +#include "lll_df_types.h" +#include "lll_conn.h" + +void ull_periph_setup(memq_link_t *link, struct node_rx_hdr *rx, struct node_rx_ftr *ftr, + struct lll_conn *lll) +{ +} + +void ull_periph_ticker_cb(uint32_t ticks_at_expire, uint32_t remainder, uint16_t lazy, void *param) +{ +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/ull_scan.c b/tests/bluetooth/controller/mock_ctrl/src/ull_scan.c new file mode 100644 index 00000000000..11ae4d8fafe --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/ull_scan.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016 Nordic Semiconductor ASA + * Copyright (c) 2016 Vinayak Kariappa Chettimada + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "hci_err.h" +#include "util/mem.h" +#include "util/memq.h" +#include "pdu.h" + +#include "lll.h" +#include "lll_scan.h" + +#include "ull_scan_types.h" +#include "ull_scan_internal.h" + +#define BT_CTLR_SCAN_MAX 1 +static struct ll_scan_set ll_scan[BT_CTLR_SCAN_MAX]; + +uint8_t ll_scan_params_set(uint8_t type, uint16_t interval, uint16_t window, uint8_t own_addr_type, + uint8_t filter_policy) +{ + struct ll_scan_set *scan; + + scan = ull_scan_is_disabled_get(0); + if (!scan) { + return BT_HCI_ERR_CMD_DISALLOWED; + } + + scan->own_addr_type = own_addr_type; + + return 0; +} + +struct ll_scan_set *ull_scan_set_get(uint8_t handle) +{ + if (handle >= BT_CTLR_SCAN_MAX) { + return NULL; + } + + return &ll_scan[handle]; +} + +struct ll_scan_set *ull_scan_is_enabled_get(uint8_t handle) +{ + struct ll_scan_set *scan; + + scan = ull_scan_set_get(handle); + if (!scan || !scan->is_enabled) { + return NULL; + } + + return scan; +} + +struct ll_scan_set *ull_scan_is_disabled_get(uint8_t handle) +{ + struct ll_scan_set *scan; + + scan = ull_scan_set_get(handle); + if (!scan || scan->is_enabled) { + return NULL; + } + + return scan; +} + +uint8_t ull_scan_enable(struct ll_scan_set *scan) +{ + return 0; +} + +void ull_scan_params_set(struct lll_scan *lll, uint8_t type, uint16_t interval, uint16_t window, + uint8_t filter_policy) +{ +} + +uint8_t ull_scan_disable(uint8_t handle, struct ll_scan_set *scan) +{ + return 0; +} diff --git a/tests/bluetooth/controller/mock_ctrl/src/util.c b/tests/bluetooth/controller/mock_ctrl/src/util.c new file mode 100644 index 00000000000..9d05fe008cb --- /dev/null +++ b/tests/bluetooth/controller/mock_ctrl/src/util.c @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2016 Nordic Semiconductor ASA + * Copyright (c) 2016 Vinayak Kariappa Chettimada + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sys/byteorder.h" +#include "util.h" +#include "pdu.h" + +#include "util/mem.h" +#include "util/memq.h" +#include "util/mayfly.h" +#include "lll.h" + +/** + * @brief Population count: Count the number of bits set to 1 + * @details + * TODO: Faster methods available at [1]. + * [1] http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + * + * @param octets Data to count over + * @param octets_len Must not be bigger than 255/8 = 31 bytes + * + * @return popcnt of 'octets' + */ +uint8_t util_ones_count_get(uint8_t *octets, uint8_t octets_len) +{ + uint8_t one_count = 0U; + + while (octets_len--) { + uint8_t bite; + + bite = *octets; + while (bite) { + bite &= (bite - 1); + one_count++; + } + octets++; + } + + return one_count; +} + +int util_rand(void *buf, size_t len) +{ + return 0xDEADBEEF; +} + +/** @brief Prepare access address as per BT Spec. + * + * - It shall have no more than six consecutive zeros or ones. + * - It shall not be the advertising channel packets' Access Address. + * - It shall not be a sequence that differs from the advertising channel + * packets Access Address by only one bit. + * - It shall not have all four octets equal. + * - It shall have no more than 24 transitions. + * - It shall have a minimum of two transitions in the most significant six + * bits. + * + * LE Coded PHY requirements: + * - It shall have at least three ones in the least significant 8 bits. + * - It shall have no more than eleven transitions in the least significant 16 + * bits. + */ +int util_aa_le32(uint8_t *dst) +{ +#if defined(CONFIG_BT_CTLR_PHY_CODED) + uint8_t transitions_lsb16; + uint8_t ones_count_lsb8; +#endif /* CONFIG_BT_CTLR_PHY_CODED */ + uint8_t consecutive_cnt; + uint8_t consecutive_bit; + uint32_t adv_aa_check; + uint32_t aa; + uint8_t transitions; + uint8_t bit_idx; + uint8_t retry; + + retry = 3U; +again: + if (!retry) { + return -EFAULT; + } + retry--; + + *dst = lll_csrand_get(dst, sizeof(uint32_t)); + aa = sys_get_le32(dst); + + bit_idx = 31U; + transitions = 0U; + consecutive_cnt = 1U; +#if defined(CONFIG_BT_CTLR_PHY_CODED) + ones_count_lsb8 = 0U; + transitions_lsb16 = 0U; +#endif /* CONFIG_BT_CTLR_PHY_CODED */ + consecutive_bit = (aa >> bit_idx) & 0x01; + while (bit_idx--) { +#if defined(CONFIG_BT_CTLR_PHY_CODED) + uint8_t transitions_lsb16_prev = transitions_lsb16; +#endif /* CONFIG_BT_CTLR_PHY_CODED */ + uint8_t consecutive_cnt_prev = consecutive_cnt; + uint8_t transitions_prev = transitions; + uint8_t bit; + + bit = (aa >> bit_idx) & 0x01; + if (bit == consecutive_bit) { + consecutive_cnt++; + } else { + consecutive_cnt = 1U; + consecutive_bit = bit; + transitions++; + +#if defined(CONFIG_BT_CTLR_PHY_CODED) + if (bit_idx < 15) { + transitions_lsb16++; + } +#endif /* CONFIG_BT_CTLR_PHY_CODED */ + } + +#if defined(CONFIG_BT_CTLR_PHY_CODED) + if ((bit_idx < 8) && consecutive_bit) { + ones_count_lsb8++; + } +#endif /* CONFIG_BT_CTLR_PHY_CODED */ + + /* It shall have no more than six consecutive zeros or ones. */ + /* It shall have a minimum of two transitions in the most + * significant six bits. + */ + if ((consecutive_cnt > 6) || +#if defined(CONFIG_BT_CTLR_PHY_CODED) + (!consecutive_bit && (((bit_idx < 6) && (ones_count_lsb8 < 1)) || + ((bit_idx < 5) && (ones_count_lsb8 < 2)) || + ((bit_idx < 4) && (ones_count_lsb8 < 3)))) || +#endif /* CONFIG_BT_CTLR_PHY_CODED */ + ((consecutive_cnt < 6) && (((bit_idx < 29) && (transitions < 1)) || + ((bit_idx < 28) && (transitions < 2))))) { + if (consecutive_bit) { + consecutive_bit = 0U; + aa &= ~BIT(bit_idx); +#if defined(CONFIG_BT_CTLR_PHY_CODED) + if (bit_idx < 8) { + ones_count_lsb8--; + } +#endif /* CONFIG_BT_CTLR_PHY_CODED */ + } else { + consecutive_bit = 1U; + aa |= BIT(bit_idx); +#if defined(CONFIG_BT_CTLR_PHY_CODED) + if (bit_idx < 8) { + ones_count_lsb8++; + } +#endif /* CONFIG_BT_CTLR_PHY_CODED */ + } + + if (transitions != transitions_prev) { + consecutive_cnt = consecutive_cnt_prev; + transitions = transitions_prev; + } else { + consecutive_cnt = 1U; + transitions++; + } + +#if defined(CONFIG_BT_CTLR_PHY_CODED) + if (bit_idx < 15) { + if (transitions_lsb16 != transitions_lsb16_prev) { + transitions_lsb16 = transitions_lsb16_prev; + } else { + transitions_lsb16++; + } + } +#endif /* CONFIG_BT_CTLR_PHY_CODED */ + } + + /* It shall have no more than 24 transitions + * It shall have no more than eleven transitions in the least + * significant 16 bits. + */ + if ((transitions > 24) || +#if defined(CONFIG_BT_CTLR_PHY_CODED) + (transitions_lsb16 > 11) || +#endif /* CONFIG_BT_CTLR_PHY_CODED */ + 0) { + if (consecutive_bit) { + aa &= ~(BIT(bit_idx + 1) - 1); + } else { + aa |= (BIT(bit_idx + 1) - 1); + } + + break; + } + } + + /* It shall not be the advertising channel packets Access Address. + * It shall not be a sequence that differs from the advertising channel + * packets Access Address by only one bit. + */ + adv_aa_check = aa ^ PDU_AC_ACCESS_ADDR; + if (util_ones_count_get((uint8_t *)&adv_aa_check, sizeof(adv_aa_check)) <= 1) { + goto again; + } + + /* It shall not have all four octets equal. */ + if (!((aa & 0xFFFF) ^ (aa >> 16)) && !((aa & 0xFF) ^ (aa >> 24))) { + goto again; + } + + sys_put_le32(aa, dst); + + return 0; +} diff --git a/tests/bluetooth/init/prj_llcp.conf b/tests/bluetooth/init/prj_llcp.conf new file mode 100644 index 00000000000..27e52e72290 --- /dev/null +++ b/tests/bluetooth/init/prj_llcp.conf @@ -0,0 +1,19 @@ +CONFIG_BT=y +CONFIG_BT_CTLR=y +CONFIG_BT_LL_SW_SPLIT=y +CONFIG_BT_HCI_ACL_FLOW_CONTROL=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_SMP=y +CONFIG_BT_SIGNING=y +CONFIG_BT_SMP_SC_ONLY=y +CONFIG_BT_TINYCRYPT_ECC=y +CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y +CONFIG_BT_GATT_CLIENT=y +CONFIG_BT_BREDR=n +CONFIG_FLASH=y +CONFIG_SOC_FLASH_NRF_RADIO_SYNC_TICKER=y +CONFIG_BT_CTLR_ADVANCED_FEATURES=y +CONFIG_BT_LL_SW_LLCP_LEGACY=n + +CONFIG_ZTEST=y diff --git a/tests/bluetooth/init/testcase.yaml b/tests/bluetooth/init/testcase.yaml index e4ee1b085eb..0f8e5ca695c 100644 --- a/tests/bluetooth/init/testcase.yaml +++ b/tests/bluetooth/init/testcase.yaml @@ -223,3 +223,11 @@ tests: bluetooth.init.test_h5_dbg: extra_args: CONF_FILE=prj_h5_dbg.conf platform_allow: qemu_cortex_m3 + bluetooth.init.test_llcp: + extra_args: CONF_FILE=prj_llcp.conf + platform_allow: nrf52840dk_nrf52840 nrf52dk_nrf52832 + rv32m1_vega_ri5cy + integration_platforms: + - nrf52840dk_nrf52840 + - nrf52dk_nrf52832 + - rv32m1_vega_ri5cy