Skip to content

Commit f9da3c4

Browse files
KyraLengfeldcvinayak
authored andcommitted
Bluetooth: Controller: Ensure controller can handle flow control buffers
The host will generate up to acl_pkts number of Host Number of Completed Packets plus a number of normal HCI commands, which use Num_HCI_Command_Packets. As such, if we do not provide enough (host) TX CMD buffers for the possible number of acl_pkts plus Num_HCI_Command_Packets, we may run into a deadlock. Currently the SDC controller delays sending disconnect events until it received all the ACKs for the ACL data packets sent to the host to avoid race conditions. Which, in case there aren't enough (host) TX CMD buffers, can result in a deadlock visible by a missing disconnect event. When Controller to Host data flow control is supported, this commit ensures that the `CONFIG_BT_BUF_CMD_TX_COUNT` is greater than or equal to `BT_BUF_ACL_RX_COUNT + Ncmd`, where Ncmd is the supported maximum Num_HCI_Command_Packets. Note: The SDC controller (currently) does not support Num_HCI_Command_Packets > 1. Ncmd is always 1. Furthermore this commit restricts the `host_total_num_acl_data_packets` communicated to the controller to how many acknowledgements + Ncmd the controller is able to receive from the host. This is especially valuable in a controller-only build where it cannot be foreseen what settings are used for the host. Signed-off-by: Kyra Lengfeld <[email protected]> (cherry picked from commit 7766183) Signed-off-by: Vinayak Kariappa Chettimada <[email protected]>
1 parent 0994b03 commit f9da3c4

File tree

10 files changed

+217
-1
lines changed

10 files changed

+217
-1
lines changed

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ Kconfig* @tejlmand
312312
/tests/modules/mcuboot/external_flash/ @hakonfam @sigvartmh
313313
/tests/nrf5340_audio/ @koffes @alexsven @erikrobstad @rick1082 @nordic-auko
314314
/tests/subsys/audio_module/ @koffes @alexsven @erikrobstad @rick1082 @gWacey
315+
/tests/subsys/bluetooth/controller/ @nrfconnect/ncs-dragoon
315316
/tests/subsys/bluetooth/gatt_dm/ @doki-nordic
316317
/tests/subsys/bluetooth/mesh/ @ludvigsj
317318
/tests/subsys/bluetooth/fast_pair/ @alstrzebonski @MarekPieta @kapi-no

boards/arm/thingy91_nrf52840/thingy91_nrf52840_defconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ CONFIG_SERIAL=y
1515
CONFIG_CONSOLE=y
1616

1717
CONFIG_BOOTLOADER_MCUBOOT=y
18+
19+
# BT
20+
CONFIG_BT_BUF_CMD_TX_COUNT=3

subsys/bluetooth/controller/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ zephyr_library()
99
zephyr_library_sources(
1010
hci_driver.c
1111
hci_internal.c
12+
hci_internal_wrappers.c
1213
)
1314

