From f023b5f611df8f8fa975bf2ebd3e80239b4e9278 Mon Sep 17 00:00:00 2001 From: Andries Kruithof Date: Mon, 15 Nov 2021 09:41:12 +0100 Subject: [PATCH] Bluetooth: controller: push topic branch to main Pushes all work done in the topic-ble-llcp branch into main branch This is a refactoring of the LL control procedures; the refactored control procedures are hidden behind a KConfig option and per default disabled Goal of the refactoring: close issue Link Layer Control Procedure overhaul #15256 make it easier to add/update control procedures Refactoring consists in principal of writing explicit state machines for the control procedures. To reduce the risk of regression errors unit-tests have been added Following control procedures are implemented: Connection update procedure Channel map update procedure Encryption procedure Feature exchange procedure Version exchange procedure ACL termination procedure Connection parameters request procedure LE Ping procedure Data Length Update procedure PHY update procedure Min. nr. Of channels used procedure Constant Tone extension request procedure This is a joined work by the people listed in the signed-off-by list (in alphabetical order) Signed-off-by: Andries Kruithof Andries.Kruithof@nordicsemi.no Signed-off-by: Erik Brockhoff erbr@oticon.com Signed-off-by: Piotr Pryga piotr.pryga@nordicsemi.no Signed-off-by: Szymon Janc szymon.janc@codecoup.pl Signed-off-by: Thomas Ebert Hansen thoh@oticon.com Signed-off-by: Tommie Skriver tosk@demant.com Signed-off-by: Andries Kruithof --- include/bluetooth/hci_vs.h | 8 + subsys/bluetooth/controller/CMakeLists.txt | 21 + .../bluetooth/controller/Kconfig.ll_sw_split | 64 + subsys/bluetooth/controller/hci/hci.c | 28 + subsys/bluetooth/controller/include/ll.h | 7 +- subsys/bluetooth/controller/include/ll_feat.h | 5 +- subsys/bluetooth/controller/ll_sw/ll_addr.c | 2 +- subsys/bluetooth/controller/ll_sw/ll_tx_pwr.c | 4 + subsys/bluetooth/controller/ll_sw/lll.h | 5 + subsys/bluetooth/controller/ll_sw/lll_conn.h | 20 + .../controller/ll_sw/nordic/lll/lll_adv.c | 2 +- .../controller/ll_sw/nordic/lll/lll_adv_aux.c | 1 + .../controller/ll_sw/nordic/lll/lll_conn.c | 8 + .../ll_sw/nordic/lll/lll_df_types.h | 20 +- .../ll_sw/nordic/lll/lll_peripheral.c | 2 + .../ll_sw/nordic/lll/lll_scan_aux.c | 8 +- .../controller/ll_sw/openisa/lll/lll_adv.c | 1 + .../ll_sw/openisa/lll/lll_central.c | 2 + .../controller/ll_sw/openisa/lll/lll_conn.c | 10 + .../ll_sw/openisa/lll/lll_peripheral.c | 2 + .../controller/ll_sw/openisa/lll/lll_scan.c | 1 + subsys/bluetooth/controller/ll_sw/pdu.h | 50 + subsys/bluetooth/controller/ll_sw/ull.c | 3 + subsys/bluetooth/controller/ll_sw/ull_adv.c | 42 +- .../bluetooth/controller/ll_sw/ull_adv_aux.c | 2 + .../bluetooth/controller/ll_sw/ull_adv_sync.c | 2 + .../bluetooth/controller/ll_sw/ull_central.c | 78 +- subsys/bluetooth/controller/ll_sw/ull_chan.c | 1 + .../controller/ll_sw/ull_chan_internal.h | 3 + subsys/bluetooth/controller/ll_sw/ull_conn.c | 746 ++++- .../controller/ll_sw/ull_conn_internal.h | 61 +- .../controller/ll_sw/ull_conn_types.h | 189 ++ .../bluetooth/controller/ll_sw/ull_filter.c | 4 + .../bluetooth/controller/ll_sw/ull_internal.h | 1 + subsys/bluetooth/controller/ll_sw/ull_llcp.c | 1109 +++++++ subsys/bluetooth/controller/ll_sw/ull_llcp.h | 160 + .../controller/ll_sw/ull_llcp_chmu.c | 322 ++ .../controller/ll_sw/ull_llcp_common.c | 1055 +++++++ .../controller/ll_sw/ull_llcp_conn_upd.c | 994 ++++++ .../bluetooth/controller/ll_sw/ull_llcp_enc.c | 1197 ++++++++ .../controller/ll_sw/ull_llcp_features.h | 170 ++ .../controller/ll_sw/ull_llcp_internal.h | 592 ++++ .../controller/ll_sw/ull_llcp_local.c | 487 +++ .../bluetooth/controller/ll_sw/ull_llcp_pdu.c | 752 +++++ .../bluetooth/controller/ll_sw/ull_llcp_phy.c | 1124 +++++++ .../controller/ll_sw/ull_llcp_remote.c | 730 +++++ .../controller/ll_sw/ull_peripheral.c | 61 +- subsys/bluetooth/controller/ll_sw/ull_scan.c | 2 +- .../bluetooth/controller/ll_sw/ull_scan_aux.c | 2 + subsys/bluetooth/controller/ll_sw/ull_sched.c | 48 +- subsys/bluetooth/controller/ll_sw/ull_sync.c | 1 + .../bluetooth/controller/ll_sw/ull_tx_queue.c | 66 + .../bluetooth/controller/ll_sw/ull_tx_queue.h | 72 + subsys/bluetooth/controller/util/util.c | 2 +- subsys/bluetooth/controller/util/util.h | 2 +- .../bsim_bt/_compile_permutate_kconfigs.sh | 91 + tests/bluetooth/bsim_bt/compile.sh | 39 +- tests/bluetooth/bsim_bt/compile.source | 41 + .../edtt_ble_test_app/gatt_test_app/prj.conf | 2 + .../hci_test_app/prj_dut.conf | 4 +- .../hci_test_app/prj_dut_llcp.conf | 32 + .../hci_test_app/prj_tst.conf | 1 + .../hci_test_app/prj_tst_llcp.conf | 30 + .../tests_scripts/_controller_tests_inner.sh | 88 +- .../edtt_ble_test_app/tests_scripts/gap.sh | 2 + .../tests_scripts/gap.test_list | 2 +- .../tests_scripts/hci.llcp.sh | 14 + .../tests_scripts/hci.llcp.test_list | 31 + .../edtt_ble_test_app/tests_scripts/hci.sh | 2 + .../tests_scripts/hci.test_list | 2 +- .../tests_scripts/ll.1.llcp.sh | 15 + .../edtt_ble_test_app/tests_scripts/ll.1.sh | 2 + .../tests_scripts/ll.2.llcp.sh | 15 + .../edtt_ble_test_app/tests_scripts/ll.2.sh | 2 + .../tests_scripts/ll.set1.llcp.test_list | 64 + .../tests_scripts/ll.set2.llcp.test_list | 62 + .../controller/common/defaults_cmake.txt | 64 + .../common/include/helper_features.h | 249 ++ .../controller/common/include/helper_pdu.h | 178 ++ .../controller/common/include/helper_util.h | 37 + .../controller/common/src/helper_pdu.c | 951 ++++++ .../controller/common/src/helper_util.c | 397 +++ .../controller/ctrl_api/CMakeLists.txt | 16 + .../bluetooth/controller/ctrl_api/src/main.c | 213 ++ .../controller/ctrl_api/testcase.yaml | 5 + .../controller/ctrl_chmu/CMakeLists.txt | 16 + .../bluetooth/controller/ctrl_chmu/src/main.c | 228 ++ .../controller/ctrl_chmu/testcase.yaml | 5 + .../ctrl_conn_update/CMakeLists.txt | 16 + .../ctrl_conn_update/src/kconfig_override.h | 13 + .../controller/ctrl_conn_update/src/main.c | 2653 +++++++++++++++++ .../controller/ctrl_conn_update/testcase.yaml | 9 + .../controller/ctrl_cte_req/CMakeLists.txt | 20 + .../controller/ctrl_cte_req/src/main.c | 626 ++++ .../controller/ctrl_cte_req/testcase.yaml | 5 + .../ctrl_data_length_update/CMakeLists.txt | 26 + .../src/kconfig_override.h | 14 + .../ctrl_data_length_update/src/main.c | 673 +++++ .../ctrl_data_length_update/testcase.yaml | 12 + .../controller/ctrl_encrypt/CMakeLists.txt | 16 + .../controller/ctrl_encrypt/src/main.c | 1775 +++++++++++ .../controller/ctrl_encrypt/testcase.yaml | 5 + .../ctrl_feature_exchange/CMakeLists.txt | 16 + .../ctrl_feature_exchange/src/main.c | 449 +++ .../ctrl_feature_exchange/src/main_hci.c | 177 ++ .../ctrl_feature_exchange/testcase.yaml | 5 + .../controller/ctrl_hci/CMakeLists.txt | 16 + tests/bluetooth/controller/ctrl_hci/prj.conf | 6 + .../bluetooth/controller/ctrl_hci/src/main.c | 530 ++++ .../controller/ctrl_hci/testcase.yaml | 6 + .../controller/ctrl_le_ping/CMakeLists.txt | 16 + .../controller/ctrl_le_ping/src/main.c | 278 ++ .../controller/ctrl_le_ping/testcase.yaml | 5 + .../ctrl_min_used_chans/CMakeLists.txt | 16 + .../controller/ctrl_min_used_chans/src/main.c | 179 ++ .../ctrl_min_used_chans/testcase.yaml | 5 + .../controller/ctrl_phy_update/CMakeLists.txt | 16 + .../controller/ctrl_phy_update/src/main.c | 1060 +++++++ .../controller/ctrl_phy_update/testcase.yaml | 5 + .../controller/ctrl_terminate/CMakeLists.txt | 16 + .../controller/ctrl_terminate/src/main.c | 149 + .../controller/ctrl_terminate/testcase.yaml | 5 + .../ctrl_tx_buffer_alloc/CMakeLists.txt | 19 + .../src/kconfig_override.h | 15 + .../src/kconfig_override_max_common.h | 16 + .../ctrl_tx_buffer_alloc/src/main.c | 158 + .../ctrl_tx_buffer_alloc/testcase.yaml | 21 + .../controller/ctrl_tx_queue/CMakeLists.txt | 16 + .../controller/ctrl_tx_queue/src/main.c | 395 +++ .../controller/ctrl_tx_queue/testcase.yaml | 5 + .../controller/ctrl_version/CMakeLists.txt | 16 + .../controller/ctrl_version/src/main.c | 370 +++ .../controller/ctrl_version/testcase.yaml | 5 + .../mock_ctrl/include/hal/cpu_vendor_hal.h | 8 + .../mock_ctrl/include/hal/debug_vendor_hal.h | 10 + .../mock_ctrl/include/hal/radio_vendor_hal.h | 10 + .../mock_ctrl/include/hal/ticker_vendor_hal.h | 40 + .../controller/mock_ctrl/include/kconfig.h | 154 + .../mock_ctrl/include/lll/lll_adv_pdu.h | 117 + .../mock_ctrl/include/lll/lll_adv_types.h | 28 + .../controller/mock_ctrl/include/lll_clock.h | 13 + .../controller/mock_ctrl/include/lll_master.h | 9 + .../controller/mock_ctrl/include/lll_slave.h | 10 + .../controller/mock_ctrl/include/soc.h | 10 + .../controller/mock_ctrl/include/util.h | 22 + .../bluetooth/controller/mock_ctrl/src/ecb.c | 12 + .../controller/mock_ctrl/src/kernel.c | 13 + tests/bluetooth/controller/mock_ctrl/src/ll.c | 36 + .../controller/mock_ctrl/src/ll_assert.c | 16 + .../bluetooth/controller/mock_ctrl/src/lll.c | 57 + .../controller/mock_ctrl/src/lll_clock.c | 53 + .../controller/mock_ctrl/src/lll_conn.c | 35 + .../controller/mock_ctrl/src/mayfly.c | 33 + .../controller/mock_ctrl/src/ticker.c | 33 + .../bluetooth/controller/mock_ctrl/src/ull.c | 349 +++ .../controller/mock_ctrl/src/ull_central.c | 38 + .../controller/mock_ctrl/src/ull_periph.c | 26 + .../controller/mock_ctrl/src/ull_scan.c | 85 + .../bluetooth/controller/mock_ctrl/src/util.c | 214 ++ tests/bluetooth/init/prj_llcp.conf | 19 + tests/bluetooth/init/testcase.yaml | 8 + 161 files changed, 24482 insertions(+), 81 deletions(-) create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp.c create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp.h create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp_chmu.c create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp_common.c create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp_conn_upd.c create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp_enc.c create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp_features.h create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp_internal.h create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp_local.c create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp_pdu.c create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp_phy.c create mode 100644 subsys/bluetooth/controller/ll_sw/ull_llcp_remote.c create mode 100644 subsys/bluetooth/controller/ll_sw/ull_tx_queue.c create mode 100644 subsys/bluetooth/controller/ll_sw/ull_tx_queue.h create mode 100755 tests/bluetooth/bsim_bt/_compile_permutate_kconfigs.sh create mode 100644 tests/bluetooth/bsim_bt/compile.source create mode 100644 tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_dut_llcp.conf create mode 100644 tests/bluetooth/bsim_bt/edtt_ble_test_app/hci_test_app/prj_tst_llcp.conf create mode 100755 tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.llcp.sh create mode 100644 tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/hci.llcp.test_list create mode 100755 tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.1.llcp.sh create mode 100755 tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.2.llcp.sh create mode 100644 tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.set1.llcp.test_list create mode 100644 tests/bluetooth/bsim_bt/edtt_ble_test_app/tests_scripts/ll.set2.llcp.test_list create mode 100644 tests/bluetooth/controller/common/defaults_cmake.txt create mode 100644 tests/bluetooth/controller/common/include/helper_features.h create mode 100644 tests/bluetooth/controller/common/include/helper_pdu.h create mode 100644 tests/bluetooth/controller/common/include/helper_util.h create mode 100644 tests/bluetooth/controller/common/src/helper_pdu.c create mode 100644 tests/bluetooth/controller/common/src/helper_util.c create mode 100644 tests/bluetooth/controller/ctrl_api/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_api/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_api/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_chmu/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_chmu/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_chmu/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_conn_update/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_conn_update/src/kconfig_override.h create mode 100644 tests/bluetooth/controller/ctrl_conn_update/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_conn_update/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_cte_req/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_cte_req/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_cte_req/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_data_length_update/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_data_length_update/src/kconfig_override.h create mode 100644 tests/bluetooth/controller/ctrl_data_length_update/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_data_length_update/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_encrypt/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_encrypt/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_encrypt/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_feature_exchange/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_feature_exchange/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_feature_exchange/src/main_hci.c create mode 100644 tests/bluetooth/controller/ctrl_feature_exchange/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_hci/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_hci/prj.conf create mode 100644 tests/bluetooth/controller/ctrl_hci/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_hci/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_le_ping/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_le_ping/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_le_ping/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_min_used_chans/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_min_used_chans/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_min_used_chans/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_phy_update/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_phy_update/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_phy_update/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_terminate/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_terminate/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_terminate/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_tx_buffer_alloc/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_tx_buffer_alloc/src/kconfig_override.h create mode 100644 tests/bluetooth/controller/ctrl_tx_buffer_alloc/src/kconfig_override_max_common.h create mode 100644 tests/bluetooth/controller/ctrl_tx_buffer_alloc/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_tx_buffer_alloc/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_tx_queue/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_tx_queue/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_tx_queue/testcase.yaml create mode 100644 tests/bluetooth/controller/ctrl_version/CMakeLists.txt create mode 100644 tests/bluetooth/controller/ctrl_version/src/main.c create mode 100644 tests/bluetooth/controller/ctrl_version/testcase.yaml create mode 100644 tests/bluetooth/controller/mock_ctrl/include/hal/cpu_vendor_hal.h create mode 100644 tests/bluetooth/controller/mock_ctrl/include/hal/debug_vendor_hal.h create mode 100644 tests/bluetooth/controller/mock_ctrl/include/hal/radio_vendor_hal.h create mode 100644 tests/bluetooth/controller/mock_ctrl/include/hal/ticker_vendor_hal.h create mode 100644 tests/bluetooth/controller/mock_ctrl/include/kconfig.h create mode 100644 tests/bluetooth/controller/mock_ctrl/include/lll/lll_adv_pdu.h create mode 100644 tests/bluetooth/controller/mock_ctrl/include/lll/lll_adv_types.h create mode 100644 tests/bluetooth/controller/mock_ctrl/include/lll_clock.h create mode 100644 tests/bluetooth/controller/mock_ctrl/include/lll_master.h create mode 100644 tests/bluetooth/controller/mock_ctrl/include/lll_slave.h create mode 100644 tests/bluetooth/controller/mock_ctrl/include/soc.h create mode 100644 tests/bluetooth/controller/mock_ctrl/include/util.h create mode 100644 tests/bluetooth/controller/mock_ctrl/src/ecb.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/kernel.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/ll.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/ll_assert.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/lll.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/lll_clock.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/lll_conn.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/mayfly.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/ticker.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/ull.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/ull_central.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/ull_periph.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/ull_scan.c create mode 100644 tests/bluetooth/controller/mock_ctrl/src/util.c create mode 100644 tests/bluetooth/init/prj_llcp.conf 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