1415
zephyr_library_sources_ifdef(

subsys/bluetooth/controller/hci_internal.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <sdc_hci_vs.h>
1717

1818
#include "hci_internal.h"
19+
#include "hci_internal_wrappers.h"
1920
#include "ecdh.h"
2021

2122
#define CMD_COMPLETE_MIN_SIZE (BT_HCI_EVT_HDR_SIZE \
@@ -892,7 +893,7 @@ static uint8_t controller_and_baseband_cmd_put(uint8_t const * const cmd,
892893
case SDC_HCI_OPCODE_CMD_CB_SET_CONTROLLER_TO_HOST_FLOW_CONTROL:
893894
return sdc_hci_cmd_cb_set_controller_to_host_flow_control((void *)cmd_params);
894895
case SDC_HCI_OPCODE_CMD_CB_HOST_BUFFER_SIZE:
895-
return sdc_hci_cmd_cb_host_buffer_size((void *)cmd_params);
896+
return sdc_hci_cmd_cb_host_buffer_size_wrapper((void *)cmd_params);
896897
case SDC_HCI_OPCODE_CMD_CB_HOST_NUMBER_OF_COMPLETED_PACKETS:
897898
return sdc_hci_cmd_cb_host_number_of_completed_packets((void *)cmd_params);
898899
#endif
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
#include "hci_internal_wrappers.h"
8+
#include <zephyr/sys/util.h>
9+
#include <zephyr/bluetooth/buf.h>
10+
11+
#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL)
12+
/*
13+
* The Host will generate up to acl_pkts number of Host Number of Completed Packets command plus a
14+
* number of normal HCI commands, as such we need to ensure the tx command buffer count is big
15+
* enough to not block incoming ACKs from the host.
16+
*
17+
* When Controller to Host data flow control is supported, ensure that `CONFIG_BT_BUF_CMD_TX_COUNT`
18+
* is greater than or equal to `BT_BUF_ACL_RX_COUNT + Ncmd`, where Ncmd is the supported maximum
19+
* Num_HCI_Command_Packets.
20+
*
21+
* The SDC controller (currently) does not support Num_HCI_Command_Packets > 1, which means Ncmd
22+
* is always 1.
23+
*
24+
* `CONFIG_BT_BUF_CMD_TX_COUNT` is used differently depending on whether `CONFIG_BT_HCI_HOST` or
25+
* `BT_HCI_RAW` is defined. And as ACL packet pools are only used in connections,
26+
* we need to limit the build assert as such. See the comments above the asserts for more
27+
* information on the specific scenario.
28+
*/
29+
#if defined(CONFIG_BT_HCI_HOST) && (BT_BUF_ACL_RX_COUNT > 0)
30+
/*
31+
* When `CONFIG_BT_HCI_HOST`, `CONFIG_BT_BUF_CMD_TX_COUNT` controls the capacity of
32+
* `bt_hci_cmd_create`. Which is used for all BT HCI Commands, and can be called concurrently from
33+
* many different application contexts, initiating various processes.
34+
*
35+
* Currently `bt_hci_cmd_create` is generally `K_FOREVER`, as such this build assert only guarantees
36+
* to avoid deadlocks due to missing CMD buffers, if the host is only allocating the next command
37+
* once the previous is completed.
38+
*/
39+
BUILD_ASSERT(BT_BUF_ACL_RX_COUNT < CONFIG_BT_BUF_CMD_TX_COUNT,
40+
"Too low HCI command buffers compared to ACL Rx buffers.");
41+
#else /* controller-only build */
42+
/*
43+
* On a controller-only build (`BT_HCI_RAW`) `CONFIG_BT_BUF_CMD_TX_COUNT` controls the capacity of
44+
* `bt_buf_get_tx(BT_BUF_CMD)`. Which is only used to receive commands from the Host at the rate the
45+
* command flow control dictates. Considering one buffer is needed to the Num_HCI_Command_Packet, to
46+
* do flow control, at least one more buffer is needed.
47+
*
48+
*/
49+
BUILD_ASSERT((CONFIG_BT_BUF_CMD_TX_COUNT - 1) > 0,
50+
"We need at least two HCI command buffers to avoid deadlocks.");
51+
#endif /* CONFIG_BT_CONN && CONFIG_BT_HCI_HOST */
52+
53+
/*
54+
* This wrapper addresses a limitation in some Zephyr HCI samples such as Zephyr hci_ipc, namely the
55+
* dropping of Host Number of Completed Packets (HNCP) messages when buffers are full. Dropping
56+
* these messages causes a resource leak.
57+
*
58+
* The buffers are full when CONFIG_BT_BUF_CMD_TX_COUNT is exhausted. This wrapper implements a
59+
* workaround that ensures the buffers are never exhausted, by limiting the
60+
* Host_Total_Num_ACL_Data_Packets value. Limiting this value naturally limits the number of HNCP to
61+
* one the sample buffers can handle.
62+
*
63+
* Considering the sample uses the same buffer pool for normal commands, which take up one buffer at
64+
* most (look up `HCI_Command_Complete` for more details), this wrapper artifically limits the
65+
* Host_Total_Num_ACL_Data_Packets to at most one less than the CONFIG_BT_BUF_CMD_TX_COUNT pool
66+
* capacity.
67+
* .
68+
*/
69+
int sdc_hci_cmd_cb_host_buffer_size_wrapper(const sdc_hci_cmd_cb_host_buffer_size_t *cmd_params)
70+
{
71+
sdc_hci_cmd_cb_host_buffer_size_t ctrl_cmd_params = *cmd_params;
72+
73+
ctrl_cmd_params.host_total_num_acl_data_packets = MIN(
74+
ctrl_cmd_params.host_total_num_acl_data_packets, (CONFIG_BT_BUF_CMD_TX_COUNT - 1));
75+
76+
return sdc_hci_cmd_cb_host_buffer_size(&ctrl_cmd_params);
77+
}
78+
#endif /* CONFIG_BT_HCI_ACL_FLOW_CONTROL */
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
/** @file
8+
* @brief Internal HCI interface Wrappers
9+
*/
10+
11+
#include <stdint.h>
12+
#include <stdbool.h>
13+
#include <sdc_hci_cmd_controller_baseband.h>
14+
15+
#ifndef HCI_INTERNAL_WRAPPERS_H__
16+
#define HCI_INTERNAL_WRAPPERS_H__
17+
18+
#if defined(__DOXYGEN__) || defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL)
19+
int sdc_hci_cmd_cb_host_buffer_size_wrapper(const sdc_hci_cmd_cb_host_buffer_size_t *cmd_params);
20+
#endif
21+
22+
#endif
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
cmake_minimum_required(VERSION 3.20.0)
8+
9+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
10+
project(hci_cmd_cb_host_buffer_size_test)
11+
12+
# Generate runner for the test
13+
test_runner_generate(src/hci_cmd_cb_host_buffer_size_test.c)
14+
15+
# Add Unit Under Test source files
16+
target_sources(app PRIVATE ${ZEPHYR_NRF_MODULE_DIR}/subsys/bluetooth/controller/hci_internal_wrappers.c)
17+
18+
# Add test source file
19+
target_sources(app PRIVATE src/hci_cmd_cb_host_buffer_size_test.c)
20+
21+
# Create mocks
22+
cmock_handle(${ZEPHYR_NRFXLIB_MODULE_DIR}/softdevice_controller/include/sdc_hci_cmd_controller_baseband.h)
23+
24+
# Include paths
25+
include_directories(${ZEPHYR_HAL_NORDIC_MODULE_DIR}/nrfx)
26+
include_directories(${ZEPHYR_NRF_MODULE_DIR}/subsys/bluetooth/controller)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
CONFIG_BT_CENTRAL=y
7+
CONFIG_BT_HCI=y
8+
CONFIG_BT=y
9+
10+
CONFIG_BT_HCI_ACL_FLOW_CONTROL=y
11+
CONFIG_BT_BUF_CMD_TX_COUNT=10
12+
13+
CONFIG_UNITY=y
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
#include <unity.h>
7+
#include <stdbool.h>
8+
#include <stdio.h>
9+
#include <errno.h>
10+
#include <zephyr/sys/util.h>
11+
12+
#include "hci_internal_wrappers.h"
13+
#include "cmock_sdc_hci_cmd_controller_baseband.h"
14+
15+
#define TEST_HOST_ACL_DATA_PCKT_LNGTH 55
16+
#define TEST_HOST_SYNC_DATA_PCKT_LNGTH 55
17+
#define TEST_HOST_NUM_SYNC_DATA_PCKTS 5555
18+
19+
/* The unity_main is not declared in any header file. It is only defined in the generated test
20+
* runner because of ncs' unity configuration. It is therefore declared here to avoid a compiler
21+
* warning.
22+
*/
23+
extern int unity_main(void);
24+
25+
typedef struct {
26+
uint16_t acl_input_pckts;
27+
uint16_t exp_acl_pckts;
28+
} test_vector_t;
29+
30+
static const test_vector_t test_vectors[] = {
31+
{5, 5}, /* Input within range, no adjustment needed */
32+
{15, (CONFIG_BT_BUF_CMD_TX_COUNT - 1)}, /* Input exceeds TX buffer count - 1 limit */
33+
{0xFF, (CONFIG_BT_BUF_CMD_TX_COUNT - 1)}, /* Maximum input value, corner case */
34+
};
35+
36+
void test_sdc_hci_cmd_cb_host_buffer_size_wrapper(void)
37+
{
38+
sdc_hci_cmd_cb_host_buffer_size_t cmd_params;
39+
40+
cmd_params.host_acl_data_packet_length = TEST_HOST_ACL_DATA_PCKT_LNGTH;
41+
cmd_params.host_sync_data_packet_length = TEST_HOST_SYNC_DATA_PCKT_LNGTH;
42+
cmd_params.host_total_num_sync_data_packets = TEST_HOST_NUM_SYNC_DATA_PCKTS;
43+
44+
for (size_t i = 0; i < ARRAY_SIZE(test_vectors); i++) {
45+
const test_vector_t *v = &test_vectors[i];
46+
47+
sdc_hci_cmd_cb_host_buffer_size_t exp_cmd_params = cmd_params;
48+
49+
exp_cmd_params.host_total_num_acl_data_packets = v->exp_acl_pckts;
50+
__cmock_sdc_hci_cmd_cb_host_buffer_size_ExpectAndReturn(&exp_cmd_params, 0);
51+
52+
cmd_params.host_total_num_acl_data_packets = v->acl_input_pckts;
53+
sdc_hci_cmd_cb_host_buffer_size_wrapper(&cmd_params);
54+
}
55+
}
56+
57+
int main(void)
58+
{
59+
(void)unity_main();
60+
61+
return 0;
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
tests:
2+
bluetooth.controller:
3+
platform_allow:
4+
- native_sim
5+
integration_platforms:
6+
- native_sim
7+
tags:
8+
- unittest
9+
- ci_tests_subsys_bluetooth_controller

0 commit comments

Comments
 (0)